nodeipc: test send/recv stdio

Summary:
Tested variations mentioned in the added README, especially the
nodejs -> parent -> child case:

Windows:

  $ node hello_parent.js
  [Parent] Got message from child: HELLO FROM CHILD
  [Child] Got message from parent: String("HELLO FROM PARENT 1")
  [Parent] Got message from child: [ 'Echo from child', 'HELLO FROM PARENT 1' ]
  [Child] Got message from parent: String("HELLO FROM PARENT 2")
  [Parent] Got message from child: [ 'Echo from child', 'HELLO FROM PARENT 2' ]
  [Child] Got message from parent: String("BYE")
  [Parent] Child process has exited

  $ cargo run --example spawn_sendfd
  Parent: spawning child
  Parent: sending hello
  Parent: sending a.txt file descriptor
  Parent: sending stdio
  Parent: waiting for child to exit
  Child: started with IPC handle 100
  Child: got message Some("hello")
  Child: got sendfd payload SendFdPayload { pid: 41528, raw_fds: [0x118] }
  Child: writing "something\n" to fd 0x118
  Child: got stdio
  Child: write to stderr
  Child: no IPC singleton

  $ node hello_parent.js spawn_sendfd
  Parent: spawning child
  Parent: sending hello
  Parent: sending a.txt file descriptor
  Parent: sending stdio
  Parent: waiting for child to exit
  Child: started with IPC handle 100
  Child: got message Some("hello")
  Child: got sendfd payload SendFdPayload { pid: 49952, raw_fds: [0x118] }
  Child: writing "something\n" to fd 0x118
  Child: got stdio
  Child: write to stderr
  Child: has IPC singleton
  [Parent] Got message from child: HELLO FROM CHILD
  Child: Got message: String("HELLO FROM PARENT 1")
  [Parent] Got message from child: [ 'Echo from child', 'HELLO FROM PARENT 1' ]
  Child: Got message: String("HELLO FROM PARENT 2")
  [Parent] Got message from child: [ 'Echo from child', 'HELLO FROM PARENT 2' ]
  Child: Got message: String("BYE")
  [Parent] Child process has exited

Linux:

  % node hello_parent.js
  [Parent] Got message from child: HELLO FROM CHILD
  [Child] Got message from parent: String("HELLO FROM PARENT 1")
  [Parent] Got message from child: [ 'Echo from child', 'HELLO FROM PARENT 1' ]
  [Child] Got message from parent: String("HELLO FROM PARENT 2")
  [Parent] Got message from child: [ 'Echo from child', 'HELLO FROM PARENT 2' ]
  [Child] Got message from parent: String("BYE")
  [Parent] Child process has exited

  % cargo run --example spawn_sendfd
  Parent: spawning child
  Parent: sending hello
  Parent: sending a.txt file descriptor
  Parent: sending stdio
  Parent: waiting for child to exit
  Child: started with IPC handle 4
  Child: got message Some("hello")
  Child: got sendfd payload SendFdPayload { raw_fds: [3] }
  Child: writing "something\n" to fd 3
  Child: got stdio
  Child: write to stderr
  Child: no IPC singleton

  % node hello_parent.js spawn_sendfd
  Parent: spawning child
  Parent: sending hello
  Parent: sending a.txt file descriptor
  Parent: sending stdio
  Parent: waiting for child to exit
  Child: started with IPC handle 5
  Child: got message Some("hello")
  Child: got sendfd payload SendFdPayload { raw_fds: [4] }
  Child: writing "something\n" to fd 4
  Child: got stdio
  Child: write to stderr
  Child: has IPC singleton
  [Parent] Got message from child: HELLO FROM CHILD
  Child: Got message: String("HELLO FROM PARENT 1")
  [Parent] Got message from child: [ 'Echo from child', 'HELLO FROM PARENT 1' ]
  Child: Got message: String("HELLO FROM PARENT 2")
  [Parent] Got message from child: [ 'Echo from child', 'HELLO FROM PARENT 2' ]
  Child: Got message: String("BYE")
  [Parent] Child process has exited

Reviewed By: muirdm

Differential Revision: D46811170

fbshipit-source-id: 96b2d4a7a3c5f8599058faea949ea3ee7be962be
This commit is contained in:
Jun Wu 2023-06-21 14:49:59 -07:00 committed by Facebook GitHub Bot
parent 633dfd6ab6
commit 60ae5c218a
3 changed files with 52 additions and 29 deletions

