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