1use std::num::NonZeroUsize;
12use std::sync::{Arc, Mutex, Weak};
13
14use anyhow::{Context, anyhow, bail};
15use dashmap::mapref::entry::Entry;
16use futures::future::{BoxFuture, FutureExt, Shared};
17use tokio::runtime::Handle;
18use tokio::sync::{mpsc, oneshot, watch};
19use tokio_util::sync::CancellationToken;
20use uuid::Uuid;
21
22use crate::common::handoff::HandoffId;
23use crate::common::protocols::{
24 DirectRequest, FpmPublisher, KvEventPublishers, MockEngineArgs, OutputSignal,
25};
26use crate::engine::{LiveEngineScheduler, create_engine_with_event_sender};
27#[cfg(test)]
28use crate::grouped_scheduler::CompletionBoundaryTestControl;
29use crate::grouped_scheduler::{
30 CompletionBoundaryDrain, GroupedSchedulerRankEventSinks, GroupedSchedulers,
31 create_grouped_scheduler_with_event_senders,
32};
33use crate::scheduler::{
34 LiveEngineEvent, MockerMetrics, SchedulerCancellationEnvelope, SchedulerCommand,
35 SchedulerCommandEnvelope, SchedulerCommandResult, SchedulerEventSender, SchedulerHandle,
36};
37
38mod handoff;
39mod request;
40
41pub use handoff::{LiveHandoffControl, LiveHandoffEvent, LiveHandoffEvents};
42
43use handoff::{
44 DestinationCancellation, HandoffRoutes, SharedHandoffRoutes, run_lifecycle_dispatcher,
45 shutdown_handoff_routes, supervise_lifecycle_dispatcher,
46};
47use request::{
48 ObservedOutput, OutputDelivery, RequestCancellation, RequestRoute, RequestRoutes, Routes,
49 remove_route, route_is_registered, shutdown_routes,
50};
51
52const SCHEDULER_EVENT_CAPACITY: usize = 8;
53const DEFAULT_REQUEST_OUTPUT_CAPACITY: usize = 8;
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum RequestOutputBuffering {
58 CancelOnOverflow { capacity: NonZeroUsize },
60 FullResponse,
62}
63
64impl Default for RequestOutputBuffering {
65 fn default() -> Self {
66 Self::CancelOnOverflow {
67 capacity: NonZeroUsize::new(DEFAULT_REQUEST_OUTPUT_CAPACITY).unwrap(),
68 }
69 }
70}
71
72impl RequestOutputBuffering {
73 fn capacity_for(self, output_length: usize) -> usize {
74 let output_length = output_length.max(1);
75 match self {
76 Self::CancelOnOverflow { capacity } => output_length.min(capacity.get()),
77 Self::FullResponse => output_length,
78 }
79 }
80}
81
82#[derive(Clone, Default)]
84pub struct LiveEngineConfig {
85 pub kv_event_publishers: KvEventPublishers,
86 pub fpm_publisher: FpmPublisher,
87}
88
89pub(crate) struct ObservedAdmission {
90 pub(crate) event: crate::scheduler::AdmissionEvent,
91 pub(crate) observed_at: tokio::time::Instant,
92}
93
94#[derive(Default)]
95pub(crate) struct LiveEngineOptions {
96 pub(crate) kv_event_publishers: KvEventPublishers,
97 pub(crate) admission_tx: Option<mpsc::UnboundedSender<ObservedAdmission>>,
98 pub(crate) fpm_publisher: FpmPublisher,
99 pub(crate) request_output_buffering: RequestOutputBuffering,
100 pub(crate) allow_zero_output: bool,
101}
102
103pub fn stable_request_uuid(seed: u64, request_id: &str) -> Uuid {
105 let mut hasher = blake3::Hasher::new();
106 hasher.update(&seed.to_le_bytes());
107 hasher.update(request_id.as_bytes());
108 let mut bytes = [0u8; 16];
109 bytes.copy_from_slice(&hasher.finalize().as_bytes()[..16]);
110 bytes[6] = (bytes[6] & 0x0f) | 0x40;
113 bytes[8] = (bytes[8] & 0x3f) | 0x80;
114 Uuid::from_bytes(bytes)
115}
116
117pub fn deterministic_output_tokens(seed: u64, request_id: &str, count: usize) -> Vec<u32> {
119 (0..count)
120 .map(|position| {
121 let mut hasher = blake3::Hasher::new();
122 hasher.update(&seed.to_le_bytes());
123 hasher.update(request_id.as_bytes());
124 hasher.update(&(position as u64).to_le_bytes());
125 let bytes = hasher.finalize();
126 1_000 + (u32::from_le_bytes(bytes.as_bytes()[..4].try_into().unwrap()) % 31_000)
127 })
128 .collect()
129}
130
131#[derive(Clone)]
133pub struct LiveEngine {
134 inner: Arc<LiveEngineInner>,
135}
136
137struct LiveEngineInner {
138 command_tx: mpsc::Sender<SchedulerCommandEnvelope>,
139 cancellation_tx: mpsc::Sender<SchedulerCancellationEnvelope>,
140 routes: Routes,
141 handoff_routes: SharedHandoffRoutes,
142 metrics_rx: tokio::sync::watch::Receiver<MockerMetrics>,
143 request_output_buffering: RequestOutputBuffering,
144 allow_zero_output: bool,
145 group: Arc<LiveEngineGroup>,
146 cancel: CancellationToken,
147 runtime: Handle,
148 tasks: Mutex<LiveEngineTasks>,
149 #[allow(dead_code)]
151 scheduler: Box<dyn SchedulerHandle>,
152}
153
154struct LiveEngineTasks {
155 dispatcher_supervisor: Option<tokio::task::JoinHandle<anyhow::Result<()>>>,
156 lifecycle_supervisor: Option<tokio::task::JoinHandle<anyhow::Result<()>>>,
157 shutdown: Option<SharedShutdown>,
158}
159
160type SharedShutdown = Shared<BoxFuture<'static, Result<(), Arc<str>>>>;
161
162struct LiveEngineGroup {
163 cancel: CancellationToken,
164 actor: Mutex<Option<tokio::task::JoinHandle<anyhow::Result<()>>>>,
165 shutdown: Mutex<Option<SharedShutdown>>,
166 completion_drain: CompletionBoundaryDrain,
167}
168
169impl LiveEngineGroup {
170 fn new(
171 cancel: CancellationToken,
172 actor: tokio::task::JoinHandle<anyhow::Result<()>>,
173 completion_drain: CompletionBoundaryDrain,
174 ) -> Self {
175 Self {
176 cancel,
177 actor: Mutex::new(Some(actor)),
178 shutdown: Mutex::new(None),
179 completion_drain,
180 }
181 }
182
183 fn shutdown(&self) -> SharedShutdown {
184 self.cancel.cancel();
185 let mut shutdown = self.shutdown.lock().unwrap();
186 if let Some(shutdown) = shutdown.as_ref() {
187 return shutdown.clone();
188 }
189 let actor = self.actor.lock().unwrap().take();
190 let future = async move {
191 let Some(actor) = actor else {
192 return Ok(());
193 };
194 match actor.await {
195 Ok(Ok(())) => Ok(()),
196 Ok(Err(error)) => Err(Arc::from(format!(
197 "live Mocker scheduler failed: {error:#}"
198 ))),
199 Err(error) => Err(Arc::from(format!(
200 "live Mocker scheduler task failed: {error}"
201 ))),
202 }
203 }
204 .boxed()
205 .shared();
206 *shutdown = Some(future.clone());
207 future
208 }
209}
210
211impl Drop for LiveEngineGroup {
212 fn drop(&mut self) {
213 self.cancel.cancel();
214 }
215}
216
217impl LiveEngine {
218 pub fn start(args: MockEngineArgs, dp_rank: u32) -> anyhow::Result<Self> {
220 Self::start_internal(args, dp_rank, LiveEngineOptions::default(), None)
221 }
222
223 pub fn start_with_config(
225 args: MockEngineArgs,
226 dp_rank: u32,
227 config: LiveEngineConfig,
228 ) -> anyhow::Result<Self> {
229 Self::start_with_config_and_request_output_buffering(
230 args,
231 dp_rank,
232 config,
233 RequestOutputBuffering::default(),
234 )
235 }
236
237 pub fn start_with_config_and_request_output_buffering(
240 args: MockEngineArgs,
241 dp_rank: u32,
242 config: LiveEngineConfig,
243 request_output_buffering: RequestOutputBuffering,
244 ) -> anyhow::Result<Self> {
245 Self::start_internal(
246 args,
247 dp_rank,
248 LiveEngineOptions {
249 kv_event_publishers: config.kv_event_publishers,
250 fpm_publisher: config.fpm_publisher,
251 request_output_buffering,
252 ..LiveEngineOptions::default()
253 },
254 None,
255 )
256 }
257
258 pub fn start_grouped_with_configs(
264 args: MockEngineArgs,
265 configs: Vec<LiveEngineConfig>,
266 ) -> anyhow::Result<Vec<Self>> {
267 Self::start_grouped_with_configs_and_request_output_buffering(
268 args,
269 configs,
270 RequestOutputBuffering::default(),
271 )
272 }
273
274 pub fn start_grouped_with_configs_and_request_output_buffering(
277 args: MockEngineArgs,
278 configs: Vec<LiveEngineConfig>,
279 request_output_buffering: RequestOutputBuffering,
280 ) -> anyhow::Result<Vec<Self>> {
281 let options = configs
282 .into_iter()
283 .map(|config| LiveEngineOptions {
284 kv_event_publishers: config.kv_event_publishers,
285 fpm_publisher: config.fpm_publisher,
286 request_output_buffering,
287 ..LiveEngineOptions::default()
288 })
289 .collect();
290 Self::start_grouped_with_options(args, options, None)
291 }
292
293 pub(crate) fn start_grouped_with_options(
294 args: MockEngineArgs,
295 options: Vec<LiveEngineOptions>,
296 output_gate: Option<watch::Receiver<bool>>,
297 ) -> anyhow::Result<Vec<Self>> {
298 let runtime = Handle::try_current()
299 .context("LiveEngine::start_grouped_with_options requires an active Tokio runtime")?;
300 let args = args
301 .normalized()
302 .context("invalid Mocker engine arguments")?;
303 anyhow::ensure!(
304 options.len() == args.dp_size as usize,
305 "grouped live Mocker requires one options value per DP rank: expected {}, got {}",
306 args.dp_size,
307 options.len()
308 );
309
310 let cancel = CancellationToken::new();
311 let mut event_receivers = Vec::with_capacity(options.len());
312 let mut rank_sinks = Vec::with_capacity(options.len());
313 for options_for_rank in &options {
314 let (event_tx, event_rx) = mpsc::channel(SCHEDULER_EVENT_CAPACITY);
315 rank_sinks.push(GroupedSchedulerRankEventSinks {
316 event_tx: Some(SchedulerEventSender::Ordered {
317 tx: event_tx,
318 forward_admissions: options_for_rank.admission_tx.is_some(),
319 cancel: cancel.clone(),
320 }),
321 kv_event_publishers: options_for_rank.kv_event_publishers.clone(),
322 fpm_publisher: options_for_rank.fpm_publisher.clone(),
323 });
324 event_receivers.push(event_rx);
325 }
326
327 let GroupedSchedulers {
328 schedulers,
329 actor,
330 completion_drain,
331 } = create_grouped_scheduler_with_event_senders(args, rank_sinks, Some(cancel.clone()))?;
332 let group = Arc::new(LiveEngineGroup::new(cancel, actor, completion_drain));
333 schedulers
334 .into_iter()
335 .zip(event_receivers)
336 .zip(options)
337 .map(|((scheduler, event_rx), options)| {
338 Self::from_scheduler(
339 runtime.clone(),
340 scheduler,
341 Arc::clone(&group),
342 event_rx,
343 options,
344 output_gate.clone(),
345 )
346 })
347 .collect()
348 }
349
350 #[cfg(test)]
351 pub(crate) fn start_with_options(
352 args: MockEngineArgs,
353 dp_rank: u32,
354 options: LiveEngineOptions,
355 ) -> anyhow::Result<Self> {
356 Self::start_internal(args, dp_rank, options, None)
357 }
358
359 #[cfg(test)]
360 fn start_with_output_gate(
361 args: MockEngineArgs,
362 dp_rank: u32,
363 output_gate: Option<watch::Receiver<bool>>,
364 request_output_capacity: usize,
365 ) -> anyhow::Result<Self> {
366 let request_output_capacity = NonZeroUsize::new(request_output_capacity)
367 .ok_or_else(|| anyhow!("request output capacity must be greater than 0"))?;
368 Self::start_internal(
369 args,
370 dp_rank,
371 LiveEngineOptions {
372 request_output_buffering: RequestOutputBuffering::CancelOnOverflow {
373 capacity: request_output_capacity,
374 },
375 ..LiveEngineOptions::default()
376 },
377 output_gate,
378 )
379 }
380
381 fn start_internal(
382 args: MockEngineArgs,
383 dp_rank: u32,
384 options: LiveEngineOptions,
385 output_gate: Option<watch::Receiver<bool>>,
386 ) -> anyhow::Result<Self> {
387 let runtime =
388 Handle::try_current().context("LiveEngine::start requires an active Tokio runtime")?;
389 let args = args
390 .normalized()
391 .context("invalid Mocker engine arguments")?;
392 let group_cancel = CancellationToken::new();
393 let (event_tx, event_rx) = mpsc::channel::<LiveEngineEvent>(SCHEDULER_EVENT_CAPACITY);
394 let forward_admissions = options.admission_tx.is_some();
395 let LiveEngineScheduler {
396 handle: scheduler,
397 actor: scheduler_actor,
398 completion_drain,
399 } = create_engine_with_event_sender(
400 args,
401 dp_rank,
402 Some(SchedulerEventSender::Ordered {
403 tx: event_tx,
404 forward_admissions,
405 cancel: group_cancel.clone(),
406 }),
407 options.kv_event_publishers.clone(),
408 Some(group_cancel.clone()),
409 options.fpm_publisher.clone(),
410 )?;
411 let group = Arc::new(LiveEngineGroup::new(
412 group_cancel,
413 scheduler_actor,
414 completion_drain,
415 ));
416 Self::from_scheduler(runtime, scheduler, group, event_rx, options, output_gate)
417 }
418
419 fn from_scheduler(
420 runtime: Handle,
421 mut scheduler: Box<dyn SchedulerHandle>,
422 group: Arc<LiveEngineGroup>,
423 event_rx: mpsc::Receiver<LiveEngineEvent>,
424 options: LiveEngineOptions,
425 output_gate: Option<watch::Receiver<bool>>,
426 ) -> anyhow::Result<Self> {
427 let cancel = group.cancel.child_token();
428 let command_tx = scheduler.command_sender();
429 let cancellation_tx = scheduler.cancellation_sender();
430 let metrics_rx = scheduler.metrics_receiver();
431 let lifecycle_rx = scheduler
432 .take_lifecycle_receiver()
433 .expect("new live scheduler must expose one lifecycle receiver");
434 let routes = Arc::new(RequestRoutes::default());
435 let handoff_routes = Arc::new(HandoffRoutes::default());
436 let dispatcher = runtime.spawn(run_event_dispatcher(
437 event_rx,
438 Arc::clone(&routes),
439 cancel.clone(),
440 output_gate,
441 options.admission_tx,
442 ));
443 let dispatcher_supervisor = runtime.spawn(supervise_event_dispatcher(
444 dispatcher,
445 Arc::clone(&routes),
446 Arc::clone(&handoff_routes),
447 cancel.clone(),
448 ));
449 let lifecycle_dispatcher = runtime.spawn(run_lifecycle_dispatcher(
450 lifecycle_rx,
451 Arc::clone(&handoff_routes),
452 cancel.clone(),
453 ));
454 let lifecycle_supervisor = runtime.spawn(supervise_lifecycle_dispatcher(
455 lifecycle_dispatcher,
456 Arc::clone(&routes),
457 Arc::clone(&handoff_routes),
458 cancel.clone(),
459 ));
460
461 Ok(Self {
462 inner: Arc::new(LiveEngineInner {
463 command_tx,
464 cancellation_tx,
465 routes,
466 handoff_routes,
467 metrics_rx,
468 request_output_buffering: options.request_output_buffering,
469 allow_zero_output: options.allow_zero_output,
470 group,
471 cancel,
472 runtime,
473 tasks: Mutex::new(LiveEngineTasks {
474 dispatcher_supervisor: Some(dispatcher_supervisor),
475 lifecycle_supervisor: Some(lifecycle_supervisor),
476 shutdown: None,
477 }),
478 scheduler,
479 }),
480 })
481 }
482
483 pub fn prepare_request(
489 &self,
490 mut request: DirectRequest,
491 ) -> anyhow::Result<(LiveRequestRegistration, LiveRequest)> {
492 anyhow::ensure!(
493 !self.inner.cancel.is_cancelled(),
494 "live Mocker engine is not running"
495 );
496 let output_length = request.effective_max_output_tokens();
497 anyhow::ensure!(
498 self.inner.allow_zero_output || output_length > 0,
499 "live requests must generate at least one output token"
500 );
501 request.max_output_tokens = output_length;
502 let client_id = request.uuid.unwrap_or_else(Uuid::new_v4);
503 let scheduler_id = Uuid::new_v4();
504 request.uuid = Some(scheduler_id);
505 let output_capacity = self
506 .inner
507 .request_output_buffering
508 .capacity_for(output_length);
509 let (tx, rx) = mpsc::channel(output_capacity);
510 let route = Arc::new(RequestRoute::new(client_id, scheduler_id, tx));
511 match self.inner.routes.by_client.entry(client_id) {
512 Entry::Occupied(_) => bail!("request {client_id} is already active"),
513 Entry::Vacant(entry) => {
514 entry.insert(Arc::clone(&route));
515 }
516 }
517 match self.inner.routes.by_scheduler.entry(scheduler_id) {
518 Entry::Occupied(_) => {
519 remove_route(&self.inner.routes, &route);
520 bail!("internal scheduler request ID collision");
521 }
522 Entry::Vacant(entry) => {
523 entry.insert(Arc::clone(&route));
524 }
525 }
526 if self.inner.cancel.is_cancelled() {
527 route.shutdown();
528 remove_route(&self.inner.routes, &route);
529 bail!("live Mocker engine is not running");
530 }
531
532 let live = LiveRequest {
533 client_id,
534 rx,
535 route: Arc::downgrade(&route),
536 routes: Arc::clone(&self.inner.routes),
537 command_tx: self.inner.command_tx.clone(),
538 cancellation_tx: self.inner.cancellation_tx.clone(),
539 runtime: self.inner.runtime.clone(),
540 };
541 let registration = LiveRequestRegistration {
542 engine: Arc::downgrade(&self.inner),
543 routes: Arc::clone(&self.inner.routes),
544 prepared: Some(PreparedRequest { request, route }),
545 };
546 Ok((registration, live))
547 }
548
549 pub async fn submit(&self, request: DirectRequest) -> anyhow::Result<LiveRequest> {
551 let (registration, live) = self.prepare_request(request)?;
552 self.submit_prepared(registration, PreparedSubmission::Ordinary, None)
553 .await?;
554 Ok(live)
555 }
556
557 async fn submit_prepared(
558 &self,
559 mut registration: LiveRequestRegistration,
560 submission: PreparedSubmission,
561 command_guard: Option<tokio::sync::OwnedMutexGuard<()>>,
562 ) -> anyhow::Result<()> {
563 anyhow::ensure!(
564 !self.inner.cancel.is_cancelled(),
565 "live Mocker engine is not running"
566 );
567 let PreparedRequest { request, route } = registration.take_for(&self.inner)?;
568 let scheduler_id = route.scheduler_id;
569 let client_id = route.client_id;
570 let routes = Arc::clone(&self.inner.routes);
571 let submission_route = Arc::clone(&route);
572 let command_tx = self.inner.command_tx.clone();
573 let task = self.inner.runtime.spawn(async move {
574 let _command_guard = command_guard;
575 let command = submission.command(request);
576 let result = send_command(&command_tx, command).await;
577 let admission = submission.validate(result, client_id, scheduler_id);
578 if admission.is_ok() {
579 submission_route.activate(submission.cancellation());
580 } else {
581 submission_route.shutdown();
582 remove_route(&routes, &submission_route);
583 }
584 admission
585 });
586 match task.await {
587 Ok(result) => result?,
588 Err(error) => {
589 route.shutdown();
590 remove_route(&self.inner.routes, &route);
591 return Err(anyhow!("live Mocker submission task failed: {error}"));
592 }
593 }
594 if self.inner.cancel.is_cancelled() {
595 route.shutdown();
596 remove_route(&self.inner.routes, &route);
597 bail!("live Mocker engine stopped during submission");
598 }
599 Ok(())
600 }
601
602 pub async fn cancel(&self, request_id: Uuid) -> anyhow::Result<bool> {
604 let Some(route) = self
605 .inner
606 .routes
607 .by_client
608 .get(&request_id)
609 .map(|entry| Arc::clone(entry.value()))
610 else {
611 return Ok(false);
612 };
613 route.abandon_stream();
617 await_cancellation(spawn_cancellation(
618 &self.inner.runtime,
619 self.inner.command_tx.clone(),
620 self.inner.cancellation_tx.clone(),
621 Arc::clone(&self.inner.routes),
622 route,
623 true,
624 ))
625 .await
626 }
627
628 pub fn metrics_receiver(&self) -> tokio::sync::watch::Receiver<MockerMetrics> {
630 self.inner.metrics_rx.clone()
631 }
632
633 pub fn active_request_count(&self) -> usize {
635 self.inner.routes.by_client.len()
636 }
637
638 pub(crate) async fn drain_completion_boundary(&self) -> anyhow::Result<()> {
639 self.inner.group.completion_drain.wait().await
640 }
641
642 #[cfg(test)]
643 pub(crate) fn pause_completion_boundary_before_finish(&self) -> CompletionBoundaryTestControl {
644 self.inner.group.completion_drain.pause_before_finish()
645 }
646
647 #[cfg(test)]
648 pub(crate) fn group_is_cancelled(&self) -> bool {
649 self.inner.group.cancel.is_cancelled()
650 }
651
652 pub async fn shutdown(&self) -> anyhow::Result<()> {
653 let group_shutdown = self.inner.group.shutdown();
654 self.inner.cancel.cancel();
655 shutdown_routes(&self.inner.routes);
656 shutdown_handoff_routes(&self.inner.handoff_routes);
657 let shutdown = {
658 let mut tasks = self.inner.tasks.lock().unwrap();
659 if let Some(shutdown) = tasks.shutdown.as_ref() {
660 shutdown.clone()
661 } else {
662 let shutdown = shutdown_engine(
663 group_shutdown,
664 tasks.dispatcher_supervisor.take(),
665 tasks.lifecycle_supervisor.take(),
666 Arc::clone(&self.inner.routes),
667 Arc::clone(&self.inner.handoff_routes),
668 )
669 .boxed()
670 .shared();
671 tasks.shutdown = Some(shutdown.clone());
672 shutdown
673 }
674 };
675 shutdown.await.map_err(|error| anyhow!("{error}"))
676 }
677}
678
679async fn shutdown_engine(
680 group_shutdown: SharedShutdown,
681 dispatcher_supervisor: Option<tokio::task::JoinHandle<anyhow::Result<()>>>,
682 lifecycle_supervisor: Option<tokio::task::JoinHandle<anyhow::Result<()>>>,
683 routes: Routes,
684 handoff_routes: SharedHandoffRoutes,
685) -> Result<(), Arc<str>> {
686 let mut first_error = group_shutdown.await.err().map(|error| anyhow!("{error}"));
687 if let Some(dispatcher_supervisor) = dispatcher_supervisor {
688 match dispatcher_supervisor.await {
689 Ok(Ok(())) => {}
690 Ok(Err(error)) if first_error.is_none() => {
691 first_error = Some(error.context("live Mocker event dispatcher failed"))
692 }
693 Err(error) if first_error.is_none() => {
694 first_error = Some(anyhow!("live Mocker dispatcher supervisor failed: {error}"))
695 }
696 Ok(Err(_)) | Err(_) => {}
697 }
698 }
699 if let Some(lifecycle_supervisor) = lifecycle_supervisor {
700 match lifecycle_supervisor.await {
701 Ok(Ok(())) => {}
702 Ok(Err(error)) if first_error.is_none() => {
703 first_error = Some(error.context("live Mocker lifecycle dispatcher failed"))
704 }
705 Err(error) if first_error.is_none() => {
706 first_error = Some(anyhow!(
707 "live Mocker lifecycle dispatcher supervisor failed: {error}"
708 ))
709 }
710 Ok(Err(_)) | Err(_) => {}
711 }
712 }
713 shutdown_routes(&routes);
714 shutdown_handoff_routes(&handoff_routes);
715 if let Some(error) = first_error {
716 return Err(Arc::from(format!("{error:#}")));
717 }
718 if !routes.by_client.is_empty() || !routes.by_scheduler.is_empty() {
719 return Err(Arc::from("live Mocker shutdown left active request routes"));
720 }
721 if !handoff_routes.is_empty() {
722 return Err(Arc::from("live Mocker shutdown left active handoff routes"));
723 }
724 Ok(())
725}
726
727impl Drop for LiveEngineInner {
728 fn drop(&mut self) {
729 self.cancel.cancel();
730 }
731}
732
733struct PreparedRequest {
734 request: DirectRequest,
735 route: Arc<RequestRoute>,
736}
737
738pub struct LiveRequestRegistration {
740 engine: Weak<LiveEngineInner>,
741 routes: Routes,
742 prepared: Option<PreparedRequest>,
743}
744
745impl LiveRequestRegistration {
746 fn take_for(&mut self, engine: &Arc<LiveEngineInner>) -> anyhow::Result<PreparedRequest> {
747 let Some(owner) = self.engine.upgrade() else {
748 bail!("live Mocker engine no longer exists");
749 };
750 anyhow::ensure!(
751 Arc::ptr_eq(&owner, engine),
752 "prepared request belongs to a different live Mocker engine"
753 );
754 self.prepared
755 .take()
756 .ok_or_else(|| anyhow!("prepared request was already consumed"))
757 }
758}
759
760impl Drop for LiveRequestRegistration {
761 fn drop(&mut self) {
762 if let Some(prepared) = self.prepared.take() {
763 prepared.route.shutdown();
764 remove_route(&self.routes, &prepared.route);
765 }
766 }
767}
768
769#[derive(Clone)]
770enum PreparedSubmission {
771 Ordinary,
772 Source(HandoffId),
773 Destination(DestinationCancellation),
774}
775
776impl PreparedSubmission {
777 fn cancellation(&self) -> RequestCancellation {
778 match self {
779 Self::Destination(cancellation) => {
780 RequestCancellation::Destination(cancellation.clone())
781 }
782 Self::Ordinary | Self::Source(_) => RequestCancellation::Request,
783 }
784 }
785
786 fn command(&self, request: DirectRequest) -> SchedulerCommand {
787 match self {
788 Self::Ordinary => SchedulerCommand::Submit(request),
789 Self::Source(handoff_id) => SchedulerCommand::SubmitHandoffPrefill {
790 handoff_id: *handoff_id,
791 request,
792 },
793 Self::Destination(cancellation) => SchedulerCommand::ReserveDestination {
794 handoff_id: cancellation.handoff_id(),
795 request,
796 },
797 }
798 }
799
800 fn validate(
801 &self,
802 result: anyhow::Result<SchedulerCommandResult>,
803 client_id: Uuid,
804 scheduler_id: Uuid,
805 ) -> anyhow::Result<()> {
806 match (self, result) {
807 (
808 Self::Ordinary | Self::Source(_),
809 Ok(SchedulerCommandResult::Submitted(submitted)),
810 ) if submitted == scheduler_id => Ok(()),
811 (
812 Self::Destination(_),
813 Ok(SchedulerCommandResult::DestinationAccepted { request_id }),
814 ) if request_id == scheduler_id => Ok(()),
815 (_, Ok(result)) => Err(anyhow!(
816 "unexpected scheduler submit result for {client_id}: {result:?}"
817 )),
818 (_, Err(error)) => Err(error),
819 }
820 }
821}
822
823pub struct LiveRequest {
825 client_id: Uuid,
826 rx: mpsc::Receiver<ObservedOutput>,
827 route: Weak<RequestRoute>,
828 routes: Routes,
829 command_tx: mpsc::Sender<SchedulerCommandEnvelope>,
830 cancellation_tx: mpsc::Sender<SchedulerCancellationEnvelope>,
831 runtime: Handle,
832}
833
834impl LiveRequest {
835 pub fn id(&self) -> Uuid {
836 self.client_id
837 }
838
839 pub async fn recv(&mut self) -> Option<OutputSignal> {
840 self.recv_observed().await.map(|output| output.event)
841 }
842
843 pub(crate) async fn recv_observed(&mut self) -> Option<ObservedOutput> {
844 self.rx.recv().await
845 }
846
847 pub async fn cancel(self) -> anyhow::Result<bool> {
849 let Some(route) = self.route.upgrade() else {
850 return Ok(false);
851 };
852 route.abandon_stream();
853 await_cancellation(spawn_cancellation(
854 &self.runtime,
855 self.command_tx.clone(),
856 self.cancellation_tx.clone(),
857 Arc::clone(&self.routes),
858 route,
859 true,
860 ))
861 .await
862 }
863}
864
865impl Drop for LiveRequest {
866 fn drop(&mut self) {
867 let Some(route) = self.route.upgrade() else {
868 return;
869 };
870 route.abandon_stream();
871 drop(spawn_cancellation(
872 &self.runtime,
873 self.command_tx.clone(),
874 self.cancellation_tx.clone(),
875 Arc::clone(&self.routes),
876 route,
877 true,
878 ));
879 }
880}
881
882async fn run_event_dispatcher(
883 mut event_rx: mpsc::Receiver<LiveEngineEvent>,
884 routes: Routes,
885 cancel: CancellationToken,
886 mut output_gate: Option<watch::Receiver<bool>>,
887 admission_tx: Option<mpsc::UnboundedSender<ObservedAdmission>>,
888) -> anyhow::Result<()> {
889 let mut pending_event = None;
890 loop {
891 if cancel.is_cancelled() {
892 drop(pending_event.take());
893 while event_rx.recv().await.is_some() {}
894 return Ok(());
895 }
896
897 if matches!(
898 pending_event.as_ref(),
899 Some(LiveEngineEvent::Outputs { .. })
900 ) && output_gate.as_ref().is_some_and(|gate| !*gate.borrow())
901 {
902 let Some(gate) = output_gate.as_mut() else {
903 unreachable!("the output gate was checked above");
904 };
905 tokio::select! {
906 biased;
907 _ = cancel.cancelled() => continue,
908 changed = gate.changed() => {
909 if changed.is_err() {
910 bail!("live Mocker output gate closed");
911 }
912 }
913 }
914 continue;
915 }
916
917 let event = if let Some(event) = pending_event.take() {
918 event
919 } else {
920 tokio::select! {
921 biased;
922 _ = cancel.cancelled() => continue,
923 event = event_rx.recv() => {
924 let Some(event) = event else {
925 if cancel.is_cancelled() {
926 return Ok(());
927 }
928 bail!("live Mocker ordered event lane closed unexpectedly");
929 };
930 event
931 }
932 }
933 };
934
935 match event {
936 LiveEngineEvent::Admissions(batch) => {
937 dispatch_admission_batch(batch, &routes, admission_tx.as_ref())?;
938 }
939 LiveEngineEvent::Outputs { signals, delivered }
940 if output_gate.as_ref().is_some_and(|gate| !*gate.borrow()) =>
941 {
942 pending_event = Some(LiveEngineEvent::Outputs { signals, delivered });
943 }
944 LiveEngineEvent::Outputs { signals, delivered } => {
945 let Some(failed) = dispatch_output_batch(signals, &routes, &cancel) else {
946 return Ok(());
947 };
948 let _ = delivered.send(failed);
953 }
954 }
955 }
956}
957
958fn dispatch_admission_batch(
959 batch: Vec<crate::scheduler::AdmissionEvent>,
960 routes: &Routes,
961 admission_tx: Option<&mpsc::UnboundedSender<ObservedAdmission>>,
962) -> anyhow::Result<()> {
963 let Some(admission_tx) = admission_tx else {
964 return Ok(());
965 };
966 let observed_at = tokio::time::Instant::now();
967 for mut admission in batch {
968 let scheduler_id = admission.uuid;
969 let Some(route) = routes
970 .by_scheduler
971 .get(&scheduler_id)
972 .map(|entry| Arc::clone(entry.value()))
973 else {
974 continue;
975 };
976 admission.uuid = route.client_id;
977 admission_tx
978 .send(ObservedAdmission {
979 event: admission,
980 observed_at,
981 })
982 .map_err(|_| anyhow!("live Mocker admission receiver closed"))?;
983 }
984 Ok(())
985}
986
987async fn supervise_event_dispatcher(
988 dispatcher: tokio::task::JoinHandle<anyhow::Result<()>>,
989 routes: Routes,
990 handoff_routes: SharedHandoffRoutes,
991 cancel: CancellationToken,
992) -> anyhow::Result<()> {
993 let result = match dispatcher.await {
994 Ok(Ok(())) => Ok(()),
995 Ok(Err(error)) => Err(error),
996 Err(error) => Err(anyhow!("live Mocker event dispatcher task failed: {error}")),
997 };
998 if let Err(error) = &result {
999 tracing::error!(%error, "live Mocker event dispatcher failed");
1000 } else if !cancel.is_cancelled() {
1001 tracing::error!("live Mocker event dispatcher exited unexpectedly");
1002 }
1003 cancel.cancel();
1004 shutdown_routes(&routes);
1005 shutdown_handoff_routes(&handoff_routes);
1006 result
1007}
1008
1009fn dispatch_output_batch(
1010 batch: Vec<OutputSignal>,
1011 routes: &Routes,
1012 cancel: &CancellationToken,
1013) -> Option<Vec<OutputSignal>> {
1014 let observed_at = tokio::time::Instant::now();
1015 let mut failed = Vec::new();
1016 for mut signal in batch {
1017 if cancel.is_cancelled() {
1018 return None;
1019 }
1020 let scheduler_signal = signal.clone();
1021 let scheduler_id = signal.uuid;
1022 let terminal = signal.completed;
1023 let Some(route) = routes
1024 .by_scheduler
1025 .get(&scheduler_id)
1026 .map(|entry| Arc::clone(entry.value()))
1027 else {
1028 continue;
1029 };
1030
1031 signal.uuid = route.client_id;
1032 let delivery = route.send_output(ObservedOutput {
1033 event: signal,
1034 observed_at,
1035 });
1036 if delivery != OutputDelivery::Delivered {
1037 let newly_abandoned = route.abandon_stream();
1038 if newly_abandoned && delivery == OutputDelivery::Full {
1039 tracing::debug!(
1040 client_id = %route.client_id,
1041 scheduler_id = %route.scheduler_id,
1042 "cancelling live Mocker request with a full output stream"
1043 );
1044 }
1045 route.shutdown();
1050 remove_route(routes, &route);
1051 failed.push(scheduler_signal);
1052 continue;
1053 }
1054 if terminal && route.observe_terminal() {
1055 remove_route(routes, &route);
1056 }
1057 }
1058 Some(failed)
1059}
1060
1061fn spawn_cancellation(
1062 runtime: &Handle,
1063 command_tx: mpsc::Sender<SchedulerCommandEnvelope>,
1064 cancellation_tx: mpsc::Sender<SchedulerCancellationEnvelope>,
1065 routes: Routes,
1066 route: Arc<RequestRoute>,
1067 abandon_stream: bool,
1068) -> tokio::task::JoinHandle<anyhow::Result<bool>> {
1069 runtime.spawn(async move {
1070 if !route.wait_for_admission().await {
1071 return Ok(false);
1072 }
1073
1074 let _cancel_guard = route.cancel_lock.lock().await;
1075 if !route_is_registered(&routes, &route) {
1076 return Ok(false);
1077 }
1078 if abandon_stream {
1079 route.abandon_stream();
1080 }
1081 let Some(cancellation) = route.begin_cancellation() else {
1082 return Ok(false);
1083 };
1084
1085 let result = match cancellation {
1086 RequestCancellation::Request => {
1087 cancel_request(
1088 &cancellation_tx,
1089 route.scheduler_id,
1090 abandon_stream,
1091 )
1092 .await
1093 }
1094 RequestCancellation::Destination(cancellation) => cancellation.cancel(&command_tx).await,
1095 };
1096 if route.finish_cancellation(&result) {
1097 remove_route(&routes, &route);
1098 }
1099 if let Err(error) = &result {
1100 tracing::debug!(client_id = %route.client_id, scheduler_id = %route.scheduler_id, %error, "live Mocker request cancellation failed");
1101 }
1102 result
1103 })
1104}
1105
1106async fn await_cancellation(
1107 cancellation: tokio::task::JoinHandle<anyhow::Result<bool>>,
1108) -> anyhow::Result<bool> {
1109 match cancellation.await {
1110 Ok(result) => result,
1111 Err(error) => Err(anyhow!("live Mocker cancellation task failed: {error}")),
1112 }
1113}
1114
1115async fn cancel_request(
1116 cancellation_tx: &mpsc::Sender<SchedulerCancellationEnvelope>,
1117 request_id: Uuid,
1118 discard_pending_output: bool,
1119) -> anyhow::Result<bool> {
1120 let (reply, response) = oneshot::channel();
1121 cancellation_tx
1122 .send(SchedulerCancellationEnvelope {
1123 request_id,
1124 discard_pending_output,
1125 reply,
1126 })
1127 .await
1128 .map_err(|_| anyhow!("Mocker scheduler is not accepting cancellations"))?;
1129 let effects = response
1130 .await
1131 .map_err(|_| anyhow!("Mocker scheduler dropped a cancellation acknowledgement"))??;
1132 match effects.result {
1133 SchedulerCommandResult::Applied => Ok(true),
1134 SchedulerCommandResult::Noop => Ok(false),
1135 result => Err(anyhow!(
1136 "unexpected scheduler cancellation result for {request_id}: {result:?}"
1137 )),
1138 }
1139}
1140
1141async fn cancel_destination(
1142 command_tx: &mpsc::Sender<SchedulerCommandEnvelope>,
1143 handoff_id: HandoffId,
1144) -> anyhow::Result<bool> {
1145 match send_command(
1146 command_tx,
1147 SchedulerCommand::CancelDestination { handoff_id },
1148 )
1149 .await?
1150 {
1151 SchedulerCommandResult::Applied => Ok(true),
1152 SchedulerCommandResult::Noop => Ok(false),
1153 result => Err(anyhow!(
1154 "unexpected scheduler destination cancellation result for {handoff_id:?}: {result:?}"
1155 )),
1156 }
1157}
1158
1159async fn send_command(
1160 command_tx: &mpsc::Sender<SchedulerCommandEnvelope>,
1161 command: SchedulerCommand,
1162) -> anyhow::Result<SchedulerCommandResult> {
1163 let (reply, response) = oneshot::channel();
1164 command_tx
1165 .send(SchedulerCommandEnvelope { command, reply })
1166 .await
1167 .map_err(|_| anyhow!("Mocker scheduler is not accepting commands"))?;
1168 let effects = response
1169 .await
1170 .map_err(|_| anyhow!("Mocker scheduler dropped a command acknowledgement"))??;
1171 Ok(effects.result)
1172}
1173
1174#[cfg(test)]
1175mod tests;