View File

@ -0,0 +1,24 @@
Examples serve as tests.
NodeIpc has multiple features:
A. Integrate to nodejs as a child process (singleton).
B. Send/receive messages (native, or compatible with nodejs).
C. Send/receive file descriptors (incompatible with nodejs).
D. Send/receive stdio (console) and nodejs IPC channel.
Different example combinations exercise different features:
- hello_parent.js + hello_child.rs exercises feature A + B
Run (hello_parent.js calls cargo build):
node hello_parent.js
- spawn_sendfd.rs exercises feature B + C + D
Run:
cargo run --example spawn_sendfd
cargo run --example spawn_sendfd 1>b.txt
- hello_parent.js + hello_child.rs + spawn_sendfd.rs exercises feature A + B + C + D
Run:
node hello_parent.js spawn_sendfd
Panic or error messages indicate something went wrong.
Windows and Unix are significantly different so the commands should be checked
on both platforms.

View File

@ -7,7 +7,8 @@
const {spawn, spawnSync} = require('child_process');
const build = spawnSync('cargo', ['build', '--message-format=json', '--release', '--example', 'hello_child'], {stdio: [0, 'pipe', 2]});
const example = process.argv[2] ?? 'hello_child';
const build = spawnSync('cargo', ['build', '--message-format=json', '--release', '--example', example], {stdio: [0, 'pipe', 2]});
const output = build.stdout.toString();
let executable = null;

View File

@ -15,6 +15,8 @@ use filedescriptor::FileDescriptor;
use filedescriptor::FromRawFileDescriptor;
use filedescriptor::RawFileDescriptor;
use nodeipc::NodeIpc;
use serde_json::json;
use serde_json::Value;
fn main() {
let is_child = env::args().nth(1).as_deref() == Some("child");
@ -53,6 +55,25 @@ fn child_main() {
// Do not make FileDescriptor close the handle. We might still need it for `println!`.
mem::forget(fd);
}
ipc.recv_stdio().unwrap();
println!("Child: got stdio");
eprintln!("Child: write to stderr");
if let Some(ipc) = nodeipc::get_singleton() {
println!("Child: has IPC singleton");
ipc.send("HELLO FROM CHILD").unwrap();
while let Some(message) = ipc.recv::<Value>().unwrap() {
println!("Child: Got message: {:?}", message);
if message.as_str() == Some("BYE") {
break;
} else {
ipc.send(json!(["Echo from child", message])).unwrap();
}
}
} else {
println!("Child: no IPC singleton");
}
}
fn parent_main() {
@ -88,16 +109,18 @@ fn parent_main() {
println!("Parent: sending hello");
ipc.send("hello").unwrap();
println!("Parent: sending stdio and a.txt file descriptors");
let mut fds = stdio_fd_vec();
println!("Parent: sending a.txt file descriptor");
let file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("a.txt")
.unwrap();
fds.push(file.as_raw_file_descriptor());
let fds = [file.as_raw_file_descriptor()];
ipc.send_fd_vec(&fds).unwrap();
println!("Parent: sending stdio");
ipc.send_stdio().unwrap();
println!("Parent: waiting for child to exit");
child.wait().unwrap();
}
@ -116,28 +139,3 @@ fn maybe_init_winsock() {
assert_eq!(ret, 0, "failed to initialize winsock");
}
}
fn stdio_fd_vec() -> Vec<RawFileDescriptor> {
let mut result = Vec::new();
#[cfg(windows)]
unsafe {
use winapi::um::processenv::GetStdHandle;
use winapi::um::winbase::STD_ERROR_HANDLE;
use winapi::um::winbase::STD_INPUT_HANDLE;
use winapi::um::winbase::STD_OUTPUT_HANDLE;
result.push(GetStdHandle(STD_INPUT_HANDLE) as _);
result.push(GetStdHandle(STD_OUTPUT_HANDLE) as _);
result.push(GetStdHandle(STD_ERROR_HANDLE) as _);
}
#[cfg(unix)]
{
result.push(libc::STDIN_FILENO);
result.push(libc::STDOUT_FILENO);
result.push(libc::STDERR_FILENO);
}
result
}