Skip to content

Commit

Permalink
Only kill zombie processes when the PID still refers to a PHPStan pro…
Browse files Browse the repository at this point in the history
…cess
  • Loading branch information
SanderRonde committed Apr 28, 2024
1 parent 68e19d2 commit 22b9a96
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 19 deletions.
Binary file modified bun.lockb
Binary file not shown.
76 changes: 57 additions & 19 deletions client/src/notificationReceivers/procSpawner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { processNotification } from '../lib/notificationChannels';
import type { LanguageClient } from 'vscode-languageclient/node';
import type { Disposable, ExtensionContext } from 'vscode';
import { PROCESS_SPAWNER_PREFIX, log } from '../lib/log';
import { wait } from '../../../shared/util';
import { lookup, kill } from 'ps-node';

Check failure on line 5 in client/src/notificationReceivers/procSpawner.ts

View workflow job for this annotation

GitHub Actions / lint-and-tsc

"ps-node" is extraneous

interface ProcessDescriptor {
timeout: number;
binStr: string;
}

export class ProcessSpawner implements Disposable {
private static STORAGE_KEY = 'phpstan.processes';
Expand All @@ -22,7 +27,14 @@ export class ProcessSpawner implements Disposable {
'with timeout',
String(timeout)
);
void this._pushPid(pid, timeout);
lookup({ pid }, (err, list) => {
if (err || !list.length) {
void this._pushPid(pid, timeout);
return;
}

void this._pushPid(pid, timeout, list[0].command);
});
})
);
const interval = setInterval(() => this._kill(), 1000 * 60 * 30);
Expand All @@ -40,40 +52,56 @@ export class ProcessSpawner implements Disposable {
}
}

private async _killProc(pid: number): Promise<void> {
private async _killProc(pid: number, binStr?: string): Promise<void> {

Check warning on line 55 in client/src/notificationReceivers/procSpawner.ts

View workflow job for this annotation

GitHub Actions / lint-and-tsc

Async method '_killProc' has no 'await' expression
if (!this._procExists(pid)) {
return;
}
try {
process.kill(pid, 'SIGINT');
// eslint-disable-next-line no-empty
} catch (e) {}
await wait(5000);
try {
process.kill(pid, 'SIGKILL');
// eslint-disable-next-line no-empty
} catch (e) {}
lookup({ pid }, (err, list) => {
if (err || list.length === 0) {
// No longer exists or something went wrong
return;
}

list.forEach(async (proc) => {

Check warning on line 65 in client/src/notificationReceivers/procSpawner.ts

View workflow job for this annotation

GitHub Actions / lint-and-tsc

Promise returned in function argument where a void return was expected

Check warning on line 65 in client/src/notificationReceivers/procSpawner.ts

View workflow job for this annotation

GitHub Actions / lint-and-tsc

Async arrow function has no 'await' expression
if (binStr && proc.command !== binStr) {
return;
}

kill(proc.pid, 'SIGINT', (err) => {
if (err) {
kill(proc.pid, 'SIGKILL');
}
});
});
});
}

private _kill(killTimeoutless: boolean = false): void {
const processes = this._context.workspaceState.get(
ProcessSpawner.STORAGE_KEY,
{}
) as Record<number, number>;
) as Record<number, number | ProcessDescriptor>;
if (Object.keys(processes).length === 0) {
return;
}

const killed: number[] = [];
Object.entries(processes).forEach(([pid, timeout]) => {
if (Date.now() > timeout && (timeout !== 0 || killTimeoutless)) {
Object.entries(processes).forEach(([pid, data]) => {
const descriptor =
typeof data === 'number'
? { timeout: data, binStr: undefined }

Check warning on line 92 in client/src/notificationReceivers/procSpawner.ts

View workflow job for this annotation

GitHub Actions / lint-and-tsc

Object properties must go on a new line
: data;
if (
Date.now() > descriptor.timeout &&
(descriptor.timeout !== 0 || killTimeoutless)
) {
const pidNum = parseInt(pid, 10);
killed.push(pidNum);
void this._killProc(pidNum);
void this._killProc(pidNum, descriptor.binStr);
}
});

const newProcesses: Record<number, number> = {};
const newProcesses: Record<number, number | ProcessDescriptor> = {};
for (const pid in processes) {
if (killed.includes(parseInt(pid, 10))) {
continue;
Expand All @@ -86,10 +114,20 @@ export class ProcessSpawner implements Disposable {
);
}

private async _pushPid(pid: number, timeout: number): Promise<void> {
private async _pushPid(
pid: number,
timeout: number,
binStr?: string
): Promise<void> {
const targetTime = timeout === 0 ? 0 : Date.now() + timeout;
await this._context.workspaceState.update(ProcessSpawner.STORAGE_KEY, {
...this._context.workspaceState.get(ProcessSpawner.STORAGE_KEY, {}),
[pid]: timeout === 0 ? 0 : Date.now() + timeout,
[pid]: binStr
? {
timeout: targetTime,
binStr,
}
: targetTime,
});
}

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@
"@types/glob": "^7.2.0",
"@types/mocha": "^9.1.0",
"@types/node": "^17.0.18",
"@types/ps-node": "^0.1.3",
"@types/rimraf": "^3",
"@typescript-eslint/eslint-plugin": "^5.12.0",
"@typescript-eslint/parser": "latest",
Expand All @@ -326,6 +327,7 @@
"vscode-test": "^1.6.1"
},
"dependencies": {
"ps-node": "^0.1.6",
"tmp-promise": "^3.0.3"
}
}

0 comments on commit 22b9a96

Please sign in to comment.