-
Notifications
You must be signed in to change notification settings - Fork 215
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
fix: enable Node.js --expose-gc via require('expose-gc') #3207
Conversation
Lurk: Would it make sense to use the function version of this where needed, to avoid adding to the global where it's not needed? |
We'd have to plumb it in as function arguments all the way through SwingSet. I'll consider doing that when refactoring the SwingSet controller, but for now I prefer not to make such an intrusive change. |
That's nearly what we're doing, at least with respect to all the non-start compartments (where the entire kernel runs). The controller (which runs in the start compartment) imports We could choose to change Also, maybe I missed something, I didn't think there was an importable form of |
...
Yes, I suppose I neglected to say something like that in a review.
All exports should be powerless; only main scripts and tests can use powerful globals, to summarize discussion in #2160 to date. The imports in this PR are indeed in main scripts, but |
As @mhofman alluded, that's possible with I will rework this PR to do that in |
df1167a
to
9879bcc
Compare
I feel like I'm missing something fundamental here. I found the very fact that you can enable the I had been thinking whole point of node having this command line flag was to withhold the power unless it had been delivered from the outside, but it appears to simply be to keep you from using the power unless you want to. In that case, am I correct in understanding that the point of these changes is just to avoid the inconvenience of an apparently pointless command line ritual? |
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 good, although I'm slightly concerned about:
- Does
expose-gc
really work? https://github.com/legraphista/expose-gc/blob/master/function.js shows that it's creating a new V8 context (is that their name for a Realm?) and and grabbing thegc
global from it with aneval
-equivalent. I suspect this also addsgc
to the globals of all subsequent new contexts, which might include threads (aka NodeWorkers). And I think we're relying upon thegc
from one context working to collect garbage across all contexts (although, if these are basically Realms, then since Realms can still talk to each other, they necessarily have a single common GC domain, so that's probably safe). expose-gc
populatesglobal.gc
, just like--expose-gc
did- so I suspect
expose-gc
is good for removing the need for command-line flags, but not for reducing authority of the start compartment or unpolluting a namespace
- so I suspect
If it's possible to shuffle the test to build gcAndFinalize
in a different function than the one where the test object is created and released, that'd make me feel slightly better. But if that's too difficult, feel free to land without it.
@@ -22,8 +16,9 @@ function setup() { | |||
finalized[0] = 'finalizer was called'; | |||
}); | |||
const wr = new WeakRef(victim); | |||
const gcAndFinalize = makeGcAndFinalize(engineGC); |
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.
Let's make a separate function to prepare gcAndFinalize
independently of where we setup the object that's about to be deleted. I'd like the number of objects that go out of scope during the execution of setup()
to be as small as possible, so we aren't accidentally triggering GC and thus masking a faulty gcAndFinalize()
.
@@ -81,7 +76,8 @@ test(`can provoke gc on xsnap`, async t => { | |||
const opts = options(); | |||
const vat = xsnap(opts); | |||
const code = ` | |||
${gcAndFinalize} | |||
${makeGcAndFinalize} | |||
const engineGC = globalThis.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.
It seems somewhat ironic that we must extract gc
from the global after going through so much work to avoid putting it on the global. Although I suppose the main benefit of using the expose-gc
NPM module is that we don't need command-line flags, rather than reducing authority added to the start-compartment global.
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.
No irony. On Node.js, import engineGC from 'expose-gc/function';
does not modify globalThis
.
The code above is evaluated under xsnap, to which the C code has deliberately endowed globalThis.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.
oh, right, I assumed their function.js
was only reachable through their index.js
, but of course you can deep-import it directly
); | ||
} | ||
return; | ||
export function makeGcAndFinalize(gcPower) { |
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 kinda glad to see that alreadyWarned
go away.. I'm not sure how it worked on our unit test (which extracted the source code of gcAndFinalize
for evaluating in xsnap, which would lose the let alreadyWarnred = false;
line). Maybe it was just relying upon unbound names being treated as properties of the global.
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.
Yeah, I don't know how that worked.
@@ -4,13 +4,14 @@ | |||
import '@agoric/install-ses'; | |||
import { parentPort } from 'worker_threads'; | |||
import anylogger from 'anylogger'; | |||
import engineGC from 'expose-gc/function'; |
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 a little surprised this works, since it means the NodeWorker thread can require('v8')
and require('vm')
, and I thought NodeWorkers were slightly less powerful than that.
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.
NodeWorkers can require(ANYTHING)
.
That's the first point. The second point is not to introduce an ambient |
ea665e4
to
2aa325e
Compare
Yes, a V8 context is roughly a Realm. Indeed, that's how
That's true. I worked around this by creating
But
I've done that, however you should note that your use (and holding onto) a FinalizationRegistry as a result from that function may be affecting the collection of the victim. Maybe this is why there needs to be multiple setImmediates? |
Cool, thanks. In my tests, if the FinalizationRegistry got collected, its callbacks wouldn't fire, so I'm pretty sure the FR is a necessary return. |
I don't think the FR is collected, I just meant that it should be created outside the function that creates the victim. However, I'm probably confusing that with V8's collection of captured variables in a closure (which are released as a frame all at once, not when just one (or a few) of them goes out of scope). Victim is not a captured variable, so nvm. 😃 |
2aa325e
to
e49813c
Compare
Or at least that the victim is Edit: technically that registry callback doesn't reference anything in its scope, so the engine could collect the victim, however that's relying on the ability of the engine to do hard core optimizations (detect which names from parent lexical scopes are actually used). |
As mentioned by @warner in #3197 (comment) this is a package-based enablement of
--expose-gc
sinceNODE_OPTIONS=--expose-gc
is rejected by Node.js: