1use super::{
2 AppAdmission, AppReadyGate, BTreeMap, CancellationToken, Cell, DiagnosticEvent,
3 DiagnosticShutdownOutcome, DiagnosticSource, DriverControl, DriverTask, Duration,
4 EventCapability, ExecutionAdapterCatalog, InvocationContext, LocalBoxFuture,
5 ManagedResourceScope, ManagedTask, ManagedTaskScope, NativeEventBindingTable,
6 NativeEventEndpointStateTable, NativeEventHandle, NativeRequestEndpoint, NativeRequestHandle,
7 NativeStreamBindingTable, NativeStreamEndpoint, NativeStreamEndpointStateTable,
8 NativeStreamHandle, PluginCriticality, PluginDependencies, PluginLifecycle, Rc, RefCell,
9 RequestAdmission, RequestCapability, RequestId, ResolvedAppPlan, RestartPolicy,
10 RuntimeDiagnostics, RuntimeFailure, ShutdownOutcome, StreamCapability,
11 begin_plugin_supervision, event, handle_supervision_schedule_failure, oneshot,
12 schedule_plugin_supervision, shutdown_native_plugins,
13};
14
15#[derive(Clone, Debug)]
16pub(super) struct NativeEndpointSnapshot {
17 pub(super) endpoint: Rc<dyn NativeRequestEndpoint>,
18 pub(super) generation: u64,
19 pub(super) cancellation: CancellationToken,
20}
21
22#[derive(Debug)]
23pub(super) struct NativeEndpointState {
24 pub(super) capability_id: &'static str,
25 pub(super) descriptor_version: &'static str,
26 pub(super) operations: &'static [&'static str],
27 pub(super) endpoint: RefCell<Option<Rc<dyn NativeRequestEndpoint>>>,
28 pub(super) generation: Cell<u64>,
29 pub(super) cancellation: RefCell<CancellationToken>,
30}
31
32#[derive(Clone, Debug)]
33pub(crate) struct NativeStreamEndpointSnapshot {
34 pub(crate) endpoint: Rc<dyn NativeStreamEndpoint>,
35 pub(crate) generation: u64,
36 pub(crate) cancellation: CancellationToken,
37}
38
39#[derive(Debug)]
40pub(crate) struct NativeStreamEndpointState {
41 pub(super) capability_id: &'static str,
42 pub(super) descriptor_version: &'static str,
43 pub(super) operations: &'static [&'static str],
44 pub(super) endpoint: RefCell<Option<Rc<dyn NativeStreamEndpoint>>>,
45 pub(super) generation: Cell<u64>,
46 pub(super) cancellation: RefCell<CancellationToken>,
47}
48
49impl NativeStreamEndpointState {
50 pub(crate) fn new(endpoint: Rc<dyn NativeStreamEndpoint>, generation: u64) -> Self {
51 Self {
52 capability_id: endpoint.capability_id(),
53 descriptor_version: endpoint.descriptor_version(),
54 operations: endpoint.operations(),
55 endpoint: RefCell::new(Some(endpoint)),
56 generation: Cell::new(generation),
57 cancellation: RefCell::new(CancellationToken::new()),
58 }
59 }
60
61 pub(crate) fn snapshot(&self) -> Option<NativeStreamEndpointSnapshot> {
62 self.endpoint
63 .borrow()
64 .clone()
65 .map(|endpoint| NativeStreamEndpointSnapshot {
66 endpoint,
67 generation: self.generation.get(),
68 cancellation: self.cancellation.borrow().clone(),
69 })
70 }
71
72 pub(crate) fn mark_unavailable(&self) {
73 self.cancellation.borrow().cancel();
74 self.endpoint.borrow_mut().take();
75 }
76
77 pub(crate) fn cancel(&self) {
78 self.cancellation.borrow().cancel();
79 }
80
81 pub(crate) fn install(&self, endpoint: Rc<dyn NativeStreamEndpoint>, generation: u64) {
82 self.generation.set(generation);
83 self.cancellation.replace(CancellationToken::new());
84 self.endpoint.replace(Some(endpoint));
85 }
86
87 pub(crate) fn is_current(&self, generation: u64) -> bool {
88 self.generation.get() == generation && self.endpoint.borrow().is_some()
89 }
90}
91
92impl NativeEndpointState {
93 pub(super) fn new(endpoint: Rc<dyn NativeRequestEndpoint>, generation: u64) -> Self {
94 Self {
95 capability_id: endpoint.capability_id(),
96 descriptor_version: endpoint.descriptor_version(),
97 operations: endpoint.operations(),
98 endpoint: RefCell::new(Some(endpoint)),
99 generation: Cell::new(generation),
100 cancellation: RefCell::new(CancellationToken::new()),
101 }
102 }
103
104 pub(super) fn snapshot(&self) -> Option<NativeEndpointSnapshot> {
105 self.endpoint
106 .borrow()
107 .clone()
108 .map(|endpoint| NativeEndpointSnapshot {
109 endpoint,
110 generation: self.generation.get(),
111 cancellation: self.cancellation.borrow().clone(),
112 })
113 }
114
115 pub(super) fn mark_unavailable(&self) {
116 self.cancellation.borrow().cancel();
117 self.endpoint.borrow_mut().take();
118 }
119
120 pub(super) fn cancel(&self) {
121 self.cancellation.borrow().cancel();
122 }
123
124 pub(super) fn install(&self, endpoint: Rc<dyn NativeRequestEndpoint>, generation: u64) {
125 self.generation.set(generation);
126 self.cancellation.replace(CancellationToken::new());
127 self.endpoint.replace(Some(endpoint));
128 }
129
130 pub(super) fn is_current(&self, generation: u64) -> bool {
131 self.generation.get() == generation && self.endpoint.borrow().is_some()
132 }
133}
134
135#[derive(Clone, Debug)]
136pub(super) struct NativeEndpointBinding {
137 pub(super) requirement_id: String,
138 pub(super) plugin_instance: String,
139 pub(super) state: Rc<NativeEndpointState>,
140 pub(super) admissions: BTreeMap<String, RequestAdmission>,
141}
142
143impl NativeEndpointBinding {
144 pub(super) fn admission(&self, operation: &str) -> Option<&RequestAdmission> {
145 self.admissions.get(operation)
146 }
147}
148
149#[derive(Clone, Debug)]
150pub(crate) struct NativeStreamEndpointBinding {
151 pub(super) requirement_id: String,
152 pub(crate) plugin_instance: String,
153 pub(crate) state: Rc<NativeStreamEndpointState>,
154 pub(super) admissions: BTreeMap<String, RequestAdmission>,
155}
156
157impl NativeStreamEndpointBinding {
158 pub(crate) fn admission(&self, operation: &str) -> Option<&RequestAdmission> {
159 self.admissions.get(operation)
160 }
161}
162
163#[derive(Debug)]
164pub(super) struct NativePluginGeneration {
165 pub(super) lifecycle: Rc<dyn PluginLifecycle>,
166 pub(super) tasks: ManagedTaskScope,
167 pub(super) resources: ManagedResourceScope,
168 pub(super) stop_attempted: bool,
169 pub(super) cleanup_timed_out: bool,
170}
171
172pub(super) enum GenerationPreparationFailure {
173 Lifecycle,
174 Cleanup { primary: RuntimeFailure },
175}
176
177#[derive(Debug)]
178pub(super) struct NativePluginRuntime {
179 pub(super) generation: RefCell<Option<NativePluginGeneration>>,
180}
181
182impl NativePluginRuntime {
183 pub(super) fn take_generation(&self) -> Option<NativePluginGeneration> {
184 self.generation.borrow_mut().take()
185 }
186
187 pub(super) fn install_generation(&self, generation: NativePluginGeneration) {
188 debug_assert!(self.generation.borrow().is_none());
189 self.generation.replace(Some(generation));
190 }
191
192 pub(super) fn generation_parts(
193 &self,
194 ) -> Option<(
195 Rc<dyn PluginLifecycle>,
196 ManagedTaskScope,
197 ManagedResourceScope,
198 )> {
199 self.generation.borrow().as_ref().map(|generation| {
200 (
201 generation.lifecycle.clone(),
202 generation.tasks.clone(),
203 generation.resources.clone(),
204 )
205 })
206 }
207}
208
209#[derive(Clone, Debug)]
210pub(super) struct PluginSupervision {
211 pub(super) policy: RestartPolicy,
212 pub(super) criticality: PluginCriticality,
213 pub(super) required_path: bool,
214 pub(super) generation: u64,
215 pub(super) attempts: Vec<Duration>,
216 pub(super) stable_since: Option<Duration>,
217 pub(super) restarting: bool,
218}
219
220#[derive(Debug, Default)]
221pub(super) struct ShutdownCoordinator {
222 pub(super) started: Cell<bool>,
223 pub(super) cleanup_started_at: Cell<Option<Duration>>,
224 pub(super) completed: Cell<bool>,
225 pub(super) outcome: RefCell<Option<ShutdownOutcome>>,
226 pub(super) waiters: RefCell<Vec<oneshot::Sender<ShutdownOutcome>>>,
227}
228
229impl ShutdownCoordinator {
230 pub(super) fn start(&self, started_at: Duration) -> bool {
231 if self.started.replace(true) {
232 return false;
233 }
234 self.cleanup_started_at.set(Some(started_at));
235 true
236 }
237
238 pub(super) fn begin_completion(&self) -> bool {
239 !self.completed.replace(true)
240 }
241
242 pub(super) fn publish(&self, outcome: &ShutdownOutcome) {
243 self.outcome.replace(Some(outcome.clone()));
244 for waiter in self.waiters.borrow_mut().drain(..) {
245 let _ = waiter.send(outcome.clone());
246 }
247 }
248
249 pub(super) fn wait(&self) -> LocalBoxFuture<'static, ShutdownOutcome> {
250 if let Some(outcome) = self.outcome.borrow().clone() {
251 return Box::pin(futures::future::ready(outcome));
252 }
253 let (complete, waiter) = oneshot::channel();
254 self.waiters.borrow_mut().push(complete);
255 Box::pin(async move {
256 waiter.await.unwrap_or(ShutdownOutcome::RuntimeFailure {
257 error: RuntimeFailure::Internal {
258 detail: "shutdown coordinator terminated before publishing an outcome"
259 .to_owned(),
260 },
261 })
262 })
263 }
264}
265
266pub(super) struct NativeAppRuntime {
267 pub(super) startup_context: RefCell<Option<InvocationContext>>,
268 pub(super) startup_cleanup: Option<super::cleanup::StartupCleanupBudget>,
269 pub(super) cleanup_timeout: Option<Duration>,
270 pub(super) executions: Rc<super::settlement::ExecutionLedger>,
271 pub(super) plan: ResolvedAppPlan,
272 pub(super) adapters: Rc<ExecutionAdapterCatalog>,
273 pub(super) plugins: BTreeMap<String, NativePluginRuntime>,
274 pub(super) dependencies: BTreeMap<String, PluginDependencies>,
275 pub(super) endpoint_states: BTreeMap<(String, String), Rc<NativeEndpointState>>,
276 pub(super) stream_endpoint_states: NativeStreamEndpointStateTable,
277 pub(super) event_endpoint_states: NativeEventEndpointStateTable,
278 pub(super) supervision: RefCell<BTreeMap<String, PluginSupervision>>,
279 pub(super) supervision_tasks: RefCell<BTreeMap<String, ManagedTask>>,
280 pub(super) activation_order: Vec<String>,
281 pub(super) ready_gate: AppReadyGate,
282 pub(super) admission: AppAdmission,
283 pub(super) driver: DriverControl,
284 pub(super) diagnostics: RuntimeDiagnostics,
285 pub(super) request_ids: Rc<Cell<RequestId>>,
286 pub(super) supervision_cancellation: CancellationToken,
287 pub(super) shutdown_started: Cell<bool>,
288 pub(super) shutdown: ShutdownCoordinator,
289 pub(super) shutdown_task: RefCell<Option<DriverTask>>,
290 pub(super) terminal_failure: RefCell<Option<RuntimeFailure>>,
291}
292
293impl std::fmt::Debug for NativeAppRuntime {
294 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 formatter
296 .debug_struct("NativeAppRuntime")
297 .field("plugin_count", &self.plugins.len())
298 .field("endpoint_count", &self.endpoint_states.len())
299 .field("stream_endpoint_count", &self.stream_endpoint_states.len())
300 .field("event_endpoint_count", &self.event_endpoint_states.len())
301 .field("ready", &self.ready_gate.is_open())
302 .field("accepting", &self.admission.is_open())
303 .field("next_request_id", &self.request_ids.get())
304 .field("shutdown_started", &self.shutdown_started.get())
305 .field("cleanup_started", &self.shutdown.started.get())
306 .field("cleanup_completed", &self.shutdown.completed.get())
307 .field(
308 "terminal_failure",
309 &self.terminal_failure.borrow().is_some(),
310 )
311 .finish_non_exhaustive()
312 }
313}
314
315impl NativeAppRuntime {
316 pub(super) fn mark_plugin_endpoints_unavailable(&self, instance_key: &str) {
317 for ((provider, _), endpoint) in &self.endpoint_states {
318 if provider == instance_key {
319 endpoint.mark_unavailable();
320 }
321 }
322 for ((provider, _), endpoint) in &self.stream_endpoint_states {
323 if provider == instance_key {
324 endpoint.mark_unavailable();
325 }
326 }
327 for ((provider, _), endpoint) in &self.event_endpoint_states {
328 if provider == instance_key {
329 endpoint.mark_unavailable();
330 }
331 }
332 }
333
334 pub(super) fn begin_shutdown(&self) {
335 let admission_closed_at = (self.driver.now)();
336 if self.shutdown_started.replace(true) {
337 return;
338 }
339 self.admission.close();
340 self.supervision_cancellation.cancel();
341 for endpoint in self.endpoint_states.values() {
342 endpoint.cancel();
343 }
344 for endpoint in self.stream_endpoint_states.values() {
345 endpoint.cancel();
346 }
347 for endpoint in self.event_endpoint_states.values() {
348 endpoint.cancel();
349 }
350 for plugin in self.plugins.values() {
351 if let Some((_, tasks, resources)) = plugin.generation_parts() {
352 tasks.close();
353 resources.close();
354 }
355 }
356 self.diagnostics
357 .emit(DiagnosticSource::Shutdown, admission_closed_at, |_| {
358 DiagnosticEvent::ShutdownAdmissionClosed
359 });
360 }
361
362 pub(super) fn complete_shutdown(&self, outcome: &ShutdownOutcome) {
363 if !self.shutdown.begin_completion() {
364 return;
365 }
366 let completed_at = (self.driver.now)();
367 let started_at = self
368 .shutdown
369 .cleanup_started_at
370 .get()
371 .unwrap_or(completed_at);
372 let diagnostic_outcome = match outcome {
373 ShutdownOutcome::Clean => DiagnosticShutdownOutcome::Clean,
374 ShutdownOutcome::RuntimeFailure { .. } => DiagnosticShutdownOutcome::RuntimeFailure,
375 ShutdownOutcome::Timeout => DiagnosticShutdownOutcome::Timeout,
376 };
377 self.diagnostics
378 .emit(DiagnosticSource::Shutdown, completed_at, |_| {
379 DiagnosticEvent::ShutdownCompleted {
380 outcome: diagnostic_outcome,
381 elapsed: completed_at.saturating_sub(started_at),
382 }
383 });
384 if let ShutdownOutcome::RuntimeFailure { error } = outcome {
385 self.diagnostics
386 .emit_runtime_failure(completed_at, None, error);
387 }
388 self.shutdown.publish(outcome);
389 }
390}
391
392#[derive(Clone, Debug)]
394pub struct NativeApp {
395 pub(super) bindings: BTreeMap<(String, &'static str), Vec<NativeEndpointBinding>>,
396 pub(super) stream_bindings: NativeStreamBindingTable,
397 pub(super) event_bindings: NativeEventBindingTable,
398 pub(super) diagnostics: RuntimeDiagnostics,
399 pub(super) runtime: Rc<NativeAppRuntime>,
400}
401
402impl NativeApp {
403 fn diagnostic_failure<T>(
404 &self,
405 instance_key: Option<&str>,
406 error: RuntimeFailure,
407 ) -> Result<T, RuntimeFailure> {
408 let instance_key = instance_key
409 .filter(|instance_key| self.runtime.plan.plugin_instance(instance_key).is_some());
410 self.runtime.diagnostics.emit_runtime_failure(
411 (self.runtime.driver.now)(),
412 instance_key,
413 &error,
414 );
415 Err(error)
416 }
417
418 pub fn ensure_binding<C: RequestCapability>(
420 &self,
421 caller_instance: &str,
422 ) -> Result<(), RuntimeFailure> {
423 if self.runtime.admission.is_closed() {
424 return self.diagnostic_failure(Some(caller_instance), RuntimeFailure::AdmissionClosed);
425 }
426 if self
427 .endpoints::<C>(caller_instance)
428 .is_some_and(|endpoints| !endpoints.is_empty())
429 {
430 return Ok(());
431 }
432 self.diagnostic_failure(
433 Some(caller_instance),
434 RuntimeFailure::Unavailable { capability: C::ID },
435 )
436 }
437
438 pub fn handle<C: RequestCapability>(
440 &self,
441 caller_instance: &str,
442 ) -> Result<NativeRequestHandle<C>, RuntimeFailure> {
443 self.validate_requirement_lookup(caller_instance, C::ID)?;
444 if self.runtime.admission.is_closed() {
445 return self.diagnostic_failure(Some(caller_instance), RuntimeFailure::AdmissionClosed);
446 }
447 let Some(endpoints) = self
448 .endpoints::<C>(caller_instance)
449 .filter(|endpoints| !endpoints.is_empty())
450 else {
451 return self.diagnostic_failure(
452 Some(caller_instance),
453 RuntimeFailure::Unavailable { capability: C::ID },
454 );
455 };
456 Ok(NativeRequestHandle::from_endpoints(
457 endpoints,
458 self.runtime.clone(),
459 caller_instance,
460 false,
461 ))
462 }
463
464 pub fn optional_handle<C: RequestCapability>(
466 &self,
467 caller_instance: &str,
468 ) -> Option<NativeRequestHandle<C>> {
469 self.validate_requirement_lookup(caller_instance, C::ID)
470 .ok()?;
471 let caller_instance = caller_instance.to_owned();
472 self.endpoints::<C>(&caller_instance)
473 .filter(|endpoints| !endpoints.is_empty())
474 .map(|endpoints| {
475 NativeRequestHandle::from_endpoints(
476 endpoints,
477 self.runtime.clone(),
478 &caller_instance,
479 false,
480 )
481 })
482 }
483
484 pub fn many_handle<C: RequestCapability>(
486 &self,
487 caller_instance: &str,
488 ) -> Result<NativeRequestHandle<C>, RuntimeFailure> {
489 self.validate_requirement_lookup(caller_instance, C::ID)?;
490 if self.runtime.admission.is_closed() {
491 return self.diagnostic_failure(Some(caller_instance), RuntimeFailure::AdmissionClosed);
492 }
493 let endpoints = self.endpoints::<C>(caller_instance).unwrap_or(&[]);
494 Ok(NativeRequestHandle::from_endpoints(
495 endpoints,
496 self.runtime.clone(),
497 caller_instance,
498 false,
499 ))
500 }
501
502 pub fn binding_count<C: RequestCapability>(&self, caller_instance: &str) -> usize {
504 self.endpoints::<C>(caller_instance).map_or(0, <[_]>::len)
505 }
506
507 pub fn is_ready(&self) -> bool {
509 self.runtime.ready_gate.is_open()
510 }
511
512 pub fn ready_gate(&self) -> AppReadyGate {
514 self.runtime.ready_gate.clone()
515 }
516
517 pub fn is_accepting(&self) -> bool {
519 self.runtime.admission.is_open()
520 }
521
522 pub fn admission(&self) -> AppAdmission {
524 self.runtime.admission.clone()
525 }
526
527 pub fn diagnostics(&self) -> RuntimeDiagnostics {
529 self.diagnostics.clone()
530 }
531
532 pub fn dependencies(
538 &self,
539 caller_instance: &str,
540 ) -> Result<PluginDependencies, RuntimeFailure> {
541 if self.runtime.admission.is_closed() {
542 return self.diagnostic_failure(Some(caller_instance), RuntimeFailure::AdmissionClosed);
543 }
544 self.runtime
545 .dependencies
546 .get(caller_instance)
547 .cloned()
548 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
549 detail: format!(
550 "Plugin Instance `{caller_instance}` has no resolved dependency table"
551 ),
552 })
553 }
554
555 pub fn instance_queue_depths(&self) -> BTreeMap<String, usize> {
560 let mut depths = BTreeMap::new();
561 for endpoints in self.bindings.values() {
562 for endpoint in endpoints {
563 let depth = endpoint
564 .admissions
565 .values()
566 .map(RequestAdmission::queue_depth)
567 .sum::<usize>();
568 *depths.entry(endpoint.plugin_instance.clone()).or_insert(0) += depth;
569 }
570 }
571 depths
572 }
573
574 pub fn terminal_failure(&self) -> Option<RuntimeFailure> {
576 self.runtime.terminal_failure.borrow().clone()
577 }
578
579 pub fn is_failed(&self) -> bool {
581 self.runtime.terminal_failure.borrow().is_some()
582 }
583
584 pub fn plugin_generation(&self, instance_key: &str) -> Option<u64> {
586 self.runtime
587 .supervision
588 .borrow()
589 .get(instance_key)
590 .and_then(|state| {
591 let request_current =
592 self.runtime
593 .endpoint_states
594 .iter()
595 .any(|((plugin, _), endpoint)| {
596 plugin == instance_key && endpoint.is_current(state.generation)
597 });
598 let stream_current =
599 self.runtime
600 .stream_endpoint_states
601 .iter()
602 .any(|((plugin, _), endpoint)| {
603 plugin == instance_key && endpoint.is_current(state.generation)
604 });
605 let event_current =
606 self.runtime
607 .event_endpoint_states
608 .iter()
609 .any(|((plugin, _), endpoint)| {
610 plugin == instance_key && endpoint.is_current(state.generation)
611 });
612 (request_current || stream_current || event_current).then_some(state.generation)
613 })
614 }
615
616 pub fn report_plugin_failure(&self, instance_key: &str) -> Result<(), RuntimeFailure> {
618 if !begin_plugin_supervision(&self.runtime, instance_key)? {
619 return Ok(());
620 }
621 schedule_plugin_supervision(&self.runtime, instance_key).map_err(|error| {
622 handle_supervision_schedule_failure(&self.runtime, instance_key, error)
623 })
624 }
625
626 pub fn request_shutdown(&self) {
628 self.runtime.begin_shutdown();
629 }
630
631 pub async fn shutdown(&self, timeout: Duration) -> ShutdownOutcome {
633 self.shutdown_with_budget(super::cleanup::CleanupBudget::after(
634 &self.runtime.driver,
635 timeout,
636 ))
637 .await
638 }
639
640 pub(super) async fn shutdown_with_budget(
641 &self,
642 budget: super::cleanup::CleanupBudget,
643 ) -> ShutdownOutcome {
644 self.runtime.begin_shutdown();
645 let cleanup_started_at = (self.runtime.driver.now)();
646 if self.runtime.shutdown.start(cleanup_started_at) {
647 let timeout = budget.remaining();
648 self.runtime
649 .diagnostics
650 .emit(DiagnosticSource::Shutdown, cleanup_started_at, |_| {
651 DiagnosticEvent::ShutdownCleanupStarted { timeout }
652 });
653 let runtime = self.runtime.clone();
654 let worker_runtime = runtime.clone();
655 match (runtime.driver.spawn_local)(Box::pin(async move {
656 let outcome = shutdown_native_plugins(&worker_runtime, budget).await;
657 worker_runtime.complete_shutdown(&outcome);
658 })) {
659 Ok(task) => {
660 runtime.shutdown_task.replace(Some(task));
661 }
662 Err(error) => {
663 runtime.complete_shutdown(&ShutdownOutcome::RuntimeFailure {
664 error: RuntimeFailure::Internal {
665 detail: format!("failed to schedule App shutdown: {error:?}"),
666 },
667 });
668 }
669 }
670 }
671 self.runtime.shutdown.wait().await
672 }
673
674 pub async fn invoke<C: RequestCapability>(
676 &self,
677 caller_instance: &str,
678 operation: &str,
679 request: C::Request,
680 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure> {
681 self.handle::<C>(caller_instance)?
682 .invoke(operation, request)
683 .await
684 }
685
686 pub fn invocation_context(
691 &self,
692 deadline: Option<Duration>,
693 cancellation: CancellationToken,
694 ) -> InvocationContext {
695 InvocationContext::new(self.next_request_id(), deadline, cancellation)
696 }
697
698 pub fn invocation_context_after(
700 &self,
701 timeout: Duration,
702 cancellation: CancellationToken,
703 ) -> InvocationContext {
704 self.invocation_context(
705 Some((self.runtime.driver.now)().saturating_add(timeout)),
706 cancellation,
707 )
708 }
709
710 pub async fn invoke_with_context<C: RequestCapability>(
712 &self,
713 caller_instance: &str,
714 operation: &str,
715 context: InvocationContext,
716 request: C::Request,
717 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure> {
718 self.handle::<C>(caller_instance)?
719 .invoke_with_context(operation, context, request)
720 .await
721 }
722
723 pub fn stream_handle<C: StreamCapability>(
725 &self,
726 caller_instance: &str,
727 ) -> Result<NativeStreamHandle<C>, RuntimeFailure> {
728 self.validate_requirement_lookup(caller_instance, C::ID)?;
729 if self.runtime.admission.is_closed() {
730 return self.diagnostic_failure(Some(caller_instance), RuntimeFailure::AdmissionClosed);
731 }
732 let Some(endpoints) = self
733 .stream_endpoints::<C>(caller_instance)
734 .filter(|endpoints| !endpoints.is_empty())
735 else {
736 return self.diagnostic_failure(
737 Some(caller_instance),
738 RuntimeFailure::Unavailable { capability: C::ID },
739 );
740 };
741 Ok(NativeStreamHandle::from_endpoints(
742 endpoints,
743 self.runtime.clone(),
744 caller_instance,
745 false,
746 ))
747 }
748
749 pub fn optional_stream_handle<C: StreamCapability>(
751 &self,
752 caller_instance: &str,
753 ) -> Option<NativeStreamHandle<C>> {
754 self.validate_requirement_lookup(caller_instance, C::ID)
755 .ok()?;
756 let caller_instance = caller_instance.to_owned();
757 self.stream_endpoints::<C>(&caller_instance)
758 .filter(|endpoints| !endpoints.is_empty())
759 .map(|endpoints| {
760 NativeStreamHandle::from_endpoints(
761 endpoints,
762 self.runtime.clone(),
763 &caller_instance,
764 false,
765 )
766 })
767 }
768
769 pub fn stream_binding_count<C: StreamCapability>(&self, caller_instance: &str) -> usize {
771 self.stream_endpoints::<C>(caller_instance)
772 .map_or(0, <[_]>::len)
773 }
774
775 pub fn event_handle<C: EventCapability>(
777 &self,
778 caller_instance: &str,
779 ) -> Result<NativeEventHandle<C>, RuntimeFailure> {
780 self.validate_requirement_lookup(caller_instance, C::ID)?;
781 if self.runtime.admission.is_closed() {
782 return self.diagnostic_failure(Some(caller_instance), RuntimeFailure::AdmissionClosed);
783 }
784 let Some(endpoints) = self
785 .event_endpoints::<C>(caller_instance)
786 .filter(|endpoints| !endpoints.is_empty())
787 else {
788 return self.diagnostic_failure(
789 Some(caller_instance),
790 RuntimeFailure::Unavailable { capability: C::ID },
791 );
792 };
793 Ok(NativeEventHandle::from_endpoints(
794 endpoints,
795 self.runtime.clone(),
796 caller_instance,
797 false,
798 ))
799 }
800
801 pub fn optional_event_handle<C: EventCapability>(
803 &self,
804 caller_instance: &str,
805 ) -> Option<NativeEventHandle<C>> {
806 self.validate_requirement_lookup(caller_instance, C::ID)
807 .ok()?;
808 let caller_instance = caller_instance.to_owned();
809 self.event_endpoints::<C>(&caller_instance)
810 .filter(|endpoints| !endpoints.is_empty())
811 .map(|endpoints| {
812 NativeEventHandle::from_endpoints(
813 endpoints,
814 self.runtime.clone(),
815 &caller_instance,
816 false,
817 )
818 })
819 }
820
821 pub fn many_event_handle<C: EventCapability>(
823 &self,
824 caller_instance: &str,
825 ) -> Result<NativeEventHandle<C>, RuntimeFailure> {
826 self.validate_requirement_lookup(caller_instance, C::ID)?;
827 if self.runtime.admission.is_closed() {
828 return self.diagnostic_failure(Some(caller_instance), RuntimeFailure::AdmissionClosed);
829 }
830 let endpoints = self.event_endpoints::<C>(caller_instance).unwrap_or(&[]);
831 Ok(NativeEventHandle::from_endpoints(
832 endpoints,
833 self.runtime.clone(),
834 caller_instance,
835 false,
836 ))
837 }
838
839 pub fn event_binding_count<C: EventCapability>(&self, caller_instance: &str) -> usize {
841 self.event_endpoints::<C>(caller_instance)
842 .map_or(0, <[_]>::len)
843 }
844
845 fn validate_requirement_lookup(
846 &self,
847 caller: &str,
848 capability: &'static str,
849 ) -> Result<(), RuntimeFailure> {
850 let declarations = self
851 .runtime
852 .plan
853 .plugin_instance(caller)
854 .map_or(0, |instance| {
855 instance
856 .required_capabilities()
857 .iter()
858 .filter(|requirement| requirement.capability_id() == capability)
859 .count()
860 });
861 if declarations > 1 {
862 return Err(RuntimeFailure::AmbiguousBinding {
863 capability,
864 providers: declarations,
865 });
866 }
867 Ok(())
868 }
869
870 pub(super) fn next_request_id(&self) -> RequestId {
871 let request_id = self.runtime.request_ids.get();
872 self.runtime.request_ids.set(request_id.saturating_add(1));
873 request_id
874 }
875
876 pub(super) fn endpoints<C: RequestCapability>(
877 &self,
878 caller_instance: &str,
879 ) -> Option<&[NativeEndpointBinding]> {
880 self.bindings
881 .get(&(caller_instance.to_owned(), C::ID))
882 .map(Vec::as_slice)
883 }
884
885 pub(super) fn stream_endpoints<C: StreamCapability>(
886 &self,
887 caller_instance: &str,
888 ) -> Option<&[NativeStreamEndpointBinding]> {
889 self.stream_bindings
890 .get(&(caller_instance.to_owned(), C::ID))
891 .map(Vec::as_slice)
892 }
893
894 pub(super) fn event_endpoints<C: EventCapability>(
895 &self,
896 caller_instance: &str,
897 ) -> Option<&[event::NativeEventEndpointBinding]> {
898 self.event_bindings
899 .get(&(caller_instance.to_owned(), C::ID))
900 .map(Vec::as_slice)
901 }
902}