1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
//! Multiprocess shared-memory transport for inference and other
//! low-latency pipelines.
//!
//! `myelon` is the full transport surface built on top of
//! [`disruptor_mp`].
//!
//! The design goal is straightforward:
//!
//! - the raw ring should remain reachable
//! - the higher layers should be available when you need them
//! - callers should not have to assemble framing, typed transport, and
//! zero-copy access as separate dependencies
//!
//! `myelon` therefore re-exports the raw substrate from
//! [`disruptor_mp`] and adds framed transport, codec-backed typed
//! transport, typed zero-copy, topology helpers, and transport-layout
//! helpers behind one dependency.
//!
//! Both `myelon` and `disruptor-mp` are valid entry points. Use
//! `myelon` when you want the higher-level transport layers available;
//! use `disruptor-mp` directly when you only need the raw substrate.
//! Type identity is preserved across the re-export boundary, so a
//! `disruptor_mp::SharedConsumer<E>` is the same type as a
//! `myelon::SharedConsumer<E>`.
//!
//! # What this crate exposes
//!
//! | Need | Layer | Type |
//! |---|---|---|
//! | Fixed-size, `Copy` event. You own the wire format. | 0 — raw | [`SharedProducer<E>`] / [`SharedConsumer<E>`] (SHM) or [`MmapProducer<E>`] / [`MmapConsumer<E>`] (mmap) |
//! | Variable-length `&[u8]` payloads, possibly larger than one ring slot. Need start/end flags + a message id. | 1 — framed | [`transport::FramedTransportProducer`] / [`transport::FramedTransportConsumer`] (SHM) or [`transport::MmapFramedTransportProducer`] / [`transport::MmapFramedTransportConsumer`] (mmap) |
//! | Typed message with serialisation (bincode / rkyv / flatbuffers). Owned decode on the consumer side. | 2 — codec | [`typed_transport::TypedProducer`] / [`typed_transport::TypedConsumer`] + a [`codec::Codec`] impl |
//! | Same as Layer 2 but consumer reads serialised data in-place — no `deserialize` allocation. | 3 — typed zero-copy | [`typed_transport::TypedProducer`] / [`typed_transport::TypedConsumer`] + a [`codec::ZeroCopyCodec`] impl |
//!
//! Pick the highest-level surface that matches your data model, then
//! measure before stepping down.
//!
//! # Choosing a frame size
//!
//! The framed transport's frame type is a **const generic** —
//! callers pick the per-frame payload capacity at compile time:
//!
//! ```rust
//! use myelon::transport::{FixedFrame, AlignedFixedFrame};
//! type Frame64K = FixedFrame<{ 64 * 1024 - 12 }>; // 12-byte header
//! type FrameTiny = FixedFrame<256>;
//! type Frame4K = FixedFrame<{ 4 * 1024 - 12 }>;
//! type ZcFrame = AlignedFixedFrame<{ 64 * 1024 - 16 }>; // 16-byte header
//! ```
//!
//! There is nothing special about 64 KB — that is just the convention
//! [`perf-bench`](https://github.com/Venkat2811/myelon/tree/main/crates/perf-bench)
//! uses. The trade-off:
//!
//! | Frame size | Effect |
//! |---|---|
//! | Larger `DATA_BYTES` | Bigger ring slot, bigger SHM footprint for the same depth, but messages that fit in one slot avoid fragmentation. |
//! | Smaller `DATA_BYTES` | Smaller slot, tighter memory, multi-frame fragmentation kicks in once payload exceeds `DATA_BYTES`. |
//! | Right-sized to dominant payload | Single-frame messages, lowest overhead, at the cost of flexibility for atypical sizes. |
//!
//! The slot size must be a compile-time constant so the ring layout
//! is known. To support a runtime-chosen size, instantiate one
//! transport per size class. `perf-bench`'s `nofrag` mode is a
//! worked example of this pattern.
//!
//! # Two senses of zero-copy
//!
//! Two different things in this stack get called "zero-copy", and
//! they're not the same:
//!
//! | Sense | Where | How |
//! |---|---|---|
//! | **Memory-level zero-copy** | Layer 0 already provides this. | `try_consume_next_leased()` returns `&E` straight into the ring slot. No allocation, no copy. Use this for fixed-size `Copy + repr(C)` events. |
//! | **Typed-format zero-copy** | Layer 3. | The bytes on the wire are already a serialised graph (rkyv `Archived<T>` or a flatbuffers root table); the consumer reads fields in-place via [`codec::ZeroCopyCodec::access`]. |
//!
//! There is no separate "raw + typed zero-copy" layer. If your event
//! is fixed-size and `Copy + repr(C)`, just put the struct in the
//! slot — Layer 0 is already memory-zero-copy and outperforms an
//! archived blob. If you want serialisation flexibility, you also
//! want framing. The four-layer picture covers both senses without
//! overlap.
//!
//! # Cross-cutting concerns
//!
//! These wrap *across* layers — pick them by what your *system* needs,
//! not by what your *wire format* needs.
//!
//! | Concern | Type | Purpose |
//! |---|---|---|
//! | Coordination | [`producer::CoordinationMode`] | When does the producer consider its peers attached? |
//! | Discovery | builder methods on [`SharedDisruptorBuilder`] | Producer locates consumers by explicit ID or by prefix. |
//! | Liveness | [`RequiredConsumerLivenessConfig`], [`RequiredConsumerFailureAction`] (see below) | A stalled required consumer becomes a failure or alert, not silent backpressure. |
//! | Topology | [`inference::FixedTopology`], [`inference::WorkerCount`] | Pre-baked one-scheduler / N-worker shape with discovery + rendezvous. |
//! | Layout | [`transport::MyelonTransportLayout`], [`transport::MyelonTransportConfig`], [`transport::RunnerMyelonTransportConfig`] | macOS-safe SHM segment names for one-engine / N-runner sessions. |
//! | Observability | [`observability`] (re-export of [`disruptor_mp::observability`]) | RFC-0040 hot-path counters file. |
//!
//! # Required-consumer liveness
//!
//! Out of the box, the base model is strict broadcast — the slowest
//! consumer gates capacity. A stalled or crashed required consumer
//! therefore backpressures the producer indefinitely. The optional
//! liveness layer turns that silent stall into a producer-observable,
//! time-bounded event.
//!
//! It is **opt-in** through a parallel `*_managed` publish surface.
//! Existing unmanaged calls (`publish`, `try_publish`, `publish_batch`)
//! keep their original semantics; nothing changes for callers that
//! don't opt in.
//!
//! Available on every layer:
//!
//! | Layer / backend | Type | Methods |
//! |---|---|---|
//! | 0, SHM | [`SharedProducer<E>`] | `enable_required_consumer_liveness`, `publish_managed`, `publish_batch_managed` |
//! | 0, mmap | [`MmapProducer<E>`] | same |
//! | 1, SHM | [`transport::FramedTransportProducer`] | `enable_required_consumer_liveness`, `publish_managed` |
//! | 1, mmap | [`transport::MmapFramedTransportProducer`] | same |
//! | 2/3, SHM | [`typed_transport::TypedProducer`] | `enable_required_consumer_liveness`, `publish_managed` |
//! | 2/3, mmap | [`typed_transport::MmapTypedProducer`] | same |
//!
//! Failure surface (returned from `publish_managed`):
//!
//! | Case | Outcome |
//! |---|---|
//! | Required consumer doesn't appear within `startup_wait_timeout`. | [`RequiredConsumerError::StartupTimeout`] |
//! | Required consumer stalls past `progress_timeout` while gating the producer. | One stderr alert + optional `RequiredConsumerAlertHook` callback. |
//! | Same consumer ID rejoins before `shutdown_grace_period` expires. | Producer recovers, alert state clears, publishing resumes. |
//! | Stall persists past `shutdown_grace_period`. | [`RequiredConsumerError::GracefulShutdownTriggered`] |
//!
//! The liveness check is **cold-path only** — it runs only while the
//! producer is blocked on a gating required consumer. Steady-state
//! publish cost is unchanged. There is no consumer-side heartbeat;
//! progress is observed from the cursor data the producer already
//! needs for gating.
//!
//! By design the liveness layer does not add dead-consumer eviction,
//! quorum modes, or degraded broadcast. The topology stays
//! strict-broadcast.
//!
//! # Feature flags
//!
//! | Feature | Layer / surface | What it enables |
//! |---|---|---|
//! | (default) | Layers 0, 1; Layer 2 with `bincode` only | Raw + framed transport, plus the bincode codec. |
//! | `rkyv` | Layer 2/3 with `rkyv` | Re-exports `rkyv` and the rkyv codec wrappers (incl. [`codec::ZeroCopyCodec`]). |
//! | `flatbuffers` | Layer 2/3 with `flatbuffers` | Re-exports `flatbuffers` and the flatbuffers codec wrappers. |
//! | `dst` | tests only | Forwards to `disruptor_mp/dst` for deterministic-simulation hooks. |
//!
//! # Quick start
//!
//! ```no_run
//! use myelon::{
//! attach_shared_consumer, build_shared_single_producer,
//! };
//! use myelon::producer::CoordinationMode;
//! use disruptor_mp::portable_shm_segment_name;
//! # #[derive(Copy, Clone, Default)]
//! # #[repr(C)]
//! # struct Event { sequence: u64 }
//! let segment = portable_shm_segment_name("demo");
//! let mut producer = build_shared_single_producer::<Event>(&segment, 4096)
//! .discover_consumer_with_prefix(1, "cp")
//! .with_coordination(CoordinationMode::Immediate)
//! .build_producer(Event::default)
//! .expect("build producer");
//! producer.publish(|slot| slot.sequence = 1);
//! ```
//!
//! See the crate README for fuller Layer 1 / Layer 2 / Layer 3 examples.
// Re-export serialization crates so downstream consumers don't need direct deps.
// Derive macros (e.g. #[derive(rkyv::Archive)]) require the crate to be accessible
// by name. This re-export makes `use myelon::rkyv` work in derive paths.
pub use rkyv;
pub use flatbuffers;
pub use ;
pub use ;
pub use ;
/// Shared-memory top level façade types.
/// Backend façade namespace for shared-memory implementation.
/// Lock-free coordination façade types.
/// Producer façade namespace for coordinated construction and policy.
/// Consumer façade namespace for shared-memory consumer behavior.
/// Re-export of `disruptor_mp::observability` for downstream users that
/// want to attach Aeron-style hot-path counters without taking a direct
/// dependency on `disruptor-mp`. RFC 0040.
///
/// Typical use:
///
/// ```ignore
/// let file = unsafe { myelon::observability::CountersFile::init(ptr) };
/// producer.attach_counters(&file);
/// consumer.attach_counters(&file);
/// // External reader (e.g. a future `myelon-stat` companion):
/// let reader = unsafe { myelon::observability::CountersFile::attach(ptr) }?;
/// for c in reader.snapshot() { println!("{} = {}", c.label, c.value); }
/// ```
pub use ;
pub use ;
/// Convenience re-exports for downstream code that wants the typical
/// transport, codec, and observability surface in one `use` line.