1mod metrics;
11mod protocol;
12
13use crate::common::protocols::{DirectRequest, OutputSignal};
14use tokio::sync::{mpsc, oneshot};
15use tokio_util::sync::CancellationToken;
16use uuid::Uuid;
17
18pub use crate::common::protocols::ForwardPassSnapshot;
19pub use metrics::MockerMetrics;
20pub use protocol::{
21 SchedulerCommand, SchedulerCommandEffects, SchedulerCommandResult, SchedulerLifecycleEvent,
22};
23
24#[derive(Debug, Clone)]
25pub(crate) struct AdmissionEvent {
26 pub(crate) uuid: Uuid,
27 pub(crate) reused_input_tokens: usize,
28}
29
30pub struct SchedulerCommandEnvelope {
31 pub command: SchedulerCommand,
32 pub reply: oneshot::Sender<anyhow::Result<SchedulerCommandEffects>>,
33}
34
35#[derive(Debug)]
36pub(crate) enum LiveEngineEvent {
37 Admissions(Vec<AdmissionEvent>),
38 Outputs {
39 signals: Vec<OutputSignal>,
40 delivered: oneshot::Sender<Vec<OutputSignal>>,
44 },
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub(crate) enum RouterEventVisibility {
52 PassStart,
53 PassEnd,
54}
55
56#[derive(Clone)]
57pub(crate) enum SchedulerEventSender {
58 Outputs(mpsc::UnboundedSender<Vec<OutputSignal>>),
59 Ordered {
60 tx: mpsc::Sender<LiveEngineEvent>,
61 forward_admissions: bool,
62 cancel: CancellationToken,
63 },
64}
65
66#[derive(Debug)]
67pub(crate) enum SchedulerEventSendError {
68 OutputClosed(Vec<OutputSignal>),
69 OrderedLaneClosed,
70 Cancelled,
71}
72
73impl SchedulerEventSender {
74 pub(crate) async fn send_admissions(
75 &self,
76 admissions: &[AdmissionEvent],
77 ) -> Result<(), SchedulerEventSendError> {
78 if admissions.is_empty() {
79 return Ok(());
80 }
81 match self {
82 Self::Outputs(_) => Ok(()),
83 Self::Ordered {
84 forward_admissions: false,
85 ..
86 } => Ok(()),
87 Self::Ordered { tx, cancel, .. } => {
88 tokio::select! {
89 biased;
90 result = tx.send(LiveEngineEvent::Admissions(admissions.to_vec())) => {
91 result.map_err(|_| {
92 if cancel.is_cancelled() {
93 SchedulerEventSendError::Cancelled
94 } else {
95 SchedulerEventSendError::OrderedLaneClosed
96 }
97 })
98 }
99 _ = cancel.cancelled() => Err(SchedulerEventSendError::Cancelled),
100 }
101 }
102 }
103 }
104
105 pub(crate) async fn send_outputs(
106 &self,
107 signals: Vec<OutputSignal>,
108 ) -> Result<(), SchedulerEventSendError> {
109 match self {
110 Self::Outputs(tx) => tx
111 .send(signals)
112 .map_err(|error| SchedulerEventSendError::OutputClosed(error.0)),
113 Self::Ordered { tx, cancel, .. } => {
114 let (delivered, acknowledged) = oneshot::channel();
115 tokio::select! {
116 biased;
117 result = tx.send(LiveEngineEvent::Outputs { signals, delivered }) => {
118 result.map_err(|_| {
119 if cancel.is_cancelled() {
120 SchedulerEventSendError::Cancelled
121 } else {
122 SchedulerEventSendError::OrderedLaneClosed
123 }
124 })?;
125 }
126 _ = cancel.cancelled() => return Err(SchedulerEventSendError::Cancelled),
127 }
128 let failed = tokio::select! {
129 biased;
130 result = acknowledged => {
131 result.map_err(|_| {
132 if cancel.is_cancelled() {
133 SchedulerEventSendError::Cancelled
134 } else {
135 SchedulerEventSendError::OrderedLaneClosed
136 }
137 })?
138 }
139 _ = cancel.cancelled() => return Err(SchedulerEventSendError::Cancelled),
140 };
141 if failed.is_empty() {
142 Ok(())
143 } else {
144 Err(SchedulerEventSendError::OutputClosed(failed))
145 }
146 }
147 }
148 }
149}
150
151impl From<mpsc::UnboundedSender<Vec<OutputSignal>>> for SchedulerEventSender {
152 fn from(tx: mpsc::UnboundedSender<Vec<OutputSignal>>) -> Self {
153 Self::Outputs(tx)
154 }
155}
156
157pub struct SchedulerCancellationEnvelope {
158 pub request_id: Uuid,
159 pub discard_pending_output: bool,
160 pub reply: oneshot::Sender<anyhow::Result<SchedulerCommandEffects>>,
161}
162
163impl From<SchedulerCancellationEnvelope> for SchedulerCommandEnvelope {
164 fn from(cancellation: SchedulerCancellationEnvelope) -> Self {
165 Self {
166 command: SchedulerCommand::CancelRequest {
167 request_id: cancellation.request_id,
168 },
169 reply: cancellation.reply,
170 }
171 }
172}
173
174pub trait SchedulerHandle: Send + Sync {
176 fn receive(&self, request: DirectRequest);
178
179 fn request_sender(&self) -> mpsc::UnboundedSender<DirectRequest>;
181
182 fn metrics_receiver(&self) -> tokio::sync::watch::Receiver<MockerMetrics>;
183
184 fn command_sender(&self) -> mpsc::Sender<SchedulerCommandEnvelope>;
185
186 fn cancellation_sender(&self) -> mpsc::Sender<SchedulerCancellationEnvelope>;
187
188 fn take_lifecycle_receiver(&mut self) -> Option<mpsc::Receiver<SchedulerLifecycleEvent>>;
189}
190
191pub(crate) fn handoff_channel_capacity(args: &crate::common::protocols::MockEngineArgs) -> usize {
192 args.effective_handoff_capacity()
193 .checked_mul(2)
194 .expect("mocker handoff channel capacity overflow")
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[tokio::test]
202 async fn ordered_output_send_waits_for_route_delivery_ack() {
203 let (tx, mut rx) = mpsc::channel(1);
204 let sender = SchedulerEventSender::Ordered {
205 tx,
206 forward_admissions: false,
207 cancel: CancellationToken::new(),
208 };
209 let send = tokio::spawn(async move {
210 sender
211 .send_outputs(vec![OutputSignal {
212 uuid: Uuid::from_u128(1),
213 token_id: Some(2),
214 completed: true,
215 rejected: false,
216 handoff_delay_ms: None,
217 cached_tokens: None,
218 }])
219 .await
220 });
221
222 let Some(LiveEngineEvent::Outputs { signals, delivered }) = rx.recv().await else {
223 panic!("expected an ordered output batch");
224 };
225 assert_eq!(signals.len(), 1);
226 tokio::task::yield_now().await;
227 assert!(
228 !send.is_finished(),
229 "enqueueing the output must not acknowledge route delivery"
230 );
231
232 delivered.send(Vec::new()).unwrap();
233 send.await.unwrap().unwrap();
234 }
235
236 #[tokio::test]
237 async fn dropped_ordered_output_ack_is_orderly_after_cancellation() {
238 let (tx, mut rx) = mpsc::channel(1);
239 let cancel = CancellationToken::new();
240 let sender = SchedulerEventSender::Ordered {
241 tx,
242 forward_admissions: false,
243 cancel: cancel.clone(),
244 };
245 let send = tokio::spawn(async move {
246 sender
247 .send_outputs(vec![OutputSignal {
248 uuid: Uuid::from_u128(2),
249 token_id: Some(3),
250 completed: true,
251 rejected: false,
252 handoff_delay_ms: None,
253 cached_tokens: None,
254 }])
255 .await
256 });
257
258 let Some(LiveEngineEvent::Outputs { delivered, .. }) = rx.recv().await else {
259 panic!("expected an ordered output batch");
260 };
261 cancel.cancel();
262 drop(delivered);
263 assert!(matches!(
264 send.await.unwrap(),
265 Err(SchedulerEventSendError::Cancelled)
266 ));
267 }
268
269 #[tokio::test]
270 async fn dropped_ordered_output_ack_without_cancellation_is_an_error() {
271 let (tx, mut rx) = mpsc::channel(1);
272 let sender = SchedulerEventSender::Ordered {
273 tx,
274 forward_admissions: false,
275 cancel: CancellationToken::new(),
276 };
277 let send = tokio::spawn(async move {
278 sender
279 .send_outputs(vec![OutputSignal {
280 uuid: Uuid::from_u128(3),
281 token_id: Some(4),
282 completed: true,
283 rejected: false,
284 handoff_delay_ms: None,
285 cached_tokens: None,
286 }])
287 .await
288 });
289
290 let Some(LiveEngineEvent::Outputs { delivered, .. }) = rx.recv().await else {
291 panic!("expected an ordered output batch");
292 };
293 drop(delivered);
294 assert!(matches!(
295 send.await.unwrap(),
296 Err(SchedulerEventSendError::OrderedLaneClosed)
297 ));
298 }
299}