Expand description
§rapace-cell
High-level cell runtime for rapace that eliminates boilerplate.
This crate provides simple APIs for building rapace cells that communicate via SHM transport. It handles all the common setup that every cell needs:
- CLI argument parsing (
--shm-pathor positional args) - Waiting for the host to create the SHM file
- SHM session setup with standard configuration
- RPC session creation with correct channel ID conventions (cells use even IDs)
- Service dispatcher setup
§Before & After
§Before (95+ lines of boilerplate)
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use rapace::transport::shm::{ShmSession, ShmSessionConfig, ShmTransport};
use rapace::{Frame, RpcError, RpcSession};
const SHM_CONFIG: ShmSessionConfig = ShmSessionConfig {
ring_capacity: 256,
slot_size: 65536,
slot_count: 128,
};
struct Args {
shm_path: PathBuf,
}
fn parse_args() -> Result<Args> {
let mut args = std::env::args();
args.next();
let shm_path = match args.next() {
Some(path) => PathBuf::from(path),
None => return Err(eyre!("Usage: cell <shm_path>")),
};
Ok(Args { shm_path })
}
fn create_dispatcher(
impl_: MyServiceImpl,
) -> impl Fn(u32, u32, Vec<u8>)
-> Pin<Box<dyn Future<Output = Result<Frame, RpcError>> + Send>>
+ Send + Sync + 'static
{
move |_channel_id, method_id, payload| {
let impl_ = impl_.clone();
Box::pin(async move {
let server = MyServiceServer::new(impl_);
server.dispatch(method_id, &payload).await
})
}
}
#[tokio::main]
async fn main() -> Result<()> {
let args = parse_args()?;
// Wait for SHM file
while !args.shm_path.exists() {
tokio::time::sleep(Duration::from_millis(100)).await;
}
let shm_session = ShmSession::open_file(&args.shm_path, SHM_CONFIG)?;
let transport = Arc::new(ShmTransport::new(shm_session));
let session = Arc::new(RpcSession::with_channel_start(transport, 2));
let dispatcher = create_dispatcher(MyServiceImpl);
session.set_dispatcher(dispatcher);
session.run().await?;
Ok(())
}§After (3 lines!)
use rapace_cell::run;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
run(MyServiceServer::new(MyServiceImpl)).await?;
Ok(())
}§Usage
§Single-service cells
For simple cells that expose a single service:
use rapace_cell::{run, ServiceDispatch};
use rapace::{Frame, RpcError};
use std::future::Future;
use std::pin::Pin;
// Your service implementation
#[derive(Clone)]
struct MyServiceImpl;
// Your generated service server
struct MyServiceServer {
impl_: MyServiceImpl,
}
impl MyServiceServer {
fn new(impl_: MyServiceImpl) -> Self {
Self { impl_ }
}
async fn dispatch(&self, method_id: u32, payload: &[u8]) -> Result<Frame, RpcError> {
// Your dispatch logic
todo!()
}
}
// Implement ServiceDispatch so the cell runtime can use it
impl ServiceDispatch for MyServiceServer {
fn dispatch(
&self,
method_id: u32,
payload: &[u8],
) -> Pin<Box<dyn Future<Output = Result<Frame, RpcError>> + Send + 'static>> {
Box::pin(Self::dispatch(self, method_id, payload))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let server = MyServiceServer::new(MyServiceImpl);
run(server).await?;
Ok(())
}§Multi-service cells
For cells that expose multiple services:
use rapace_cell::{run_multi, DispatcherBuilder, ServiceDispatch};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
run_multi(|builder| {
builder
.add_service(MyServiceServer::new(MyServiceImpl))
.add_service(AnotherServiceServer::new(AnotherServiceImpl))
}).await?;
Ok(())
}§Using RpcSessionExt for custom setups
If you need more control but still want simplified service setup:
use rapace_cell::{RpcSessionExt, DEFAULT_SHM_CONFIG};
use rapace::transport::shm::{ShmSession, ShmTransport};
use rapace::RpcSession;
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Your custom setup logic...
let shm_session = ShmSession::open_file(&shm_path, DEFAULT_SHM_CONFIG)?;
let transport = Arc::new(ShmTransport::new(shm_session));
let session = Arc::new(RpcSession::with_channel_start(transport, 2));
// Simple service setup with extension trait
session.set_service(MyServiceServer::new(MyServiceImpl));
session.run().await?;
Ok(())
}§Configuration
The default SHM configuration is:
pub const DEFAULT_SHM_CONFIG: ShmSessionConfig = ShmSessionConfig {
ring_capacity: 256, // 256 descriptors in flight
slot_size: 65536, // 64KB per slot
slot_count: 128, // 128 slots = 8MB total
};You can customize this with run_with_config() or run_multi_with_config():
use rapace_cell::{run_with_config, DEFAULT_SHM_CONFIG};
use rapace::transport::shm::ShmSessionConfig;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let custom_config = ShmSessionConfig {
ring_capacity: 512,
slot_size: 131072, // 128KB
slot_count: 256,
};
run_with_config(MyServiceServer::new(MyServiceImpl), custom_config).await?;
Ok(())
}§CLI Arguments
The cell runtime accepts the SHM path in two formats:
# Flag format (recommended)
./my-cell --shm-path=/tmp/my-app.shm
# Positional format
./my-cell /tmp/my-app.shm§Channel ID Conventions
The cell runtime automatically uses the correct channel ID convention:
- Cells: Even channel IDs starting from 2 (2, 4, 6, …)
- Hosts: Odd channel IDs starting from 1 (1, 3, 5, …)
You don’t need to worry about this - it’s handled automatically.
§Error Handling
The cell runtime provides a CellError type that covers common failure modes:
CellError::Args- Invalid command-line argumentsCellError::ShmTimeout- SHM file not created by host within 5 secondsCellError::ShmOpen- Failed to open SHM sessionCellError::Rpc- RPC session error
§Tracing
The cell runtime doesn’t configure tracing by default - you should set it up yourself in main():
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Simple console logging
tracing_subscriber::fmt::init();
// Or use rapace-tracing to forward logs to the host
// (see rapace-tracing documentation)
run(MyServiceServer::new(MyServiceImpl)).await?;
Ok(())
}§License
MIT OR Apache-2.0
Re-exports§
pub use lifecycle::CellLifecycle;pub use lifecycle::CellLifecycleClient;pub use lifecycle::CellLifecycleServer;pub use lifecycle::ReadyAck;pub use lifecycle::ReadyMsg;pub use tracing_setup::TracingConfigService;
Modules§
- lifecycle
- Cell lifecycle protocol
- tracing_
setup - Tracing integration for rapace cells.
Macros§
- cell_
service - Macro to wrap a generated server type as a
ServiceDispatchcell service. - run_
cell - Macro to run a cell with minimal boilerplate.
- run_
cell_ with_ session - Macro to run a cell whose setup needs access to the RPC session.
Structs§
- Dispatcher
Builder - Builder for creating multi-service dispatchers
- Frame
- Owned frame for sending, receiving, or routing.
- RpcSession
- RpcSession owns a transport and multiplexes frames between clients and servers.
Enums§
Constants§
- DEFAULT_
SHM_ CONFIG - Default SHM configuration for two-peer sessions.
Traits§
- RpcSession
Ext - Extension trait for RpcSession to support single-service setup
- Service
Dispatch - Trait for service servers that can be dispatched
Functions§
- run
- Run a single-service cell
- run_
multi - Run a multi-service cell
- run_
multi_ with_ config - Run a multi-service cell with custom SHM configuration
- run_
with_ config - Run a single-service cell with custom SHM configuration
- run_
with_ session - Run a single-service cell, but let the service factory access the
RpcSession. - run_
with_ session_ and_ config - Run a single-service cell with session access and custom SHM configuration.