ipc-channel

repository·main·Indexed 22 days ago

https://github.com/servo/ipc-channel

A multiprocess drop-in replacement for Rust channels (version 0.22.0) that enables message passing between operating system processes using serde for serialization. It provides features such as IpcOneShotServer for bootstrapping connections, IpcReceiverSet for polling multiple channels, and IpcSharedMemory for efficient large data transfers. The library supports various platforms including Linux, Windows, macOS, Android, and iOS, and offers an optional asynch module for asynchronous IPC operations.

Tokens
9K
Snippets
35
Records
44
Agent score
77%

What's inside ipc-channel

  1. Understand semantic differences between Rust and IPC channels

    main

    When moving from thread-based channels to ipc-channel, be aware of these key differences:

    • Blocking: Unlike Rust channels which can be bounded, ipc-channel is always unbounded and send() never blocks.
    • Resources: ipc-channel consumes OS resources (sockets, file descriptors, shared memory, or named pipes) depending on the platform.
    • Data Transfer: Rust channels transfer ownership of messages in memory; ipc-channel serializes and deserializes messages across process boundaries.
    • Type Safety: While Rust channels are strictly type-safe, ipc-channel relies on both the client and server using identical or compatible message types for serialization.
  2. Map Rust channels to ipc-channel APIs

    main

    If you are familiar with standard Rust channels, ipc-channel provides a similar API designed for inter-process communication. Note that ipc-channel uses serde for serialization, so message types must implement Serialize and Deserialize.

    | Rust Channel | ipc-channel API |
    | --- | --- |
    | `channel()` | `ipc::channel().unwrap()` |
    | `Sender<T>` | `ipc::IpcSender<T>` (requires `T: Serialize`) |
    | `Receiver<T>` | `ipc::IpcReceiver<T>` (requires `T: Deserialize`) |
  3. Run tests and benchmarks

    main

    To verify your implementation or check performance, use the standard Cargo commands:

    Run tests

    cargo test

    Note: Some tests are platform-dependent. Supported platforms include iOS, macOS, Android, FreeBSD, Illumos, Linux, OpenBSD, WASI, and Windows.

    Run benchmarks

    cargo bench
    cargo test
    cargo bench
  4. Bootstrap IPC channels between processes using IpcOneShotServer

    main

    To establish a connection between two separate processes, use the IpcOneShotServer. This follows a specific handshake pattern:

    1. Server Side: Create an IpcOneShotServer. This generates a unique server name. Call accept() on the server, which blocks until a client connects and sends its first message. accept() returns the IpcReceiver<T> and the initial message.
    2. Communication: Pass the server name to the client process (e.g., via an environment variable or CLI flag).
    3. Client Side: Call connect(server_name) in the client process. This returns an IpcSender<T> that allows the client to send messages to the server.

    Restriction: A one-shot server can only be connected to by a client at most once.

  5. Understand Unix IPC message reassembly

    main

    The Unix implementation of ipc-channel handles large messages by splitting them into fragments.

    1. Initial Fragment: The first fragment contains metadata (fragment size, total size) and a dedicated receive end of a channel.
    2. Follow-up Fragments: Subsequent fragments are sent through the dedicated channel established in the first fragment.
    3. Reassembly: The receiver uses the dedicated channel to pull fragments into a single contiguous buffer until the total_size is reached.

    This mechanism ensures that large payloads (exceeding system buffer sizes) are transmitted reliably without losing data or aborting mid-message.

  6. Embed and transfer IPC channels

    main

    Because IpcSender<T> and IpcReceiver<T> implement Serialize and Deserialize, you can send the channel handles themselves through another IPC channel. This allows for complex topologies where a process receives a channel handle and then uses it to communicate with a third party.

    use ipc_channel::ipc;
    
    let (tx, rx) = ipc::channel().unwrap();
    let (embedded_tx, embedded_rx) = ipc::channel().unwrap();
    
    // Send the IpcReceiver over the first channel
    tx.send(embedded_rx).unwrap();
    
    // Receive the IpcReceiver in the other process
    let received_rx: IpcReceiver<Vec<u8>> = rx.recv().unwrap();
    
    // Use the received receiver to get data from the embedded sender
    let rx_data = received_rx.recv().unwrap();
  7. Use the `force-inprocess` feature for testing

    main
    The force-inprocess feature enables a dummy backend that behaves like real OS-specific backends but does not actually facilitate communication between different processes. This is useful for testing logic that depends on IPC without requiring actual inter-process setup.
  8. Enable Windows IPC debug tracing

    main

    You can enable detailed Win32-specific tracing for ipc-channel by setting the IPC_CHANNEL_WIN_DEBUG_TRACE environment variable. This is useful for debugging low-level pipe operations and handle recovery issues. Note that tracing is only emitted if the win32-trace feature is enabled during compilation.

    # Example for Linux/macOS shells to set the env var for a Windows process
    export IPC_CHANNEL_WIN_DEBUG_TRACE=1
  9. Create a high-performance bytes channel with bytes_channel()

    main

    Use ipc::bytes_channel() to create a connected pair of IpcBytesSender and IpcBytesReceiver. Unlike channel(), this version transfers raw [u8] slices (the sender sends [u8] and the receiver receives Vec<u8>) and bypasses serde serialization, making it more efficient for raw byte transfers.

    use ipc_channel::ipc;
    
    let payload = b"'Tis but a scratch!!";
    
    // Create a channel
    let (tx, rx) = ipc::bytes_channel().unwrap();
    
    // Send data
    tx.send(payload).unwrap();
    
    // Receive the data
    let response = rx.recv().unwrap();
    
    assert_eq!(response, payload);
  10. Use OsIpcOneShotServer for single-connection transfers

    main

    The OsIpcOneShotServer is designed for scenarios where a server creates a temporary socket, waits for a single client to connect, and receives an initial message. The server manages a temporary directory that is automatically cleaned up when the server is dropped.

    Use new() to start the server and get a path string. The client can then use connect(path) to reach it. Use accept() to wait for a client, which returns the OsIpcReceiver and the first IpcMessage sent by the client.

    use ipc_channel::platform::unix::OsIpcOneShotServer;
    
    // Server side
    let (server, path) = OsIpcOneShotServer::new().unwrap();
    let (receiver, initial_msg) = server.accept().unwrap();
    
    // Client side (in another process)
    // use connect(path) to connect to the server
  11. Handle OsIpcSelectionResult events

    main

    When using OsIpcReceiverSet::select, the returned OsIpcSelectionResult enum indicates what happened on a specific receiver ID:

    • DataReceived(u64, IpcMessage): Data was received for the given ID.
    • ChannelClosed(u64): The sender associated with the given ID has closed the channel.

    You can use OsIpcSelectionResult::unwrap() to quickly extract the ID and message, but be aware that it will panic if the result is a ChannelClosed variant.

    // Example usage (conceptual)
    // let (id, msg) = result.unwrap(); // Panics if result is ChannelClosed