rusteron-client 0.1.168

Provides core client functionalities for interacting with the Aeron protocol, such as establishing connections, subscribing, and publishing. It uses the Aeron C bindings from aeron-client module.
Documentation

rusteron-client

rusteron-client is a core component of the Rusteron project.
It provides a Rust wrapper around the Aeron C client API, enabling high-performance, low-latency communication in distributed systems built with Rust.

This crate supports publishing, subscribing, and managing Aeron resources, while exposing a flexible and idiomatic interface over unsafe C bindings.
Due to its reliance on raw FFI, developers must take care to manage resource lifetimes and concurrency correctly.


Sponsored by GSR

Rusteron is proudly sponsored and maintained by GSR, a global leader in algorithmic trading and market making in digital assets.

It powers mission-critical infrastructure in GSR's real-time trading stack and is now developed under the official GSR GitHub organization as part of our commitment to open-source excellence and community collaboration.

We welcome contributions, feedback, and discussions. If you're interested in integrating or contributing, please open an issue or reach out directly.


Features

  • Client Setup – Create and start an Aeron client using Rust.
  • Publications – Send messages via offer() or try_claim().
  • Subscriptions – Poll for incoming messages and handle fragments.
  • Callbacks & Handlers – React to driver events like availability, errors, and stream lifecycle changes.
  • Cloneable Wrappers – All client types are cloneable and share ownership of the underlying C resources.
  • Automatic Resource Management – Objects created with .new() automatically call *_init and *_close, where supported.
  • Result-Focused API – Methods returning primitive C results return Result<T, AeronCError> for ergonomic error handling.
  • Efficient String Interop – Inputs use &CStr, outputs return &str, giving developers precise allocation control.

Installation

Add rusteron-client to your Cargo.toml:

# Dynamic linking (default)
rusteron-client = "0.1"

# Static linking
rusteron-client = { version = "0.1", features = ["static"] }

# Static linking with precompiled C libraries (best for Mac users, no Java/cmake needed)
rusteron-client = { version = "0.1", features = ["static", "precompile"] }

# Static linking with precompiled C libraries using rustls downloader
rusteron-client = { version = "0.1", features = ["static", "precompile-rustls"] }

When using the default dynamic configuration, you must ensure Aeron C libraries are available at runtime. The static option embeds them automatically into the binary.


General Patterns

  • new() Initialization: Automatically calls the corresponding *_init method.
  • Automatic Cleanup (Partial): When possible, Drop will invoke the appropriate *_close or *_destroy methods.
  • Manual Resource Responsibility: For methods like set_aeron() or where lifetimes aren't managed internally, users are responsible for safety.
  • Handlers Must Be Leaked and Released: Callbacks passed to the C layer require explicit memory management using Handlers::leak(...) and Handlers::release(...).

Handlers and Callbacks

Handlers allow users to customize responses to Aeron events (errors, image availability, etc). There are two ways to use them:

1. Implementing a Trait (Recommended)

This is the most performant and idiomatic approach for long-lived handlers (e.g. an error handler installed on the context).

use rusteron_client::*;

pub struct MyErrorHandler;

impl AeronErrorHandlerCallback for MyErrorHandler {
    fn handle_aeron_error_handler(&mut self, code: i32, msg: &str) {
        eprintln!("Aeron error ({}): {}", code, msg);
    }
}

Wrap it with Handler::leak(...) to pass it into the C layer, and call release() once it's no longer needed:

let handler = Handler::leak(MyErrorHandler);
ctx.set_error_handler(Some(&handler))?;
// ...when done:
handler.release();

2. Short-lived Polls: Closures

For one-off message consumption, skip the trait and pass a closure directly to poll_once — no leak/release bookkeeping needed:

subscription.poll_once(
    |buf: &[u8], header: AeronHeader| {
        println!("received {} bytes at position {:?}", buf.len(), header.position());
    },
    10,
)?;

For messages larger than the MTU that need reassembling, use a fragment assembler with subscription.poll(Some(&handler), limit)poll_once delivers raw fragments and does not reassemble.

No handler needed for an optional slot? Use the typed None helpers, e.g. Handlers::no_error_handler_handler().


Minimal Pub/Sub

use rusteron_client::*;
use std::time::Duration;

let ctx = AeronContext::new()?;
// Reuse the built-in logger for async client errors (Aeron samples always set one).
let mut error_handler = Handler::leak(AeronErrorHandlerLogger);
ctx.set_error_handler(Some(&error_handler))?;
let aeron = Aeron::new(&ctx)?;
aeron.start()?;

let channel = &"aeron:ipc".into_c_string();
let publication = aeron
    .async_add_publication(channel, 123)?
    .poll_blocking(Duration::from_secs(5))?;
let subscription = aeron
    .async_add_subscription(
        channel, 123,
        Handlers::no_available_image_handler(),
        Handlers::no_unavailable_image_handler(),
    )?
    .poll_blocking(Duration::from_secs(5))?;

// offer() returns the log position (>0) or a negative code — see "Errors & offer results".
while publication.offer(b"hello", Handlers::no_reserved_value_supplier_handler()) <= 0 {}
subscription.poll_once(|buf: &[u8], _hdr: AeronHeader| println!("got {} bytes", buf.len()), 10)?;
error_handler.release();

Errors & offer results

  • Client errors: install an error handler on the context (ctx.set_error_handler(Some(&handler))) so async errors aren't silently lost — Aeron's samples always do. Leaked handlers must be release()d.
  • offer() / try_claim() results: a positive value is the resulting log position; the negatives are classified by the PUBLICATION_* constants. PUBLICATION_CLOSED, PUBLICATION_MAX_POSITION_EXCEEDED, and PUBLICATION_ERROR are fatal (stop offering); PUBLICATION_BACK_PRESSURED, PUBLICATION_NOT_CONNECTED, and PUBLICATION_ADMIN_ACTION are transient (retry, ideally with an idle strategy). This mirrors Aeron's BasicPublisher.checkResult.
    let r = publication.offer(msg, Handlers::no_reserved_value_supplier_handler());
    if r == PUBLICATION_CLOSED || r == PUBLICATION_MAX_POSITION_EXCEEDED {
        return Err("publication gone".into());
    }
    
  • Image handlers: Handlers::no_available_image_handler() / no_unavailable_image_handler() are fine as a default, but real apps usually react to image availability (logging, synchronization) — Aeron's Ping sample uses one as a latch.

Idle strategies

Poll loops should back off when a cycle does no work. Rusteron ports Aeron's IdleStrategy (idle(work_count) returns immediately when work was done, otherwise backs off):

use rusteron_client::{BackoffIdleStrategy, IdleStrategy};

let mut idle = BackoffIdleStrategy::new(); // spin → yield → sleep, Aeron's default
loop {
    let fragments = subscription.poll(Some(&handler), 10)?;
    if fragments == 0 { /* break when done */ }
    idle.idle(fragments);
}

Available: [BusySpinIdleStrategy] (lowest latency, pins a core), [YieldingIdleStrategy], [SleepingIdleStrategy] (fixed sleep), [BackoffIdleStrategy] (adaptive, general-purpose), [NoOpIdleStrategy]. Latency benchmarks should keep busy-spin.

For recording, replay, and persistent subscriptions (replay history, then seamlessly join a live stream), see rusteron-archive.


Contributing & License

See the root README and CONTRIBUTING.md. Dual-licensed under MIT or Apache-2.0.


Acknowledgments

Special thanks to:

  • @mimran1980, a core low-latency developer at GSR and the original creator of Rusteron - your work made this possible!
  • @bspeice for the original libaeron-sys
  • The Aeron community for open protocol excellence