Skip to content

Commit 97115db

Browse files
committed
fix: destroy piped streams on child exit to prevent grandchild deadlock
When a child process spawns a grandchild that inherits the piped stdout/stderr file descriptors, the grandchild holds them open after the child exits. Node's close event waits for all fds to be released, so it never fires. readStream and combineStreams hang indefinitely because the streams never end. Listen for the exit event (fires when the process exits, regardless of pipe state) and destroy the piped streams. This forces the PassThrough and readline consumers to complete. Refs: lint-staged/lint-staged#1800
1 parent bf59661 commit 97115db

1 file changed

Lines changed: 10 additions & 0 deletions

File tree

src/main.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ export class ExecProcess implements Result {
334334

335335
this._process = handle;
336336
handle.once('error', this._onError);
337+
handle.once('exit', this._onExit);
337338
handle.once('close', this._onClose);
338339

339340
if (handle.stdin) {
@@ -366,6 +367,15 @@ export class ExecProcess implements Result {
366367
this._thrownError = err;
367368
};
368369

370+
protected _onExit = (): void => {
371+
// Destroy piped streams when the child process exits, even if grandchild
372+
// processes still hold the underlying file descriptors open. Without this,
373+
// the 'close' event never fires (it waits for all fds to be released),
374+
// causing readStream and combineStreams to hang indefinitely.
375+
this._streamOut?.destroy();
376+
this._streamErr?.destroy();
377+
};
378+
369379
protected _onClose = (): void => {
370380
if (this._resolveClose) {
371381
this._resolveClose();

0 commit comments

Comments
 (0)