2026-07-18 09:38:16 +00:00
|
|
|
//! The per-stream mux: the single ordering authority (spec §4.4).
|
2026-06-05 07:25:43 +00:00
|
|
|
//!
|
2026-07-18 09:38:16 +00:00
|
|
|
//! Producers submit bytes tagged with a stream-local channel id. The mux accepts
|
|
|
|
|
//! payloads into a bounded queue first, then assigns a single monotonic position
|
|
|
|
|
//! sequence while draining accepted payloads.
|
2026-06-05 07:25:43 +00:00
|
|
|
|
2026-07-18 09:38:16 +00:00
|
|
|
use crossbeam_channel::{Receiver, Sender, TryRecvError, TrySendError, bounded};
|
|
|
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
2026-06-05 07:25:43 +00:00
|
|
|
|
enforce datastream telemetry-only invariant: ban frame types from control code
The datastream is metrics/logging only; control decisions must never branch
on a frame. This was a recurring cultural problem with no structural
enforcement. This change makes it a compile-time and CI-enforced fact.
datastream crate (lib.rs):
- Stop re-exporting Frame, DatastreamEvent, FrameDelivery at crate root.
is now a compile error (E0425). These types live
only in datastream::frame::* and are documented as the observer surface.
- Safe identity types (ChannelId, StreamId, Position, Record, etc.) remain
re-exported at root for producer-side callers.
orchestration/app.rs:
- Extracted all frame-touching code (CollectedDatastreamFrame,
drain_datastream_connections, update_load_progress_from_frame,
drain_frames, archive_collected_frame, pump, OrchDatastream,
DashboardSupport) into two new observability modules:
frame_collector.rs and orch_datastream.rs.
- The orchestrator now interacts through a FrameCollector whose
drain/drain_with_progress methods take closures; it never names Frame,
DatastreamEvent, or CollectedDatastreamFrame.
- StageLoadProgress (the one control-relevant signal previously scraped
from frame payloads) is extracted inside FrameCollector and handed to
the control loop as plain data.
xtask:
- New check-telemetry-isolation command scans control-plane modules
(orchestration/, distribution/, data-plane/, provisioning/) for
forbidden frame-type references and fails the build if any are found.
Verified: workspace builds (myelin + dashboard feature), datastream 29
tests pass, myelin 64 lib tests pass, check-telemetry-isolation passes
clean.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-08-12 12:14:57 +00:00
|
|
|
use crate::frame::{ChannelId, Frame, Position, StreamId};
|
2026-07-18 09:38:16 +00:00
|
|
|
|
|
|
|
|
struct PendingFrame {
|
|
|
|
|
channel: ChannelId,
|
|
|
|
|
payload: Vec<u8>,
|
|
|
|
|
}
|
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-18 09:38:16 +00:00
|
|
|
tx: Sender<PendingFrame>,
|
|
|
|
|
rx: Receiver<PendingFrame>,
|
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);
|
2026-07-18 09:38:16 +00:00
|
|
|
let (tx, rx) = bounded(capacity);
|
2026-06-05 07:25:43 +00:00
|
|
|
Mux {
|
|
|
|
|
stream,
|
|
|
|
|
next: AtomicU64::new(0),
|
|
|
|
|
dropped: AtomicU64::new(0),
|
2026-07-12 06:14:34 +00:00
|
|
|
tx,
|
2026-07-18 09:38:16 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 09:38:16 +00:00
|
|
|
/// The stream this mux produces (spec §2.2, §7.1 ingest key).
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn stream_id(&self) -> &StreamId {
|
|
|
|
|
&self.stream
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 06:14:34 +00:00
|
|
|
/// Submit opaque bytes on a registered channel id.
|
2026-07-18 09:38:16 +00:00
|
|
|
pub fn submit(&self, channel: ChannelId, payload: Vec<u8>) -> bool {
|
|
|
|
|
match self.tx.try_send(PendingFrame { channel, payload }) {
|
|
|
|
|
Ok(()) => true,
|
2026-07-12 06:14:34 +00:00
|
|
|
Err(TrySendError::Full(_)) | Err(TrySendError::Disconnected(_)) => {
|
|
|
|
|
self.dropped.fetch_add(1, Ordering::Relaxed);
|
2026-07-18 09:38:16 +00:00
|
|
|
false
|
2026-07-05 09:59:51 +00:00
|
|
|
}
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 09:38:16 +00:00
|
|
|
/// Pull all currently queued frames in mux queue order.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn drain(&self) -> Vec<Frame> {
|
2026-07-12 06:14:34 +00:00
|
|
|
let mut frames = Vec::new();
|
|
|
|
|
loop {
|
2026-07-18 09:38:16 +00:00
|
|
|
match self.rx.try_recv() {
|
|
|
|
|
Ok(pending) => {
|
|
|
|
|
// Position is consumed only after a pending frame has left
|
|
|
|
|
// the queue; failed submit never reaches this point.
|
|
|
|
|
let position = Position(self.next.fetch_add(1, Ordering::Relaxed));
|
|
|
|
|
frames.push(Frame {
|
|
|
|
|
channel: pending.channel,
|
|
|
|
|
position,
|
|
|
|
|
payload: pending.payload,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Err(TryRecvError::Empty) | Err(TryRecvError::Disconnected) => break,
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
frames
|
2026-06-05 07:25:43 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-18 09:38:16 +00:00
|
|
|
/// How many positions have been assigned while draining accepted frames.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn assigned(&self) -> u64 {
|
|
|
|
|
self.next.load(Ordering::Relaxed)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 09:38:16 +00:00
|
|
|
/// How many submissions have been dropped before entering the mux.
|
2026-06-05 07:25:43 +00:00
|
|
|
pub fn dropped(&self) -> u64 {
|
|
|
|
|
self.dropped.load(Ordering::Relaxed)
|
|
|
|
|
}
|
|
|
|
|
}
|