-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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 Deno.core.setGCObserver() for callbacks invoked on GC #2106
Conversation
2fe56f9
to
395471c
Compare
Does this have the same name in d8 and node? I totally didn’t know about this feature! |
@ry Er this is just a custom wrapper using (Seems cannot really find any usage of |
Is IIFE required and not scope just for tests/the gc call or something fundamental? Perhaps this could be called onDrop 😬 |
@hayd From my experiment, only by going out of function scope is garbage collector guaranteed to dispose the unreferenced object. Also from v8 header:
Basically if user really decides to do the "silly" stuff in the callback, the object would not get GC-ed, and the observer would no longer have any effect on this object anymore: we have called I agree |
@@ -37,4 +37,7 @@ declare interface DenoCore { | |||
evalContext(code: string): [any, EvalErrorInfo | null]; | |||
|
|||
errorToJSON: (e: Error) => string; | |||
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |||
setGCObserver: <T>(o: T, callback: (o: T) => void) => T; |
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.
I think this would be better named "setWeakCallback"
I also think this would be a good place for some JSDOC. The docs from v8.h should be forwarded here:
/** Install a finalization callback on this object.
* NOTE: There is no guarantee as to *when* or even *if* the callback is
* invoked. The invocation is performed solely on a best effort basis.
* As always, GC-based finalization should *not* be relied upon for any
* critical form of resource management!
*/
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.
Is it really so bad?
Is it guaranteed to execute prior to the program exiting?
What then should / can this be used for?
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.
@hayd There is no guarantee about when this will be invoked. It is possible that we can force a GC on program exit, for sanity (just like how fds are closed on program exit) (the callback should always be invoked if the object is actually being GCed)
That being said, this is way better than simply leaving program hang forever due to a resource not closed while losing all reference to it. Also I do remember v8 now has some very interesting GC policies.
Node also uses weak callbacks for cleaning up resources e.g. for the vm
module
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.
Does explicit gc ensure it calls? Ry's doc/comment suggests there's an "if" here too.
I like the idea of an explicit gc call prior to exit.
@@ -162,6 +162,68 @@ void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args) { | |||
args.GetReturnValue().Set(v8_str(json_string.c_str())); | |||
} | |||
|
|||
class GCObserver { |
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.
I'm embarrassed to say but I totally forgot that V8 has this weak callback functionality.
I don't like introducing new nomenclature like "GCObserver". Maybe call it"PersistentWithWeakCallback" ?
Have you looked at the TracedGlobal class? It seems like that could be used here as well?
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 requires manual setup of a custom EmbedderHeapTracer
implementation, and closer to kInternalFields
(have some examples here).
Before experimenting with this I actually want to set if the observed is actually GC-ed after a proper use of callback instead of being resurrected and leaked. (Theoretically it should not, but this still needs to be examined before we can use this with confidence) (Though I'm having trouble now with native syntaxes in snapshots to use the %DebugTrackRetainingPath
builtin. Also attempts to run --prof
on Deno failed due to a flag parsing issue)
|
||
global.LibDenoGCObserver = () => { | ||
// Basic usage. | ||
let num = 1; |
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.
s/num/counter/
})(); | ||
// Guarantee GC. | ||
gc(); | ||
assert(num2 === 10); |
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 stanza, using num2, seems like it could be a standalone test: weakCallback2
. It would be good to break up the test into distinct components so that they will fail individually during test_cc
@@ -315,6 +315,13 @@ TEST(LibDenoTest, LibDenoEvalContextError) { | |||
deno_delete(d); | |||
} | |||
|
|||
TEST(LibDenoTest, LibDenoGCObserver) { | |||
Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); | |||
deno_execute(d, nullptr, "a.js", "LibDenoGCObserver();"); |
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.
s/LibDenoGCObserver/weakCallback/
@@ -76,7 +76,7 @@ def main(argv): | |||
|
|||
test_cc = os.path.join(build_dir, "test_cc" + executable_suffix) | |||
check_exists(test_cc) | |||
run([test_cc]) | |||
run([test_cc, "--expose-gc"]) |
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.
Can you move the --expose-gc
into C++ so that it is always specified. That should be done here:
Line 29 in 635ad2a
deno_set_v8_flags(&argc, argv); |
(It's slightly annoying, I guess you have to allocate a new argv with size argc+1 and copy over the existing flags, and add the new "--expose-gc")
This is generally speaking not a good idea, except maybe for debugging purposes. |
@kevinkassimo As we discussed a few months ago in LA, the problem with this is that it cannot be used for cleaning up unused resources because the GC is triggered very lazily. I think it's not appropriate to add this right now. (And it's gone stale) |
Deno.core.setGCObserver(target, handler)
setshandler: (target) => void
to be invoked ontarget
immediately before being GC-ed e.g. to clear up open resources such as connections.Sample usage (as shown in libdeno tests):