diff --git a/doc/api/child_process.md b/doc/api/child_process.md index e90759b16d3f..66e9971c6af8 100644 --- a/doc/api/child_process.md +++ b/doc/api/child_process.md @@ -1055,7 +1055,9 @@ pipes between the parent and child. The value is one of the following: file descriptor is duplicated in the child process to the fd that corresponds to the index in the `stdio` array. The stream must have an underlying descriptor (file streams do not start until the `'open'` event has - occurred). + occurred). Pipe endpoints returned by [`net.createPipe()`][] may be passed + here. A readable pipe endpoint returned by [`net.createPipe()`][] must not + be flowing when it is passed here. **NOTE:** While it is technically possible to pass `stdin` as a writable or `stdout`/`stderr` as readable, it is not recommended. Readable and writable streams are designed with distinct behaviors, and using @@ -1441,6 +1443,12 @@ streams of a child process have been closed. This is distinct from the [`'exit'`][] event, since multiple processes might share the same stdio streams. The `'close'` event will always emit after [`'exit'`][] was already emitted, or [`'error'`][] if the child process failed to spawn. +Readable stdio streams created by Node.js are resumed after the child process +exits so they can be fully consumed and closed before the `'close'` event is +emitted. Endpoints created by [`net.createPipe()`][] are an exception to this +rule and are not resumed by the child process. Their stream lifecycle remains +owned by the parent process, and consequently the child process `'close'` event +does not wait for such streams to close. If the process exited, `code` is the final exit code of the process, otherwise `null`. If the process terminated due to receipt of a signal, `signal` is the @@ -2374,6 +2382,7 @@ or [`child_process.fork()`][]. [`maxBuffer` and Unicode]: #maxbuffer-and-unicode [`net.Server`]: net.md#class-netserver [`net.Socket`]: net.md#class-netsocket +[`net.createPipe()`]: net.md#netcreatepipe [`options.detached`]: #optionsdetached [`process.disconnect()`]: process.md#processdisconnect [`process.env`]: process.md#processenv diff --git a/doc/api/net.md b/doc/api/net.md index 410a3742462c..86c2ec85dc13 100644 --- a/doc/api/net.md +++ b/doc/api/net.md @@ -8,9 +8,10 @@ -The `node:net` module provides an asynchronous network API for creating stream-based -TCP or [IPC][] servers ([`net.createServer()`][]) and clients -([`net.createConnection()`][]). +The `node:net` module provides an asynchronous network API for creating +stream-based TCP or [IPC][] servers ([`net.createServer()`][]) and clients +([`net.createConnection()`][]), and operating system pipe pairs +([`net.createPipe()`][]). It can be accessed using: @@ -22,6 +23,90 @@ import net from 'node:net'; const net = require('node:net'); ``` +## `net.createPipe()` + + + +* Returns: {Object} + * `readable` {net.Socket} The readable end of the pipe. + * `writable` {net.Socket} The writable end of the pipe. + +The `net.createPipe()` method creates an operating system pipe pair. The +returned `readable` and `writable` streams are owned by the current process and +may be passed to [`child_process.spawn()`][] using the [`stdio`][] option. + +When a `readable` endpoint is passed as child stdin or as another child fd, the +child leases a readable handle. When a `writable` endpoint is passed as child +stdout, stderr, or another child fd, the child leases a writable handle. A +`readable` endpoint may not be passed as child stdout or stderr, and a +`writable` endpoint may not be passed as child stdin. An endpoint may be leased +to only one child process at a time. After the child process exits, endpoints +created by [`net.createPipe()`][] are released from their lease and may be +passed to another [`child_process.spawn()`][] call. Endpoints created by +[`net.createPipe()`][] are not supported by synchronous child process APIs such +as [`child_process.spawnSync()`][]. + +A `readable` endpoint created by [`net.createPipe()`][] must not be flowing +when it is passed to [`child_process.spawn()`][]. The child process +[`'close'` event][child-process-close] does not wait for such an endpoint to +close and does not resume it after the child process exits. + +The current process is responsible for the endpoint streams. Use normal stream +idioms such as `end()` to finish writing and stream consumption to drain a +readable endpoint. Use `resume()` when an unread readable endpoint should be +drained without observing its data, and use `destroy()` when an endpoint is no +longer needed without being naturally ended or drained. + +```cjs +const { spawn } = require('node:child_process'); +const { createPipe } = require('node:net'); +const { text } = require('node:stream/consumers'); + +const { readable, writable } = createPipe(); +const child = spawn(process.execPath, ['-e', ` + const fs = require('node:fs'); + const buffer = Buffer.alloc(1); + const count = fs.readSync(0, buffer, 0, 1, null); + fs.writeSync(1, buffer.subarray(0, count)); +`], { + stdio: [readable, 'pipe', 'inherit'], +}); + +const output = text(child.stdout); +writable.end('abc'); + +child.on('close', async () => { + console.log(await output); // Prints: a + console.log(await text(readable)); // Prints: bc +}); +``` + +```mjs +import { spawn } from 'node:child_process'; +import { createPipe } from 'node:net'; +import { text } from 'node:stream/consumers'; + +const { readable, writable } = createPipe(); +const child = spawn(process.execPath, ['-e', ` + const fs = require('node:fs'); + const buffer = Buffer.alloc(1); + const count = fs.readSync(0, buffer, 0, 1, null); + fs.writeSync(1, buffer.subarray(0, count)); +`], { + stdio: [readable, 'pipe', 'inherit'], +}); + +const output = text(child.stdout); +writable.end('abc'); + +child.on('close', async () => { + console.log(await output); // Prints: a + console.log(await text(readable)); // Prints: bc +}); +``` + ## IPC support