Skip to main content

pipecircuit/delay/
ledger.rs

1use crate::storage::warp_table::{
2    Capacity, WarpId, WarpTable, WarpTableCreationError, WarpTableInsertOutcome,
3    WarpTableRemoveOutcome, WarpTableRow,
4};
5use crate::{TimerArmDecision, TimerArmOutcome, TimerArmRefusal, TimerIn, TimerToken, WakeAfter};
6
7use super::Delayed;
8
9#[derive(WarpTableRow)]
10struct PendingDelayedMessage<Message> {
11    message: Message,
12}
13
14type PendingDelayedMessageCreationError<Message> = WarpTableCreationError<
15    <PendingDelayedMessage<Message> as WarpTableRow>::ColumnsCreationError,
16    <PendingDelayedMessage<Message> as WarpTableRow>::IndexesCreationError,
17>;
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum DelayLedgerCreationFailure {
21    StorageUnavailable,
22}
23
24#[derive(Debug)]
25pub struct DelayLedgerCreationError<Message> {
26    failure: DelayLedgerCreationFailure,
27    _source: PendingDelayedMessageCreationError<Message>,
28}
29
30impl<Message> DelayLedgerCreationError<Message> {
31    pub const fn failure(&self) -> DelayLedgerCreationFailure {
32        self.failure
33    }
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum DelayUnavailable {
38    AtCapacity { maximum_pending: u32 },
39    IdentifiersExhausted,
40    WakeRefused { reason: TimerArmRefusal },
41}
42
43pub struct DelayLedger<Message> {
44    pending: WarpTable<PendingDelayedMessage<Message>>,
45}
46
47impl<Message> DelayLedger<Message> {
48    pub fn try_new(capacity: Capacity) -> Result<Self, DelayLedgerCreationError<Message>> {
49        let pending = WarpTable::try_new(capacity).map_err(|source| DelayLedgerCreationError {
50            failure: DelayLedgerCreationFailure::StorageUnavailable,
51            _source: source,
52        })?;
53        Ok(Self { pending })
54    }
55
56    pub const fn capacity(&self) -> Capacity {
57        self.pending.capacity()
58    }
59
60    pub const fn len(&self) -> usize {
61        self.pending.len()
62    }
63
64    pub const fn is_empty(&self) -> bool {
65        self.pending.is_empty()
66    }
67
68    pub fn admit(&mut self, delayed: Delayed<Message>) -> DelayAdmissionOutcome<Message> {
69        let (message, wake_after) = delayed.into_parts();
70        match self.pending.insert(PendingDelayedMessage { message }) {
71            WarpTableInsertOutcome::Inserted { id, .. } => DelayAdmissionOutcome::Admitted {
72                admission: DelayAdmission {
73                    token: TimerToken::from_nonzero(id.value()),
74                    wake_after,
75                },
76            },
77            WarpTableInsertOutcome::Full {
78                rejected: PendingDelayedMessage { message },
79            } => DelayAdmissionOutcome::Refused {
80                reason: DelayUnavailable::AtCapacity {
81                    maximum_pending: self.capacity().value().get(),
82                },
83                rejected: Delayed::new(message, wake_after),
84            },
85            WarpTableInsertOutcome::IdentifiersExhausted {
86                rejected: PendingDelayedMessage { message },
87            } => DelayAdmissionOutcome::Refused {
88                reason: DelayUnavailable::IdentifiersExhausted,
89                rejected: Delayed::new(message, wake_after),
90            },
91            WarpTableInsertOutcome::UniqueConflict {
92                conflict,
93                rejected: _,
94            } => match conflict {},
95        }
96    }
97
98    pub fn refuse(&mut self, refusal: DelayArmRefusal) -> DelayRefusalOutcome<Message> {
99        match self.pending.remove(WarpId::new(refusal.token.value())) {
100            WarpTableRemoveOutcome::Removed {
101                removed: PendingDelayedMessage { message },
102                ..
103            } => DelayRefusalOutcome::Refused {
104                reason: DelayUnavailable::WakeRefused {
105                    reason: refusal.reason,
106                },
107                rejected: Delayed::new(message, refusal.wake_after),
108            },
109            WarpTableRemoveOutcome::Absent { .. } => DelayRefusalOutcome::Stale { refusal },
110        }
111    }
112
113    pub fn withdraw(&mut self, admission: DelayAdmission) -> DelayWithdrawalOutcome<Message> {
114        match self.pending.remove(WarpId::new(admission.token.value())) {
115            WarpTableRemoveOutcome::Removed {
116                removed: PendingDelayedMessage { message },
117                ..
118            } => DelayWithdrawalOutcome::Withdrawn {
119                rejected: Delayed::new(message, admission.wake_after),
120            },
121            WarpTableRemoveOutcome::Absent { .. } => DelayWithdrawalOutcome::Absent { admission },
122        }
123    }
124
125    pub fn release(&mut self, token: TimerToken) -> DelayReleaseOutcome<Message> {
126        match self.pending.remove(WarpId::new(token.value())) {
127            WarpTableRemoveOutcome::Removed {
128                removed: PendingDelayedMessage { message },
129                ..
130            } => DelayReleaseOutcome::Released { message },
131            WarpTableRemoveOutcome::Absent { .. } => DelayReleaseOutcome::Stale { token },
132        }
133    }
134}
135
136#[must_use = "admitted delayed work or exact refusal must be handled by the fitting"]
137pub enum DelayAdmissionOutcome<Message> {
138    Admitted {
139        admission: DelayAdmission,
140    },
141    Refused {
142        reason: DelayUnavailable,
143        rejected: Delayed<Message>,
144    },
145}
146
147#[must_use = "delayed-message admission must be armed before returning"]
148pub struct DelayAdmission {
149    token: TimerToken,
150    wake_after: WakeAfter,
151}
152
153impl DelayAdmission {
154    pub const fn timer_input(&self) -> TimerIn<TimerToken> {
155        TimerIn::Arm {
156            token: self.token,
157            after: self.wake_after,
158        }
159    }
160
161    pub fn settle_timer_arm(
162        self,
163        outcome: TimerArmOutcome<TimerToken>,
164    ) -> Result<DelayArmOutcome, DelayArmMismatch> {
165        let decision = outcome.into_decision();
166        let matches_admission = match decision {
167            TimerArmDecision::Armed { token } => token == self.token,
168            TimerArmDecision::Refused { token, after, .. } => {
169                token == self.token && after == self.wake_after
170            }
171        };
172        if !matches_admission {
173            return Err(DelayArmMismatch {
174                admission: self,
175                decision,
176            });
177        }
178        match decision {
179            TimerArmDecision::Armed { .. } => Ok(DelayArmOutcome::Armed),
180            TimerArmDecision::Refused { reason, .. } => Ok(DelayArmOutcome::Refused {
181                refusal: DelayArmRefusal {
182                    token: self.token,
183                    wake_after: self.wake_after,
184                    reason,
185                },
186            }),
187        }
188    }
189}
190
191#[must_use = "armed delayed work or exact timer refusal must be handled by the fitting"]
192pub enum DelayArmOutcome {
193    Armed,
194    Refused { refusal: DelayArmRefusal },
195}
196
197pub struct DelayArmMismatch {
198    admission: DelayAdmission,
199    decision: TimerArmDecision<TimerToken>,
200}
201
202impl DelayArmMismatch {
203    pub fn into_parts(self) -> (DelayAdmission, TimerArmDecision<TimerToken>) {
204        (self.admission, self.decision)
205    }
206}
207
208#[must_use = "timer refusal must be returned to the delay ledger"]
209pub struct DelayArmRefusal {
210    token: TimerToken,
211    wake_after: WakeAfter,
212    reason: TimerArmRefusal,
213}
214
215impl DelayArmRefusal {
216    pub const fn reason(&self) -> TimerArmRefusal {
217        self.reason
218    }
219}
220
221#[must_use = "resolved or stale timer refusal must be handled by the fitting"]
222pub enum DelayRefusalOutcome<Message> {
223    Refused {
224        reason: DelayUnavailable,
225        rejected: Delayed<Message>,
226    },
227    Stale {
228        refusal: DelayArmRefusal,
229    },
230}
231
232#[must_use = "released or stale timer tokens must be handled by the fitting"]
233pub enum DelayReleaseOutcome<Message> {
234    Released { message: Message },
235    Stale { token: TimerToken },
236}
237
238#[must_use = "withdrawn delayed work or an absent admission must be handled by the fitting"]
239pub enum DelayWithdrawalOutcome<Message> {
240    Withdrawn { rejected: Delayed<Message> },
241    Absent { admission: DelayAdmission },
242}