Expand description
Efficient and seamless cross-process communication, providing semantics similar to
std::thread::spawn
and alike, both synchronously and asynchronously (via tokio or smol).
This crate allows you to easily perform computations in another process without creating a separate executable or parsing command line arguments manually. For example, the simplest example, computing a sum of several numbers in a one-shot subprocess, looks like this:
#[crossmist::main]
fn main() {
println!("5 + 7 = {}", add.run(vec![5, 7]).unwrap());
}
#[crossmist::func]
fn add(nums: Vec<i32>) -> i32 {
nums.into_iter().sum()
}
This crate also supports long-lived tasks with constant cross-process communication:
#[crossmist::main]
fn main() {
let (mut ours, theirs) = crossmist::duplex().unwrap();
add.spawn(theirs).expect("Failed to spawn child");
for i in 1..=5 {
for j in 1..=5 {
println!("{i} + {j} = {}", ours.request(&vec![i, j]).unwrap());
}
}
}
#[crossmist::func]
fn add(mut chan: crossmist::Duplex<i32, Vec<i32>>) {
while let Some(nums) = chan.recv().unwrap() {
chan.send(&nums.into_iter().sum());
}
}
§Passing objects
Almost arbitrary objects can be passed between processes and across channels, including file handles, sockets, and other channels.
For numeric types, strings, vectors, hashmaps, other common containers, and files/sockets, the
Object
trait is implemented automatically. For user-defined structures and enums, use
#[derive(Object)]
. You may use generics, but make sure to add : Object
constraint to stored
types:
use crossmist::Object;
#[derive(Object)]
struct MyPair<T: Object, U: Object> {
first: T,
second: U,
}
Occasionally, e.g. for custom hash tables or externally defined types, you might have to
implement Object
manually. Check out the documentation for Object
for more information.
§Channels
As the second example demonstrates, cross-process communication may be achieved not only via
arguments and return values, but via long-lived channels. Channels may be unidirectional (one
process has a Sender
instance and another process has a connected Receiver
instance) or
bidirectional (both processes have Duplex
instances). Channels are typed: you don’t just
send byte streams à la TCP, you send objects of a well-defined type implementing the Object
trait, making channels type-safe.
Channels implement Object
. This means that not only can you pass channels to subprocesses
as arguments (they wouldn’t be useful otherwise), but you can pass channels across other
channels, just like you can pass files across channels.
Channels are trusted. This means that if one side reads from Receiver
and another side
writes garbage to the corresponding file descriptor instead of using Sender
, the receiver
side may crash and burn, potentially leading to arbitrary code execution.
The communication protocol is not fixed and may not only change in minor versions, but be architecture- or build-dependent. This is done to both ensure performance optimizations can be implemented and to let us fix bugs quickly when they arise. As channels may only be used between two processes started from the same executable file, this does not violate semver.
§Aborting computations
If, at any point, you determine that you are no longer interested in the output of a process, you can kill it:
#[crossmist::main]
fn main() {
let mut child = long_computation.spawn().expect("Failed to spawn child");
let kill_handle = child.get_kill_handle();
std::thread::spawn(move || {
// Wait, I don't need this, actually!
std::thread::sleep(std::time::Duration::from_millis(500));
kill_handle.kill();
});
// This will fail in 0.5 seconds:
child.join().unwrap_err();
}
#[crossmist::func]
fn long_computation() {
loop {}
}
§Features
This crate provides the following features:
Re-exports§
pub use blocking::channel;
pub use blocking::duplex;
pub use blocking::Child;
pub use blocking::Duplex;
pub use blocking::Receiver;
pub use blocking::Sender;
pub use delayed::Delayed;
pub use static_ref::StaticRef;
pub use serde::*;
pub use fns::*;
Modules§
- Generic asynchronous implementation.
- Uni- and bidirectional channels between processes.
- A wrapper for objects that require global state to be configured before deserialization.
- Utilities for passing function callbacks between processes.
- Cross-platform alternative to file descriptors.
- Serialization and deserialization.
- smol
smol
Asynchronous implementation using smol runtime. - Serializing references to constant objects.
- tokio
tokio
Asynchronous implementation using tokio runtime.
Macros§
- A short-cut for turning a (possible capturing) closure into an object function, just like as if
#[func]
was used. - Create a
StaticRef
safely.
Structs§
- A handle that allows to kill the process.
Traits§
- A serializable object.
Functions§
- Initialize the crossmist runtime.
Attribute Macros§
- Enable a function to be used as an entrypoint of a child process, and turn it into an
Object
. - Setup an entrypoint.
Derive Macros§
- Make a structure or a enum serializable.