diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index a12b2954db81de..fab5ec9f1692ec 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -332,8 +332,21 @@ function flushStdio(subprocess) { } -function createSocket(pipe, readable) { - return net.Socket({ handle: pipe, readable }); +function createSocket(pipe, readable, index) { + // Determine socket readability based on stdio index: + // - stdin (0): parent writes → readable: false + // - stdout/stderr (1,2): parent reads → readable: true + // - custom pipes (3+): parent writes by default → readable: false + // See: https://github.com/nodejs/node/issues/[fork-stdio-bug] + let actualReadable; + if (index !== undefined) { + // Use index-based logic for correctness + actualReadable = (index === 1 || index === 2); + } else { + // Fallback for when index is not provided (for compatibility) + actualReadable = readable; + } + return net.Socket({ handle: pipe, readable: actualReadable, writable: !actualReadable }); } @@ -489,7 +502,7 @@ ChildProcess.prototype.spawn = function spawn(options) { if (stream.handle) { stream.socket = createSocket(this.pid !== 0 ? - stream.handle : null, i > 0); + stream.handle : null, i > 0, i); if (i > 0 && this.pid !== 0) { this._closesNeeded++; diff --git a/test/parallel/test-child-process-custom-pipe.js b/test/parallel/test-child-process-custom-pipe.js new file mode 100644 index 00000000000000..eb79ea3d4d4637 --- /dev/null +++ b/test/parallel/test-child-process-custom-pipe.js @@ -0,0 +1,39 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +// Test that custom stdio pipes have correct readable/writable properties +// Regression test for pre-existing fork-stdio bug where custom pipes (stdio[3+]) +// were incorrectly marked as readable instead of writable. + +if (process.argv[2] === 'child') { + // Child process just exits + process.exit(0); +} else { + // Parent process: verify socket properties + const child = cp.fork(__filename, ['child'], { + stdio: [0, 'ignore', 'pipe', 'ipc', 'pipe'] + }); + + // stdio[0] = inherit (stdin) + // stdio[1] = ignore + // stdio[2] = pipe (stderr) + // stdio[3] = ipc + // stdio[4] = pipe (custom pipe) + + const customPipe = child.stdio[4]; + + // Custom pipe should be writable (parent writes data to child) + assert.strictEqual(customPipe.writable, true, + 'Custom stdio pipe should be writable'); + + // Custom pipe should not be readable by default + assert.strictEqual(customPipe.readable, false, + 'Custom stdio pipe should not be readable by default'); + + child.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + })); +}