Files
alknet/docs/research/references/iroh/irpc/10-quick-reference.md

7.8 KiB

irpc: Quick Reference

Crate Info

Feature Flags

Feature Default Dependencies Added
rpc noq, postcard, smallvec, tracing, tokio/io-util
derive irpc-derive
spans tracing
stream futures-util
noq_endpoint_setup rustls, rcgen, futures-buffered
varint-util postcard, smallvec, tokio/io-util

Type Quick Reference

Core Types

Service          trait — implemented on protocol enum, defines Message type
Channels<S>      trait — implemented on request types, defines Tx/Rx types
RpcMessage       trait — blanket impl for Debug+Serialize+DeserializeOwned+Send+Sync+Unpin+'static
Sender           trait — sealed marker for sender types
Receiver         trait — sealed marker for receiver types
WithChannels<I,S> struct — wraps request I with tx/rx/span for service S
Client<S>        struct — client to service S (local or remote)
LocalSender<S>   struct — local sender wrapping mpsc::Sender<S::Message>
Request<L,R>     enum  — Local(L) or Remote(R) request
RemoteSender<S>  struct — holds QUIC stream pair for sending initial message

Channel Types

oneshot::Sender<T>    — Tokio or Boxed; single value; async send
oneshot::Receiver<T>  — Tokio or Boxed; single value; Future impl
mpsc::Sender<T>       — Tokio or Arc<DynSender>; stream; async send/try_send
mpsc::Receiver<T>    — Tokio or Box<DynReceiver>; stream; async recv
NoSender              — No-op sender
NoReceiver            — No-op receiver

Remote Types (rpc feature)

RemoteConnection     trait — open_bi(), zero_rtt_accepted(), clone_boxed()
NoqLazyRemoteConnection  — lazy noq connection with cache
Handler<R>           type  — Arc<dyn Fn(R, RecvStream, SendStream) -> ...>

irpc-iroh Types

IrohRemoteConnection       — wraps iroh::Connection
IrohZrttRemoteConnection   — wraps iroh::OutgoingZeroRttConnection
IrohLazyRemoteConnection   — lazy iroh connection with cache
IrohProtocol<R>            — ProtocolHandler for iroh Router
Iroh0RttProtocol<R>        — ProtocolHandler with 0-RTT support
IncomingRemoteConnection   trait — abstraction over Connection and ZeroRttConnection

Interaction Patterns Cheatsheet

// ═══════════════════════════════════════════
// Protocol Definition
// ═══════════════════════════════════════════

#[rpc_requests(message = MyMessage)]
#[derive(Debug, Serialize, Deserialize)]
enum MyProtocol {
    // Unary RPC
    #[rpc(tx=oneshot::Sender<Response>)]
    #[wrap(GetReq)]
    Get(String),

    // Server streaming
    #[rpc(tx=mpsc::Sender<Item>)]
    #[wrap(ListReq)]
    List(ListParams),

    // Client streaming
    #[rpc(tx=oneshot::Sender<Count>, rx=mpsc::Receiver<Item>)]
    #[wrap(UploadReq)]
    Upload,

    // Bidirectional streaming
    #[rpc(tx=mpsc::Sender<Result>, rx=mpsc::Receiver<Update>)]
    #[wrap(ProcessReq)]
    Process(ProcessConfig),

    // Fire and forget
    #[rpc]
    #[wrap(LogReq)]
    Log(String),
}

// ═══════════════════════════════════════════
// Client Usage
// ═══════════════════════════════════════════

// Local
let (tx, rx) = tokio::sync::mpsc::channel(16);
tokio::task::spawn(actor(rx));
let client: Client<MyProtocol> = Client::local(tx);

// Remote (noq)
let client: Client<MyProtocol> = Client::noq(endpoint, addr);

// Remote (iroh)
let client: Client<MyProtocol> = irpc_iroh::client(endpoint, addr, alpn);

// ═══════════════════════════════════════════
// Making Requests
// ═══════════════════════════════════════════

// Unary
let result: Response = client.rpc(GetReq("key".into())).await?;

// Server streaming
let mut rx: mpsc::Receiver<Item> = client.server_streaming(ListReq(params), 16).await?;
while let Some(item) = rx.recv().await? { ... }

// Client streaming
let (update_tx, response_rx): (mpsc::Sender<Item>, oneshot::Receiver<Count>) =
    client.client_streaming(Upload, 4).await?;
update_tx.send(item).await?;
let count = response_rx.await?;

// Bidirectional
let (update_tx, mut result_rx): (mpsc::Sender<Update>, mpsc::Receiver<Result>) =
    client.bidi_streaming(ProcessReq(config), 4, 16).await?;
update_tx.send(update).await?;
while let Some(result) = result_rx.recv().await? { ... }

// Fire and forget
client.notify(LogReq("message".into())).await?;

// ═══════════════════════════════════════════
// Server Setup
// ═══════════════════════════════════════════

// noq
let handler = MyProtocol::remote_handler(local_sender);
irpc::rpc::listen(endpoint, handler).await;

// iroh
let protocol = IrohProtocol::with_sender(local_sender);
Router::builder(endpoint).accept(ALPN, protocol).spawn();

// ═══════════════════════════════════════════
// Actor Message Handling
// ═══════════════════════════════════════════

async fn handle(&mut self, msg: MyMessage) {
    match msg {
        MyMessage::Get(wc) => {
            let WithChannels { inner, tx, .. } = wc;
            let result = self.db.get(&inner.0).cloned();
            tx.send(result).await.ok();
        }
        MyMessage::List(wc) => {
            let WithChannels { tx, .. } = wc;
            for item in &self.items {
                if tx.send(item.clone()).await.is_err() { break; }
            }
        }
        MyMessage::Upload(wc) => {
            let WithChannels { tx, mut rx, .. } = wc;
            let mut count = 0;
            while let Ok(Some(item)) = rx.recv().await {
                self.process(item);
                count += 1;
            }
            tx.send(count).await.ok();
        }
        MyMessage::Process(wc) => {
            let WithChannels { tx, mut rx, inner, .. } = wc;
            tokio::task::spawn(async move {
                while let Ok(Some(update)) = rx.recv().await {
                    if let Some(result) = process(update, &inner) {
                        if tx.send(result).await.is_err() { break; }
                    }
                }
            });
        }
        MyMessage::Log(wc) => {
            let WithChannels { inner, .. } = wc;
            println!("{}", inner.0);
        }
    }
}

Error Handling Quick Reference

// Client-side errors
use irpc::{Error, RequestError, Result};

// Request errors (connection/stream open failures)
match client.rpc(GetReq("key".into())).await {
    Ok(result) => { ... }
    Err(Error::Request { source }) => { ... }  // Connection failed
    Err(Error::OneshotRecv { source }) => { ... }  // Response channel error
}

// Channel errors
use irpc::channel::{SendError, mpsc::RecvError, oneshot::RecvError};

// SendError: ReceiverClosed | MaxMessageSizeExceeded | Io
// RecvError (oneshot): SenderClosed | MaxMessageSizeExceeded | Io
// RecvError (mpsc): MaxMessageSizeExceeded | Io

Constants

pub const MAX_MESSAGE_SIZE: u64 = 16 * 1024 * 1024;  // 16 MiB
pub const ERROR_CODE_MAX_MESSAGE_SIZE_EXCEEDED: u32 = 1;
pub const ERROR_CODE_INVALID_POSTCARD: u32 = 2;
// Connection close code 0 = clean shutdown