//! Internal transport types used by [`super::cluster::SimulatedCluster`]. use crate::{ db::TidalDb, replication::{ WalSegmentId, shard::{RegionId, ShardId}, transport::{Transport, TransportError, WalSegmentPayload}, }, }; // ── ChannelTransport: bidirectional crossbeam transport ───────────────── /// Bidirectional crossbeam-channel transport for in-process cluster simulation. /// /// Wraps both sender and receiver so `SimulatedCluster` can call /// `send_segment` to ship WAL payloads and `spawn_receiver` can call /// `recv_segment` to apply them. pub(super) struct ChannelTransport { pub(super) local_shard: ShardId, pub(super) tx: crossbeam::channel::Sender, pub(super) rx: crossbeam::channel::Receiver, } impl Transport for ChannelTransport { fn send_segment(&self, _to: ShardId, payload: WalSegmentPayload) -> Result<(), TransportError> { self.tx.send(payload).map_err(|_| TransportError::Closed) } fn recv_segment(&self) -> Option { self.rx.recv().ok() } fn local_shard(&self) -> ShardId { self.local_shard } } /// Receive-only crossbeam transport handed to the receiver thread. /// /// Crucially this holds **only** the receiver end. The matching `Sender` lives /// in [`super::cluster::SimulatedCluster`]; dropping the cluster drops every /// sender, which closes the channel and makes [`recv_segment`] return `None`, /// letting the receiver thread exit so its `SegmentReceiverHandle` can be /// joined. (A bidirectional [`ChannelTransport`] cannot be joined on shutdown: /// the receiver thread's own `Arc` keeps a `Sender` alive, so `recv` would /// block forever.) /// /// [`recv_segment`]: Transport::recv_segment pub(super) struct RecvOnlyChannelTransport { pub(super) local_shard: ShardId, pub(super) rx: crossbeam::channel::Receiver, } impl Transport for RecvOnlyChannelTransport { fn send_segment( &self, _to: ShardId, _payload: WalSegmentPayload, ) -> Result<(), TransportError> { // The receiver thread never ships; only the cluster's send side does. Err(TransportError::Closed) } fn recv_segment(&self) -> Option { self.rx.recv().ok() } fn local_shard(&self) -> ShardId { self.local_shard } } // ── Batch log entry ────────────────────────────────────────────────────── pub(super) struct BatchEntry { /// Which leader shard produced this batch. pub(super) source_shard: ShardId, /// 1-indexed sequence number scoped to `source_shard`. pub(super) seqno: u64, /// Encoded WAL batch bytes (from [`crate::wal::format::batch::encode_batch`]). pub(super) bytes: Vec, } // ── Re-delivery helper ──────────────────────────────────────────────────── /// Re-deliver all batch-log entries not yet applied to `db`, sending through /// the given transport. /// /// Called by `SimulatedCluster::heal_region` for immediate recovery after a /// partition is healed, and by `await_convergence` during the polling loop. pub(super) fn redeliver_missed(transport: &dyn Transport, db: &TidalDb, log: &[BatchEntry]) { for entry in log { let applied = db .replication_state() .applied_seqno(entry.source_shard) .unwrap_or(0); if entry.seqno > applied { let payload = WalSegmentPayload { id: WalSegmentId::new(RegionId::SINGLE, entry.source_shard, entry.seqno), bytes: entry.bytes.clone(), event_count: 1, }; let _ = transport.send_segment(entry.source_shard, payload); } } }