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 let mut future = invoke(execution_context.clone());
255 let ready = poll_fn(|cx| {
256 Poll::Ready(match future.as_mut().poll(cx) {
257 Poll::Ready(output) => Some(output),
258 Poll::Pending => None,
259 })
260 })
261 .await;
262 if let Some(output) = ready {
263 ledger.settle(id);
264 ensure_context_active(driver, context)?;
265 if generation.is_cancelled() {
266 return Err(RuntimeFailure::Unavailable { capability });
267 }
268 return Ok(output);
269 }
270 let (sender, mut receiver) = oneshot::channel();
271 let execution_driver = driver.clone();
272 let execution_generation = generation.clone();
273 (driver.spawn_local)(Box::pin(async move {
274 let output = future.await;
275 let result = ensure_context_active(&execution_driver, &execution_context).and_then(|()| {
276 if execution_generation.is_cancelled() {
277 Err(RuntimeFailure::Unavailable { capability })
278 } else {
279 Ok(output)
280 }
281 });
282 ledger.settle(id);
283 let _ = sender.send(result);
285 }))
286 .map_err(|error| RuntimeFailure::Internal {
287 detail: format!("cannot schedule execution owner: {error}"),
288 })?;
289 let mut cancelled = context.cancellation.cancelled().boxed_local();
290 let mut generation_cancelled = generation.cancelled().boxed_local();
291 let mut deadline = context.deadline().map_or_else(
292 || futures::future::pending().boxed_local(),
293 |deadline| (driver.sleep_until)(deadline),
294 );
295 poll_fn(|cx| {
296 if let Poll::Ready(result) = std::pin::Pin::new(&mut receiver).poll(cx) {
298 return Poll::Ready(result.unwrap_or_else(|_| {
299 Err(RuntimeFailure::Internal {
300 detail: "execution owner ended without settlement".to_owned(),
301 })
302 }));
303 }
304 let _ = cancelled.as_mut().poll(cx);
305 let _ = deadline.as_mut().poll(cx);
306 if let Err(error) = ensure_context_active(driver, context) {
307 return Poll::Ready(Err(error));
308 }
309 if generation_cancelled.as_mut().poll(cx).is_ready() {
310 return Poll::Ready(Err(RuntimeFailure::Unavailable { capability }));
311 }
312 Poll::Pending
313 })
314 .await
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::{DeterministicDriver, RequestAdmission, RequestAdmissionPlan, RuntimeDriver};
321 use std::time::Duration;
322
323 #[test]
324 fn cancelled_waiter_retains_execution_and_capacity_until_work_really_finishes() {
325 let driver = DeterministicDriver::new();
326 let control = DriverControl::new(&driver);
327 let ledger = Rc::new(ExecutionLedger::default());
328 let cancellation = CancellationToken::new();
329 let context = InvocationContext::new(1, None, cancellation.clone());
330 let admission = RequestAdmission::new(RequestAdmissionPlan::new(0, 1));
331 let permit = admission
332 .try_acquire("test", "work", &context, &control)
333 .unwrap();
334 let (finish, work) = oneshot::channel::<()>();
335 let execution = execute(
336 ledger.clone(),
337 &control,
338 "provider",
339 &context,
340 CancellationToken::new(),
341 "test",
342 vec![permit],
343 |_| work.boxed_local(),
344 );
345 driver.run(async {
346 futures::pin_mut!(execution);
347 assert!(execution.as_mut().now_or_never().is_none());
348 cancellation.cancel();
349 assert!(matches!(
350 execution.await,
351 Err(RuntimeFailure::Cancelled { request_id: 1 })
352 ));
353 });
354 assert!(!ledger.is_settled(Some("provider")));
355 let new_context = InvocationContext::new(2, None, CancellationToken::new());
356 for _ in 0..32 {
357 assert!(matches!(
358 admission.try_acquire("test", "work", &new_context, &control),
359 Err(RuntimeFailure::ResourceExhausted { .. })
360 ));
361 }
362 assert_eq!(ledger.entries.borrow().len(), 1);
363 finish.send(()).unwrap();
364 driver.run(driver.yield_now());
365 assert!(ledger.is_settled(None));
366 assert!(
367 admission
368 .try_acquire("test", "work", &new_context, &control)
369 .is_ok()
370 );
371 }
372
373 #[test]
374 fn dropped_waiter_does_not_drop_the_execution_owner() {
375 let driver = DeterministicDriver::new();
376 let control = DriverControl::new(&driver);
377 let ledger = Rc::new(ExecutionLedger::default());
378 let context = InvocationContext::new(1, None, CancellationToken::new());
379 let (finish, work) = oneshot::channel::<()>();
380 assert!(
381 execute(
382 ledger.clone(),
383 &control,
384 "provider",
385 &context,
386 CancellationToken::new(),
387 "test",
388 vec![],
389 |_| work.boxed_local()
390 )
391 .now_or_never()
392 .is_none()
393 );
394 assert!(!ledger.is_settled(None));
395 finish.send(()).unwrap();
396 driver.run(driver.yield_now());
397 assert!(ledger.is_settled(None));
398 }
399
400 #[test]
401 fn cancellation_precedes_inclusive_deadline_and_same_poll_completion() {
402 for cancel in [false, true] {
403 let driver = DeterministicDriver::new();
404 let control = DriverControl::new(&driver);
405 let cancellation = CancellationToken::new();
406 let context =
407 InvocationContext::new(1, Some(Duration::from_secs(1)), cancellation.clone());
408 let worker_driver = driver.clone();
409 let work = async move {
410 worker_driver.advance(Duration::from_secs(1));
411 if cancel {
412 cancellation.cancel();
413 }
414 42
415 }
416 .boxed_local();
417 let result = driver.run(execute(
418 Rc::default(),
419 &control,
420 "provider",
421 &context,
422 CancellationToken::new(),
423 "test",
424 vec![],
425 |_| work,
426 ));
427 assert_eq!(
428 result,
429 Err(if cancel {
430 RuntimeFailure::Cancelled { request_id: 1 }
431 } else {
432 RuntimeFailure::DeadlineExceeded { request_id: 1 }
433 })
434 );
435 }
436 }
437
438 #[test]
439 fn an_already_accepted_success_survives_later_cancellation() {
440 let driver = DeterministicDriver::new();
441 let control = DriverControl::new(&driver);
442 let cancellation = CancellationToken::new();
443 let context = InvocationContext::new(1, None, cancellation.clone());
444 let (finish, work) = oneshot::channel::<u32>();
445 let execution = execute(
446 Rc::default(),
447 &control,
448 "provider",
449 &context,
450 CancellationToken::new(),
451 "test",
452 vec![],
453 |_| work.boxed_local(),
454 );
455 futures::pin_mut!(execution);
456 assert!(execution.as_mut().now_or_never().is_none());
457 finish.send(42).unwrap();
458 driver.run(driver.yield_now());
459 cancellation.cancel();
460 assert_eq!(driver.run(execution), Ok(Ok(42)));
461 }
462}