1use std::sync::Arc;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9use rustev_contract::run::RunRecord;
10use rustev_core::seams::EvidenceSink;
11use tokio::sync::{mpsc, oneshot};
12
13use crate::clock::Clock;
14use crate::wait::{CatchUnwind, cut};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SinkPolicy {
19 FailDecision { timeout_ms: u64 },
22 Backpressure {
25 max_pending: usize,
26 enqueue_timeout_ms: u64,
27 delivery_timeout_ms: u64,
28 },
29 DropCounted {
31 max_pending: usize,
32 delivery_timeout_ms: u64,
33 },
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct DeliveryFailure {
39 pub detail: String,
40 pub uncertain: bool,
42}
43
44pub struct DeliveryTicket(pub(crate) oneshot::Receiver<Result<String, DeliveryFailure>>);
47
48impl DeliveryTicket {
49 pub async fn wait(self) -> Result<String, DeliveryFailure> {
50 self.0.await.unwrap_or_else(|_| {
51 Err(DeliveryFailure {
52 detail: "the delivery worker stopped".into(),
53 uncertain: true,
54 })
55 })
56 }
57}
58
59impl std::fmt::Debug for DeliveryTicket {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.write_str("DeliveryTicket")
62 }
63}
64
65#[derive(Debug, Default)]
68pub(crate) struct Stats {
69 pub acknowledged: AtomicU64,
70 pub failed: AtomicU64,
71 pub timed_out: AtomicU64,
72 pub dropped: AtomicU64,
73 pub queued: AtomicU64,
74 pub in_delivery: AtomicU64,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
79pub struct SinkStats {
80 pub acknowledged: u64,
81 pub failed: u64,
82 pub timed_out: u64,
83 pub dropped: u64,
84 pub queued: u64,
86 pub in_delivery: u64,
88}
89
90impl Stats {
91 pub(crate) fn snapshot(&self) -> SinkStats {
92 SinkStats {
93 acknowledged: self.acknowledged.load(Ordering::SeqCst),
94 failed: self.failed.load(Ordering::SeqCst),
95 timed_out: self.timed_out.load(Ordering::SeqCst),
96 dropped: self.dropped.load(Ordering::SeqCst),
97 queued: self.queued.load(Ordering::SeqCst),
98 in_delivery: self.in_delivery.load(Ordering::SeqCst),
99 }
100 }
101}
102
103pub(crate) struct Job {
104 pub record: Arc<RunRecord>,
105 pub done: oneshot::Sender<Result<String, DeliveryFailure>>,
106}
107
108pub(crate) async fn deliver_once(
110 sink: &dyn EvidenceSink,
111 clock: &dyn Clock,
112 record: &RunRecord,
113 timeout_ms: u64,
114 stats: &Stats,
115) -> Result<String, DeliveryFailure> {
116 let work = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| sink.deliver(record)))
118 {
119 Ok(f) => f,
120 Err(p) => {
121 stats.failed.fetch_add(1, Ordering::SeqCst);
122 return Err(DeliveryFailure {
123 detail: cut(
124 &format!(
125 "the sink panicked: {}",
126 crate::wait::panic_detail(p.as_ref())
127 ),
128 256,
129 ),
130 uncertain: true,
131 });
132 }
133 };
134 let mut work = CatchUnwind::new(work);
135 let mut timer = clock.sleep_until(clock.now().saturating_add(timeout_ms));
136 let never = rustev_core::seams::CancelSignal::new();
137 let r = match crate::wait::race(&mut work, &never, &mut timer).await {
138 crate::wait::Raced::Done(Ok(Ok(receipt))) => Ok(receipt),
139 crate::wait::Raced::Done(Ok(Err(e))) => Err(DeliveryFailure {
140 detail: cut(&e, 256),
141 uncertain: false,
142 }),
143 crate::wait::Raced::Done(Err(panic)) => Err(DeliveryFailure {
144 detail: cut(&format!("the sink panicked: {panic}"), 256),
145 uncertain: true,
146 }),
147 crate::wait::Raced::Expired | crate::wait::Raced::Cancelled => {
148 stats.timed_out.fetch_add(1, Ordering::SeqCst);
149 Err(DeliveryFailure {
150 detail: format!("no answer within {timeout_ms} ms"),
151 uncertain: true,
152 })
153 }
154 };
155 match &r {
156 Ok(_) => stats.acknowledged.fetch_add(1, Ordering::SeqCst),
157 Err(_) => stats.failed.fetch_add(1, Ordering::SeqCst),
158 };
159 r
160}
161
162pub(crate) async fn worker(
164 mut rx: mpsc::Receiver<Job>,
165 sink: Arc<dyn EvidenceSink>,
166 clock: Arc<dyn Clock>,
167 timeout_ms: u64,
168 stats: Arc<Stats>,
169) {
170 while let Some(job) = rx.recv().await {
171 stats.queued.fetch_sub(1, Ordering::SeqCst);
172 stats.in_delivery.fetch_add(1, Ordering::SeqCst);
173 let r = deliver_once(&*sink, &*clock, &job.record, timeout_ms, &stats).await;
174 stats.in_delivery.fetch_sub(1, Ordering::SeqCst);
175 let _ = job.done.send(r);
176 }
177}