Skip to main content

Crate myelin

Crate myelin 

Source
Expand description

§myelin

Define async service APIs as traits, communicate over channels.

The trait definition is the single source of truth. A proc macro generates the channel plumbing: request/response enums, client stubs, and server dispatch. Transport and serialization are pluggable — local in-process channels just move Send structs; cross-boundary transports serialize transparently.

§Transports

Implementations:

  • transport_tokio (feature tokio) — tokio mpsc + oneshot, local, no serialization.
  • transport_smol (feature smol) — async-channel mpsc + bounded(1) reply channel, local, no serialization.
  • transport_embassy (feature embassy) — embassy static channels + signals.
  • transport_postcard (feature postcard) — postcard serialization over any sync Read + Write stream, length-prefix framing. Internally wraps the sync I/O in io::BlockingIo so the async transport stack runs each read/write inline — suitable for stdio-style binaries driven by a trivial BlockOn.

§Async I/O

The stream transport (stream::StreamTransport) operates on io::AsyncBytesRead / io::AsyncBytesWrite — myelin’s own runtime-neutral async byte-stream traits (just read_exact, write_all, flush). Bring your own reader/writer by implementing those traits, or use one of the feature-gated adapters:

  • io::BlockingIo — wrap any sync std::io::Read/Write. The async methods resolve inline; used by transport_postcard for stdio transport.
  • io::futures_io (feature futures-io) — adapt any type bounded by futures_io::AsyncRead / AsyncWrite. Covers smol’s smol::Async<T>, async-std, and anything in the futures-io ecosystem.
  • io::tokio_io (feature tokio-io) — adapt tokio::io::AsyncRead / AsyncWrite. Independent of the tokio feature (channels-only transport).

Inside myelin core, shared access to a reader/writer between concurrent async tasks is mediated by a small single-waiter async mutex, io::LocalLock. It replaces the old RefCell-based scheme (unsound across .await) and is cancel-safe.

§Duplex transport

For peers that both call and serve over one byte stream, use stream::DuplexStreamTransport. It owns one (reader, writer) pair and vends:

  • stream::DuplexClientHalfimpl ClientTransport per API that this peer calls into.
  • stream::DuplexServerHalfimpl ServerTransport per API that this peer serves.
  • stream::DuplexPump — a run() future the user’s runtime spawns; it drives the reader and demultiplexes frames into either the slot router (responses) or a per-api_id server inbox (requests).

Wire format inside each framed payload: [u8 kind][u16 api_id LE] [u8 slot_id][codec bytes]. kind is 0 (request) or 1 (response); api_id identifies the API; slot_id is the caller’s echo-back value. See stream::duplex for details.

Example (sketch, full example in tests/duplex_smol_integration.rs):

use myelin::io::futures_io::{FuturesIoReader, FuturesIoWriter};
use myelin::stream::{DuplexStreamTransport, LengthPrefixed, PostcardCodec};

type Dx<R, W> = DuplexStreamTransport<R, W, LengthPrefixed, PostcardCodec, 8, 512>;

let dx: Dx<_, _> = Dx::new(r, w);
let server = dx.server_half::<u32, u32>(0x0001);
let client = dx.client_half::<u32, u32>(0x0002);
let (pump, _handle) = dx.split();
smol::block_on(async {
    // Run pump concurrently with your server loop and client calls.
    let _ = futures_lite::future::zip(pump.run(), async { /* ... */ }).await;
});

§Cancel Safety

All myelin transports provide the following cancel safety guarantees:

§Cancelling a client call is always safe

A client’s call() future can be dropped at any .await point without corrupting the transport or leaking state.

  • Dropped before the request is sent: No effect. The request was never enqueued and no server-side resources are consumed.

  • Dropped after the request is sent, before the reply is received: The server will still process the request and produce a response, but that response is discarded. This means the server does wasted work, but there is no protocol corruption, no leaked memory, and no poisoned state. The client can immediately make another call.

§Cancelling a server task affects in-flight clients

If the server task/thread is cancelled or shut down, clients with in-flight requests will observe a transport-specific outcome:

  • Tokio: The client receives a ChannelClosed error (the mpsc/oneshot senders are dropped).
  • Embassy: The client’s Signal::wait() will never complete — the client hangs. Avoid cancelling embassy server tasks while clients are in-flight.
  • PostcardStream: The underlying I/O stream is closed, producing an I/O error on the client side.

§Summary

ScenarioResult
Client cancelled before sendClean, no effect
Client cancelled after sendServer does wasted work, reply discarded
Server cancelledIn-flight clients get an error (tokio/postcard) or hang (embassy)

Re-exports§

pub use block_on::BlockOn;
pub use error::CallError;
pub use error::TransportResult;
pub use transport::ClientTransport;
pub use transport::ServerTransport;

Modules§

block_on
Blocking executor trait for sync client wrappers.
error
Error types and result helpers for myelin service calls.
io
Async byte-stream I/O traits owned by myelin.
stream
Layered stream transport: Framing × Encoding × ReplyRouting.
transport
The core transport traits.
transport_embassy
Embassy-based local transport: static Channel for requests, per-client Signal for replies.
transport_postcard
Postcard transport over any Read + Write stream.
transport_smol
Smol-compatible local transport, mirroring transport_tokio.
transport_tokio
Tokio-based local transport: mpsc for requests, oneshot for replies.

Macros§

compose_service
Compose multiple service APIs into a single multiplexed service.

Attribute Macros§

service
Generate channel-API plumbing from an async trait definition.