Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve documentation #384

Merged
merged 2 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 44 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# ipc-channel
`ipc-channel` is an inter-process implementation of Rust channels (which were inspired by CSP[^CSP]).

📚 [Documentation](https://docs.rs/ipc-channel) 📚
A Rust channel is a unidirectional, FIFO queue of messages which can be used to send messages between threads in a single operating system process.
For an excellent introduction to Rust channels, see [Using Message Passing to Transfer Data Between Threads](https://doc.rust-lang.org/stable/book/ch16-02-message-passing.html) in the Rust reference.

## Overview

`ipc-channel` is an implementation of the Rust channel API (a form of communicating sequential processes, CSP) over the native OS abstractions. Under the hood, this API uses Mach ports on the Mac and file descriptor passing over Unix sockets on Linux. The `serde` library is used to serialize values for transport over the wire.
`ipc-channel` extends Rust channels to support inter-process communication (IPC) in a single operating system instance. The `serde` library is used to serialize and deserialize messages sent over `ipc-channel`.

As much as possible, `ipc-channel` has been designed to be a drop-in replacement for Rust channels. The mapping from the Rust channel APIs to `ipc-channel` APIs is as follows:

Expand All @@ -14,10 +13,47 @@ As much as possible, `ipc-channel` has been designed to be a drop-in replacement

Note that both `IpcSender<T>` and `IpcReceiver<T>` implement `Serialize` and `Deserialize`, so you can send IPC channels over IPC channels freely, just as you can with Rust channels.

The easiest way to make your types implement `Serialize` and `Deserialize` is to use the `serde_macros` crate from crates.io as a plugin and then annotate the types you want to send with `#[derive(Deserialize, Serialize])`. In many cases, that's all you need to do—the compiler generates all the tedious boilerplate code needed to save and restore instances of your types.
The easiest way to make your types implement `Serialize` and `Deserialize` is to use the `serde_macros` crate from crates.io as a plugin and then annotate the types you want to send with `#[derive(Deserialize, Serialize])`. In many cases, that's all you need to do — the compiler generates all the tedious boilerplate code needed to serialize and deserialize instances of your types.

## Semantic differences from Rust channels

* Rust channels are MPSC (multi-producer, single-consumer) whereas ipc-channels are SPSC (single-producer, single-consumer).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't true—you can clone IPC senders and the receiver will receive from all of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. I was confusing this with the single-connect behaviour of one-shot servers.

* Rust channels can be either unbounded or bounded whereas ipc-channels are always unbounded and `send()` never blocks.
* Rust channels do not consume OS IPC resources whereas ipc-channels consume IPC resources such as sockets, file descriptors, shared memory segments, named pipes, and such like, depending on the OS.
* Rust channels transfer ownership of messages whereas ipc-channels serialize and deserialize messages.
* Rust channels are type safe whereas ipc-channels depend on client and server programs using identical message types (or at least message types with compatible serial forms).

## Bootstrapping channels between processes

`ipc-channel` provides a one-shot server to help establish a channel between two processes. When a one-shot server is created, a server name is generated and returned along with the server.

The client process calls `connect()` passing the server name and this returns the sender end of an ipc-channel from
the client to the server. Note that there is a restriction in `ipc-channel`: `connect()` may be called at most once per one-shot server.

The server process calls `accept()` on the server to accept connect requests from clients. `accept()` blocks until a client has connected to the server and sent a message. It then returns a pair consisting of the receiver end of the ipc-channel from client to server and the first message received from the client.

In order to bootstrap an IPC connection across processes, you create an instance of the `IpcOneShotServer` type, register a global name, pass that name into the client process (perhaps with an environment variable or command line flag), and connect to the server in the client. See `cross_process_embedded_senders()` in `test.rs` for an example of how to do this using Unix `fork()` to spawn the process.
So, in order to bootstrap an IPC channel between processes, you create an instance of the `IpcOneShotServer` type, pass the resultant server name into the client process (perhaps via an environment variable or command line flag), and connect to the server in the client. See `spawn_one_shot_server_client()` in `integration_test.rs` for an example of how to do this using a command to spawn the client process and `cross_process_embedded_senders_fork()` in `test.rs` for an example of how to do this using Unix `fork()`[^fork] to create the client process.

## Implementation overview

`ipc-channel` is implemented in terms of native IPC primitives: file descriptor passing over Unix sockets on Unix variants, Mach ports on macOS, and named pipes on Windows.

One-shot server names are implemented as a file system path (for Unix variants, with the file system path bound to the socket) or other kinds of generated names on macOS and Windows.

## Major missing features

* Servers only accept one client at a time. This is fine if you simply want to use this API to split your application up into a fixed number of mutually untrusting processes, but it's not suitable for implementing a system service. An API for multiple clients may be added later if demand exists for it.
* Each one-shot server accepts only one client connect request. This is fine if you simply want to use this API to split your application up into a fixed number of mutually untrusting processes, but it's not suitable for implementing a system service. An API for multiple clients may be added later if demand exists for it.

## Related

* [Rust channel](https://doc.rust-lang.org/std/sync/mpsc/index.html): MPSC (multi-producer, single-consumer) channels in the Rust standard library. The implementation
consists of a single consumer wrapper of a port of Crossbeam channel.
* [Crossbeam channel](https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-channel): extends Rust channels to be more like their Go counterparts. Crossbeam channels are MPMC (multi-producer, multi-consumer)
* [Channels](https://docs.rs/channels/latest/channels/): provides Sender and Receiver types for communicating with a channel-like API across generic IO streams.

[^CSP]: Tony Hoare conceived Communicating Sequential Processes (CSP) as a concurrent programming language.
Stephen Brookes and A.W. Roscoe developed a sound mathematical basis for CSP as a process algebra.
CSP can now be used to reason about concurrency and to verify concurrency properties using model checkers such as FDR4.
Go channels were also inspired by CSP.

[^fork]: `fork()` has a number of semantic rough edges and is not recommended for general use. See "A fork() in the road" by Andrew Baumann _et al._, Proceedings of the Workshop on Hot Topics in Operating Systems, ACM, 2019. ([PDF](https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf))
3 changes: 2 additions & 1 deletion src/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,8 @@ impl Serialize for OpaqueIpcReceiver {
}
}

/// A server associated with a given name.
/// A server associated with a given name. The server is "one-shot" because
/// it accepts only one connect request from a client.
///
/// # Examples
///
Expand Down
13 changes: 1 addition & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,14 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! An implementation of the Rust channel API over process boundaries. Under the
//! hood, this API uses Mach ports on Mac and file descriptor passing over Unix
//! sockets on Linux. The serde library is used to serialize values for transport
//! over the wire.
#![doc = include_str!("../README.md")]
//!
//! # Features
//! ## `force-inprocess`
//!
//! Force the `inprocess` backend to be used instead of the OS specific backend.
//! The `inprocess` backend is a dummy back-end, that behaves like the real ones,
//! but doesn't actually work between processes.
//!
//! ## `unstable`
//!
//! [IpcReceiver]: ipc/struct.IpcReceiver.html
//! [IpcSender]: ipc/struct.IpcSender.html
//! [IpcReceiverSet]: ipc/struct.IpcReceiverSet.html
//! [IpcSharedMemory]: ipc/struct.IpcSharedMemory.html
//! [OsIpcSharedMemory]: platform/struct.OsIpcSharedMemory.html

#[cfg(any(
feature = "force-inprocess",
Expand Down
2 changes: 1 addition & 1 deletion src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// except according to those terms.

//! Routers allow converting IPC channels to crossbeam channels.
//! The [RouterProxy](crate::router::RouterProxy) provides various methods to register
//! The [RouterProxy] provides various methods to register
//! `IpcReceiver<T>`s. The router will then either call the appropriate callback or route the
//! message to a crossbeam `Sender<T>` or `Receiver<T>`. You should use the global `ROUTER` to
//! access the `RouterProxy` methods (via `ROUTER`'s `Deref` for `RouterProxy`.
Expand Down