From abaa6900ecbbe014339e70299ec5745399c44fab Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Mon, 8 Dec 2025 20:53:13 +0800 Subject: [PATCH 1/3] Fix isolate cleanup segfault (#245) --- bindings/profilers/heap.cc | 48 +++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/bindings/profilers/heap.cc b/bindings/profilers/heap.cc index dc80992b..7c6eaad4 100644 --- a/bindings/profilers/heap.cc +++ b/bindings/profilers/heap.cc @@ -22,6 +22,8 @@ #include #include +#include +#include #include #include @@ -29,6 +31,24 @@ namespace dd { +// Track which isolates have cleanup hooks registered for heap profiler +static std::unordered_set g_heap_profiler_isolates; +static std::mutex g_heap_profiler_mutex; + +// Cleanup hook to stop heap profiler before isolate is destroyed +static void HeapProfilerCleanupHook(void* data) { + auto isolate = static_cast(data); + { + const std::lock_guard lock(g_heap_profiler_mutex); + g_heap_profiler_isolates.erase(isolate); + } + // Stop the sampling heap profiler to prevent crash during V8 teardown + auto heap_profiler = isolate->GetHeapProfiler(); + if (heap_profiler) { + heap_profiler->StopSamplingHeapProfiler(); + } +} + static size_t NearHeapLimit(void* data, size_t current_heap_limit, size_t initial_heap_limit); @@ -497,6 +517,19 @@ size_t NearHeapLimit(void* data, } NAN_METHOD(HeapProfiler::StartSamplingHeapProfiler) { + auto isolate = info.GetIsolate(); + + // Register cleanup hook if not already registered for this isolate + { + const std::lock_guard lock(g_heap_profiler_mutex); + if (g_heap_profiler_isolates.find(isolate) == + g_heap_profiler_isolates.end()) { + node::AddEnvironmentCleanupHook( + isolate, HeapProfilerCleanupHook, isolate); + g_heap_profiler_isolates.insert(isolate); + } + } + if (info.Length() == 2) { if (!info[0]->IsUint32()) { return Nan::ThrowTypeError("First argument type must be uint32."); @@ -508,10 +541,10 @@ NAN_METHOD(HeapProfiler::StartSamplingHeapProfiler) { uint64_t sample_interval = info[0].As()->Value(); int stack_depth = info[1].As()->Value(); - info.GetIsolate()->GetHeapProfiler()->StartSamplingHeapProfiler( - sample_interval, stack_depth); + isolate->GetHeapProfiler()->StartSamplingHeapProfiler(sample_interval, + stack_depth); } else { - info.GetIsolate()->GetHeapProfiler()->StartSamplingHeapProfiler(); + isolate->GetHeapProfiler()->StartSamplingHeapProfiler(); } } @@ -521,6 +554,15 @@ NAN_METHOD(HeapProfiler::StopSamplingHeapProfiler) { auto isolate = info.GetIsolate(); isolate->GetHeapProfiler()->StopSamplingHeapProfiler(); PerIsolateData::For(isolate)->GetHeapProfilerState().reset(); + + // Remove cleanup hook since profiler is explicitly stopped + { + const std::lock_guard lock(g_heap_profiler_mutex); + if (g_heap_profiler_isolates.erase(isolate) == 1) { + node::RemoveEnvironmentCleanupHook( + isolate, HeapProfilerCleanupHook, isolate); + } + } } // Signature: From f1cbf308aa7513a089acf059bc54893c010be398 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Thu, 11 Dec 2025 14:54:42 +0100 Subject: [PATCH 2/3] Start using AsyncContextFrame already on Node.js 22 (#247) --- bindings/profilers/wall.cc | 31 ++++++++++++++++++++----------- ts/src/index.ts | 1 - ts/test/test-time-profiler.ts | 6 ++++-- ts/test/worker.ts | 7 +++++-- ts/test/worker2.ts | 7 +++++-- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/bindings/profilers/wall.cc b/bindings/profilers/wall.cc index 6c6e5db1..11564677 100644 --- a/bindings/profilers/wall.cc +++ b/bindings/profilers/wall.cc @@ -42,17 +42,25 @@ struct TimeTicks { static int64_t Now(); }; } // namespace base -#if NODE_MAJOR_VERSION >= 24 +#if NODE_MAJOR_VERSION >= 22 + +// Available from 22.7.0 +#define DD_WALL_USE_CPED true + namespace internal { -#if NODE_MAJOR_VERSION == 24 +#if NODE_MAJOR_VERSION < 25 struct HandleScopeData { v8::internal::Address* next; v8::internal::Address* limit; }; -#endif +#endif // NODE_MAJOR_VERSION < 25 +#if NODE_MAJOR_VERSION >= 24 constexpr int kHandleBlockSize = v8::internal::KB - 2; +#endif // NODE_MAJOR_VERSION >= 24 } // namespace internal -#endif +#else // NODE_MAJOR_VERSION >= 22 +#define DD_WALL_USE_CPED false +#endif // } // namespace v8 static int64_t Now() { @@ -61,18 +69,14 @@ static int64_t Now() { #else #define DD_WALL_USE_SIGPROF false +#define DD_WALL_USE_CPED false + static int64_t Now() { return 0; }; #endif -#if NODE_MAJOR_VERSION >= 23 -#define DD_WALL_USE_CPED true -#else -#define DD_WALL_USE_CPED false -#endif - using namespace v8; namespace dd { @@ -905,7 +909,12 @@ NAN_METHOD(WallProfiler::New) { #if !DD_WALL_USE_CPED if (useCPED) { return Nan::ThrowTypeError( - "useCPED is not supported on this Node.js version."); +#ifndef _WIN32 + "useCPED is not supported on this Node.js version." +#else + "useCPED is not supported on Windows." +#endif + ); } #endif diff --git a/ts/src/index.ts b/ts/src/index.ts index 42454629..51cceddb 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -40,7 +40,6 @@ export const time = { v8ProfilerStuckEventLoopDetected: timeProfiler.v8ProfilerStuckEventLoopDetected, getState: timeProfiler.getState, - getMetrics: timeProfiler.getMetrics, constants: timeProfiler.constants, }; diff --git a/ts/test/test-time-profiler.ts b/ts/test/test-time-profiler.ts index 6c5b8e00..1c887525 100644 --- a/ts/test/test-time-profiler.ts +++ b/ts/test/test-time-profiler.ts @@ -29,8 +29,10 @@ import {satisfies} from 'semver'; import assert from 'assert'; const useCPED = - satisfies(process.versions.node, '>=24.0.0') && - !process.execArgv.includes('--no-async-context-frame'); + (satisfies(process.versions.node, '>=24.0.0') && + !process.execArgv.includes('--no-async-context-frame')) || + (satisfies(process.versions.node, '>=22.7.0') && + process.execArgv.includes('--experimental-async-context-frame')); const collectAsyncId = satisfies(process.versions.node, '>=24.0.0'); diff --git a/ts/test/worker.ts b/ts/test/worker.ts index eeffd3c3..bcc7d147 100644 --- a/ts/test/worker.ts +++ b/ts/test/worker.ts @@ -13,8 +13,11 @@ const intervalMicros = 10000; const withContexts = process.platform === 'darwin' || process.platform === 'linux'; const useCPED = - satisfies(process.versions.node, '>=24.0.0') && - !process.execArgv.includes('--no-async-context-frame'); + withContexts && + ((satisfies(process.versions.node, '>=24.0.0') && + !process.execArgv.includes('--no-async-context-frame')) || + (satisfies(process.versions.node, '>=22.7.0') && + process.execArgv.includes('--experimental-async-context-frame'))); const collectAsyncId = withContexts && satisfies(process.versions.node, '>=24.0.0'); diff --git a/ts/test/worker2.ts b/ts/test/worker2.ts index e06f5578..eaacd07c 100644 --- a/ts/test/worker2.ts +++ b/ts/test/worker2.ts @@ -10,8 +10,11 @@ const withContexts = process.platform === 'darwin' || process.platform === 'linux'; const useCPED = - satisfies(process.versions.node, '>=24.0.0') && - !process.execArgv.includes('--no-async-context-frame'); + withContexts && + ((satisfies(process.versions.node, '>=24.0.0') && + !process.execArgv.includes('--no-async-context-frame')) || + (satisfies(process.versions.node, '>=22.7.0') && + process.execArgv.includes('--experimental-async-context-frame'))); const collectAsyncId = withContexts && satisfies(process.versions.node, '>=24.0.0'); From 413e7a8b9e37960fc26798d307c990c338f66aa4 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Mon, 15 Dec 2025 16:38:35 +0100 Subject: [PATCH 3/3] v5.13.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e9ae169..10ad1a02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@datadog/pprof", - "version": "5.12.0", + "version": "5.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@datadog/pprof", - "version": "5.12.0", + "version": "5.13.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 000c5a08..f1317ad9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/pprof", - "version": "5.12.0", + "version": "5.13.0", "description": "pprof support for Node.js", "repository": { "type": "git",