| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent b643fe7 commit 779310a
8 files changed
| 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 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -224,6 +224,7 @@ function stdioStringToArray(stdio, channel) { | |||
| 224 | 224 | ||
| 225 | 225 | switch (stdio) { | |
| 226 | 226 | case 'ignore': | |
| 227 | + case 'overlapped': | ||
| 227 | 228 | case 'pipe': options.push(stdio, stdio, stdio); break; | |
| 228 | 229 | case 'inherit': options.push(0, 1, 2); break; | |
| 229 | 230 | default: | |
@@ -968,9 +969,10 @@ function getValidStdio(stdio, sync) { | |||
| 968 | 969 | ||
| 969 | 970 | if (stdio === 'ignore') { | |
| 970 | 971 | acc.push({ type: 'ignore' }); | |
| 971 | - } else if (stdio === 'pipe' || (typeof stdio === 'number' && stdio < 0)) { | ||
| 972 | + } else if (stdio === 'pipe' || stdio === 'overlapped' || | ||
| 973 | + (typeof stdio === 'number' && stdio < 0)) { | ||
| 972 | 974 | const a = { | |
| 973 | - type: 'pipe', | ||
| 975 | + type: stdio === 'overlapped' ? 'overlapped' : 'pipe', | ||
| 974 | 976 | readable: i === 0, | |
| 975 | 977 | writable: i !== 0 | |
| 976 | 978 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1367,6 +1367,24 @@ | |||
| 1367 | 1367 | ], | |
| 1368 | 1368 | }, # embedtest | |
| 1369 | 1369 | ||
| 1370 | + { | ||
| 1371 | + 'target_name': 'overlapped-checker', | ||
| 1372 | + 'type': 'executable', | ||
| 1373 | + | ||
| 1374 | + 'conditions': [ | ||
| 1375 | + ['OS=="win"', { | ||
| 1376 | + 'sources': [ | ||
| 1377 | + 'test/overlapped-checker/main_win.c' | ||
| 1378 | + ], | ||
| 1379 | + }], | ||
| 1380 | + ['OS!="win"', { | ||
| 1381 | + 'sources': [ | ||
| 1382 | + 'test/overlapped-checker/main_unix.c' | ||
| 1383 | + ], | ||
| 1384 | + }], | ||
| 1385 | + ] | ||
| 1386 | + }, # overlapped-checker | ||
| 1387 | + | ||
| 1370 | 1388 | # TODO(joyeecheung): do not depend on node_lib, | |
| 1371 | 1389 | # instead create a smaller static library node_lib_base that does | |
| 1372 | 1390 | # just enough for node_native_module.cc and the cache builder to | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -319,6 +319,7 @@ constexpr size_t kFsStatsBufferLength = | |||
| 319 | 319 | V(options_string, "options") \ | |
| 320 | 320 | V(order_string, "order") \ | |
| 321 | 321 | V(output_string, "output") \ | |
| 322 | + V(overlapped_string, "overlapped") \ | ||
| 322 | 323 | V(parse_error_string, "Parse Error") \ | |
| 323 | 324 | V(password_string, "password") \ | |
| 324 | 325 | V(path_string, "path") \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -125,6 +125,11 @@ class ProcessWrap : public HandleWrap { | |||
| 125 | 125 | options->stdio[i].flags = static_cast<uv_stdio_flags>( | |
| 126 | 126 | UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE); | |
| 127 | 127 | options->stdio[i].data.stream = StreamForWrap(env, stdio); | |
| 128 | + } else if (type->StrictEquals(env->overlapped_string())) { | ||
| 129 | + options->stdio[i].flags = static_cast<uv_stdio_flags>( | ||
| 130 | + UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE | | ||
| 131 | + UV_OVERLAPPED_PIPE); | ||
| 132 | + options->stdio[i].data.stream = StreamForWrap(env, stdio); | ||
| 128 | 133 | } else if (type->StrictEquals(env->wrap_string())) { | |
| 129 | 134 | options->stdio[i].flags = UV_INHERIT_STREAM; | |
| 130 | 135 | options->stdio[i].data.stream = StreamForWrap(env, stdio); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,51 @@ | |||
| 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 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,85 @@ | |||
| 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 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,75 @@ | |||
| 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 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments