GeezerVison is showing github.com — child_process: add 'overlapped' stdio flag · nodejs/node@9bc2cec
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/child_process.md +16-6Lines changed: 16 additions & 6 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -660,6 +660,9 @@ subprocess.unref(); | 660 | 660 | <!-- YAML | 661 | 661 | added: v0.7.10 | 662 | 662 | changes: | | 663 | + - version: REPLACEME | | 664 | + pr-url: https://github.com/nodejs/node/pull/29412 | | 665 | + description: Added the `overlapped` stdio flag. | 663 | 666 | - version: v3.3.1 | 664 | 667 | pr-url: https://github.com/nodejs/node/pull/2727 | 665 | 668 | description: The value `0` is now accepted as a file descriptor. | @@ -675,6 +678,7 @@ equal to `['pipe', 'pipe', 'pipe']`. | 675 | 678 | For convenience, `options.stdio` may be one of the following strings: | 676 | 679 |
| 677 | 680 | * `'pipe'`: equivalent to `['pipe', 'pipe', 'pipe']` (the default) | | 681 | +* `'overlapped'`: equivalent to `['overlapped', 'overlapped', 'overlapped']` | 678 | 682 | * `'ignore'`: equivalent to `['ignore', 'ignore', 'ignore']` | 679 | 683 | * `'inherit'`: equivalent to `['inherit', 'inherit', 'inherit']` or `[0, 1, 2]` | 680 | 684 |
| @@ -688,7 +692,13 @@ pipes between the parent and child. The value is one of the following: | 688 | 692 | `child_process` object as [`subprocess.stdio[fd]`][`subprocess.stdio`]. Pipes | 689 | 693 | created for fds 0, 1, and 2 are also available as [`subprocess.stdin`][], | 690 | 694 | [`subprocess.stdout`][] and [`subprocess.stderr`][], respectively. | 691 | | -2. `'ipc'`: Create an IPC channel for passing messages/file descriptors | | 695 | +1. `'overlapped'`: Same as `'pipe'` except that the `FILE_FLAG_OVERLAPPED` flag | | 696 | + is set on the handle. This is necessary for overlapped I/O on the child | | 697 | + process's stdio handles. See the | | 698 | + [docs](https://docs.microsoft.com/en-us/windows/win32/fileio/synchronous-and-asynchronous-i-o) | | 699 | + for more details. This is exactly the same as `'pipe'` on non-Windows | | 700 | + systems. | | 701 | +1. `'ipc'`: Create an IPC channel for passing messages/file descriptors | 692 | 702 | between parent and child. A [`ChildProcess`][] may have at most one IPC | 693 | 703 | stdio file descriptor. Setting this option enables the | 694 | 704 | [`subprocess.send()`][] method. If the child is a Node.js process, the | @@ -699,25 +709,25 @@ pipes between the parent and child. The value is one of the following: | 699 | 709 | Accessing the IPC channel fd in any way other than [`process.send()`][] | 700 | 710 | or using the IPC channel with a child process that is not a Node.js instance | 701 | 711 | is not supported. | 702 | | -3. `'ignore'`: Instructs Node.js to ignore the fd in the child. While Node.js | | 712 | +1. `'ignore'`: Instructs Node.js to ignore the fd in the child. While Node.js | 703 | 713 | will always open fds 0, 1, and 2 for the processes it spawns, setting the fd | 704 | 714 | to `'ignore'` will cause Node.js to open `/dev/null` and attach it to the | 705 | 715 | child's fd. | 706 | | -4. `'inherit'`: Pass through the corresponding stdio stream to/from the | | 716 | +1. `'inherit'`: Pass through the corresponding stdio stream to/from the | 707 | 717 | parent process. In the first three positions, this is equivalent to | 708 | 718 | `process.stdin`, `process.stdout`, and `process.stderr`, respectively. In | 709 | 719 | any other position, equivalent to `'ignore'`. | 710 | | -5. {Stream} object: Share a readable or writable stream that refers to a tty, | | 720 | +1. {Stream} object: Share a readable or writable stream that refers to a tty, | 711 | 721 | file, socket, or a pipe with the child process. The stream's underlying | 712 | 722 | file descriptor is duplicated in the child process to the fd that | 713 | 723 | corresponds to the index in the `stdio` array. The stream must have an | 714 | 724 | underlying descriptor (file streams do not until the `'open'` event has | 715 | 725 | occurred). | 716 | | -6. Positive integer: The integer value is interpreted as a file descriptor | | 726 | +1. Positive integer: The integer value is interpreted as a file descriptor | 717 | 727 | that is currently open in the parent process. It is shared with the child | 718 | 728 | process, similar to how {Stream} objects can be shared. Passing sockets | 719 | 729 | is not supported on Windows. | 720 | | -7. `null`, `undefined`: Use default value. For stdio fds 0, 1, and 2 (in other | | 730 | +1. `null`, `undefined`: Use default value. For stdio fds 0, 1, and 2 (in other | 721 | 731 | words, stdin, stdout, and stderr) a pipe is created. For fd 3 and up, the | 722 | 732 | default is `'ignore'`. | 723 | 733 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/child_process.js +4-2Lines changed: 4 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -231,6 +231,7 @@ function stdioStringToArray(stdio, channel) { | 231 | 231 |
| 232 | 232 | switch (stdio) { | 233 | 233 | case 'ignore': | | 234 | + case 'overlapped': | 234 | 235 | case 'pipe': ArrayPrototypePush(options, stdio, stdio, stdio); break; | 235 | 236 | case 'inherit': ArrayPrototypePush(options, 0, 1, 2); break; | 236 | 237 | default: | @@ -976,9 +977,10 @@ function getValidStdio(stdio, sync) { | 976 | 977 |
| 977 | 978 | if (stdio === 'ignore') { | 978 | 979 | ArrayPrototypePush(acc, { type: 'ignore' }); | 979 | | - } else if (stdio === 'pipe' || (typeof stdio === 'number' && stdio < 0)) { | | 980 | + } else if (stdio === 'pipe' || stdio === 'overlapped' || | | 981 | + (typeof stdio === 'number' && stdio < 0)) { | 980 | 982 | const a = { | 981 | | - type: 'pipe', | | 983 | + type: stdio === 'overlapped' ? 'overlapped' : 'pipe', | 982 | 984 | readable: i === 0, | 983 | 985 | writable: i !== 0 | 984 | 986 | }; | |
Collapse file Copy file name to clipboardExpand all lines: node.gyp +18Lines changed: 18 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1469 | 1469 | ], | 1470 | 1470 | }, # embedtest | 1471 | 1471 |
| | 1472 | + { | | 1473 | + 'target_name': 'overlapped-checker', | | 1474 | + 'type': 'executable', | | 1475 | + | | 1476 | + 'conditions': [ | | 1477 | + ['OS=="win"', { | | 1478 | + 'sources': [ | | 1479 | + 'test/overlapped-checker/main_win.c' | | 1480 | + ], | | 1481 | + }], | | 1482 | + ['OS!="win"', { | | 1483 | + 'sources': [ | | 1484 | + 'test/overlapped-checker/main_unix.c' | | 1485 | + ], | | 1486 | + }], | | 1487 | + ] | | 1488 | + }, # overlapped-checker | | 1489 | + | 1472 | 1490 | # TODO(joyeecheung): do not depend on node_lib, | 1473 | 1491 | # instead create a smaller static library node_lib_base that does | 1474 | 1492 | # just enough for node_native_module.cc and the cache builder to | |
Collapse file Copy file name to clipboardExpand all lines: src/env.h +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -343,6 +343,7 @@ constexpr size_t kFsStatsBufferLength = | 343 | 343 | V(options_string, "options") \ | 344 | 344 | V(order_string, "order") \ | 345 | 345 | V(output_string, "output") \ | | 346 | + V(overlapped_string, "overlapped") \ | 346 | 347 | V(parse_error_string, "Parse Error") \ | 347 | 348 | V(password_string, "password") \ | 348 | 349 | V(path_string, "path") \ | |
Collapse file Copy file name to clipboardExpand all lines: src/process_wrap.cc +5Lines changed: 5 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -120,6 +120,11 @@ class ProcessWrap : public HandleWrap { | 120 | 120 | options->stdio[i].flags = static_cast<uv_stdio_flags>( | 121 | 121 | UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE); | 122 | 122 | options->stdio[i].data.stream = StreamForWrap(env, stdio); | | 123 | + } else if (type->StrictEquals(env->overlapped_string())) { | | 124 | + options->stdio[i].flags = static_cast<uv_stdio_flags>( | | 125 | + UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE | | | 126 | + UV_OVERLAPPED_PIPE); | | 127 | + options->stdio[i].data.stream = StreamForWrap(env, stdio); | 123 | 128 | } else if (type->StrictEquals(env->wrap_string())) { | 124 | 129 | options->stdio[i].flags = UV_INHERIT_STREAM; | 125 | 130 | options->stdio[i].data.stream = StreamForWrap(env, stdio); | |
Collapse file Copy file name to clipboard +51Lines changed: 51 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +#include <stdio.h> | | 2 | +#include <string.h> | | 3 | +#include <stdlib.h> | | 4 | + | | 5 | +#include <errno.h> | | 6 | +#include <unistd.h> | | 7 | + | | 8 | +static size_t r(char* buf, size_t buf_size) { | | 9 | + ssize_t read_count; | | 10 | + do | | 11 | + read_count = read(0, buf, buf_size); | | 12 | + while (read_count < 0 && errno == EINTR); | | 13 | + if (read_count <= 0) | | 14 | + abort(); | | 15 | + return (size_t)read_count; | | 16 | +} | | 17 | + | | 18 | +static void w(const char* buf, size_t count) { | | 19 | + const char* end = buf + count; | | 20 | + | | 21 | + while (buf < end) { | | 22 | + ssize_t write_count; | | 23 | + do | | 24 | + write_count = write(1, buf, count); | | 25 | + while (write_count < 0 && errno == EINTR); | | 26 | + if (write_count <= 0) | | 27 | + abort(); | | 28 | + buf += write_count; | | 29 | + } | | 30 | + | | 31 | + fprintf(stderr, "%zu", count); | | 32 | + fflush(stderr); | | 33 | +} | | 34 | + | | 35 | +int main(void) { | | 36 | + w("0", 1); | | 37 | + | | 38 | + while (1) { | | 39 | + char buf[256]; | | 40 | + size_t read_count = r(buf, sizeof(buf)); | | 41 | + // The JS part (test-child-process-stdio-overlapped.js) only writes the | | 42 | + // "exit" string when the buffer is empty, so the read is guaranteed to be | | 43 | + // atomic due to it being less than PIPE_BUF. | | 44 | + if (!strncmp(buf, "exit", read_count)) { | | 45 | + break; | | 46 | + } | | 47 | + w(buf, read_count); | | 48 | + } | | 49 | + | | 50 | + return 0; | | 51 | +} |
Collapse file Copy file name to clipboard +85Lines changed: 85 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +#include <stdlib.h> | | 2 | +#include <stdio.h> | | 3 | +#include <string.h> | | 4 | + | | 5 | +#include <windows.h> | | 6 | + | | 7 | +static char buf[256]; | | 8 | +static DWORD read_count; | | 9 | +static DWORD write_count; | | 10 | +static HANDLE stdin_h; | | 11 | +static OVERLAPPED stdin_o; | | 12 | + | | 13 | +static void die(const char* buf) { | | 14 | + fprintf(stderr, "%s\n", buf); | | 15 | + fflush(stderr); | | 16 | + exit(100); | | 17 | +} | | 18 | + | | 19 | +static void overlapped_read(void) { | | 20 | + if (ReadFile(stdin_h, buf, sizeof(buf), NULL, &stdin_o)) { | | 21 | + // Since we start the read operation immediately before requesting a write, | | 22 | + // it should never complete synchronously since no data would be available | | 23 | + die("read completed synchronously"); | | 24 | + } | | 25 | + if (GetLastError() != ERROR_IO_PENDING) { | | 26 | + die("overlapped read failed"); | | 27 | + } | | 28 | +} | | 29 | + | | 30 | +static void write(const char* buf, size_t buf_size) { | | 31 | + overlapped_read(); | | 32 | + DWORD write_count; | | 33 | + HANDLE stdout_h = GetStdHandle(STD_OUTPUT_HANDLE); | | 34 | + if (!WriteFile(stdout_h, buf, buf_size, &write_count, NULL)) { | | 35 | + die("overlapped write failed"); | | 36 | + } | | 37 | + fprintf(stderr, "%d", write_count); | | 38 | + fflush(stderr); | | 39 | +} | | 40 | + | | 41 | +int main(void) { | | 42 | + HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL); | | 43 | + if (event == NULL) { | | 44 | + die("failed to create event handle"); | | 45 | + } | | 46 | + | | 47 | + stdin_h = GetStdHandle(STD_INPUT_HANDLE); | | 48 | + stdin_o.hEvent = event; | | 49 | + | | 50 | + write("0", 1); | | 51 | + | | 52 | + while (1) { | | 53 | + DWORD result = WaitForSingleObject(event, INFINITE); | | 54 | + if (result == WAIT_OBJECT_0) { | | 55 | + if (!GetOverlappedResult(stdin_h, &stdin_o, &read_count, FALSE)) { | | 56 | + die("failed to get overlapped read result"); | | 57 | + } | | 58 | + if (strncmp(buf, "exit", read_count) == 0) { | | 59 | + break; | | 60 | + } | | 61 | + write(buf, read_count); | | 62 | + } else { | | 63 | + char emsg[0xfff]; | | 64 | + int ecode = GetLastError(); | | 65 | + DWORD rv = FormatMessage( | | 66 | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | | 67 | + NULL, | | 68 | + ecode, | | 69 | + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | | 70 | + (LPSTR)emsg, | | 71 | + sizeof(emsg), | | 72 | + NULL); | | 73 | + if (rv > 0) { | | 74 | + snprintf(emsg, sizeof(emsg), | | 75 | + "WaitForSingleObject failed. Error %d (%s)", ecode, emsg); | | 76 | + } else { | | 77 | + snprintf(emsg, sizeof(emsg), | | 78 | + "WaitForSingleObject failed. Error %d", ecode); | | 79 | + } | | 80 | + die(emsg); | | 81 | + } | | 82 | + } | | 83 | + | | 84 | + return 0; | | 85 | +} |
Collapse file Copy file name to clipboard +75Lines changed: 75 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// Test for "overlapped" stdio option. This test uses the "overlapped-checker" | | 2 | +// helper program which basically a specialized echo program. | | 3 | +// | | 4 | +// The test has two goals: | | 5 | +// | | 6 | +// - Verify that overlapped I/O works on windows. The test program will deadlock | | 7 | +// if stdin doesn't have the FILE_FLAG_OVERLAPPED flag set on startup (see | | 8 | +// test/overlapped-checker/main_win.c for more details). | | 9 | +// - Verify that "overlapped" stdio option works transparently as a pipe (on | | 10 | +// unix/windows) | | 11 | +// | | 12 | +// This is how the test works: | | 13 | +// | | 14 | +// - This script assumes only numeric strings are written to the test program | | 15 | +// stdout. | | 16 | +// - The test program will be spawned with "overlapped" set on stdin and "pipe" | | 17 | +// set on stdout/stderr and at startup writes a number to its stdout | | 18 | +// - When this script receives some data, it will parse the number, add 50 and | | 19 | +// write to the test program's stdin. | | 20 | +// - The test program will then echo the number back to us which will repeat the | | 21 | +// cycle until the number reaches 200, at which point we send the "exit" | | 22 | +// string, which causes the test program to exit. | | 23 | +// - Extra assertion: Every time the test program writes a string to its stdout, | | 24 | +// it will write the number of bytes written to stderr. | | 25 | +// - If overlapped I/O is not setup correctly, this test is going to hang. | | 26 | +'use strict'; | | 27 | +const common = require('../common'); | | 28 | +const assert = require('assert'); | | 29 | +const path = require('path'); | | 30 | +const child_process = require('child_process'); | | 31 | + | | 32 | +const exeExtension = process.platform === 'win32' ? '.exe' : ''; | | 33 | +const exe = 'overlapped-checker' + exeExtension; | | 34 | +const exePath = path.join(path.dirname(process.execPath), exe); | | 35 | + | | 36 | +const child = child_process.spawn(exePath, [], { | | 37 | + stdio: ['overlapped', 'pipe', 'pipe'] | | 38 | +}); | | 39 | + | | 40 | +child.stdin.setEncoding('utf8'); | | 41 | +child.stdout.setEncoding('utf8'); | | 42 | +child.stderr.setEncoding('utf8'); | | 43 | + | | 44 | +function writeNext(n) { | | 45 | + child.stdin.write((n + 50).toString()); | | 46 | +} | | 47 | + | | 48 | +child.stdout.on('data', (s) => { | | 49 | + const n = Number(s); | | 50 | + if (n >= 200) { | | 51 | + child.stdin.write('exit'); | | 52 | + return; | | 53 | + } | | 54 | + writeNext(n); | | 55 | +}); | | 56 | + | | 57 | +let stderr = ''; | | 58 | +child.stderr.on('data', (s) => { | | 59 | + stderr += s; | | 60 | +}); | | 61 | + | | 62 | +child.stderr.on('end', common.mustCall(() => { | | 63 | + // This is the sequence of numbers sent to us: | | 64 | + // - 0 (1 byte written) | | 65 | + // - 50 (2 bytes written) | | 66 | + // - 100 (3 bytes written) | | 67 | + // - 150 (3 bytes written) | | 68 | + // - 200 (3 bytes written) | | 69 | + assert.strictEqual(stderr, '12333'); | | 70 | +})); | | 71 | + | | 72 | +child.on('exit', common.mustCall((status) => { | | 73 | + // The test program will return the number of writes as status code. | | 74 | + assert.strictEqual(status, 0); | | 75 | +})); |
|