- Add BUILD.bazel across tidal, tidal-net, tidal-server, tidalctl for bzlmod build - Add tidal/ crate docs (README, CHANGELOG, CONTRIBUTING, AGENTS, CLAUDE, API, ARCHITECTURE) and ai-lookup reference - Add docker standalone/cluster/deploy images, compose, and prometheus config - Harden WAL (batch format, writer, dedup, diagnostics), text syncer/collectors, and vector registry - Expand tidalctl CLI and tests; restructure WAL/visibility integration test suites - Refine tidal-net transport/client/server and tidal-server cluster/scatter-gather
108 lines
3.9 KiB
Rust
108 lines
3.9 KiB
Rust
//! 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<WalSegmentPayload>,
|
|
pub(super) rx: crossbeam::channel::Receiver<WalSegmentPayload>,
|
|
}
|
|
|
|
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<WalSegmentPayload> {
|
|
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<WalSegmentPayload>,
|
|
}
|
|
|
|
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<WalSegmentPayload> {
|
|
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<u8>,
|
|
}
|
|
|
|
// ── 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);
|
|
}
|
|
}
|
|
}
|