Skip to content

Commit 0fc1cf4

Browse files
worker: fix stream racing with terminate
`OnStreamAfterReqFinished` uses `v8::Object::Has` to check if it needs to call `oncomplete`. `v8::Object::Has` needs to execute Javascript. However when worker threads are involved, `OnStreamAfterReqFinished` may be called after the worker thread termination has begun via `worker.terminate()`. This makes `v8::Object::Has` return `Nothing`, which triggers an assert. This diff fixes the issue by simply defaulting us to `false` in the case where `Nothing` is returned. This is sound because we can't execute `oncomplete` anyway as the isolate is terminating. Fixes: #38418 PR-URL: #42874 Reviewed-By: Darshan Sen <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 187b99b commit 0fc1cf4

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

src/stream_base.cc

+1
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@ void ReportWritesToJSStreamListener::OnStreamAfterReqFinished(
601601
StreamReq* req_wrap, int status) {
602602
StreamBase* stream = static_cast<StreamBase*>(stream_);
603603
Environment* env = stream->stream_env();
604+
if (env->is_stopping()) return;
604605
AsyncWrap* async_wrap = req_wrap->GetAsyncWrap();
605606
HandleScope handle_scope(env->isolate());
606607
Context::Scope context_scope(env->context());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (!common.hasCrypto)
4+
common.skip('missing crypto');
5+
const assert = require('assert');
6+
const http2 = require('http2');
7+
const makeDuplexPair = require('../common/duplexpair');
8+
const { Worker, parentPort } = require('worker_threads');
9+
10+
// This test ensures that workers can be terminated without error while
11+
// stream activity is ongoing, in particular the C++ function
12+
// ReportWritesToJSStreamListener::OnStreamAfterReqFinished.
13+
14+
const MAX_ITERATIONS = 20;
15+
const MAX_THREADS = 10;
16+
17+
// Do not use isMainThread so that this test itself can be run inside a Worker.
18+
if (!process.env.HAS_STARTED_WORKER) {
19+
process.env.HAS_STARTED_WORKER = 1;
20+
21+
function spinWorker(iter) {
22+
const w = new Worker(__filename);
23+
w.on('message', common.mustCall((msg) => {
24+
assert.strictEqual(msg, 'terminate');
25+
w.terminate();
26+
}));
27+
28+
w.on('exit', common.mustCall(() => {
29+
if (iter < MAX_ITERATIONS)
30+
spinWorker(++iter);
31+
}));
32+
}
33+
34+
for (let i = 0; i < MAX_THREADS; i++) {
35+
spinWorker(0);
36+
}
37+
} else {
38+
const server = http2.createServer();
39+
let i = 0;
40+
server.on('stream', (stream, headers) => {
41+
if (i === 1) {
42+
parentPort.postMessage('terminate');
43+
}
44+
i++;
45+
46+
stream.end('');
47+
});
48+
49+
const { clientSide, serverSide } = makeDuplexPair();
50+
server.emit('connection', serverSide);
51+
52+
const client = http2.connect('http://localhost:80', {
53+
createConnection: () => clientSide,
54+
});
55+
56+
function makeRequests() {
57+
for (let i = 0; i < 3; i++) {
58+
client.request().end();
59+
}
60+
setImmediate(makeRequests);
61+
}
62+
makeRequests();
63+
}

0 commit comments

Comments
 (0)