GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — src: stop waking task_queues_async_ before closing it by spankyed · Pull Request #67417 · nodejs/node Conversation
|
| Node | gc({ type: 'minor' }) before exit |
no explicit gc |
|---|---|---|
v27.0.0-alpha (setup-node latest) |
27 | 0 |
| v26.13.0 | 0 | 26 |
| v24.22.2 | 0 | 36 |
| v22.23.4 | 0 | 0 |
| v24.22.2, Linux | 0 | 0 |
// One export, `make(size)`: an external Buffer of that many bytes over malloc'd memory, freed by its finalizer. #include <node_api.h> #include <stdint.h> #include <stdlib.h> static void release(napi_env env, void* data, void* hint) { (void)env; (void)hint; free(data); } static napi_value make(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value arg; uint32_t size = 16; napi_get_cb_info(env, info, &argc, &arg, NULL, NULL); if (argc == 1) napi_get_value_uint32(env, arg, &size); void* data = calloc(1, size); napi_value buffer; if (data == NULL || napi_create_external_buffer(env, size, data, release, NULL, &buffer) != napi_ok) { free(data); napi_throw_error(env, NULL, "no external buffer"); return NULL; } return buffer; } NAPI_MODULE_INIT() { napi_value fn; napi_create_function(env, "make", NAPI_AUTO_LENGTH, make, NULL, &fn); napi_set_named_property(env, exports, "make", fn); return exports; }
// Makes COUNT external Buffers of SIZE bytes, drops each, starts a collection of KIND (minor, major, or none to start // none) and runs out of work, so the process ends while V8 may still be freeing them: // node child.js ADDON [COUNT] [SIZE] [KIND] const [addon, count = '20000', size = '16', kind = 'minor'] = process.argv.slice(2); const { make } = require(addon); for (let i = 0; i < Number(count); i++) make(Number(size)); if (kind !== 'none') { require('node:v8').setFlagsFromString('--expose-gc'); require('node:vm').runInNewContext('gc')({ type: kind }); }
The new test, test/addons/buffer-free-callback/test-exit.js, does the same with the existing buffer-free-callback binding. It can only fail on Windows, and not on every run, so it spawns 20 children.
main at 50ce73f built on a GitHub-hosted windows-2025 runner (vcbuild.bat clang-cl), as it is and with this patch, then the reproduction above run 200 times each way against both builds (2026-11-11). Processes that died with the assertion:
| buffers, size, gc before exit | main |
main with this patch |
|---|---|---|
| 20000, 16 bytes, minor | 35 | 0 |
| 200000, 16 bytes, minor | 47 | 0 |
| 20000, 4096 bytes, minor | 40 | 0 |
| 20000, 16 bytes, major | 30 | 0 |
| 20000, 16 bytes, none | 0 | 0 |
| 200000, 64 bytes, none | 0 | 0 |
On the patched build test/addons/buffer-free-callback/test-exit.js passed 10 runs out of 10. I did not run that test file against the unpatched build; the first row above is the same workload, so 20 children should fail it about 98% of the time there. I have not run the rest of the test suite on Windows.
#56645 and #64322 report the same assertion. #61999 fixed it for the platform's task queues and is in v24.22.2, which still aborts through this path.
An AI coding assistant was used to track down the race, write the patch and the test, and draft this description. The counts above are from CI runs of the reproduction.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Environment::RunCleanup() closed task_queues_async_ and only afterwards cleared task_queues_async_initialized_, so another thread could still call uv_async_send() on the closing handle through SetImmediateThreadsafe() or RequestInterrupt(). On Windows that aborts with "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)". This happens when V8 frees the backing store of an external Buffer on a sweeper thread while the process exits. Clear the flag, under the mutex the senders hold, before the handle is closed. Signed-off-by: Angel <[email protected]>
|
Welcome to Node.js, and thank you for your first contribution! Before review, please take a moment to read:
Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.