Skip to content

Commit f348140

Browse files
committed
fix(native): remove AdjustExternalMemory from destructors to fix Node 24 crash
Node.js 24's V8 has stricter validation in ArrayBufferSweeper::~ArrayBufferSweeper() during Heap::TearDown(). Calling AdjustExternalMemory() from ObjectWrap destructors races with the sweeper's teardown, causing the assertion: Check failed: static_cast<int64_t>(amount_before) >= -delta. This manifested as crashes on Node 24.x only, with all tests passing but the process crashing during shutdown. The fix removes AdjustExternalMemory() calls from VideoFrame and AudioData destructors. External memory tracking is now handled exclusively in Close(), which the WebCodecs spec mandates users must call for proper resource management. If users violate the spec and don't call Close(), external memory accounting will be slightly off, but the process won't crash. This is acceptable since spec-violating code is a user error. Ref: nodejs/node-addon-api#1153 Ref: nodejs/node#38492
1 parent 8b7044d commit f348140

File tree

2 files changed

+25
-12
lines changed

2 files changed

+25
-12
lines changed

src/audio_data.cc

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,18 @@ AudioData::AudioData(const Napi::CallbackInfo& info)
207207

208208
AudioData::~AudioData() {
209209
webcodecs::counterAudioData--;
210-
// Release external memory tracking if close() was never called.
211-
if (!data_.empty()) {
212-
Napi::MemoryManagement::AdjustExternalMemory(
213-
Env(), -static_cast<int64_t>(data_.size()));
214-
}
210+
// Note: We intentionally DO NOT call AdjustExternalMemory here.
211+
//
212+
// Calling NAPI functions (including AdjustExternalMemory) from destructors
213+
// during V8 shutdown is unsafe and causes crashes on Node.js 24+ due to
214+
// race conditions with V8's ArrayBufferSweeper during Heap::TearDown().
215+
// See: https://github.com/nodejs/node-addon-api/issues/1153
216+
//
217+
// The WebCodecs spec mandates that close() must be called for proper
218+
// resource management. External memory tracking is handled exclusively
219+
// in Close() to avoid shutdown crashes.
220+
data_.clear();
221+
data_.shrink_to_fit();
215222
}
216223

217224
size_t AudioData::GetBytesPerSample() const {

src/video_frame.cc

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -424,13 +424,19 @@ VideoFrame::VideoFrame(const Napi::CallbackInfo& info)
424424

425425
VideoFrame::~VideoFrame() {
426426
webcodecs::counterVideoFrames--;
427-
// If close() wasn't called, we still need to release external memory.
428-
// Note: If close() was called, data_ is already empty, so this is a no-op.
429-
if (!data_.empty()) {
430-
// Env() is available in destructor via ObjectWrap.
431-
Napi::MemoryManagement::AdjustExternalMemory(
432-
Env(), -static_cast<int64_t>(data_.size()));
433-
}
427+
// Note: We intentionally DO NOT call AdjustExternalMemory here.
428+
//
429+
// Calling NAPI functions (including AdjustExternalMemory) from destructors
430+
// during V8 shutdown is unsafe and causes crashes on Node.js 24+ due to
431+
// race conditions with V8's ArrayBufferSweeper during Heap::TearDown().
432+
// See: https://github.com/nodejs/node-addon-api/issues/1153
433+
//
434+
// The WebCodecs spec mandates that close() must be called for proper
435+
// resource management. External memory tracking is handled exclusively
436+
// in Close() to avoid shutdown crashes.
437+
//
438+
// If close() wasn't called, external memory accounting will be slightly off,
439+
// but this is a user error (violating WebCodecs spec) and won't crash.
434440
data_.clear();
435441
data_.shrink_to_fit();
436442
}

0 commit comments

Comments
 (0)