1use super::{
2 ActivateContext, AppAdmission, AppReadyGate, BTreeMap, CancellationToken, Cell,
3 DeactivationReason, DriverControl, ExecutionAdapterCatalog, ManagedResourceScope,
4 ManagedTaskScope, NativeApp, NativeAppRuntime, NativeBindingTable, NativeEndpointBinding,
5 NativeEndpointState, NativeEndpointStateTable, NativeEventBindingTable,
6 NativeEventEndpointStateTable, NativeExecutionAdapter, NativePluginGeneration,
7 NativePluginRuntime, NativeStreamBindingTable, NativeStreamEndpointBinding,
8 NativeStreamEndpointState, NativeStreamEndpointStateTable, PlanResolutionError,
9 PluginDependencies, PluginDependency, PluginDependencyHandle, PluginEventDependencyHandle,
10 PluginStreamDependencyHandle, PrepareContext, PreparedBinding, PreparedEventBinding,
11 PreparedNativeApp, PreparedNativePlugin, PreparedStreamBinding, Rc, RefCell, RequestAdmission,
12 ResolvedAppPlan, RuntimeDiagnostics, RuntimeDriver, RuntimeFailure, ShutdownCoordinator, Weak,
13 begin_plugin_supervision, deactivate_in_reverse, event, handle_supervision_schedule_failure,
14 plugin_supervision, schedule_plugin_supervision, validate_native_endpoint_set,
15};
16
17#[derive(Clone, Debug, Eq, PartialEq)]
19pub enum PlanValidationError {
20 UnsupportedSchemaVersion { expected: u32, actual: u32 },
22 InvalidResolvedPlan { detail: String },
24}
25
26impl std::fmt::Display for PlanValidationError {
27 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 Self::UnsupportedSchemaVersion { expected, actual } => write!(
30 formatter,
31 "unsupported Resolved App Plan schema {actual}; expected {expected}"
32 ),
33 Self::InvalidResolvedPlan { detail } => {
34 write!(formatter, "invalid Resolved App Plan: {detail}")
35 }
36 }
37 }
38}
39
40impl std::error::Error for PlanValidationError {}
41
42#[derive(Debug)]
44pub struct Kernel;
45
46impl Kernel {
47 pub async fn start_native<D: RuntimeDriver, A: NativeExecutionAdapter>(
49 plan: ResolvedAppPlan,
50 driver: D,
51 adapter: A,
52 ) -> Result<NativeApp, RuntimeFailure> {
53 Self::start_native_with_diagnostics(plan, driver, adapter, RuntimeDiagnostics::new()).await
54 }
55
56 pub async fn start_native_with_diagnostics<D: RuntimeDriver, A: NativeExecutionAdapter>(
58 plan: ResolvedAppPlan,
59 driver: D,
60 adapter: A,
61 diagnostics: RuntimeDiagnostics,
62 ) -> Result<NativeApp, RuntimeFailure> {
63 Self::start_with_diagnostics(
64 plan,
65 driver,
66 ExecutionAdapterCatalog::single(adapter),
67 diagnostics,
68 )
69 .await
70 }
71
72 pub async fn start<D: RuntimeDriver>(
74 plan: ResolvedAppPlan,
75 driver: D,
76 adapters: ExecutionAdapterCatalog,
77 ) -> Result<NativeApp, RuntimeFailure> {
78 Self::start_with_diagnostics(plan, driver, adapters, RuntimeDiagnostics::new()).await
79 }
80
81 #[allow(
83 clippy::too_many_lines,
84 reason = "startup remains linear so validation, preparation, and activation fail closed in order"
85 )]
86 pub async fn start_with_diagnostics<D: RuntimeDriver>(
87 plan: ResolvedAppPlan,
88 driver: D,
89 adapters: ExecutionAdapterCatalog,
90 diagnostics: RuntimeDiagnostics,
91 ) -> Result<NativeApp, RuntimeFailure> {
92 if plan
93 .plugin_instances()
94 .iter()
95 .any(|instance| instance.authoring_version() == 2)
96 {
97 return Self::start_controlled(
98 plan,
99 driver,
100 adapters,
101 diagnostics,
102 super::InvocationContext::new(0, None, CancellationToken::new()),
103 super::startup::DEFAULT_STARTUP_CLEANUP_TIMEOUT,
104 )
105 .await;
106 }
107 Self::start_owned(plan, driver, adapters, diagnostics, None, None).await
108 }
109
110 pub async fn start_controlled<D: RuntimeDriver>(
113 plan: ResolvedAppPlan,
114 driver: D,
115 adapters: ExecutionAdapterCatalog,
116 diagnostics: RuntimeDiagnostics,
117 context: super::InvocationContext,
118 cleanup_timeout: std::time::Duration,
119 ) -> Result<NativeApp, RuntimeFailure> {
120 super::startup::start(
121 plan,
122 driver,
123 adapters,
124 diagnostics,
125 context,
126 cleanup_timeout,
127 )
128 .await
129 }
130
131 #[allow(
132 clippy::too_many_lines,
133 reason = "startup remains linear so validation, preparation and activation fail closed"
134 )]
135 pub(super) async fn start_owned<D: RuntimeDriver>(
136 plan: ResolvedAppPlan,
137 driver: D,
138 adapters: ExecutionAdapterCatalog,
139 diagnostics: RuntimeDiagnostics,
140 startup_context: Option<super::InvocationContext>,
141 startup_cleanup: Option<super::cleanup::StartupCleanupBudget>,
142 ) -> Result<NativeApp, RuntimeFailure> {
143 if let Err(error) = plan.validate() {
144 let error = runtime_plan_error(&error);
145 diagnostics.emit_runtime_failure(driver.now(), None, &error);
146 return Err(error);
147 }
148
149 let activation_order = match plan.activation_order() {
150 Ok(order) => order,
151 Err(error) => {
152 let error = runtime_plan_error(&error);
153 diagnostics.emit_runtime_failure(driver.now(), None, &error);
154 return Err(error);
155 }
156 };
157 let adapters = Rc::new(adapters);
158 let PreparedNativeApp {
159 bindings: prepared_bindings,
160 stream_bindings: prepared_stream_bindings,
161 event_bindings: prepared_event_bindings,
162 generations,
163 } = match adapters.prepare(&plan) {
164 Ok(prepared) => prepared,
165 Err(error) => {
166 diagnostics.emit_runtime_failure(driver.now(), None, &error);
167 return Err(error);
168 }
169 };
170 if let Err(error) = validate_prepared_native_app(
171 &plan,
172 &prepared_bindings,
173 &prepared_stream_bindings,
174 &prepared_event_bindings,
175 &generations,
176 ) {
177 diagnostics.emit_runtime_failure(driver.now(), None, &error);
178 return Err(error);
179 }
180 let (bindings, endpoint_states) = native_bindings(&plan, &prepared_bindings);
181 let (stream_bindings, stream_endpoint_states) =
182 native_stream_bindings(&plan, &prepared_stream_bindings);
183 let (event_bindings, event_endpoint_states) =
184 native_event_bindings(&plan, &prepared_event_bindings);
185 let runtime_link = Rc::new(RefCell::new(Weak::new()));
186 let dependencies = plugin_dependencies(
187 &plan,
188 &bindings,
189 &stream_bindings,
190 &event_bindings,
191 &runtime_link,
192 );
193 let driver_control = DriverControl::new(&driver);
194 let admission = AppAdmission::new();
195 let plugin_runtimes = native_plugin_runtimes(&plan, &driver, generations);
196 let ready_gate = AppReadyGate::new();
197 let supervision = plugin_supervision(&plan);
198 let cleanup_timeout = startup_cleanup
199 .as_ref()
200 .map(super::cleanup::StartupCleanupBudget::timeout);
201 let runtime = Rc::new(NativeAppRuntime {
202 startup_context: RefCell::new(startup_context),
203 startup_cleanup,
204 cleanup_timeout,
205 executions: Rc::default(),
206 plan,
207 adapters,
208 plugins: plugin_runtimes,
209 dependencies,
210 endpoint_states,
211 stream_endpoint_states,
212 event_endpoint_states,
213 supervision: RefCell::new(supervision),
214 supervision_tasks: RefCell::new(BTreeMap::new()),
215 activation_order,
216 ready_gate,
217 admission,
218 driver: driver_control,
219 diagnostics: diagnostics.clone(),
220 request_ids: Rc::new(Cell::new(1)),
221 supervision_cancellation: CancellationToken::new(),
222 shutdown_started: Cell::new(false),
223 shutdown: ShutdownCoordinator::default(),
224 shutdown_task: RefCell::new(None),
225 terminal_failure: RefCell::new(None),
226 });
227 runtime_link.replace(Rc::downgrade(&runtime));
228 attach_managed_task_failure_handlers(&runtime);
229 runtime.diagnostics.emit(
230 super::DiagnosticSource::Lifecycle,
231 (runtime.driver.now)(),
232 |_| super::DiagnosticEvent::AppStarted {
233 plugin_count: runtime.plan.plugin_instances().len(),
234 },
235 );
236 let prepared_instances = prepare_native_plugins(&runtime).await?;
237 if let Err(error) = construct_native_plugins(&runtime).await {
238 let cleanup_error = deactivate_in_reverse(
239 &runtime.plugins,
240 &runtime.dependencies,
241 &prepared_instances,
242 DeactivationReason::StartupRollback,
243 &runtime.admission,
244 &runtime.diagnostics,
245 &runtime.driver,
246 runtime
247 .startup_cleanup
248 .as_ref()
249 .map(super::cleanup::StartupCleanupBudget::establish),
250 )
251 .await;
252 retain_unsafe_startup(&runtime, cleanup_error.as_ref());
253 runtime
254 .diagnostics
255 .emit_runtime_failure((runtime.driver.now)(), None, &error);
256 return Err(error);
257 }
258 if let Err(error) = activate_native_plugins(&runtime).await {
259 let cleanup_error = deactivate_in_reverse(
260 &runtime.plugins,
261 &runtime.dependencies,
262 &prepared_instances,
263 DeactivationReason::StartupRollback,
264 &runtime.admission,
265 &runtime.diagnostics,
266 &runtime.driver,
267 runtime
268 .startup_cleanup
269 .as_ref()
270 .map(super::cleanup::StartupCleanupBudget::establish),
271 )
272 .await;
273 retain_unsafe_startup(&runtime, cleanup_error.as_ref());
274 runtime
275 .diagnostics
276 .emit_runtime_failure((runtime.driver.now)(), None, &error);
277 return Err(error);
278 }
279 open_native_readiness(&runtime).await;
280 Ok(NativeApp {
281 bindings,
282 stream_bindings,
283 event_bindings,
284 diagnostics,
285 runtime,
286 })
287 }
288}
289
290pub(super) fn attach_managed_task_failure_handlers(runtime: &Rc<NativeAppRuntime>) {
291 for (instance_key, plugin) in &runtime.plugins {
292 let Some((_, tasks, _)) = plugin.generation_parts() else {
293 continue;
294 };
295 attach_managed_task_failure_handler(runtime, instance_key, &tasks);
296 }
297}
298
299fn startup_active(runtime: &NativeAppRuntime) -> Result<(), RuntimeFailure> {
300 let result = runtime
301 .startup_context
302 .borrow()
303 .as_ref()
304 .map_or(Ok(()), |context| {
305 super::ensure_context_active(&runtime.driver, context)
306 });
307 if result.is_err()
308 && let Some(cleanup) = &runtime.startup_cleanup
309 {
310 let now = (runtime.driver.now)();
311 let cleanup_started_at = runtime
312 .startup_context
313 .borrow()
314 .as_ref()
315 .and_then(super::InvocationContext::deadline)
316 .filter(|deadline| now >= *deadline)
317 .unwrap_or(now);
318 cleanup.establish_at(cleanup_started_at);
319 }
320 result
321}
322
323fn lifecycle_cancellation(
324 runtime: &NativeAppRuntime,
325 tasks: &ManagedTaskScope,
326) -> CancellationToken {
327 runtime.startup_context.borrow().as_ref().map_or_else(
328 || tasks.cancellation(),
329 super::InvocationContext::cancellation,
330 )
331}
332
333pub(super) fn attach_managed_task_failure_handler(
334 runtime: &Rc<NativeAppRuntime>,
335 instance_key: &str,
336 tasks: &ManagedTaskScope,
337) {
338 let task_runtime = Rc::downgrade(runtime);
339 let task_instance_key = instance_key.to_owned();
340 let handler: Rc<dyn Fn()> = Rc::new(move || {
341 let Some(runtime) = task_runtime.upgrade() else {
342 return;
343 };
344 if begin_plugin_supervision(&runtime, &task_instance_key).unwrap_or(false)
345 && let Err(error) = schedule_plugin_supervision(&runtime, &task_instance_key)
346 {
347 let _ = handle_supervision_schedule_failure(&runtime, &task_instance_key, error);
348 }
349 });
350 tasks.set_failure_handler(&handler);
351}
352
353pub(super) fn runtime_plan_error(error: &PlanResolutionError) -> RuntimeFailure {
354 RuntimeFailure::InvalidResolvedPlan {
355 detail: error.to_string(),
356 }
357}
358
359#[allow(
360 clippy::too_many_lines,
361 reason = "one fail-closed pass keeps request, stream, event, and generation validation aligned"
362)]
363pub(super) fn validate_prepared_native_app(
364 plan: &ResolvedAppPlan,
365 bindings: &[PreparedBinding],
366 stream_bindings: &[PreparedStreamBinding],
367 event_bindings: &[PreparedEventBinding],
368 generations: &BTreeMap<String, PreparedNativePlugin>,
369) -> Result<(), RuntimeFailure> {
370 if generations.len() != plan.plugin_instances().len() {
371 return Err(RuntimeFailure::InvalidResolvedPlan {
372 detail: format!(
373 "Execution Adapters prepared {} Plugin generations; expected {}",
374 generations.len(),
375 plan.plugin_instances().len()
376 ),
377 });
378 }
379 for instance in plan.plugin_instances() {
380 let generation = generations.get(instance.instance_key()).ok_or_else(|| {
381 RuntimeFailure::InvalidResolvedPlan {
382 detail: format!(
383 "Execution Adapters did not prepare Plugin Instance `{}`",
384 instance.instance_key()
385 ),
386 }
387 })?;
388 validate_native_endpoint_set(
389 instance.instance_key(),
390 instance,
391 generation.endpoints(),
392 generation.stream_endpoints(),
393 generation.event_endpoints(),
394 )?;
395 }
396 if let Some(instance_key) = generations.keys().find(|instance_key| {
397 !plan
398 .plugin_instances()
399 .iter()
400 .any(|instance| instance.instance_key() == instance_key.as_str())
401 }) {
402 return Err(RuntimeFailure::InvalidResolvedPlan {
403 detail: format!("Execution Adapter prepared unknown Plugin Instance `{instance_key}`"),
404 });
405 }
406
407 let expected_request_bindings = plan
408 .capability_bindings()
409 .iter()
410 .filter(|binding| {
411 plan.plugin_instance(binding.provider_instance())
412 .and_then(|provider| {
413 provider
414 .provided_capabilities()
415 .iter()
416 .find(|endpoint| endpoint.capability_id() == binding.capability_id())
417 })
418 .is_some_and(|endpoint| !endpoint.request_operations().is_empty())
419 })
420 .count();
421 let expected_stream_bindings = plan
422 .capability_bindings()
423 .iter()
424 .filter(|binding| {
425 plan.plugin_instance(binding.provider_instance())
426 .and_then(|provider| {
427 provider
428 .provided_capabilities()
429 .iter()
430 .find(|endpoint| endpoint.capability_id() == binding.capability_id())
431 })
432 .is_some_and(|endpoint| !endpoint.stream_operations().is_empty())
433 })
434 .count();
435 let expected_event_bindings = plan
436 .capability_bindings()
437 .iter()
438 .filter(|binding| {
439 plan.plugin_instance(binding.provider_instance())
440 .and_then(|provider| {
441 provider
442 .provided_capabilities()
443 .iter()
444 .find(|endpoint| endpoint.capability_id() == binding.capability_id())
445 })
446 .is_some_and(|endpoint| !endpoint.event_operations().is_empty())
447 })
448 .count();
449 if bindings.len() != expected_request_bindings {
450 return Err(RuntimeFailure::InvalidResolvedPlan {
451 detail: if expected_stream_bindings == 0 && stream_bindings.is_empty() {
452 format!(
453 "Execution Adapters prepared {} bindings; expected {}",
454 bindings.len(),
455 expected_request_bindings
456 )
457 } else {
458 format!(
459 "Execution Adapters prepared {} request bindings; expected {}",
460 bindings.len(),
461 expected_request_bindings
462 )
463 },
464 });
465 }
466 if stream_bindings.len() != expected_stream_bindings {
467 return Err(RuntimeFailure::InvalidResolvedPlan {
468 detail: format!(
469 "Execution Adapters prepared {} stream bindings; expected {}",
470 stream_bindings.len(),
471 expected_stream_bindings
472 ),
473 });
474 }
475 if event_bindings.len() != expected_event_bindings {
476 return Err(RuntimeFailure::InvalidResolvedPlan {
477 detail: format!(
478 "Execution Adapters prepared {} Event bindings; expected {}",
479 event_bindings.len(),
480 expected_event_bindings
481 ),
482 });
483 }
484 for planned in plan.capability_bindings() {
485 let provider = generations
486 .get(planned.provider_instance())
487 .expect("the resolved Plan references one validated provider generation");
488 let descriptor = plan
489 .plugin_instance(planned.provider_instance())
490 .and_then(|provider| {
491 provider
492 .provided_capabilities()
493 .iter()
494 .find(|endpoint| endpoint.capability_id() == planned.capability_id())
495 })
496 .expect("the resolved Plan references one validated provider endpoint");
497 if !descriptor.request_operations().is_empty() {
498 let matching: Vec<_> = bindings
499 .iter()
500 .filter(|prepared| {
501 prepared.requirement_id() == planned.requirement_id()
502 && prepared.consumer_instance == planned.consumer_instance()
503 && prepared.provider_instance == planned.provider_instance()
504 && prepared.endpoint.capability_id() == planned.capability_id()
505 && prepared.endpoint.descriptor_version() == planned.descriptor_version()
506 })
507 .collect();
508 if matching.len() != 1 {
509 return Err(RuntimeFailure::InvalidResolvedPlan {
510 detail: format!(
511 "Execution Adapters prepared {} request bindings for `{}:{}:{}`; expected 1",
512 matching.len(),
513 planned.consumer_instance(),
514 planned.capability_id(),
515 planned.provider_instance()
516 ),
517 });
518 }
519 if !provider
520 .endpoints()
521 .iter()
522 .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
523 {
524 return Err(RuntimeFailure::InvalidResolvedPlan {
525 detail: format!(
526 "request binding `{}:{}:{}` does not reference its provider generation endpoint",
527 planned.consumer_instance(),
528 planned.capability_id(),
529 planned.provider_instance()
530 ),
531 });
532 }
533 }
534 if !descriptor.stream_operations().is_empty() {
535 let matching: Vec<_> = stream_bindings
536 .iter()
537 .filter(|prepared| {
538 prepared.requirement_id() == planned.requirement_id()
539 && prepared.consumer_instance == planned.consumer_instance()
540 && prepared.provider_instance == planned.provider_instance()
541 && prepared.endpoint.capability_id() == planned.capability_id()
542 && prepared.endpoint.descriptor_version() == planned.descriptor_version()
543 })
544 .collect();
545 if matching.len() != 1 {
546 return Err(RuntimeFailure::InvalidResolvedPlan {
547 detail: format!(
548 "Execution Adapters prepared {} stream bindings for `{}:{}:{}`; expected 1",
549 matching.len(),
550 planned.consumer_instance(),
551 planned.capability_id(),
552 planned.provider_instance()
553 ),
554 });
555 }
556 if !provider
557 .stream_endpoints()
558 .iter()
559 .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
560 {
561 return Err(RuntimeFailure::InvalidResolvedPlan {
562 detail: format!(
563 "stream binding `{}:{}:{}` does not reference its provider generation endpoint",
564 planned.consumer_instance(),
565 planned.capability_id(),
566 planned.provider_instance()
567 ),
568 });
569 }
570 }
571 if !descriptor.event_operations().is_empty() {
572 let matching: Vec<_> = event_bindings
573 .iter()
574 .filter(|prepared| {
575 prepared.requirement_id() == planned.requirement_id()
576 && prepared.consumer_instance == planned.consumer_instance()
577 && prepared.provider_instance == planned.provider_instance()
578 && prepared.endpoint.capability_id() == planned.capability_id()
579 && prepared.endpoint.descriptor_version() == planned.descriptor_version()
580 })
581 .collect();
582 if matching.len() != 1 {
583 return Err(RuntimeFailure::InvalidResolvedPlan {
584 detail: format!(
585 "Execution Adapters prepared {} Event bindings for `{}:{}:{}`; expected 1",
586 matching.len(),
587 planned.consumer_instance(),
588 planned.capability_id(),
589 planned.provider_instance()
590 ),
591 });
592 }
593 if !provider
594 .event_endpoints()
595 .iter()
596 .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
597 {
598 return Err(RuntimeFailure::InvalidResolvedPlan {
599 detail: format!(
600 "Event binding `{}:{}:{}` does not reference its provider generation endpoint",
601 planned.consumer_instance(),
602 planned.capability_id(),
603 planned.provider_instance()
604 ),
605 });
606 }
607 }
608 }
609 Ok(())
610}
611
612pub(super) fn native_plugin_runtimes<D: RuntimeDriver>(
613 plan: &ResolvedAppPlan,
614 driver: &D,
615 mut generations: BTreeMap<String, PreparedNativePlugin>,
616) -> BTreeMap<String, NativePluginRuntime> {
617 let mut runtimes = BTreeMap::new();
618 for instance in plan.plugin_instances() {
619 let lifecycle = generations
620 .remove(instance.instance_key())
621 .map(|generation| generation.lifecycle())
622 .expect("prepared App validation requires one generation per planned Instance");
623 runtimes.insert(
624 instance.instance_key().to_owned(),
625 NativePluginRuntime {
626 generation: RefCell::new(Some(NativePluginGeneration {
627 lifecycle,
628 tasks: ManagedTaskScope::new(driver),
629 resources: ManagedResourceScope::new(),
630 stop_attempted: false,
631 cleanup_timed_out: false,
632 })),
633 },
634 );
635 }
636 runtimes
637}
638
639pub(super) async fn prepare_native_plugins(
640 runtime: &Rc<NativeAppRuntime>,
641) -> Result<Vec<String>, RuntimeFailure> {
642 let mut prepared_instances = Vec::with_capacity(runtime.activation_order.len());
643 for instance_key in &runtime.activation_order {
644 startup_active(runtime)?;
645 let instance = runtime
646 .plan
647 .plugin_instances()
648 .iter()
649 .find(|instance| instance.instance_key() == instance_key)
650 .expect("activation order only contains planned Plugin Instances");
651 let plugin = runtime
652 .plugins
653 .get(instance_key)
654 .expect("activation order only contains planned Plugin Instances");
655 let (lifecycle, tasks, resources) = plugin
656 .generation_parts()
657 .expect("every startup Plugin Instance has a generation");
658 let cancellation = lifecycle_cancellation(runtime, &tasks);
659 prepared_instances.push(instance_key.clone());
660 let started_at = (runtime.driver.now)();
661 runtime
662 .diagnostics
663 .emit(super::DiagnosticSource::Lifecycle, started_at, |_| {
664 super::DiagnosticEvent::LifecycleStarted {
665 instance: instance_key.clone(),
666 generation: 1,
667 phase: super::PluginLifecyclePhase::Prepare,
668 }
669 });
670 let context = PrepareContext {
671 instance_key: instance_key.clone(),
672 entrypoint: instance.entrypoint().to_owned(),
673 configuration: instance.configuration().to_owned(),
674 dependencies: runtime
675 .dependencies
676 .get(instance_key)
677 .cloned()
678 .unwrap_or_default(),
679 resources,
680 cancellation,
681 admission: runtime.admission.clone(),
682 };
683 let result = lifecycle
684 .prepare(context)
685 .await
686 .and_then(|()| startup_active(runtime));
687 let outcome = result.as_ref().map_or_else(
688 |error| super::DiagnosticOutcome::RuntimeFailure(error.into()),
689 |()| super::DiagnosticOutcome::Succeeded,
690 );
691 runtime.diagnostics.emit(
692 super::DiagnosticSource::Lifecycle,
693 (runtime.driver.now)(),
694 |_| super::DiagnosticEvent::LifecycleCompleted {
695 instance: instance_key.clone(),
696 generation: 1,
697 phase: super::PluginLifecyclePhase::Prepare,
698 outcome,
699 elapsed: (runtime.driver.now)().saturating_sub(started_at),
700 },
701 );
702 if let Err(error) = result {
703 let cleanup_error = deactivate_in_reverse(
704 &runtime.plugins,
705 &runtime.dependencies,
706 &prepared_instances,
707 DeactivationReason::StartupRollback,
708 &runtime.admission,
709 &runtime.diagnostics,
710 &runtime.driver,
711 runtime
712 .startup_cleanup
713 .as_ref()
714 .map(super::cleanup::StartupCleanupBudget::establish),
715 )
716 .await;
717 retain_unsafe_startup(runtime, cleanup_error.as_ref());
718 runtime.diagnostics.emit_runtime_failure(
719 (runtime.driver.now)(),
720 Some(instance_key),
721 &error,
722 );
723 return Err(error);
724 }
725 }
726 Ok(prepared_instances)
727}
728
729fn retain_unsafe_startup(runtime: &Rc<NativeAppRuntime>, cleanup_error: Option<&RuntimeFailure>) {
730 if matches!(cleanup_error, Some(RuntimeFailure::DeadlineExceeded { .. })) {
731 std::mem::forget(runtime.clone());
735 }
736}
737
738pub(super) async fn activate_native_plugins(
739 runtime: &Rc<NativeAppRuntime>,
740) -> Result<(), RuntimeFailure> {
741 for instance_key in &runtime.activation_order {
742 startup_active(runtime)?;
743 let plugin = runtime
744 .plugins
745 .get(instance_key)
746 .expect("activation order only contains planned Plugin Instances");
747 let (lifecycle, tasks, resources) = plugin
748 .generation_parts()
749 .expect("every startup Plugin Instance has a generation");
750 let cancellation = lifecycle_cancellation(runtime, &tasks);
751 let started_at = (runtime.driver.now)();
752 runtime
753 .diagnostics
754 .emit(super::DiagnosticSource::Lifecycle, started_at, |_| {
755 super::DiagnosticEvent::LifecycleStarted {
756 instance: instance_key.clone(),
757 generation: 1,
758 phase: super::PluginLifecyclePhase::Activate,
759 }
760 });
761 let context = ActivateContext {
762 instance_key: instance_key.clone(),
763 dependencies: runtime
764 .dependencies
765 .get(instance_key)
766 .cloned()
767 .unwrap_or_default(),
768 ready_gate: runtime.ready_gate.clone(),
769 tasks,
770 resources,
771 cancellation,
772 admission: runtime.admission.clone(),
773 };
774 let result = lifecycle
775 .activate(context)
776 .await
777 .and_then(|()| startup_active(runtime));
778 let outcome = result.as_ref().map_or_else(
779 |error| super::DiagnosticOutcome::RuntimeFailure(error.into()),
780 |()| super::DiagnosticOutcome::Succeeded,
781 );
782 runtime.diagnostics.emit(
783 super::DiagnosticSource::Lifecycle,
784 (runtime.driver.now)(),
785 |_| super::DiagnosticEvent::LifecycleCompleted {
786 instance: instance_key.clone(),
787 generation: 1,
788 phase: super::PluginLifecyclePhase::Activate,
789 outcome,
790 elapsed: (runtime.driver.now)().saturating_sub(started_at),
791 },
792 );
793 if let Err(error) = result {
794 runtime.diagnostics.emit_runtime_failure(
795 (runtime.driver.now)(),
796 Some(instance_key),
797 &error,
798 );
799 return Err(error);
800 }
801 }
802 Ok(())
803}
804
805pub(super) async fn construct_native_plugins(
806 runtime: &Rc<NativeAppRuntime>,
807) -> Result<(), RuntimeFailure> {
808 for instance_key in &runtime.activation_order {
809 let instance = runtime
810 .plan
811 .plugin_instance(instance_key)
812 .expect("construction order contains planned Instances");
813 if instance.authoring_version() == 1 {
814 continue;
815 }
816 startup_active(runtime)?;
817 let plugin = runtime
818 .plugins
819 .get(instance_key)
820 .expect("construction order contains planned Instances");
821 let (lifecycle, tasks, resources) = plugin
822 .generation_parts()
823 .expect("startup generation exists");
824 let started_at = (runtime.driver.now)();
825 runtime
826 .diagnostics
827 .emit(super::DiagnosticSource::Lifecycle, started_at, |_| {
828 super::DiagnosticEvent::LifecycleStarted {
829 instance: instance_key.clone(),
830 generation: 1,
831 phase: super::PluginLifecyclePhase::Construct,
832 }
833 });
834 let result = lifecycle
835 .construct(ActivateContext {
836 instance_key: instance_key.clone(),
837 dependencies: runtime
838 .dependencies
839 .get(instance_key)
840 .cloned()
841 .unwrap_or_default(),
842 ready_gate: runtime.ready_gate.clone(),
843 tasks: tasks.clone(),
844 resources,
845 cancellation: lifecycle_cancellation(runtime, &tasks),
846 admission: runtime.admission.clone(),
847 })
848 .await
849 .and_then(|()| startup_active(runtime));
850 let outcome = result.as_ref().map_or_else(
851 |error| super::DiagnosticOutcome::RuntimeFailure(error.into()),
852 |()| super::DiagnosticOutcome::Succeeded,
853 );
854 runtime.diagnostics.emit(
855 super::DiagnosticSource::Lifecycle,
856 (runtime.driver.now)(),
857 |_| super::DiagnosticEvent::LifecycleCompleted {
858 instance: instance_key.clone(),
859 generation: 1,
860 phase: super::PluginLifecyclePhase::Construct,
861 outcome,
862 elapsed: (runtime.driver.now)().saturating_sub(started_at),
863 },
864 );
865 result?;
866 }
867 Ok(())
868}
869
870pub(super) async fn open_native_readiness(runtime: &Rc<NativeAppRuntime>) {
871 runtime.startup_context.borrow_mut().take();
872 runtime.ready_gate.open();
873 runtime.admission.open();
874 runtime.diagnostics.emit(
875 super::DiagnosticSource::Lifecycle,
876 (runtime.driver.now)(),
877 |_| super::DiagnosticEvent::AppReady,
878 );
879 (runtime.driver.yield_now)().await;
880}
881
882pub(super) fn native_bindings(
883 plan: &ResolvedAppPlan,
884 prepared: &[PreparedBinding],
885) -> (NativeBindingTable, NativeEndpointStateTable) {
886 let mut bindings = BTreeMap::new();
887 let mut endpoint_states = BTreeMap::new();
888 for binding in plan.capability_bindings() {
889 let Some(descriptor) =
890 plan.plugin_instance(binding.provider_instance())
891 .and_then(|provider| {
892 provider
893 .provided_capabilities()
894 .iter()
895 .find(|endpoint| endpoint.capability_id() == binding.capability_id())
896 })
897 else {
898 continue;
899 };
900 if descriptor.request_operations().is_empty() {
901 continue;
902 }
903 let Some(endpoint) = prepared.iter().find_map(|prepared| {
904 (prepared.requirement_id() == binding.requirement_id()
905 && prepared.consumer_instance == binding.consumer_instance()
906 && prepared.provider_instance == binding.provider_instance()
907 && prepared.endpoint.capability_id() == binding.capability_id())
908 .then_some(&prepared.endpoint)
909 }) else {
910 continue;
911 };
912 let state = endpoint_states
913 .entry((
914 binding.provider_instance().to_owned(),
915 endpoint.capability_id().to_owned(),
916 ))
917 .or_insert_with(|| Rc::new(NativeEndpointState::new(endpoint.clone(), 1)))
918 .clone();
919 let admissions = endpoint
920 .operations()
921 .iter()
922 .map(|operation| {
923 (
924 (*operation).to_owned(),
925 RequestAdmission::new(plan.request_admission_for(binding, operation)),
926 )
927 })
928 .collect();
929 bindings
930 .entry((
931 binding.consumer_instance().to_owned(),
932 endpoint.capability_id(),
933 ))
934 .or_insert_with(Vec::new)
935 .push(NativeEndpointBinding {
936 requirement_id: binding.requirement_id().to_owned(),
937 plugin_instance: binding.provider_instance().to_owned(),
938 state,
939 admissions,
940 });
941 }
942 (bindings, endpoint_states)
943}
944
945pub(super) fn native_stream_bindings(
946 plan: &ResolvedAppPlan,
947 prepared: &[PreparedStreamBinding],
948) -> (NativeStreamBindingTable, NativeStreamEndpointStateTable) {
949 let mut bindings = BTreeMap::new();
950 let mut endpoint_states = BTreeMap::new();
951 for binding in plan.capability_bindings() {
952 let Some(descriptor) =
953 plan.plugin_instance(binding.provider_instance())
954 .and_then(|provider| {
955 provider
956 .provided_capabilities()
957 .iter()
958 .find(|endpoint| endpoint.capability_id() == binding.capability_id())
959 })
960 else {
961 continue;
962 };
963 if descriptor.stream_operations().is_empty() {
964 continue;
965 }
966 let Some(endpoint) = prepared.iter().find_map(|prepared| {
967 (prepared.requirement_id() == binding.requirement_id()
968 && prepared.consumer_instance == binding.consumer_instance()
969 && prepared.provider_instance == binding.provider_instance()
970 && prepared.endpoint.capability_id() == binding.capability_id())
971 .then_some(&prepared.endpoint)
972 }) else {
973 continue;
974 };
975 let state = endpoint_states
976 .entry((
977 binding.provider_instance().to_owned(),
978 endpoint.capability_id().to_owned(),
979 ))
980 .or_insert_with(|| Rc::new(NativeStreamEndpointState::new(endpoint.clone(), 1)))
981 .clone();
982 let admissions = endpoint
983 .operations()
984 .iter()
985 .map(|operation| {
986 (
987 (*operation).to_owned(),
988 RequestAdmission::new(plan.request_admission_for(binding, operation)),
989 )
990 })
991 .collect();
992 bindings
993 .entry((
994 binding.consumer_instance().to_owned(),
995 endpoint.capability_id(),
996 ))
997 .or_insert_with(Vec::new)
998 .push(NativeStreamEndpointBinding {
999 requirement_id: binding.requirement_id().to_owned(),
1000 plugin_instance: binding.provider_instance().to_owned(),
1001 state,
1002 admissions,
1003 });
1004 }
1005 (bindings, endpoint_states)
1006}
1007
1008pub(super) fn native_event_bindings(
1009 plan: &ResolvedAppPlan,
1010 prepared: &[PreparedEventBinding],
1011) -> (NativeEventBindingTable, NativeEventEndpointStateTable) {
1012 let mut bindings = BTreeMap::new();
1013 let mut endpoint_states = BTreeMap::new();
1014 for binding in plan.capability_bindings() {
1015 let Some(descriptor) =
1016 plan.plugin_instance(binding.provider_instance())
1017 .and_then(|provider| {
1018 provider
1019 .provided_capabilities()
1020 .iter()
1021 .find(|endpoint| endpoint.capability_id() == binding.capability_id())
1022 })
1023 else {
1024 continue;
1025 };
1026 if descriptor.event_operations().is_empty() {
1027 continue;
1028 }
1029 let Some(endpoint) = prepared.iter().find_map(|prepared| {
1030 (prepared.requirement_id() == binding.requirement_id()
1031 && prepared.consumer_instance == binding.consumer_instance()
1032 && prepared.provider_instance == binding.provider_instance()
1033 && prepared.endpoint.capability_id() == binding.capability_id())
1034 .then_some(&prepared.endpoint)
1035 }) else {
1036 continue;
1037 };
1038 let state = endpoint_states
1039 .entry((
1040 binding.provider_instance().to_owned(),
1041 endpoint.capability_id().to_owned(),
1042 ))
1043 .or_insert_with(|| Rc::new(event::NativeEventEndpointState::new(endpoint.clone(), 1)))
1044 .clone();
1045 let queue = event::NativeEventQueue::new(plan.event_admission_for(binding));
1046 state.register_queue(&queue);
1047 bindings
1048 .entry((
1049 binding.consumer_instance().to_owned(),
1050 endpoint.capability_id(),
1051 ))
1052 .or_insert_with(Vec::new)
1053 .push(event::NativeEventEndpointBinding {
1054 requirement_id: binding.requirement_id().to_owned(),
1055 plugin_instance: binding.provider_instance().to_owned(),
1056 state,
1057 queue,
1058 });
1059 }
1060 (bindings, endpoint_states)
1061}
1062
1063pub(super) fn plugin_dependencies(
1064 plan: &ResolvedAppPlan,
1065 endpoints: &BTreeMap<(String, &'static str), Vec<NativeEndpointBinding>>,
1066 stream_endpoints: &NativeStreamBindingTable,
1067 event_endpoints: &NativeEventBindingTable,
1068 runtime: &Rc<RefCell<Weak<NativeAppRuntime>>>,
1069) -> BTreeMap<String, PluginDependencies> {
1070 let mut dependencies: BTreeMap<String, PluginDependencies> = plan
1071 .plugin_instances()
1072 .iter()
1073 .map(|instance| {
1074 (
1075 instance.instance_key().to_owned(),
1076 PluginDependencies::new(
1077 instance.instance_key(),
1078 runtime.clone(),
1079 instance.required_capabilities().to_vec(),
1080 ),
1081 )
1082 })
1083 .collect();
1084 for binding in plan.capability_bindings() {
1085 dependencies
1086 .get_mut(binding.consumer_instance())
1087 .expect("every resolved binding consumer has Plugin dependencies")
1088 .bindings
1089 .push(PluginDependency::new(
1090 binding.requirement_id(),
1091 binding.capability_id(),
1092 binding.provider_instance(),
1093 binding.provider_order(),
1094 endpoints
1095 .iter()
1096 .find(|((consumer, capability), _)| {
1097 consumer == binding.consumer_instance()
1098 && *capability == binding.capability_id()
1099 })
1100 .and_then(|(_, endpoints)| {
1101 endpoints.iter().find(|endpoint| {
1102 endpoint.requirement_id == binding.requirement_id()
1103 && endpoint.plugin_instance == binding.provider_instance()
1104 })
1105 })
1106 .map(|endpoint| PluginDependencyHandle {
1107 binding: endpoint.clone(),
1108 caller_instance: binding.consumer_instance().to_owned(),
1109 runtime: runtime.clone(),
1110 }),
1111 stream_endpoints
1112 .iter()
1113 .find(|((consumer, capability), _)| {
1114 consumer == binding.consumer_instance()
1115 && *capability == binding.capability_id()
1116 })
1117 .and_then(|(_, endpoints)| {
1118 endpoints.iter().find(|endpoint| {
1119 endpoint.requirement_id == binding.requirement_id()
1120 && endpoint.plugin_instance == binding.provider_instance()
1121 })
1122 })
1123 .map(|endpoint| PluginStreamDependencyHandle {
1124 binding: endpoint.clone(),
1125 caller_instance: binding.consumer_instance().to_owned(),
1126 runtime: runtime.clone(),
1127 }),
1128 event_endpoints
1129 .iter()
1130 .find(|((consumer, capability), _)| {
1131 consumer == binding.consumer_instance()
1132 && *capability == binding.capability_id()
1133 })
1134 .and_then(|(_, endpoints)| {
1135 endpoints.iter().find(|endpoint| {
1136 endpoint.requirement_id == binding.requirement_id()
1137 && endpoint.plugin_instance == binding.provider_instance()
1138 })
1139 })
1140 .map(|endpoint| PluginEventDependencyHandle {
1141 binding: endpoint.clone(),
1142 caller_instance: binding.consumer_instance().to_owned(),
1143 runtime: runtime.clone(),
1144 }),
1145 ));
1146 }
1147 dependencies
1148}