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