Skip to content

Commit 519ef0e

Browse files
authored
🛑 Kill process trees on Linux (#2529)
Fixes jupyter-book/jupyter-book#2484
1 parent ef668bd commit 519ef0e

File tree

7 files changed

+33
-7
lines changed

7 files changed

+33
-7
lines changed

.changeset/wide-shoes-laugh.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'myst-cli-utils': patch
3+
'myst-execute': patch
4+
'myst-cli': patch
5+
---
6+
7+
Kill process trees on linux when shutting down the application and jupyter servers

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/myst-cli-utils/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
},
3434
"dependencies": {
3535
"chalk": "^5.2.0",
36-
"pretty-hrtime": "^1.0.3"
36+
"pretty-hrtime": "^1.0.3",
37+
"tree-kill": "^1.2.2"
3738
},
3839
"devDependencies": {
3940
"@types/pretty-hrtime": "^1.0.1",

packages/myst-cli-utils/src/exec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import util from 'util';
22
import type { ExecOptions } from 'child_process';
33
import child_process from 'child_process';
44
import type { Logger } from './types.js';
5+
import treeKill from 'tree-kill';
56

67
function execWrapper(
78
command: string,
@@ -45,3 +46,19 @@ export function makeExecutable(
4546
) {
4647
return util.promisify(makeExecWrapper(command, log, options)) as () => Promise<string>;
4748
}
49+
50+
/**
51+
* On Linux, child processes will not be terminated when attempting to kill their parent
52+
* This function uses `tree-kill` to kill the process and all its child processes
53+
*/
54+
export function killProcessTree(process: child_process.ChildProcess) {
55+
if (!process.pid) {
56+
process.kill('SIGTERM');
57+
return;
58+
}
59+
treeKill(process.pid, 'SIGTERM', (err) => {
60+
if (err && process.pid) {
61+
treeKill(process.pid, 'SIGKILL');
62+
}
63+
});
64+
}

packages/myst-cli-utils/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export {
77
createGitLogger,
88
createNpmLogger,
99
} from './logger.js';
10-
export { exec, makeExecutable } from './exec.js';
10+
export { exec, makeExecutable, killProcessTree } from './exec.js';
1111
export { clirun, tic } from './utils.js';
1212
export { isUrl } from './isUrl.js';
1313
export { Session, getSession } from './session.js';

packages/myst-cli/src/build/site/start.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import chalk from 'chalk';
22
import cors from 'cors';
33
import express from 'express';
44
import getPort, { portNumbers } from 'get-port';
5-
import { makeExecutable } from 'myst-cli-utils';
5+
import { makeExecutable, killProcessTree } from 'myst-cli-utils';
66
import type child_process from 'child_process';
77
import { nanoid } from 'nanoid';
88
import { join } from 'node:path';
@@ -177,7 +177,7 @@ export async function startServer(
177177
start().catch((e) => session.log.debug(e));
178178
});
179179
appServer.stop = () => {
180-
appServer.process.kill();
180+
killProcessTree(appServer.process);
181181
server.stop();
182182
};
183183
return appServer;

packages/myst-execute/src/manager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import which from 'which';
33
import { spawn } from 'node:child_process';
44
import * as readline from 'node:readline';
55
import type { ISession, Logger } from 'myst-cli-utils';
6+
import { killProcessTree } from 'myst-cli-utils';
67
import chalk from 'chalk';
78

89
export type JupyterServerSettings = Partial<ServerConnection.ISettings> & {
@@ -129,5 +130,5 @@ export async function launchJupyterServer(
129130
log.info(`🪐 ${chalk.greenBright('Jupyter server started')}\n ${chalk.dim(url)}`);
130131

131132
// Register settings destructor (to kill server)
132-
return { ...settings, dispose: () => proc.kill() };
133+
return { ...settings, dispose: () => killProcessTree(proc) };
133134
}

0 commit comments

Comments
 (0)