1use anyhow::Context as _;
2use platform_core::{
3 AppConfig, AppContext, EventHandlerRegistry, LoggingEventPublisher, OutboxRelay,
4 PostgresRuntimeConfigProvider, RuntimeConfigRegistry, Shutdown, WorkerRuntimeConfig,
5 connect_pool, telemetry,
6};
7use platform_runtime::{
8 FunctionRegistry, RuntimeScheduler, RuntimeWorker, ScheduledFunctionDefinition,
9};
10use std::path::Path;
11use std::sync::Arc;
12use std::time::Duration;
13use tracing::info;
14
15pub async fn run_from_env() -> anyhow::Result<()> {
16 run_from_env_with_composition(lenso_bootstrap::HostComposition::default()).await
17}
18
19pub async fn run_from_env_with_composition(
20 composition: lenso_bootstrap::HostComposition,
21) -> anyhow::Result<()> {
22 let config = AppConfig::try_from_env().context("invalid application configuration")?;
23 telemetry::init(&config.telemetry)?;
24 let provider_plan = lenso_bootstrap::provider_runtime_plan_from_workspace(Path::new("."))
25 .context("failed to compile Provider Runtime Plan")?;
26
27 let db = connect_pool(&config.database).await?;
28 let ctx = AppContext::new(config, db, Arc::new(LoggingEventPublisher));
29
30 let descriptors =
31 lenso_bootstrap::runtime_config_descriptors_with_composition(&ctx, &composition)
32 .context("failed to collect runtime-config descriptors")?;
33 let groups =
34 lenso_bootstrap::runtime_config_group_descriptors_with_composition(&ctx, &composition)
35 .context("failed to collect runtime-config groups")?;
36 let runtime_config_registry = RuntimeConfigRegistry::try_new_with_groups(descriptors, groups)
37 .context("duplicate runtime-config descriptor registered")?;
38 let runtime_config = PostgresRuntimeConfigProvider::connect(
39 ctx.db.clone(),
40 Arc::new(runtime_config_registry),
41 "worker",
42 )
43 .await
44 .context("failed to load runtime-config snapshot")?;
45 runtime_config.spawn_listener();
46 let ctx = ctx.with_runtime_config_provider(runtime_config);
47
48 let modules = lenso_bootstrap::load_modules_with_composition_and_provider_plan(
49 &ctx,
50 &composition,
51 provider_plan.as_ref(),
52 )
53 .await
54 .context("failed to load modules")?;
55 let registry = Arc::new(lenso_bootstrap::function_registry(&modules));
56 let activation_run_ids =
57 lenso_bootstrap::enqueue_lifecycle_activation_jobs(&ctx, &modules, ®istry)
58 .await
59 .context("failed to enqueue module lifecycle activation jobs")?;
60 let schedules = lenso_bootstrap::scheduled_functions(&modules, registry.as_ref())
61 .context("failed to collect scheduled runtime functions")?;
62 let event_handlers =
63 lenso_bootstrap::event_handlers_with_runtime_actions(&ctx, &modules, registry.clone());
64
65 info!(
66 functions = registry.all().count(),
67 lifecycle_activation_jobs = activation_run_ids.len(),
68 scheduled_functions = schedules.len(),
69 "starting worker"
70 );
71
72 run_worker_loop(ctx.clone(), event_handlers, registry, schedules).await;
73 Ok(())
74}
75
76async fn run_worker_loop(
77 ctx: AppContext,
78 dispatcher: EventHandlerRegistry,
79 registry: Arc<FunctionRegistry>,
80 schedules: Vec<ScheduledFunctionDefinition>,
81) {
82 let shutdown = ctx.shutdown.clone();
83 let mut shutdown_rx = shutdown.subscribe();
84 let relay = OutboxRelay::new(ctx.db.clone(), "worker-local");
85 let scheduler = RuntimeScheduler::new(ctx.db.clone(), "worker-local");
86 let runtime_worker = RuntimeWorker::new(ctx.db.clone(), registry, "worker-local");
87 loop {
88 let cfg: WorkerRuntimeConfig = ctx
89 .runtime_config
90 .snapshot()
91 .get("worker")
92 .unwrap_or_default();
93 let batch_size = cfg.batch_size as i64;
95 tokio::select! {
96 changed = shutdown_rx.changed() => {
97 if changed.is_ok() && *shutdown_rx.borrow() {
98 break;
99 }
100 }
101 () = Shutdown::wait_for_signal() => {
102 shutdown.signal();
103 }
104 () = tokio::time::sleep(Duration::from_millis(cfg.poll_interval_ms)) => {
105 match scheduler.enqueue_due(&schedules).await {
106 Ok(run_ids) => {
107 if !run_ids.is_empty() {
108 tracing::debug!(
109 scheduled_function_runs = run_ids.len(),
110 "runtime scheduler tick"
111 );
112 }
113 }
114 Err(error) => {
115 tracing::warn!(error = ?error, "runtime scheduler tick failed");
116 }
117 }
118 match relay.relay_once(&dispatcher, batch_size).await {
119 Ok(count) => {
120 tracing::debug!(claimed_outbox_events = count, "outbox relay tick");
121 }
122 Err(error) => {
123 tracing::warn!(error = ?error, "outbox relay tick failed");
124 }
125 }
126 match runtime_worker.claim_and_run_batch(batch_size).await {
127 Ok(count) => {
128 tracing::debug!(claimed_function_runs = count, "runtime worker tick");
129 }
130 Err(error) => {
131 tracing::warn!(error = ?error, "runtime worker tick failed");
132 }
133 }
134 }
135 }
136 }
137}