mod open;
use std::time::Duration;
use crate::OpNumber;
use ozzy_proto::RequestId;
use super::{
Channel, FlowError, Operation, PipelineLimits, Prefix, Probe, ProbeError, ProbeScheduler,
ProbeTiming, Report, Scope, Sender, VecDeque, ledger,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OpenRequest {
request_id: Option<RequestId>,
report: Report,
}
impl OpenRequest {
pub const fn report(self) -> Report {
self.report
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Repair {
pub channel: Channel,
pub after: Prefix,
pub through: Prefix,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatusOutcome {
Ignored,
Observed,
Verify,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum TransmitError {
#[error(transparent)]
Flow(#[from] FlowError),
#[error(transparent)]
Probe(#[from] ProbeError),
}
#[derive(Debug)]
pub struct Transmitter {
scope: Scope,
limits: PipelineLimits,
probes: ProbeScheduler,
sender: Option<Sender>,
announced_channel: Option<Channel>,
spare: VecDeque<Operation>,
active: bool,
candidate: Option<OpenRequest>,
candidate_limit: Option<OpNumber>,
repair: Option<Repair>,
broadcast: bool,
catch_up: Option<OpNumber>,
receipt_progress: ozzy_core::live::LiveProgress,
}
impl Transmitter {
pub fn new(
scope: Scope,
limits: PipelineLimits,
first: RequestId,
timing: ProbeTiming,
now: Duration,
) -> Result<Self, TransmitError> {
let probes = ProbeScheduler::new(first, timing, now)?;
Ok(Self {
scope,
limits,
probes,
sender: None,
announced_channel: None,
spare: ledger(limits)?,
active: false,
candidate: None,
candidate_limit: None,
repair: None,
broadcast: false,
catch_up: None,
receipt_progress: ozzy_core::live::LiveProgress::new(timing.maximum),
})
}
pub fn enable_broadcast(&mut self) {
self.broadcast = true;
self.probes.pace_new_work();
}
pub fn needs_catch_up(&self) -> bool {
self.catch_up.is_some_and(|target| {
self.sender()
.is_some_and(|sender| sender.sent().op < target)
})
}
pub const fn catch_up_through(&self) -> Option<OpNumber> {
self.catch_up
}
pub fn sender(&self) -> Option<&Sender> {
self.sender.as_ref().filter(|_| self.active)
}
pub const fn candidate(&self) -> Option<OpenRequest> {
self.candidate
}
pub const fn pending_probe(&self) -> Option<Probe> {
self.probes.pending()
}
pub fn replace_session(&mut self, now: Duration) -> Result<(), TransmitError> {
self.probes.invalidate(now)?;
self.announced_channel = None;
self.candidate = None;
self.candidate_limit = None;
Ok(())
}
pub fn change_scope(&mut self, scope: Scope, now: Duration) -> Result<(), TransmitError> {
if scope == self.scope {
return Ok(());
}
self.probes.invalidate(now)?;
self.scope = scope;
self.active = false;
self.announced_channel = None;
self.candidate = None;
self.candidate_limit = None;
self.repair = None;
self.catch_up = None;
self.receipt_progress.reset();
Ok(())
}
pub fn next_probe(&self) -> Option<Duration> {
self.repair.is_none().then(|| self.probes.due())
}
pub fn poll_probe(
&mut self,
available: Prefix,
transport_pending: bool,
now: Duration,
) -> Result<Option<Probe>, TransmitError> {
if transport_pending || self.repair.is_some() {
return Ok(None);
}
let tail = if self.broadcast {
available
} else {
self.sender().map_or(available, Sender::sent)
};
Ok(self
.probes
.poll(self.scope, tail, available.op.max(tail.op), now)?)
}
pub fn record_send(&mut self, operations: &[Operation]) -> Result<(), FlowError> {
let Some(sender) = self.sender.as_mut().filter(|_| self.active) else {
return Err(FlowError::Channel);
};
if self.repair.is_some()
|| self.broadcast
&& self.catch_up.is_some_and(|through| {
operations.last().is_some_and(|op| op.prefix.op > through)
})
{
return Err(FlowError::Capacity);
}
sender.record_send(sender.channel(), operations)
}
pub fn observe(
&mut self,
report: Report,
request_id: Option<RequestId>,
now: Duration,
) -> Result<StatusOutcome, TransmitError> {
self.observe_with_repair_limit(report, request_id, None, now)
}
pub fn observe_with_repair_limit(
&mut self,
report: Report,
request_id: Option<RequestId>,
repair_limit: Option<OpNumber>,
now: Duration,
) -> Result<StatusOutcome, TransmitError> {
if report.channel.scope != self.scope {
return Ok(StatusOutcome::Ignored);
}
let correlated = if let Some(id) = request_id {
let Some(probe) = self.probes.pending().filter(|probe| probe.request_id == id) else {
return Ok(StatusOutcome::Ignored);
};
self.probes.completion_deadline(probe.scope, id, now)?;
Some(probe)
} else {
None
};
report.validate_shape()?;
let request = OpenRequest { request_id, report };
if self
.sender()
.is_none_or(|sender| sender.channel() != report.channel)
{
if correlated.is_none() {
if self.sender().is_some() && self.announced_channel != Some(report.channel) {
self.probes.expedite(now)?;
self.announced_channel = Some(report.channel);
}
return Ok(StatusOutcome::Ignored);
}
return Ok(self.hold(request, repair_limit));
}
if self.superseded(report) {
return Ok(StatusOutcome::Ignored);
}
if self.broadcast
&& self
.sender()
.is_some_and(|sender| report.received.op > sender.sent().op)
{
return Ok(self.hold(request, repair_limit));
}
let changed = self.observe_receipt(report, now)?;
if let Some(probe) = correlated {
self.answer_probe(probe, repair_limit, now)?;
}
self.trim_repair();
Ok(if changed || correlated.is_some() {
StatusOutcome::Observed
} else {
StatusOutcome::Ignored
})
}
fn hold(&mut self, request: OpenRequest, repair_limit: Option<OpNumber>) -> StatusOutcome {
if self.superseded(request.report) {
return StatusOutcome::Ignored;
}
self.candidate = Some(request);
self.candidate_limit = repair_limit;
StatusOutcome::Verify
}
fn superseded(&self, report: Report) -> bool {
self.candidate.is_some_and(|old| {
old.report.channel == report.channel && old.report.revision > report.revision
})
}
fn clear_candidate(&mut self) {
self.candidate = None;
self.candidate_limit = None;
}
fn observe_receipt(&mut self, report: Report, now: Duration) -> Result<bool, FlowError> {
let Some(sender) = self.sender.as_mut() else {
return Ok(false);
};
let before = sender.received();
let changed = sender.observe(report)?;
if sender.received().op > before.op {
self.receipt_progress.advanced(sender.received().op.0, now);
}
Ok(changed)
}
fn answer_probe(
&mut self,
probe: Probe,
repair_limit: Option<OpNumber>,
now: Duration,
) -> Result<(), TransmitError> {
if let Some(sender) = self.sender.as_ref() {
let target = if self.broadcast {
repair_limit.unwrap_or(probe.tail.op).min(probe.tail.op)
} else {
probe.tail.op
};
if self.broadcast {
self.catch_up = (sender.received().op < target).then_some(target);
}
let through = sender
.outstanding()
.take_while(|op| op.prefix.op <= target)
.last()
.map_or(sender.received(), |op| op.prefix);
let quiet = !self.broadcast
|| self
.receipt_progress
.repair_limit(sender.received().op.0, None, now)
.is_none();
self.repair = if quiet && sender.received().op < through.op {
Some(Repair {
channel: sender.channel(),
after: sender.received(),
through,
})
} else {
None
};
}
self.probes.complete(probe.scope, probe.request_id, now)?;
self.clear_candidate();
Ok(())
}
pub fn repair(&self, transport_pending: bool) -> Option<Repair> {
if transport_pending { None } else { self.repair }
}
pub fn record_repair(&mut self, repair: Repair, through: Prefix) -> Result<(), FlowError> {
if self.repair != Some(repair) {
return Err(FlowError::Channel);
}
if through.op <= repair.after.op
|| through.op > repair.through.op
|| self
.sender()
.is_none_or(|sender| !sender.outstanding().any(|op| op.prefix == through))
{
return Err(FlowError::History);
}
self.repair = if through == repair.through {
None
} else {
Some(Repair {
after: through,
..repair
})
};
Ok(())
}
fn trim_repair(&mut self) {
if self.catch_up.is_some_and(|target| {
self.sender()
.is_some_and(|sender| sender.received().op >= target)
}) {
self.catch_up = None;
}
let (Some(repair), Some(received)) = (self.repair, self.sender().map(Sender::received))
else {
return;
};
self.repair = if received.op >= repair.through.op {
None
} else if received.op > repair.after.op {
Some(Repair {
after: received,
..repair
})
} else {
Some(repair)
};
}
}