From ea83153791bdfb8da96ccbdc2c6becb716b2b6cd Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 21 Sep 2022 15:57:24 +0200 Subject: [PATCH] fix(node-runtime-worker-thread): close child process on parent disconnect This ensure that the auxilliary child processes stops once its parent process has terminated, even if it did not perform a proper shutdown of the child process. --- .../src/child-process-proxy.spec.ts | 40 ++++++++++++++++++- .../src/child-process-proxy.ts | 1 + 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/node-runtime-worker-thread/src/child-process-proxy.spec.ts b/packages/node-runtime-worker-thread/src/child-process-proxy.spec.ts index 503c297bd..2b7571ca9 100644 --- a/packages/node-runtime-worker-thread/src/child-process-proxy.spec.ts +++ b/packages/node-runtime-worker-thread/src/child-process-proxy.spec.ts @@ -1,8 +1,10 @@ import path from 'path'; -import { ChildProcess, fork } from 'child_process'; +import { ChildProcess, fork, spawn } from 'child_process'; import { Caller, cancel, createCaller } from './rpc'; import { expect } from 'chai'; import { WorkerRuntime } from './worker-runtime'; +import { once } from 'events'; +import { promisify } from 'util'; const childProcessModulePath = path.resolve( __dirname, @@ -22,7 +24,7 @@ describe('child process worker proxy', () => { } if (childProcess) { - childProcess.kill('SIGTERM'); + childProcess.disconnect(); childProcess = null; } }); @@ -34,4 +36,38 @@ describe('child process worker proxy', () => { const result = await caller.evaluate('1 + 1'); expect(result.printable).to.equal(2); }); + + it('should exit on its own when the parent process disconnects', async() => { + const intermediateProcess = spawn(process.execPath, + ['-e', `require("child_process") + .fork(${JSON.stringify(childProcessModulePath)}) + .on("message", function(m) { console.log("message " + m + " from " + this.pid) })`], + { stdio: ['pipe', 'pipe', 'inherit'] }); + + // Make sure the outer child process runs and has created the inner child process + const [message] = await once(intermediateProcess.stdout.setEncoding('utf8'), 'data'); + const match = message.trim().match(/^message ready from (?\d+)$/); + expect(match).to.not.equal(null); + + // Make sure the inner child process runs + const childPid = +match.groups.pid; + process.kill(childPid, 0); + + // Kill the intermediate process and wait for the inner child process to also close + intermediateProcess.kill('SIGTERM'); + let innerChildHasStoppedRunning = false; + for (let i = 0; i < 200; i++) { + try { + process.kill(childPid, 0); + } catch (err) { + if (err.code === 'ESRCH') { + innerChildHasStoppedRunning = true; + break; + } + throw err; + } + await promisify(setTimeout)(10); + } + expect(innerChildHasStoppedRunning).to.equal(true); + }); }); diff --git a/packages/node-runtime-worker-thread/src/child-process-proxy.ts b/packages/node-runtime-worker-thread/src/child-process-proxy.ts index 945e44a56..ff805939d 100644 --- a/packages/node-runtime-worker-thread/src/child-process-proxy.ts +++ b/packages/node-runtime-worker-thread/src/child-process-proxy.ts @@ -114,6 +114,7 @@ const messageBus = createCaller(['emit', 'on'], process); exposeAll(messageBus, workerProcess); +process.once('disconnect', () => process.exit()); process.nextTick(() => { // eslint-disable-next-line chai-friendly/no-unused-expressions process.send?.('ready');