Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use V8 instead of Otto for JavaScript #5980

Open
wants to merge 147 commits into
base: main
Choose a base branch
from
Open

Use V8 instead of Otto for JavaScript #5980

wants to merge 147 commits into from

Conversation

snej
Copy link
Contributor

@snej snej commented Dec 16, 2022

CBG-2949
Replaces Otto with V8 for all uses of JavaScript.

The new js package is a general purpose thread-safe library for creating "services" that call a JavaScript function in an isolated runtime context.

Pre-review checklist

  • Removed debug logging (fmt.Print, log.Print, ...)
  • Logging sensitive data? Make sure it's tagged (e.g. base.UD(docID), base.MD(dbName))
  • Updated relevant information in the API specifications (such as endpoint descriptions, schemas, ...) in docs/api

Dependencies (if applicable)

Integration Tests

Functions: Moved limits into config itself

- Functions config is now a struct not a map
  - The map is now its Definitions field
  - Added limits to the struct
- Added limits to GraphQLConfig
- REST and BLIP clients check the max_request_size limit
- delete(docID) deletes the doc with that ID.
- delete(body) uses body._id (required) and body._rev (optional).
  If _rev is given, uses MVCC.
This isn't 100% reliable because it just uses a simple regex.
A mutating query could be put after a SELECT and a semicolon.
I think there might also be clauses that can validly go before SELECT (?)
(parent, args, context, info) ... not (context, args, parent, info)
Now uses ${...} and supports multiple path components and array indexing.
Field resolvers of the Query and Mutation types should treat a missing "allow"
object as meaning admin-only, the way functions do.
I added a js.VMPool to Database. Since UserFunctions and GraphQL require the
VMPool to initialize themselves, those objects can't be part of the
DatabaseContextOptions, which is created earlier.
So I defined an interface for the functions/GraphQL configuration (which is
defined up in the functions package) and put that on the Options instead.
sync.Pool drops objects fairly often, and the VM objects and their services
are pretty expensive to recreate; this was bad for performance.
torcolvin and others added 13 commits May 26, 2023 07:45
* CBG-3024 Make sure import feed uses checkpoints
* CBG-3001 Avoid bucket retrieval error during OnFeedClose

OnFeedError was checking for bucket existence to determine whether to call NotifyMgrOnClose().  This handling isn't necessary for SG, as we want database close to handle shutdown of the import feed (via importListener.Stop()) in the case of a deleted bucket.
* CBG-3028: fixes for failing CE tests

* remove print line

* updates off comment
…6258)

* CBG-2793: attachment compaction code erroneously sets failOnRollback

* spelling errors and test logging removal + lint error

* updates for comments and try fix race condition

* linter error fix

* changes for jenkins

* updates to add handling for cleanup phase

* remove logging line

* fix race in Jenkins

* updates to fix collections issue

* updates based off comment

* updates

* updates to address complexity comments
Copy link
Collaborator

@torcolvin torcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't review all of this code by any means.

Things I didn't review due to time constraints:

  • any JS/typescript code
  • understanding the GetDeepMutableBody and the ShallowCopy in the crud code.

channels/channelmapper.go Show resolved Hide resolved
On a clean build, or after any changes in the `engine` source tree:

1. `cd db/functions/engine`
2. `npm run build`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth writing some CI to make sure that we run the tests here and that if running npm run build changes, it fails if we don't update the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be good, but I myself am not good at CI stuff so I don't trust myself to write it.

channels/channelmapper.go Show resolved Hide resolved
Comment on lines -35 to -37
func init() {
underscore.Disable() // It really slows down unit tests (by making otto.New take a lot longer)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are still running some of these tests with otto - that said, I'd be fine with putting this into a separate PR and moving this to main_test.go for relevant tests - and we can remove when we remove otto.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took that line out because it doesn't make a significant difference anymore, i.e. the tests don't really take longer with it removed. I remember adding that back in 2013 or so, and either MacBooks have gotten a lot faster or the Go runtime has (probably more of the latter.)

db/crud.go Outdated
base.DebugfCtx(ctx, base.KeyCRUD, "updateDoc(%q): Rev %q causes %q to become current again",
base.UD(doc.ID), newRevID, doc.CurrentRev)
channelSet, access, roles, syncExpiry, oldBodyJSON, err = db.getChannelsAndAccess(ctx, doc, curBody, metaMap, doc.CurrentRev)
channelSet, access, roles, syncExpiry, oldBodyJSON, err = db.getChannelsAndAccess(ctx, doc, curBodyBytes, metaMap, doc.CurrentRev, false /*TODO: Is false correct??*/)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

address this TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right. false is correct, so I'll remove the comment.

return func(ctx context.Context, doc Body) (bool, error) {
if timeout > 0 {
var cancelFn context.CancelFunc
ctx, cancelFn = context.WithTimeout(ctx, timeout)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otto doesn't use context cancellation, is this being obeyed in otto?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, see the other comment above (it's handled in js.OttoRunner.)

case bool:
return result, nil
case string:
return strconv.ParseBool(result)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the justification for dropping this error handling?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I dropped anything; the equivalent code in the master branch does the same thing, only with some extra no-op lines that unpack and repack the result.

db/import.go Outdated
case string:
return strconv.ParseBool(result)
}
return false, fmt.Errorf("import filter function returned non-boolean type %T", result)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false, fmt.Errorf("import filter function returned non-boolean type %T", result)
return false, fmt.Errorf("import filter function returned non-boolean type %T: %+v", result, result)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea.

rest/config.go Outdated
multiError = multiError.Append(fmt.Errorf("collection %q sync function error: %w", collectionName, err))
} else if isEmpty {
collectionConfig.SyncFn = nil
}

