src: avoid draining platform tasks at FreeEnvironment - #51290
Conversation
f9c4eb9 to
0cd1a72
Compare
0cd1a72 to
8324ab0
Compare
There was a problem hiding this comment.
Why is Isolate::DisallowJavascriptExecutionScope only called when DEBUG is set?
There was a problem hiding this comment.
Disallowing JavaScript execution in this function is primarily a rule to avoid footguns, i.e. Environment has been freed and most Node.js APIs would not work. However, it is still valid to evaluate JavaScript as the Isolate has not been freed.
So, to enforce the rule, the Isolate::DisallowJavascriptExecutionScope is opened and will strictly crash the process if JavaScript is invoked in this scope. This would allow us to identify possible problems in debug build. While in the release build, it would be better to be more lenient and let the JavaScript run at the best effort.
I've updated the patch to include a comment about this.
At the point of `FreeEnvironment` and onwards, no JavaScript execution associated with the Environment should be triggered. Avoid draining platform tasks that can trigger JavaScript execution in `FreeEnvironment`. The holder of `node::Environment` should immediately call `node::MultiIsolatePlatform::UnregisterIsolate` and `v8::Isolate::Dispose` to cancel pending foreground tasks and join concurrent tasks after the environment was freed. `NodePlatform` can properly handle the case in `RunForegroundTask` when an Isolate out-lives its associated `node::Environment`.
8324ab0 to
beab806
Compare
bcoe
left a comment
There was a problem hiding this comment.
LGTM, I'm excited to see if this addresses the deadlock with coverage.
|
Landed in 5db35b4 |
|
Thanks. Can we know which version version of node will first include this fix ? |
|
Will this be ported to the node 20.x branch? Thanks! |
|
If there are no unplanned side effects, yes! |
At the point of `FreeEnvironment` and onwards, no JavaScript execution associated with the Environment should be triggered. Avoid draining platform tasks that can trigger JavaScript execution in `FreeEnvironment`. The holder of `node::Environment` should immediately call `node::MultiIsolatePlatform::UnregisterIsolate` and `v8::Isolate::Dispose` to cancel pending foreground tasks and join concurrent tasks after the environment was freed. `NodePlatform` can properly handle the case in `RunForegroundTask` when an Isolate out-lives its associated `node::Environment`. PR-URL: #51290 Fixes: #47748 Fixes: #49344 Reviewed-By: VinΓcius LourenΓ§o Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
At the point of `FreeEnvironment` and onwards, no JavaScript execution associated with the Environment should be triggered. Avoid draining platform tasks that can trigger JavaScript execution in `FreeEnvironment`. The holder of `node::Environment` should immediately call `node::MultiIsolatePlatform::UnregisterIsolate` and `v8::Isolate::Dispose` to cancel pending foreground tasks and join concurrent tasks after the environment was freed. `NodePlatform` can properly handle the case in `RunForegroundTask` when an Isolate out-lives its associated `node::Environment`. PR-URL: nodejs#51290 Fixes: nodejs#47748 Fixes: nodejs#49344 Reviewed-By: VinΓcius LourenΓ§o Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
At the point of `FreeEnvironment` and onwards, no JavaScript execution associated with the Environment should be triggered. Avoid draining platform tasks that can trigger JavaScript execution in `FreeEnvironment`. The holder of `node::Environment` should immediately call `node::MultiIsolatePlatform::UnregisterIsolate` and `v8::Isolate::Dispose` to cancel pending foreground tasks and join concurrent tasks after the environment was freed. `NodePlatform` can properly handle the case in `RunForegroundTask` when an Isolate out-lives its associated `node::Environment`. PR-URL: nodejs#51290 Fixes: nodejs#47748 Fixes: nodejs#49344 Reviewed-By: VinΓcius LourenΓ§o Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
At the point of `FreeEnvironment` and onwards, no JavaScript execution associated with the Environment should be triggered. Avoid draining platform tasks that can trigger JavaScript execution in `FreeEnvironment`. The holder of `node::Environment` should immediately call `node::MultiIsolatePlatform::UnregisterIsolate` and `v8::Isolate::Dispose` to cancel pending foreground tasks and join concurrent tasks after the environment was freed. `NodePlatform` can properly handle the case in `RunForegroundTask` when an Isolate out-lives its associated `node::Environment`. PR-URL: #51290 Fixes: #47748 Fixes: #49344 Reviewed-By: VinΓcius LourenΓ§o Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
* chore: bump node in DEPS to v20.12.0 * chore: update build_add_gn_build_files.patch * chore: update patches * chore: bump node in DEPS to v20.12.1 * chore: update patches * build: encode non-ASCII Latin1 characters as one byte in JS2C nodejs/node#51605 * crypto: use EVP_MD_fetch and cache EVP_MD for hashes nodejs/node#51034 * chore: update filenames.json * chore: bump node in DEPS to v20.12.2 * chore: update patches * src: support configurable snapshot nodejs/node#50453 * test: remove test-domain-error-types flaky designation nodejs/node#51717 * src: avoid draining platform tasks at FreeEnvironment nodejs/node#51290 * chore: fix accidentally deleted v8 dep * lib: define FormData and fetch etc. in the built-in snapshot nodejs/node#51598 * chore: rebase on main * chore: remove stray log --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: Cheng <zcbenz@gmail.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
At the point of `FreeEnvironment` and onwards, no JavaScript execution associated with the Environment should be triggered. Avoid draining platform tasks that can trigger JavaScript execution in `FreeEnvironment`. The holder of `node::Environment` should immediately call `node::MultiIsolatePlatform::UnregisterIsolate` and `v8::Isolate::Dispose` to cancel pending foreground tasks and join concurrent tasks after the environment was freed. `NodePlatform` can properly handle the case in `RunForegroundTask` when an Isolate out-lives its associated `node::Environment`. PR-URL: #51290 Fixes: #47748 Fixes: #49344 Reviewed-By: VinΓcius LourenΓ§o Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
* chore: bump node in DEPS to v20.13.1 * chore: bump node in DEPS to v20.14.0 * chore: update build_add_gn_build_files.patch * chore: update patches * chore: update patches * build: encode non-ASCII Latin1 characters as one byte in JS2C nodejs/node#51605 * crypto: use EVP_MD_fetch and cache EVP_MD for hashes nodejs/node#51034 * chore: update filenames.json * chore: update patches * src: support configurable snapshot nodejs/node#50453 * test: remove test-domain-error-types flaky designation nodejs/node#51717 * src: avoid draining platform tasks at FreeEnvironment nodejs/node#51290 * chore: fix accidentally deleted v8 dep * lib: define FormData and fetch etc. in the built-in snapshot nodejs/node#51598 * chore: remove stray log * crypto: enable NODE_EXTRA_CA_CERTS with BoringSSL nodejs/node#52217 * test: skip test for dynamically linked OpenSSL nodejs/node#52542 * lib, url: add a `windows` option to path parsing nodejs/node#52509 * src: use dedicated routine to compile function for builtin CJS loader nodejs/node#52016 * test: mark test as flaky nodejs/node#52671 * build,tools: add test-ubsan ci nodejs/node#46297 * src: preload function for Environment nodejs/node#51539 * deps: update c-ares to 1.28.1 nodejs/node#52285 * chore: fixup * events: extract addAbortListener for safe internal use nodejs/node#52081 * module: print location of unsettled top-level await in entry points nodejs/node#51999 * fs: add stacktrace to fs/promises nodejs/node#49849 * chore: fixup indices --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: Cheng <zcbenz@gmail.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
β¦velock (#218) ## Problem The recurring `frontend_unit_tests` TIMEOUT flake (`close timed out after 120000ms` β `Failed to terminate worker while running ...`) is caused by a bug in Node 18.19.1 itself, not by test code. gdb backtrace of a live hung worker shows the livelock: on the worker's **normal exit path**, `FreeEnvironment() β NodePlatform::DrainTasks()` tries to drain the foreground task queue, but `FinalizationRegistryCleanupTask`s keep re-spawning faster than they drain β a native infinite loop at 100% CPU that `worker.terminate()` cannot interrupt. The FinalizationRegistry instances come from jsdom's `iterable-weak-set` (one per Document), which is why every test file is equally exposed and the blamed filename in the error is arbitrary. Upstream fix: nodejs/node#51290 (fixes nodejs/node#47748 "Infinite loop at shutdown"), backported to Node 18.20.3+. We are pinned one patch line short at 18.19.1. ## Fix Bump the bazel Node toolchain to 18.20.4 β the newest 18.x bundled with rules_nodejs 6.3.0, so no rules bump is needed. ## Verification A/B/C stress runs of shard 10/10 under identical 9-shard concurrent load (mimicking bazel's parallel shard execution): | Node | Hangs | |---|---| | 18.19.1 (current) | 12 / 31 iterations (~39%) | | 18.20.4 (this PR) | 0 / 60 | | 18.20.8 | 0 / 60 | Full bazel frontend suite on the 18.20.4 toolchain: 10/10 shards pass. π€ Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
β¦velock (#218) ## Problem The recurring `frontend_unit_tests` TIMEOUT flake (`close timed out after 120000ms` β `Failed to terminate worker while running ...`) is caused by a bug in Node 18.19.1 itself, not by test code. gdb backtrace of a live hung worker shows the livelock: on the worker's **normal exit path**, `FreeEnvironment() β NodePlatform::DrainTasks()` tries to drain the foreground task queue, but `FinalizationRegistryCleanupTask`s keep re-spawning faster than they drain β a native infinite loop at 100% CPU that `worker.terminate()` cannot interrupt. The FinalizationRegistry instances come from jsdom's `iterable-weak-set` (one per Document), which is why every test file is equally exposed and the blamed filename in the error is arbitrary. Upstream fix: nodejs/node#51290 (fixes nodejs/node#47748 "Infinite loop at shutdown"), backported to Node 18.20.3+. We are pinned one patch line short at 18.19.1. ## Fix Bump the bazel Node toolchain to 18.20.4 β the newest 18.x bundled with rules_nodejs 6.3.0, so no rules bump is needed. ## Verification A/B/C stress runs of shard 10/10 under identical 9-shard concurrent load (mimicking bazel's parallel shard execution): | Node | Hangs | |---|---| | 18.19.1 (current) | 12 / 31 iterations (~39%) | | 18.20.4 (this PR) | 0 / 60 | | 18.20.8 | 0 / 60 | Full bazel frontend suite on the 18.20.4 toolchain: 10/10 shards pass. π€ Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit d93131d)
An async WebAssembly.compile() job still running on the platform worker threads when a program was torn down could crash the process in uv_async_send() (SIGSEGV at 0x78, a NULL uv_async_t): the node::CommonEnvironmentSetup destructor unregisters the isolate from the platform before disposing of it, and a compilation step completing in between posts a foreground task to the per-isolate task runner whose async handle is concurrently being nulled and closed. Node's FreeEnvironment() no longer drains the platform (nodejs/node#51290), so deleteIntern() now drains all platform tasks after node::Stop(), while the isolate is still registered, with JavaScript execution disallowed. Also fixes bugs found while testing: - JavaScriptPromise: the promise callback info held a V8 persistent handle without a weak program reference, so releasing a promise after its program was destroyed reset the handle in a disposed isolate (use-after-free) - node's per-process initialization marked the process' stdio file descriptors close-on-exec, so all child processes started after the module was loaded (system(), backquote(), ...) ran without stdin, stdout, and stderr; stdio inheritance is now enabled - JavaScriptObject::toData(): member functions of objects were bound to the function itself instead of the containing object as 'this'
At the point of
FreeEnvironmentand onwards, no JavaScript executionassociated with the Environment should be triggered.
Avoid draining platform tasks that can trigger JavaScript execution in
FreeEnvironment. The holder ofnode::Environmentshould immediatelycall
node::MultiIsolatePlatform::UnregisterIsolateandv8::Isolate::Disposeto cancel pending foreground tasks and joinconcurrent tasks after the environment was freed.
NodePlatformcan properly handle the case inRunForegroundTaskwhenan Isolate out-lives its associated
node::Environment.Fixes: #47748
Fixes: #49344