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