-
Notifications
You must be signed in to change notification settings - Fork 3
[SCP-759] Fix profiler shutdown when isolate is terminated #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
Conversation
BenchmarksBenchmark execution time: 2025-06-10 09:16:55 Comparing candidate commit 17343f3 in PR branch Found 2 performance improvements and 0 performance regressions! Performance is the same for 88 metrics, 30 unstable metrics. scenario:profiler-light-load-no-wall-profiler-22
scenario:profiler-light-load-with-wall-profiler-24
|
2da68aa
to
488b05f
Compare
Overall package sizeSelf size: 1.58 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | source-map | 0.7.4 | 226 kB | 226 kB | | pprof-format | 2.1.0 | 111.69 kB | 111.69 kB | | p-limit | 3.1.0 | 7.75 kB | 13.78 kB | | delay | 5.0.0 | 11.17 kB | 11.17 kB | | node-gyp-build | 3.9.0 | 8.81 kB | 8.81 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
87e6b98
to
f9a54c2
Compare
ts/test/test-worker-threads.ts
Outdated
it('should not crash when worker is terminated', async function () { | ||
this.timeout(20000); | ||
const nruns = 5; | ||
const concurrentWorkers = 100; | ||
for (let i = 0; i < nruns; i++) { | ||
const workers = []; | ||
for (let j = 0; j < concurrentWorkers; j++) { | ||
const worker = new Worker('./out/test/worker2.js') | ||
worker.postMessage('hello') | ||
|
||
worker.on('message', () => { | ||
worker.terminate() | ||
}) | ||
|
||
workers.push(new Promise<void>((resolve, reject) => { | ||
worker.on('exit', (exitCode) => { | ||
if (exitCode === 1) { | ||
resolve() | ||
} else { | ||
reject(new Error('Worker exited with code 0')) | ||
} | ||
}) | ||
})) | ||
} | ||
await Promise.all(workers) | ||
} | ||
}); |
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.
a03101a
to
10b3b89
Compare
When an isolate is terminated abruptly (eg. by Worker.terminate()), the profiler is not stopped and not removed from the isolate->profiler map. This can lead to a crash. This commit adds a cleanup hook with `node::AddEnvironmentCleanupHook` that is called when the isolate is destroyed. The cleanup hook stops the profiler and removes it from the profiler map.
10b3b89
to
ab2ddb2
Compare
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.
Overall looks great! Going to approve as-is but I wouldn't mind if you considered my suggestion for that if (isolate != nullptr)
block in Dispose
.
if (cpuProfiler_ != nullptr) { | ||
cpuProfiler_->Dispose(); | ||
cpuProfiler_ = nullptr; | ||
|
||
g_profilers.RemoveProfiler(isolate, this); | ||
if (removeFromMap) { |
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.
You could have one big if (isolate != nullptr)
block here, and move both this and the if (collectAsyncId_)
logic (and the cleanup hook removal) below into it. I know they're no-ops when isolate is null, but I think it'd read better as in "here's what is executed when isolate is known".
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 removed the call to Dispose
from destructor
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.
Well, that's one way to simplify it 😁. I like it. I did feel our disposal and destruction story was a bit overcomplicated, so this is 👍.
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.
Dispose
call in destructor felt like a desperate attempt to clean up, now that we have the environment cleanup hook, this should not be needed anymore (and I am not even sure in which cases, other than profiler stop, the destructor is called).
return {}; | ||
} | ||
|
||
std::string WallProfiler::StartInternal() { | ||
v8::ProfilerId WallProfiler::StartInternal() { |
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.
Nice that we can use this now 😄
bindings/profilers/wall.cc
Outdated
@@ -548,20 +583,25 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod, | |||
} | |||
|
|||
WallProfiler::~WallProfiler() { | |||
Dispose(nullptr); | |||
Dispose(nullptr, true); |
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.
isolate == nullptr
will make g_profilers.RemoveProfiler(isolate, this);
in Dispose
a no-op anyway so you might as well be passing false. On the other hand, if we do the transformation in Dispose I suggested then it'd get short-circuited by the nullptr anyway so it doesn't matter what's the value here.
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 think we should remove this call to Dispose
, the only case when it's not a no-op is when dispose
is called on a running profiler, and that should be an error.
@@ -366,6 +380,27 @@ static int64_t GetV8ToEpochOffset() { | |||
return V8toEpochOffset; | |||
} | |||
|
|||
void WallProfiler::CleanupHook(void* data) { |
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.
Making this a private static method on WallProfiler actually isn't a bad idea. I should probably do an equivalent for GC callbacks so I can make them private.
it('should not crash when worker is terminated', async function () { | ||
this.timeout(30000); | ||
const nruns = 5; | ||
const concurrentWorkers = 20; | ||
for (let i = 0; i < nruns; i++) { | ||
const workers = []; | ||
for (let j = 0; j < concurrentWorkers; j++) { | ||
const worker = new Worker('./out/test/worker2.js'); | ||
worker.postMessage('hello'); | ||
|
||
worker.on('message', () => { | ||
worker.terminate(); | ||
}); | ||
|
||
workers.push( | ||
new Promise<void>((resolve, reject) => { | ||
worker.on('exit', exitCode => { | ||
if (exitCode === 1) { | ||
resolve(); | ||
} else { | ||
reject(new Error('Worker exited with code 0')); | ||
} | ||
}); | ||
}) | ||
); | ||
} | ||
await Promise.all(workers); | ||
} | ||
}); |
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.
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.
🚢
* Fix profiler shutdown when isolate is terminated When an isolate is terminated abruptly (eg. by Worker.terminate()), the profiler is not stopped and not removed from the isolate->profiler map. This can lead to a crash. This commit adds a cleanup hook with `node::AddEnvironmentCleanupHook` that is called when the isolate is destroyed. The cleanup hook stops the profiler and removes it from the profiler map.
* Fix profiler shutdown when isolate is terminated When an isolate is terminated abruptly (eg. by Worker.terminate()), the profiler is not stopped and not removed from the isolate->profiler map. This can lead to a crash. This commit adds a cleanup hook with `node::AddEnvironmentCleanupHook` that is called when the isolate is destroyed. The cleanup hook stops the profiler and removes it from the profiler map.
What does this PR do?:
When an isolate is terminated abruptly (eg. by Worker.terminate()), the profiler is not stopped and not removed from the isolate->profiler map. This can lead to a crash.
This commit adds a cleanup hook with
node::AddEnvironmentCleanupHook
that is called when the isolate is destroyed.The cleanup hook stops the profiler and removes it from the profiler map.