fix: add graceful shutdown path when heapprofile is enabled

This commit is contained in:
deepak1556
2026-03-04 13:08:01 +09:00
parent 92de3b63d5
commit f6da4b4e1a

View File

@@ -24,6 +24,12 @@ const contentLengthSize: number = Buffer.byteLength(contentLength, 'utf8');
const blank: number = Buffer.from(' ', 'utf8')[0]; const blank: number = Buffer.from(' ', 'utf8')[0];
const backslashR: number = Buffer.from('\r', 'utf8')[0]; const backslashR: number = Buffer.from('\r', 'utf8')[0];
const backslashN: number = Buffer.from('\n', 'utf8')[0]; const backslashN: number = Buffer.from('\n', 'utf8')[0];
const gracefulExitTimeout = 5000;
const tsServerExitRequest: Proto.Request = {
seq: 0,
type: 'request',
command: 'exit',
};
class ProtocolBuffer { class ProtocolBuffer {
@@ -207,10 +213,15 @@ function getTssDebugBrk(): string | undefined {
} }
class IpcChildServerProcess extends Disposable implements TsServerProcess { class IpcChildServerProcess extends Disposable implements TsServerProcess {
private _killTimeout: NodeJS.Timeout | undefined;
private _isShuttingDown = false;
constructor( constructor(
private readonly _process: child_process.ChildProcess, private readonly _process: child_process.ChildProcess,
private readonly _useGracefulShutdown: boolean,
) { ) {
super(); super();
this._process.once('exit', () => this.clearKillTimeout());
} }
write(serverRequest: Proto.Request): void { write(serverRequest: Proto.Request): void {
@@ -230,18 +241,47 @@ class IpcChildServerProcess extends Disposable implements TsServerProcess {
} }
kill(): void { kill(): void {
this._process.kill(); if (!this._useGracefulShutdown) {
this._process.kill();
return;
}
if (this._isShuttingDown) {
return;
}
this._isShuttingDown = true;
try {
this._process.send(tsServerExitRequest);
} catch {
this._process.kill();
return;
}
this._killTimeout = setTimeout(() => this._process.kill(), gracefulExitTimeout);
this._killTimeout.unref?.();
}
private clearKillTimeout(): void {
if (this._killTimeout) {
clearTimeout(this._killTimeout);
this._killTimeout = undefined;
}
} }
} }
class StdioChildServerProcess extends Disposable implements TsServerProcess { class StdioChildServerProcess extends Disposable implements TsServerProcess {
private readonly _reader: Reader<Proto.Response>; private readonly _reader: Reader<Proto.Response>;
private _killTimeout: NodeJS.Timeout | undefined;
private _isShuttingDown = false;
constructor( constructor(
private readonly _process: child_process.ChildProcess, private readonly _process: child_process.ChildProcess,
private readonly _useGracefulShutdown: boolean,
) { ) {
super(); super();
this._reader = this._register(new Reader<Proto.Response>(this._process.stdout!)); this._reader = this._register(new Reader<Proto.Response>(this._process.stdout!));
this._process.once('exit', () => this.clearKillTimeout());
} }
write(serverRequest: Proto.Request): void { write(serverRequest: Proto.Request): void {
@@ -262,7 +302,39 @@ class StdioChildServerProcess extends Disposable implements TsServerProcess {
} }
kill(): void { kill(): void {
this._process.kill(); if (!this._useGracefulShutdown) {
this._process.kill();
this._reader.dispose();
return;
}
if (this._isShuttingDown) {
return;
}
this._isShuttingDown = true;
try {
this._process.stdin?.write(JSON.stringify(tsServerExitRequest) + '\r\n', 'utf8');
this._process.stdin?.end();
} catch {
this._process.kill();
this._reader.dispose();
return;
}
this._killTimeout = setTimeout(() => {
this._process.kill();
this._reader.dispose();
}, gracefulExitTimeout);
this._killTimeout.unref?.();
}
private clearKillTimeout(): void {
if (this._killTimeout) {
clearTimeout(this._killTimeout);
this._killTimeout = undefined;
}
this._reader.dispose(); this._reader.dispose();
} }
} }
@@ -290,6 +362,7 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory {
const env = generatePatchedEnv(process.env, tsServerPath, !!execPath); const env = generatePatchedEnv(process.env, tsServerPath, !!execPath);
const runtimeArgs = [...args]; const runtimeArgs = [...args];
const execArgv = getExecArgv(kind, configuration); const execArgv = getExecArgv(kind, configuration);
const useGracefulShutdown = configuration.heapProfile.enabled;
const useIpc = !execPath && version.apiVersion?.gte(API.v460); const useIpc = !execPath && version.apiVersion?.gte(API.v460);
if (useIpc) { if (useIpc) {
runtimeArgs.push('--useNodeIpc'); runtimeArgs.push('--useNodeIpc');
@@ -309,6 +382,6 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory {
stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined, stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined,
}); });
return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess); return useIpc ? new IpcChildServerProcess(childProcess, useGracefulShutdown) : new StdioChildServerProcess(childProcess, useGracefulShutdown);
} }
} }