Skip to main content

ergo_aeron_cluster/
lib.rs

1//! # ergo-aeron-cluster
2//!
3//! Experimental Rust **client** for [Aeron Cluster](https://github.com/real-logic/aeron)
4//! on [`rusteron_client`] **0.2** (latest 0.2.x), using **ergo-sbe-generated**
5//! session codecs (schema 111).
6//!
7//! # Documentation
8//!
9//! - **[ergo-sbe book](https://mimran1980.github.io/ergon/)** — cluster client
10//!   guide
11//! - [Overview](https://mimran1980.github.io/ergon/cluster/overview.html) ·
12//!   [SessionBuilder](https://mimran1980.github.io/ergon/cluster/session-builder.html) ·
13//!   [Egress listeners](https://mimran1980.github.io/ergon/cluster/egress-listeners.html) ·
14//!   [Chained decoding](https://mimran1980.github.io/ergon/cluster/chained-decoding.html)
15//! - [Crate README](https://github.com/mimran1980/ergon/blob/main/cluster/README.md)
16//!
17//! ⚠️ **Prototype.** LLM-assisted and less tested than the Java reference.
18//! Bugs in Rusteron pub/sub **or** this reimplementation may cause undefined
19//! behaviour, segfaults, or data loss. Replace when official Cluster C client
20//! bindings are suitable for your deployment.
21//!
22//! ## Client-only — the Java process *is* the cluster
23//!
24//! This crate implements the **client** side of the Aeron Cluster protocol
25//! only — parity with Java
26//! [`io.aeron.cluster.client`](https://github.com/real-logic/aeron/tree/master/aeron-cluster/src/main/java/io/aeron/cluster/client)
27//! (connect, offer/`try_claim`, poll egress, leader failover, keep-alive,
28//! admin snapshot, challenge-response auth). It does **not** implement — and
29//! never will — the cluster **server**: no consensus module (Raft), no
30//! clustered-service container, no leader election, no snapshots/recovery, no
31//! archive, no backup node, no `ClusterTool` CLI. You run all of that as the
32//! **Java Aeron process**; this client connects to it over the standard Aeron
33//! wire protocol.
34//!
35//! # Hot path
36//!
37//! 1. [`AeronCluster::try_claim`] — SessionMessageHeader into the claim via ergo-sbe
38//! 2. Egress decode (`egress` / `poller` / `controlled`) — SessionEvent, NewLeader, app
39//! 3. Keep-alive encode — periodic
40//! 4. Connect / auth / failover — cold path (correctness over nanoseconds)
41//!
42//! # Codecs
43//!
44//! Production modules: `codecs::session` (schema 111) and `codecs::mark`,
45//! generated in `build.rs` from the vendored Aeron schemas. The sbe-tool
46//! reference runtime lives at `cluster/benches/reference_sbe/`
47//! (Criterion-private — never imported from library, test, or example code).
48//!
49//! # Quick connect
50//!
51//! ```rust
52//! use std::sync::Arc;
53//! use ergo_aeron_cluster::{
54//!     SessionBuilder,
55//!     NullCredentialsSupplier,
56//!     StaticCredentials,
57//! };
58//! fn demo() -> Result<(), ergo_aeron_cluster::ClusterError> {
59//!     let builder = SessionBuilder::default()
60//!         .ingress_channel("aeron:udp?endpoint=localhost:9010")?
61//!         .egress_channel("aeron:udp?endpoint=localhost:9020")?
62//!         .credentials(Arc::new(StaticCredentials::from_utf8("user:pass")))
63//!         .message_timeout(std::time::Duration::from_secs(5))?;
64//!     builder.validate()
65//! }
66//! demo().expect("valid config");
67//! ```
68//!
69//! ```rust,no_run
70//! use ergo_aeron_cluster::{AeronCluster, ClusterError, SessionBuilder};
71//!
72//! fn publish(aeron_dir: &str, app_bytes: &[u8]) -> Result<(), ClusterError> {
73//!     let builder = SessionBuilder::default()
74//!         .ingress_channel("aeron:udp?endpoint=localhost:9010")?
75//!         .egress_channel("aeron:udp?endpoint=localhost:9020")?;
76//!     let mut client = AeronCluster::connect(&builder, aeron_dir)?;
77//!     let mut claim = client.try_claim(app_bytes.len())?;
78//!     claim.payload_mut().copy_from_slice(app_bytes);
79//!     claim.commit()?;
80//!     Ok(())
81//! }
82//! ```
83//!
84//! See the [book](https://mimran1980.github.io/ergon/cluster/overview.html) and
85//! [crate README](https://github.com/mimran1980/ergon/blob/main/cluster/README.md)
86//! for recipes, maintained benches, and the HA sample.
87
88// Verify rusteron-client types are accessible across the crate boundary
89#[doc(hidden)]
90pub mod transport {
91    pub use rusteron_client::Aeron;
92    pub use rusteron_client::AeronContext;
93    pub use rusteron_client::AeronExclusivePublication;
94    pub use rusteron_client::AeronPublication;
95    pub use rusteron_client::AeronSubscription;
96}
97
98/// High-level cluster client: connect, try_claim, offer, keep-alive, close.
99pub mod client;
100/// SBE codecs: ergo-sbe production modules + residual sbe-tool trees for benches.
101pub(crate) mod codecs;
102
103/// Generated codec types re-exported for integration tests and benches only.
104/// These are not a stable consumer API — use `AeronCluster` for normal usage.
105#[doc(hidden)]
106pub mod cluster_codec_types {
107    pub use crate::codecs::session::{
108        AdminRequestEncoder, AdminRequestFixedFields, AdminRequestType, AdminResponseCode, AdminResponseEncoder,
109        AdminResponseFixedFields, AnyMessage, ChallengeDecoder, ChallengeEncoder, ChallengeFixedFields,
110        ChallengeResponseEncoder, ChallengeResponseFixedFields, EventCode, NewLeaderEventDecoder,
111        NewLeaderEventEncoder, NewLeaderEventFixedFields, SessionCloseRequestEncoder, SessionCloseRequestFixedFields,
112        SessionConnectRequestEncoder, SessionConnectRequestFixedFields, SessionEventDecoder, SessionEventEncoder,
113        SessionEventFixedFields, SessionKeepAliveEncoder, SessionKeepAliveFixedFields, SessionMessageHeaderDecoder,
114        SessionMessageHeaderEncoder, SessionMessageHeaderFixedFields,
115    };
116}
117/// [`SessionBuilder`] configuration for connect.
118pub mod config;
119/// Controlled egress poll (Java `ControlledEgressAdapter` analogue).
120pub mod controlled;
121/// Credential supplier traits for challenge-response auth.
122pub mod credentials;
123/// Egress adapter + listener dispatch for session and app messages.
124pub mod egress;
125/// Multi-member ingress endpoint maps (`0=host:port,…`).
126pub mod endpoints;
127/// Cluster client error type.
128pub mod error;
129/// Shared fragment decode — canonical AnyMessage dispatch used by egress,
130/// controlled, and poller paths. Not public API.
131pub(crate) mod fragment;
132/// Poll-loop idle helpers ([`rusteron_client::IdleStrategy`]).
133pub mod idle;
134/// Low-level egress event parse helpers (SessionEvent, NewLeader, redirects).
135pub mod poller;
136/// [`SessionState`] machine for connected / new-leader / closed.
137pub mod state;
138/// Aeron channel URI helpers (`AeronUriStringBuilder`).
139mod uri;
140
141pub use client::{AeronCluster, AsyncClusterConnect, ClusterClaim, ConnectStep};
142pub use config::SessionBuilder;
143pub use controlled::{ControlledEgressAdapter, ControlledEgressListener, ControlledPollAction};
144pub use credentials::{CredentialsSupplier, NullCredentialsSupplier, StaticCredentials};
145pub use egress::{EgressAdapter, EgressListener, NullListener};
146pub use endpoints::{IngressEndpoint, parse_ingress_endpoints};
147pub use error::{AeronErrorSource, ClusterError, PublicationFailure};
148pub use idle::{default_idle, poll_connect_until_done};
149pub use poller::{EgressEvent, parse_event};
150pub use state::SessionState;
151pub use uri::AERON_IPC_STREAM;
152
153/// Java Aeron Cluster spawn harness (integration tests / examples only).
154///
155/// Enable with `--features test-harness` (requires Java 17+ and
156/// `just build-aeron-jars`). Not for production and not published as a separate crate.
157#[cfg(feature = "test-harness")]
158pub mod test_support;
159
160#[cfg(feature = "test-harness")]
161pub use test_support::{EmbeddedArchiveDriver, TestCluster};
162
163#[cfg(test)]
164#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
165mod tests {
166    #[test]
167    fn scaffold_compiles() -> Result<(), Box<dyn std::error::Error>> {
168        // Smoke-check ergo-sbe production codecs are wired into the lib.
169        assert_eq!(crate::codecs::session::SessionConnectRequestEncoder::SCHEMA_ID, 111);
170
171        Ok(())
172    }
173}