servo / ipc-channel

A multiprocess drop-in replacement for Rust channels
Apache License 2.0
857 stars 129 forks source link

Panic on large data transfer #277

Open Geens opened 3 years ago

Geens commented 3 years ago

I get this strange panic when sending large messages to a child process. I was able to recreate the problem in this minimal example. The program runs correctly for smaller payload sizes, e.g. 60000.

Tested on:

thread 'main' panicked at 'Windows IPC channel received handles intended for pid 10012, but this is pid 872. This likely happened because a receiver was transferred while it had outstanding data that contained a channel or shared memory in its pipe. This isn't supported in the Windows implementation.', C:\Users\geens\.cargo\registry\src\github.com-1ecc6299db9ec823\ipc-channel-0.15.0\src\platform\windows\mod.rs:158:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\ipc_channel_test.exe` (exit code: 101)
use ipc_channel::ipc;

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() == 2 {
        child(args[1].clone());
    } else {
        parent(args[0].clone());
    }
}

fn parent(executable: String) {
    let (server, server_name) = ipc::IpcOneShotServer::new().unwrap();
    let mut child = std::process::Command::new(executable)
        .arg(server_name)
        .stdout(std::process::Stdio::piped())
        .spawn()
        .unwrap();
    let (_, (tx, rx)): (_, (ipc::IpcSender<String>, ipc::IpcReceiver<String>)) = server.accept().unwrap();
    let payload = "a".repeat(70000).to_owned();
    tx.send(payload.clone()).unwrap();
    let received = rx.recv().unwrap();
    assert_eq!(payload, received);
    child.wait().unwrap();
}

fn child(server_name: String) {
    let one_shot_sender = ipc::IpcSender::connect(server_name).unwrap();
    let (tx, rx_send) = ipc::channel().unwrap();
    let (tx_send, rx) = ipc::channel().unwrap();
    one_shot_sender.send((tx_send, rx_send)).unwrap();
    let payload: String = rx.recv().unwrap();
    tx.send(payload).unwrap();
}
SamRodri commented 3 years ago

I encountered the same panic, it only happens if the receiver was created in a different process, so a workaround is to create the channel in the process the receiver will be used:

use ipc_channel::ipc;

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() == 2 {
        child(args[1].clone());
    } else {
        parent(args[0].clone());
    }
}

fn parent(executable: String) {
    let (server, server_name) = ipc::IpcOneShotServer::new().unwrap();
    let mut child = std::process::Command::new(executable)
        .arg(server_name)
        .stdout(std::process::Stdio::piped())
        .spawn()
        .unwrap();

    let (_, (tx, workaround_tx)): (_, (ipc::IpcSender<String>, ipc::IpcSender<ipc::IpcSender<String>>)) = server.accept().unwrap();
    let (tx_send, rx) = ipc::channel().unwrap();
    workaround_tx.send(tx_send).unwrap();

    let payload = "a".repeat(70000);
    tx.send(payload.clone()).unwrap();
    let received = rx.recv().unwrap();
    assert_eq!(payload, received);
    child.wait().unwrap();
}

fn child(server_name: String) {
    let one_shot_sender = ipc::IpcSender::connect(server_name).unwrap();
    let (workaround_tx_send, workaround_rx) = ipc::channel().unwrap();

    let (tx_send, rx) = ipc::channel().unwrap();
    one_shot_sender.send((tx_send, workaround_tx_send)).unwrap();

    let tx: ipc::IpcSender<String>  = workaround_rx.recv().unwrap();

    let payload: String = rx.recv().unwrap();
    tx.send(payload).unwrap();
}