1use super::driver::RequestPermit;
4use super::{
5 CancellationToken, DriverControl, InvocationContext, LocalBoxFuture, RuntimeFailure,
6 ensure_context_active,
7};
8use futures::{FutureExt, channel::oneshot};
9use std::{
10 cell::{Cell, RefCell},
11 collections::BTreeMap,
12 future::{Future, poll_fn},
13 rc::Rc,
14 task::Poll,
15};
16
17#[derive(Default, Debug)]
18pub(super) struct ExecutionLedger {
19 next_id: Cell<u64>,
20 entries: RefCell<BTreeMap<u64, ExecutionEntry>>,
21 provider_admissions: RefCell<BTreeMap<(String, String, String), super::RequestAdmission>>,
22}
23
24#[allow(
25 clippy::too_many_arguments,
26 reason = "request execution carries explicit admission and generation context"
27)]
28pub(super) async fn request<T: 'static>(
29 runtime: &Rc<super::NativeAppRuntime>,
30 provider: &str,
31 operation: &str,
32 context: &InvocationContext,
33 generation: CancellationToken,
34 capability: &'static str,
35 permit: RequestPermit,
36 invoke: impl FnOnce(InvocationContext) -> LocalBoxFuture<'static, Result<T, RuntimeFailure>>,
37) -> Result<Result<T, RuntimeFailure>, RuntimeFailure> {
38 let instance = runtime
39 .plan
40 .plugin_instance(provider)
41 .expect("prepared endpoint has a planned provider");
42 let named_caller = context
43 .caller_instance()
44 .and_then(|caller| runtime.plan.plugin_instance(caller))
45 .is_some_and(|caller| caller.authoring_version() == 2);
46 if instance.authoring_version() == 1 && !named_caller {
47 let output = super::await_with_generation_context(
48 &runtime.driver,
49 context,
50 generation,
51 capability,
52 invoke(context.clone()),
53 )
54 .await?;
55 drop(permit);
56 return Ok(output.map_err(|error| {
57 super::schedule_plugin_supervision_after_failure(runtime, provider, error)
58 }));
59 }
60 let limits = instance
61 .provided_capabilities()
62 .iter()
63 .find(|endpoint| endpoint.capability_id() == capability)
64 .and_then(|endpoint| endpoint.operation_admission(operation))
65 .unwrap_or_default();
66 let aggregate = runtime
67 .executions
68 .provider_admissions
69 .borrow_mut()
70 .entry((
71 provider.to_owned(),
72 capability.to_owned(),
73 operation.to_owned(),
74 ))
75 .or_insert_with(|| super::RequestAdmission::new(limits))
76 .clone();
77 let provider_permit = aggregate
78 .acquire(capability, operation, context, &runtime.driver)
79 .await?;
80 if instance.authoring_version() == 1 {
81 let permits = (permit, provider_permit);
82 let output = super::await_with_generation_context(
83 &runtime.driver,
84 context,
85 generation,
86 capability,
87 invoke(context.clone()),
88 )
89 .await?;
90 drop(permits);
91 return Ok(output.map_err(|error| {
92 super::schedule_plugin_supervision_after_failure(runtime, provider, error)
93 }));
94 }
95 execute(
96 runtime.executions.clone(),
97 &runtime.driver,
98 provider,
99 context,
100 generation,
101 capability,
102 vec![permit, provider_permit],
103 invoke,
104 failure_observer(runtime, provider),
105 )
106 .await
107}
108
109pub(super) async fn operation<T: 'static>(
112 runtime: &Rc<super::NativeAppRuntime>,
113 provider: &str,
114 context: &InvocationContext,
115 generation: CancellationToken,
116 capability: &'static str,
117 invoke: impl FnOnce(InvocationContext) -> LocalBoxFuture<'static, Result<T, RuntimeFailure>>,
118) -> Result<Result<T, RuntimeFailure>, RuntimeFailure> {
119 let instance = runtime
120 .plan
121 .plugin_instance(provider)
122 .expect("prepared endpoint has a planned provider");
123 if instance.authoring_version() == 1 {
124 let output = super::await_with_generation_context(
125 &runtime.driver,
126 context,
127 generation,
128 capability,
129 invoke(context.clone()),
130 )
131 .await?;
132 return Ok(output.map_err(|error| {
133 super::schedule_plugin_supervision_after_failure(runtime, provider, error)
134 }));
135 }
136 execute(
137 runtime.executions.clone(),
138 &runtime.driver,
139 provider,
140 context,
141 generation,
142 capability,
143 vec![],
144 invoke,
145 failure_observer(runtime, provider),
146 )
147 .await
148}
149
150fn failure_observer<T>(
151 runtime: &Rc<super::NativeAppRuntime>,
152 provider: &str,
153) -> impl FnOnce(&mut Result<T, RuntimeFailure>) + 'static {
154 let generation = runtime.supervision.borrow()[provider].generation;
155 let runtime = Rc::downgrade(runtime);
156 let provider = provider.to_owned();
157 move |output| {
158 let Err(error) = output else { return };
159 let Some(runtime) = runtime.upgrade() else {
160 return;
161 };
162 if runtime.supervision.borrow()[&provider].generation == generation {
165 *error = super::schedule_plugin_supervision_after_failure(
166 &runtime,
167 &provider,
168 error.clone(),
169 );
170 }
171 }
172}
173
174#[derive(Debug)]
175struct ExecutionEntry {
176 outstanding: usize,
177 provider: String,
178 _permits: Vec<RequestPermit>,
181}
182
183impl ExecutionLedger {
184 pub(super) fn is_settled(&self, provider: Option<&str>) -> bool {
185 !self
186 .entries
187 .borrow()
188 .values()
189 .any(|entry| provider.is_none_or(|provider| entry.provider == provider))
190 }
191
192 fn admit(&self, provider: &str, permits: Vec<RequestPermit>) -> Result<u64, RuntimeFailure> {
193 let mut entries = self.entries.borrow_mut();
194 let mut candidate = self.next_id.get();
195 let mut available = None;
196 for _ in 0..=entries.len() {
197 if !entries.contains_key(&candidate) {
198 available = Some(candidate);
199 break;
200 }
201 candidate = candidate.wrapping_add(1);
202 }
203 let id = available.ok_or(RuntimeFailure::AdmissionClosed)?;
204 self.next_id.set(id.wrapping_add(1));
205 entries.insert(
206 id,
207 ExecutionEntry {
208 outstanding: 1,
209 provider: provider.to_owned(),
210 _permits: permits,
211 },
212 );
213 Ok(id)
214 }
215
216 fn settle(&self, id: u64) {
217 let mut entries = self.entries.borrow_mut();
218 if let Some(entry) = entries.get_mut(&id) {
219 entry.outstanding -= 1;
220 if entry.outstanding == 0 {
221 entries.remove(&id);
222 }
223 }
224 }
225}
226
227#[derive(Debug)]
230#[must_use = "call settle only after retained execution has actually terminated"]
231pub struct ExecutionLease {
232 scope: ExecutionScope,
233}
234
235impl ExecutionLease {
236 pub fn settle(self) {
238 self.scope.ledger.settle(self.scope.id);
239 }
240}
241
242#[derive(Clone, Debug)]
243pub(crate) struct ExecutionScope {
244 ledger: Rc<ExecutionLedger>,
245 id: u64,
246}
247
248impl ExecutionScope {
249 pub(crate) fn retain(&self) -> Result<ExecutionLease, RuntimeFailure> {
250 let mut entries = self.ledger.entries.borrow_mut();
251 let entry = entries
252 .get_mut(&self.id)
253 .ok_or(RuntimeFailure::AdmissionClosed)?;
254 entry.outstanding = entry
255 .outstanding
256 .checked_add(1)
257 .ok_or(RuntimeFailure::AdmissionClosed)?;
258 Ok(ExecutionLease {
259 scope: self.clone(),
260 })
261 }
262}
263
264#[allow(
267 clippy::too_many_arguments,
268 reason = "explicit execution ownership transfer"
269)]
270pub(super) async fn execute<T: 'static>(
271 ledger: Rc<ExecutionLedger>,
272 driver: &DriverControl,
273 provider: &str,
274 context: &InvocationContext,
275 generation: CancellationToken,
276 capability: &'static str,
277 permits: Vec<RequestPermit>,
278 invoke: impl FnOnce(InvocationContext) -> LocalBoxFuture<'static, T>,
279 observe: impl FnOnce(&mut T) + 'static,
280) -> Result<T, RuntimeFailure> {
281 ensure_context_active(driver, context)?;
282 if generation.is_cancelled() {
283 return Err(RuntimeFailure::Unavailable { capability });
284 }
285 let id = ledger.admit(provider, permits)?;
286 let mut execution_context = context.clone();
287 execution_context.execution = Some(ExecutionScope {
288 ledger: ledger.clone(),
289 id,
290 });
291 execution_context.remaining_budget = context
292 .deadline()
293 .map(|deadline| deadline.saturating_sub((driver.now)()));
294 let mut future = invoke(execution_context.clone());
295 let ready = poll_fn(|cx| {
296 Poll::Ready(match future.as_mut().poll(cx) {
297 Poll::Ready(output) => Some(output),
298 Poll::Pending => None,
299 })
300 })
301 .await;
302 if let Some(mut output) = ready {
303 let accepted = completion_status(driver, context, &generation, capability);
304 observe(&mut output);
305 ledger.settle(id);
306 return accepted.map(|()| output);
307 }
308 let (sender, mut receiver) = oneshot::channel();
309 let execution_driver = driver.clone();
310 let execution_generation = generation.clone();
311 (driver.spawn_local)(Box::pin(async move {
312 let mut output = future.await;
313 let accepted = completion_status(
316 &execution_driver,
317 &execution_context,
318 &execution_generation,
319 capability,
320 );
321 observe(&mut output);
322 let result = accepted.map(|()| output);
323 ledger.settle(id);
324 let _ = sender.send(result);
326 }))
327 .map_err(|error| RuntimeFailure::Internal {
328 detail: format!("cannot schedule execution owner: {error}"),
329 })?;
330 let mut cancelled = context.cancellation.cancelled().boxed_local();
331 let mut generation_cancelled = generation.cancelled().boxed_local();
332 let mut deadline = context.deadline().map_or_else(
333 || futures::future::pending().boxed_local(),
334 |deadline| (driver.sleep_until)(deadline),
335 );
336 poll_fn(|cx| {
337 if let Poll::Ready(result) = std::pin::Pin::new(&mut receiver).poll(cx) {
339 return Poll::Ready(result.unwrap_or_else(|_| {
340 Err(RuntimeFailure::Internal {
341 detail: "execution owner ended without settlement".to_owned(),
342 })
343 }));
344 }
345 let _ = cancelled.as_mut().poll(cx);
346 let _ = deadline.as_mut().poll(cx);
347 if let Err(error) = ensure_context_active(driver, context) {
348 return Poll::Ready(Err(error));
349 }
350 if generation_cancelled.as_mut().poll(cx).is_ready() {
351 return Poll::Ready(Err(RuntimeFailure::Unavailable { capability }));
352 }
353 Poll::Pending
354 })
355 .await
356}
357
358fn completion_status(
359 driver: &DriverControl,
360 context: &InvocationContext,
361 generation: &CancellationToken,
362 capability: &'static str,
363) -> Result<(), RuntimeFailure> {
364 ensure_context_active(driver, context)?;
365 if generation.is_cancelled() {
366 return Err(RuntimeFailure::Unavailable { capability });
367 }
368 Ok(())
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use crate::{DeterministicDriver, RequestAdmission, RequestAdmissionPlan, RuntimeDriver};
375 use std::time::Duration;
376
377 #[test]
378 fn cancelled_waiter_retains_execution_and_capacity_until_work_really_finishes() {
379 let driver = DeterministicDriver::new();
380 let control = DriverControl::new(&driver);
381 let ledger = Rc::new(ExecutionLedger::default());
382 let cancellation = CancellationToken::new();
383 let context = InvocationContext::new(1, None, cancellation.clone());
384 let admission = RequestAdmission::new(RequestAdmissionPlan::new(0, 1));
385 let permit = admission
386 .try_acquire("test", "work", &context, &control)
387 .unwrap();
388 let (finish, work) = oneshot::channel::<()>();
389 let execution = execute(
390 ledger.clone(),
391 &control,
392 "provider",
393 &context,
394 CancellationToken::new(),
395 "test",
396 vec![permit],
397 |_| work.boxed_local(),
398 |_| {},
399 );
400 driver.run(async {
401 futures::pin_mut!(execution);
402 assert!(execution.as_mut().now_or_never().is_none());
403 cancellation.cancel();
404 assert!(matches!(
405 execution.await,
406 Err(RuntimeFailure::Cancelled { request_id: 1 })
407 ));
408 });
409 assert!(!ledger.is_settled(Some("provider")));
410 let new_context = InvocationContext::new(2, None, CancellationToken::new());
411 for _ in 0..32 {
412 assert!(matches!(
413 admission.try_acquire("test", "work", &new_context, &control),
414 Err(RuntimeFailure::ResourceExhausted { .. })
415 ));
416 }
417 assert_eq!(ledger.entries.borrow().len(), 1);
418 finish.send(()).unwrap();
419 driver.run(driver.yield_now());
420 assert!(ledger.is_settled(None));
421 assert!(
422 admission
423 .try_acquire("test", "work", &new_context, &control)
424 .is_ok()
425 );
426 }
427
428 #[test]
429 fn dropped_waiter_does_not_drop_the_execution_owner() {
430 let driver = DeterministicDriver::new();
431 let control = DriverControl::new(&driver);
432 let ledger = Rc::new(ExecutionLedger::default());
433 let context = InvocationContext::new(1, None, CancellationToken::new());
434 let (finish, work) = oneshot::channel::<()>();
435 assert!(
436 execute(
437 ledger.clone(),
438 &control,
439 "provider",
440 &context,
441 CancellationToken::new(),
442 "test",
443 vec![],
444 |_| work.boxed_local(),
445 |_| {},
446 )
447 .now_or_never()
448 .is_none()
449 );
450 assert!(!ledger.is_settled(None));
451 finish.send(()).unwrap();
452 driver.run(driver.yield_now());
453 assert!(ledger.is_settled(None));
454 }
455
456 #[test]
457 fn cancellation_precedes_inclusive_deadline_and_same_poll_completion() {
458 for cancel in [false, true] {
459 let driver = DeterministicDriver::new();
460 let control = DriverControl::new(&driver);
461 let cancellation = CancellationToken::new();
462 let context =
463 InvocationContext::new(1, Some(Duration::from_secs(1)), cancellation.clone());
464 let worker_driver = driver.clone();
465 let work = async move {
466 worker_driver.advance(Duration::from_secs(1));
467 if cancel {
468 cancellation.cancel();
469 }
470 42
471 }
472 .boxed_local();
473 let result = driver.run(execute(
474 Rc::default(),
475 &control,
476 "provider",
477 &context,
478 CancellationToken::new(),
479 "test",
480 vec![],
481 |_| work,
482 |_| {},
483 ));
484 assert_eq!(
485 result,
486 Err(if cancel {
487 RuntimeFailure::Cancelled { request_id: 1 }
488 } else {
489 RuntimeFailure::DeadlineExceeded { request_id: 1 }
490 })
491 );
492 }
493 }
494
495 #[test]
496 fn provider_context_carries_a_relative_dispatch_budget() {
497 let driver = DeterministicDriver::new();
498 let control = DriverControl::new(&driver);
499 driver.advance(Duration::from_millis(250));
500 let context =
501 InvocationContext::new(1, Some(Duration::from_secs(1)), CancellationToken::new());
502
503 let remaining = driver
504 .run(execute(
505 Rc::default(),
506 &control,
507 "provider",
508 &context,
509 CancellationToken::new(),
510 "test",
511 vec![],
512 |context| futures::future::ready(context.remaining_budget()).boxed_local(),
513 |_| {},
514 ))
515 .unwrap();
516
517 assert_eq!(remaining, Some(Duration::from_millis(750)));
518 assert_eq!(context.remaining_budget(), None);
519 }
520
521 #[test]
522 fn an_already_accepted_success_survives_later_cancellation() {
523 let driver = DeterministicDriver::new();
524 let control = DriverControl::new(&driver);
525 let cancellation = CancellationToken::new();
526 let context = InvocationContext::new(1, None, cancellation.clone());
527 let (finish, work) = oneshot::channel::<u32>();
528 let execution = execute(
529 Rc::default(),
530 &control,
531 "provider",
532 &context,
533 CancellationToken::new(),
534 "test",
535 vec![],
536 |_| work.boxed_local(),
537 |_| {},
538 );
539 futures::pin_mut!(execution);
540 assert!(execution.as_mut().now_or_never().is_none());
541 finish.send(42).unwrap();
542 driver.run(driver.yield_now());
543 cancellation.cancel();
544 assert_eq!(driver.run(execution), Ok(Ok(42)));
545 }
546}