if isEmpty, err := validateJavascriptFunction(collectionConfig.ImportFilter); err != nil {
if isEmpty, err := validateJavascriptFunction(jsvm, collectionConfig.ImportFilter, 1, 99); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you allow 999 args above, and this makes me think you want to put some constants for minJSArgs, maxImportFilterArguments

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like an import filter only takes one arg, so I'll change the 999 and the 99 to 1.

rest/persistent_config_test.go Show resolved Hide resolved
if err != nil {
return "", nil, err
}
localDocBody := localDoc.Body().ShallowCopy()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be GetDeepMutableBody() for otto and probably Body().ShallowCopy() for V8 - this is enough that I'd be tempted to write a shim to get the right part of the body?

I think not interacting with otto as go objects directly has the potential to slow down otto, which I don't think we want to do.

snej added 2 commits June 7, 2023 16:02
Otto exposes Go objects directly to JS, meaning that the resolver fn is capable of
modifying the document maps. A conflict resolver isn't allowed to do that, so make
deep copies of both docs before calling it.
@snej
Copy link
Contributor Author

snej commented Jun 8, 2023

Hm, got a single test failure: in "test v8 (ubuntu) (pull_request)" the test TestReconnectReplicator failed:

2023-06-07T23:42:31.2727087Z     utilities_testing_resttester.go:199: 
2023-06-07T23:42:31.2727596Z         	Error Trace:	/home/runner/work/sync_gateway/sync_gateway/rest/utilities_testing_resttester.go:199
2023-06-07T23:42:31.2728496Z         	            				/home/runner/work/sync_gateway/sync_gateway/rest/utilities_testing_resttester.go:203
2023-06-07T23:42:31.2729429Z         	            				/home/runner/work/sync_gateway/sync_gateway/rest/replicatortest/replicator_test.go:2451
2023-06-07T23:42:31.2729672Z         	Error:      	Received unexpected error:
2023-06-07T23:42:31.2730325Z         	            	RetryLoop for Wait for condition options giving up after 201 attempts
2023-06-07T23:42:31.2730658Z         	Test:       	TestReconnectReplicator/max_backoff_not_specified
2023-06-07T23:42:31.2731010Z         	Messages:   	Expected status: reconnecting, actual status: running

@torcolvin
Copy link
Collaborator

Hm, got a single test failure: in "test v8 (ubuntu) (pull_request)" the test TestReconnectReplicator failed:

2023-06-07T23:42:31.2727087Z     utilities_testing_resttester.go:199: 
2023-06-07T23:42:31.2727596Z         	Error Trace:	/home/runner/work/sync_gateway/sync_gateway/rest/utilities_testing_resttester.go:199
2023-06-07T23:42:31.2728496Z         	            				/home/runner/work/sync_gateway/sync_gateway/rest/utilities_testing_resttester.go:203
2023-06-07T23:42:31.2729429Z         	            				/home/runner/work/sync_gateway/sync_gateway/rest/replicatortest/replicator_test.go:2451
2023-06-07T23:42:31.2729672Z         	Error:      	Received unexpected error:
2023-06-07T23:42:31.2730325Z         	            	RetryLoop for Wait for condition options giving up after 201 attempts
2023-06-07T23:42:31.2730658Z         	Test:       	TestReconnectReplicator/max_backoff_not_specified
2023-06-07T23:42:31.2731010Z         	Messages:   	Expected status: reconnecting, actual status: running

This is in a new test, @gregns1 can you have a look?

@gregns1
Copy link
Contributor

gregns1 commented Jun 8, 2023

Hm, got a single test failure: in "test v8 (ubuntu) (pull_request)" the test TestReconnectReplicator failed:

2023-06-07T23:42:31.2727087Z utilities_testing_resttester.go:199:

2023-06-07T23:42:31.2727596Z Error Trace: /home/runner/work/sync_gateway/sync_gateway/rest/utilities_testing_resttester.go:199

2023-06-07T23:42:31.2728496Z /home/runner/work/sync_gateway/sync_gateway/rest/utilities_testing_resttester.go:203

2023-06-07T23:42:31.2729429Z /home/runner/work/sync_gateway/sync_gateway/rest/replicatortest/replicator_test.go:2451

2023-06-07T23:42:31.2729672Z Error: Received unexpected error:

2023-06-07T23:42:31.2730325Z RetryLoop for Wait for condition options giving up after 201 attempts

2023-06-07T23:42:31.2730658Z Test: TestReconnectReplicator/max_backoff_not_specified

2023-06-07T23:42:31.2731010Z Messages: Expected status: reconnecting, actual status: running

This is in a new test, @gregns1 can you have a look?

This looks like a race in the test. I'll have a look tomorrow to get a fix for it, you can ignore in the context of this PR.

@snej
Copy link
Contributor Author

snej commented Jun 8, 2023

I can't even find that test! I've got feature/v8+subgraph checked out, it's up-to-date with Github, but the string TestReconnectReplicator doesn't exist in the source tree and definitely not at rest/replicatortest/replicator_test.go:2451. Am I losing my mind?

@snej
Copy link
Contributor Author

snej commented Jun 8, 2023

The test log says HEAD is now at e326d6b Merge 0891f2eda0e468ff4354c2e23090e4a7674f2d17 into 3e36d801b8c6a659622d3d148dfcedd772c0b5ca -- that means it's merged master into the PR branch. I had no idea CI was testing these hypothetical merges ... I'm used to it testing the exact branch of the PR. Huh.

snej added 4 commits June 20, 2023 14:01
- Updated sg-bucket to latest (v8 branch) in go.mod.
- Calls to V8BasicTemplate's and V8Runner's NewString() methods now return an
  error  that needs to be checked.
- Took out the 'mustSucceed()' utility since it can call panic; instead the
  error is now handled at the call site.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants