Skip to content

Commit 29adb96

Browse files
nimo71AgentEnder
authored andcommitted
fix(gradle): destroy streams on exit to prevent daemon pipe hang
When `execGradleAsync()` spawns Gradle via `execFile` with `shell: true`, the Gradle daemon inherits the shell's stdout/stderr pipe file descriptors. After the task completes, the daemon continues running and holds these pipes open, preventing the shell wrapper from exiting. Since Node.js resolves the `exit` event based on the shell process terminating, the Promise hangs indefinitely. Fix: Forcibly destroy stdout/stderr streams after the `exit` event fires. By this point all output has been buffered, so no data is lost. Destroying the streams releases the pipe FDs from Node's perspective, allowing the shell to exit and the Promise to resolve. Refs: nodejs/node#5637, gradle/gradle#3987
1 parent e1e4ac8 commit 29adb96

File tree

1 file changed

+11
-0
lines changed

1 file changed

+11
-0
lines changed

packages/gradle/src/utils/exec-gradle.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ export function execGradleAsync(
6363

6464
cp.on('exit', (code, signal) => {
6565
if (code === null) code = signalToCode(signal);
66+
// Forcibly destroy streams to prevent Gradle daemon pipe hang.
67+
// When shell: true is used, the Gradle daemon inherits the shell's
68+
// stdout/stderr pipes and holds them open after the task completes.
69+
// This prevents the shell wrapper from exiting, which in turn
70+
// prevents the 'exit' event from firing on the ChildProcess.
71+
// By this point all output has already been buffered, so destroying
72+
// the streams is safe and releases the pipe FDs from Node's
73+
// perspective.
74+
// See: nodejs/node#5637, gradle/gradle#3987
75+
cp.stdout?.destroy();
76+
cp.stderr?.destroy();
6677
if (code === 0) {
6778
res(stdout);
6879
} else {

0 commit comments

Comments
 (0)