2026-06-05 07:25:43 +00:00
|
|
|
//! The per-node mux: the single ordering authority (spec §5).
|
|
|
|
|
//!
|
2026-07-12 06:14:34 +00:00
|
|
|
//! Every producer on a node submits bytes tagged with a stream-local channel id
|
|
|
|
|
//! to one mux, and the mux assigns a single monotonic position sequence across
|
|
|
|
|
//! all channels. A drop consumes a position and is therefore visible downstream
|
|
|
|
|
//! as a gap.
|
2026-06-05 07:25:43 +00:00
|
|
|
|
|
|
|
|
use std::sync::Mutex;
|
2026-07-05 09:59:51 +00:00
|
|
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
2026-07-12 06:14:34 +00:00
|
|
|
use std::sync::mpsc::{Receiver, SyncSender, TryRecvError, TrySendError, sync_channel};
|
2026-07-05 09:59:51 +00:00
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
2026-06-05 07:25:43 +00:00
|
|
|
|
|
|
|
|
use super::frame::{ChannelId, Frame, Position, StreamId};
|
2026-07-05 09:59:51 +00:00
|
|
|
use super::record::Record;
|
2026-07-12 06:14:34 +00:00
|
|
|
use super::timing::FRAME_TIME_CHANNEL_ID;
|
|
|
|
|
use super::timing::FrameTimeSample;
|
2026-06-05 07:25:43 +00:00
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
/// A node's single position authority and outgoing telemetry queue.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub struct Mux {
|
|
|
|
|
stream: StreamId,
|
|
|
|
|
next: AtomicU64,
|
|
|
|
|
dropped: AtomicU64,
|
2026-07-05 09:59:51 +00:00
|
|
|
frame_timing_enabled: AtomicBool,
|
2026-07-12 06:14:34 +00:00
|
|
|
tx: SyncSender<Frame>,
|
|
|
|
|
rx: Mutex<Receiver<Frame>>,
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Mux {
|
2026-07-12 06:14:34 +00:00
|
|
|
/// Create a mux for `stream` with a bounded outgoing queue.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn new(stream: StreamId, capacity: usize) -> Self {
|
2026-07-12 06:14:34 +00:00
|
|
|
let capacity = capacity.max(1).min(1_048_576);
|
|
|
|
|
let (tx, rx) = sync_channel(capacity);
|
2026-06-05 07:25:43 +00:00
|
|
|
Mux {
|
|
|
|
|
stream,
|
|
|
|
|
next: AtomicU64::new(0),
|
|
|
|
|
dropped: AtomicU64::new(0),
|
2026-07-05 09:59:51 +00:00
|
|
|
frame_timing_enabled: AtomicBool::new(true),
|
2026-07-12 06:14:34 +00:00
|
|
|
tx,
|
|
|
|
|
rx: Mutex::new(rx),
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
/// Create a mux whose queue is large enough for tests that drain promptly.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn unbounded(stream: StreamId) -> Self {
|
|
|
|
|
Mux::new(stream, usize::MAX)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The stream this mux produces (spec §8.4 ingest key).
|
|
|
|
|
pub fn stream_id(&self) -> &StreamId {
|
|
|
|
|
&self.stream
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
/// Submit opaque bytes on a registered channel id.
|
|
|
|
|
pub fn submit(&self, channel: ChannelId, payload: Vec<u8>) -> Position {
|
2026-06-05 07:25:43 +00:00
|
|
|
let position = Position(self.next.fetch_add(1, Ordering::Relaxed));
|
2026-07-05 09:59:51 +00:00
|
|
|
let timing_sample = self
|
|
|
|
|
.frame_timing_enabled
|
|
|
|
|
.load(Ordering::Relaxed)
|
|
|
|
|
.then(|| now_unix_ns())
|
2026-07-12 06:14:34 +00:00
|
|
|
.filter(|_| channel != FRAME_TIME_CHANNEL_ID)
|
2026-07-05 09:59:51 +00:00
|
|
|
.map(|created_at_unix_ns| FrameTimeSample::new(position, created_at_unix_ns));
|
2026-06-23 15:42:28 +00:00
|
|
|
let frame = Frame {
|
2026-07-05 09:59:51 +00:00
|
|
|
channel,
|
2026-06-23 15:42:28 +00:00
|
|
|
position,
|
|
|
|
|
payload,
|
|
|
|
|
};
|
2026-06-05 07:25:43 +00:00
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
match self.tx.try_send(frame) {
|
|
|
|
|
Ok(()) => {
|
|
|
|
|
if let Some(sample) = timing_sample {
|
|
|
|
|
self.push_timing_sample_if_room(sample);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(TrySendError::Full(_)) | Err(TrySendError::Disconnected(_)) => {
|
|
|
|
|
self.dropped.fetch_add(1, Ordering::Relaxed);
|
2026-07-05 09:59:51 +00:00
|
|
|
}
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
|
|
|
|
position
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
/// Pull all currently queued frames, sorted by mux position.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn drain(&self) -> Vec<Frame> {
|
2026-07-12 06:14:34 +00:00
|
|
|
let rx = self.rx.lock().expect("mux receiver poisoned");
|
|
|
|
|
let mut frames = Vec::new();
|
|
|
|
|
loop {
|
|
|
|
|
match rx.try_recv() {
|
|
|
|
|
Ok(frame) => frames.push(frame),
|
|
|
|
|
Err(TryRecvError::Empty) => break,
|
|
|
|
|
Err(TryRecvError::Disconnected) => break,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
frames.sort_by_key(|frame| frame.position);
|
|
|
|
|
frames
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
/// Enable or disable optional sidecar timing samples for newly submitted frames.
|
2026-07-05 09:59:51 +00:00
|
|
|
pub fn set_frame_timing_enabled(&self, enabled: bool) {
|
|
|
|
|
self.frame_timing_enabled.store(enabled, Ordering::Relaxed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether this mux currently emits sidecar frame timing samples.
|
|
|
|
|
pub fn frame_timing_enabled(&self) -> bool {
|
|
|
|
|
self.frame_timing_enabled.load(Ordering::Relaxed)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
fn push_timing_sample_if_room(&self, sample: FrameTimeSample) {
|
2026-07-05 09:59:51 +00:00
|
|
|
let position = Position(self.next.fetch_add(1, Ordering::Relaxed));
|
2026-07-12 06:14:34 +00:00
|
|
|
let frame = Frame {
|
|
|
|
|
channel: FRAME_TIME_CHANNEL_ID,
|
2026-07-05 09:59:51 +00:00
|
|
|
position,
|
|
|
|
|
payload: sample.encode(),
|
2026-07-12 06:14:34 +00:00
|
|
|
};
|
|
|
|
|
let _ = self.tx.try_send(frame);
|
2026-07-05 09:59:51 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
/// How many positions have been assigned — the gap-free high-water mark.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn assigned(&self) -> u64 {
|
|
|
|
|
self.next.load(Ordering::Relaxed)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
/// How many data frames have been dropped on overflow.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn dropped(&self) -> u64 {
|
|
|
|
|
self.dropped.load(Ordering::Relaxed)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-05 09:59:51 +00:00
|
|
|
|
|
|
|
|
fn now_unix_ns() -> u64 {
|
|
|
|
|
SystemTime::now()
|
|
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
|
.map(|duration| u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX))
|
|
|
|
|
.unwrap_or(0)
|
|
|
|
|
}
|