-
-
Notifications
You must be signed in to change notification settings - Fork 225
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
Add SnapshotCreator API #281
base: master
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## master #281 +/- ##
==========================================
- Coverage 95.86% 95.76% -0.11%
==========================================
Files 17 18 +1
Lines 580 637 +57
==========================================
+ Hits 556 610 +54
- Misses 15 17 +2
- Partials 9 10 +1
Continue to review full report at Codecov.
|
6ddb67c
to
74b2e56
Compare
We could allow passing |
Local<Context> local_ctx = ctx->ptr.Get(iso); | ||
Context::Scope context_scope(local_ctx); | ||
|
||
ContextFree(ctx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason we have to free the internal context before adding it into the snapshot creator class is that the snapshot creator class doesn't know how to deserialize the internal values we store in the local context. Every time we allocate a new m_value
and pushed to the context tracked_value(ctx, val)
creates a reference that we have to remove before adding the context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be made more consistent with V8 by just freeing the tracked values in SetDefaultContext or AddContext, along with mentioning this value invalidation in the documentation. It isn't ideal there are no use after free checks on values, but that was already the case (e.g. for Context.Close), so outside the scope of this PR.
be2acf1
to
cc6e686
Compare
cc6e686
to
fbf1281
Compare
@rogchap If you have sometime I would appreciate your feedback on the new API for snapshot creation 😄 |
On a very quick glance, the API remains very close to the CPP equivalent, and remains mostly backward compatible which is great. I'll need to spend a little more time to look at this in detail. Appreciate your contributions 💪 |
Hi @rogchap 👋 By any chance have you got some time to look into it? |
// This Isolate must be use to create the contexts that later will be use to create the snapshot blob. | ||
func (s *SnapshotCreator) GetIsolate() (*Isolate, error) { | ||
if s.ptr == nil { | ||
return nil, errors.New("v8go: Cannot get Isolate after creating the blob") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth panicking here trying to call a function on a snapshot creator thats likely been disposed of (either through Dispose
or from calling Create
to make the blob)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that the pattern among other functions #118 (comment)
Should we panic in all of the instances. It seems that it would make working with the Snapshot creator API much easier.
snapshotCreator := v8.NewSnapshotCreator()
snapshotCreatorIso := snapshotCreator.GetIsolate()
snapshotCreatorCtx := v8.NewContext(snapshotCreatorIso)
defer snapshotCreatorCtx.Close()
snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js")
snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js")
snapshotCreator.SetDefaultContext(snapshotCreatorCtx)
data := snapshotCreator.Create(v8.FunctionCodeHandlingClear)
iso := v8.NewIsolate(v8.WithStartupData(data))
defer iso.Dispose()
ctx, err := v8.NewContextFromSnapshot(iso, index)
defer ctx.Close()
The only one that I'm not sure 100% we should panic is the one that errors when calling Create
before adding a default context since this is something a developer can fix.
What do you think @genevieve?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets stick with the error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should return errors when we expect the caller to handle them. The V8 API already does that, so we can normally keep the API similar to V8 in that respect, except that we use a panic to protect against crashes. The API will especially be annoying when getters like this one return an error.
return &StartupData{data: data, raw_size: raw_size}, nil | ||
} | ||
|
||
// Dispose deletes the reference to the SnapshotCreator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems worth mentioning that this also disposes of the isolate associated with it
func (s *SnapshotCreator) Dispose() { | ||
if s.ptr != nil { | ||
C.DeleteSnapshotCreator(s.ptr) | ||
s.ptr = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the isolate's internal pointer should be marked as nil
(i.e. s.iso.ptr = nil
), as is done in (*v8go.Isolate).Dispose()
, to reflect the fact that it will be disposed by SnapshotCreator::~SnapshotCreator()
.
SnapshotCreator* creator = new SnapshotCreator; | ||
Isolate* iso = creator->GetIsolate(); | ||
rtn.creator = creator; | ||
rtn.iso = iso; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isolate differs from the one created from NewIsolate in that it doesn't have an internal context. I think that would likely result in a crash from using any API that tries to use that internal context, such as NewValue.
Can that internal context be created and just not be added to the snapshot?
// StartupData stores the snapshot blob data | ||
type StartupData struct { | ||
data []byte | ||
raw_size C.int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this field could be removed, since the slice holds the data size
Local<Context> local_ctx = ctx->ptr.Get(iso); | ||
Context::Scope context_scope(local_ctx); | ||
|
||
ContextFree(ctx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be made more consistent with V8 by just freeing the tracked values in SetDefaultContext or AddContext, along with mentioning this value invalidation in the documentation. It isn't ideal there are no use after free checks on values, but that was already the case (e.g. for Context.Close), so outside the scope of this PR.
|
||
// StartupData stores the snapshot blob data | ||
type StartupData struct { | ||
data []byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we want to be able to persist or transfer the bytes so that the snapshot can be reused cross-process, similar to CompilerCachedData? To allow that, we need the bytes field to be exported so they can be used for IPC and so the struct can be constructed in another process.
data []byte | |
Bytes []byte |
// This Isolate must be use to create the contexts that later will be use to create the snapshot blob. | ||
func (s *SnapshotCreator) GetIsolate() (*Isolate, error) { | ||
if s.ptr == nil { | ||
return nil, errors.New("v8go: Cannot get Isolate after creating the blob") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should return errors when we expect the caller to handle them. The V8 API already does that, so we can normally keep the API similar to V8 in that respect, except that we use a panic to protect against crashes. The API will especially be annoying when getters like this one return an error.
s.iso.ptr = nil | ||
|
||
raw_size := rtn.raw_size | ||
data := C.GoBytes(unsafe.Pointer(rtn.data), raw_size) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The v8::SnapshotCreator::CreateBlob documentation says it returns "{ nullptr, 0 } on failure". If this function is going to return an error, then I would definitely expect an error to be returned when V8 would return an error. Unfortunately, V8 is quite vague on what possible reasons there are for an error and the API doesn't expose an error message.
|
||
// Create creates a snapshot data blob. | ||
// The snapshot creator instance is unsable after creating the snapshot data blob. | ||
func (s *SnapshotCreator) Create(functionCode FunctionCodeHandling) (*StartupData, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StartupData is basically just a wrapper around a slice. It may as well be returned without the extra pointer indirection, since the error would indicate whether or not it is an error. Since V8 doesn't actually expose an error message, ok
could be used for the error and the s.ptr == nil
and !s.defaultContextAdded
cases could result in a panic.
Hi, |
This PR allows users of v8go to use the existing API from V8 to create HeapSnapshots.
The v8go API is similar to the one provided by V8 and the SnapshotCreator class.
The API still need some work to have the full support that V8 provides, such as passing options to
NewSnapshotCreator
as well as allowing to store data arbitrary data on the snapshotAddData
In other to support passing the created snapshot_blob to the
NewIsolate
method, we had to allow passing options when creating a new isolate.The old API:
The new API:
Allowing to pass options to NewIsolate allows us to keep the existing API with breaking existing codebases 🎉
Creating a context from v8go now can be done using two methods:
v8go.NewContext(iso)
v8go.NewContextFromSnapshot(iso, index)
The snapshot creator API
For every context that gets added to the snapshot creator, we need to make sure the Isolate is the same as the one associated with the snapshot creator class itself. That is the reason we have the
GetIsolate()
method, so we can create a new context using that one. This isolate is allocated by V8 when callingSnapshotCreator* creator = new SnapshotCreator;
When creating the snapshot blob we have two methods to add existing contexts to the snapshot creator:
SetDefaultContext
-> This will set the context as the default one. From there every new context created from that isolate will have that context loaded, so callingv8go.NewContext(iso)
will return a context with the default context loaded.AddContext
-> This will append to the list of additional contexts that a snapshot creator can have. The method returns an index, which we will be able to use to indicate to V8 when creating a new context from it.v8go.NewContextFromSnapshot(iso, index)
The V8 code that I used as a reference for creating the API is the one in the
v8/test/cctest/test-serialize.cc
file.