1use std::{
2 collections::BTreeMap,
3 fmt,
4 sync::{
5 Arc,
6 atomic::{AtomicBool, Ordering},
7 mpsc as std_mpsc,
8 },
9 thread,
10 time::{Duration, Instant},
11};
12
13use cpu_time::ThreadTime;
14use futures::{channel::oneshot, future::LocalBoxFuture};
15use lenso_app_plan::{CapabilityBinding, ExecutionLaneId, ResolvedAppPlan};
16use lenso_kernel::{
17 CancellationToken, DiagnosticEvent, DiagnosticFilter, DiagnosticSource,
18 ExecutionAdapterCatalog, NativeApp, RequestCapability, RuntimeDiagnostics, RuntimeFailure,
19 ShutdownOutcome,
20};
21use tokio::sync::mpsc;
22
23use crate::TokioDriver;
24
25mod diagnostics;
26mod projection;
27mod transfer;
28
29pub use diagnostics::LaneDiagnosticsSnapshot;
30use diagnostics::LaneDiagnosticsState;
31use projection::{LaneProxyAdapter, project_lane};
32pub use transfer::CrossLaneRequestCatalog;
33
34const LANE_PROXY_EXECUTION_CLASS: &str = "lenso.native-lane-proxy@1";
35
36#[derive(Clone, Debug, Default)]
38pub struct LaneInvocationOptions {
39 timeout: Option<Duration>,
40 cancellation: Option<LaneCancellationToken>,
41}
42
43impl LaneInvocationOptions {
44 pub const fn new() -> Self {
46 Self {
47 timeout: None,
48 cancellation: None,
49 }
50 }
51
52 #[must_use]
54 pub const fn with_timeout(mut self, timeout: Duration) -> Self {
55 self.timeout = Some(timeout);
56 self
57 }
58
59 #[must_use]
61 pub fn with_cancellation(mut self, cancellation: LaneCancellationToken) -> Self {
62 self.cancellation = Some(cancellation);
63 self
64 }
65}
66
67#[derive(Clone, Debug, Default)]
69pub struct LaneCancellationToken {
70 cancelled: Arc<AtomicBool>,
71}
72
73impl LaneCancellationToken {
74 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn cancel(&self) {
81 self.cancelled.store(true, Ordering::Release);
82 }
83
84 pub fn is_cancelled(&self) -> bool {
86 self.cancelled.load(Ordering::Acquire)
87 }
88}
89
90type LaneTask = Box<dyn FnOnce(NativeApp) -> LocalBoxFuture<'static, ()> + Send + 'static>;
91type LaneSender = mpsc::Sender<LaneCommand>;
92type LaneRoute = mpsc::WeakSender<LaneCommand>;
93
94enum LaneCommand {
95 Run(LaneTask),
96 Shutdown {
97 timeout: Duration,
98 completed: oneshot::Sender<ShutdownOutcome>,
99 },
100}
101
102struct LaneHandle {
103 id: ExecutionLaneId,
104 commands: LaneSender,
105 thread: thread::JoinHandle<()>,
106}
107
108#[derive(Clone, Debug, Eq, PartialEq)]
110pub enum ReplicatedRunnerError {
111 InvalidPlan { detail: String },
113 LaneStartup { lane: String, detail: String },
115 MissingCrossLaneRequestTransfer { capability: String },
117 LaneUnavailable { lane: String },
119 LanePanicked { lane: String },
121 LaneShutdown {
123 lane: String,
124 outcome: ShutdownOutcome,
125 },
126}
127
128impl fmt::Display for ReplicatedRunnerError {
129 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Self::InvalidPlan { detail } => {
132 write!(formatter, "invalid Resolved App Plan: {detail}")
133 }
134 Self::LaneStartup { lane, detail } => {
135 write!(
136 formatter,
137 "Execution Lane `{lane}` failed to start: {detail}"
138 )
139 }
140 Self::MissingCrossLaneRequestTransfer { capability } => write!(
141 formatter,
142 "Capability `{capability}` has no registered native cross-lane request transfer"
143 ),
144 Self::LaneUnavailable { lane } => {
145 write!(formatter, "Execution Lane `{lane}` is unavailable")
146 }
147 Self::LanePanicked { lane } => write!(formatter, "Execution Lane `{lane}` panicked"),
148 Self::LaneShutdown { lane, outcome } => write!(
149 formatter,
150 "Execution Lane `{lane}` stopped with {outcome:?}"
151 ),
152 }
153 }
154}
155
156impl std::error::Error for ReplicatedRunnerError {}
157
158pub struct ReplicatedNativeApp {
160 plan: Arc<ResolvedAppPlan>,
161 lanes: BTreeMap<ExecutionLaneId, LaneHandle>,
162 diagnostics: Arc<LaneDiagnosticsState>,
163 epoch: Instant,
164}
165
166impl fmt::Debug for ReplicatedNativeApp {
167 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
168 formatter
169 .debug_struct("ReplicatedNativeApp")
170 .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
171 .finish_non_exhaustive()
172 }
173}
174
175impl ReplicatedNativeApp {
176 pub fn start<F>(plan: ResolvedAppPlan, adapters: F) -> Result<Self, ReplicatedRunnerError>
178 where
179 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
180 {
181 Self::start_with_transfers(plan, adapters, CrossLaneRequestCatalog::new())
182 }
183
184 pub fn start_with_transfers<F>(
186 plan: ResolvedAppPlan,
187 adapters: F,
188 transfers: CrossLaneRequestCatalog,
189 ) -> Result<Self, ReplicatedRunnerError>
190 where
191 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
192 {
193 plan.validate()
194 .map_err(|error| ReplicatedRunnerError::InvalidPlan {
195 detail: error.to_string(),
196 })?;
197 transfers.validate_plan(&plan)?;
198 let plan = Arc::new(plan);
199 let adapters = Arc::new(adapters);
200 let diagnostics = Arc::new(LaneDiagnosticsState::new(Arc::clone(&plan)));
201 let epoch = Instant::now();
202 let mut receivers = BTreeMap::new();
203 let senders = plan
204 .execution_lanes()
205 .iter()
206 .map(|lane| {
207 let (sender, receiver) = mpsc::channel(64);
208 receivers.insert(lane.id().clone(), receiver);
209 (lane.id().clone(), sender)
210 })
211 .collect::<BTreeMap<_, _>>();
212 let routes = Arc::new(
213 senders
214 .iter()
215 .map(|(lane, sender)| (lane.clone(), sender.downgrade()))
216 .collect::<BTreeMap<_, _>>(),
217 );
218 let projected = plan
219 .execution_lanes()
220 .iter()
221 .map(|lane| {
222 project_lane(&plan, lane.id()).map(|projected| (lane.id().clone(), projected))
223 })
224 .collect::<Result<Vec<_>, _>>()?;
225 let mut lanes = BTreeMap::new();
226 let mut startups = Vec::new();
227
228 for (lane_id, lane_plan) in projected {
229 let commands = senders
230 .get(&lane_id)
231 .expect("every declared lane has a command route")
232 .clone();
233 let receiver = receivers
234 .remove(&lane_id)
235 .expect("every declared lane has one command receiver");
236 let (started, startup) = std_mpsc::sync_channel(1);
237 let lane_adapters = Arc::clone(&adapters);
238 let lane_diagnostics = Arc::clone(&diagnostics);
239 let proxy_adapter = LaneProxyAdapter::new(
240 Arc::clone(&plan),
241 transfers.clone(),
242 Arc::clone(&routes),
243 epoch,
244 );
245 let thread_lane = lane_id.clone();
246 let lane_thread = match thread::Builder::new()
247 .name(format!("lenso-lane-{}", lane_id.as_str()))
248 .spawn(move || {
249 run_lane(
250 thread_lane,
251 lane_plan,
252 receiver,
253 started,
254 lane_adapters,
255 proxy_adapter,
256 lane_diagnostics,
257 epoch,
258 );
259 }) {
260 Ok(thread) => thread,
261 Err(error) => {
262 drop(receivers);
263 drop(routes);
264 drop(senders);
265 stop_lanes(lanes);
266 return Err(ReplicatedRunnerError::LaneStartup {
267 lane: lane_id.to_string(),
268 detail: error.to_string(),
269 });
270 }
271 };
272 startups.push((lane_id.clone(), startup));
273 lanes.insert(
274 lane_id.clone(),
275 LaneHandle {
276 id: lane_id,
277 commands,
278 thread: lane_thread,
279 },
280 );
281 }
282
283 for (lane, startup) in startups {
284 match startup.recv() {
285 Ok(Ok(())) => {}
286 Ok(Err(detail)) => {
287 drop(routes);
288 drop(senders);
289 stop_lanes(lanes);
290 return Err(ReplicatedRunnerError::LaneStartup {
291 lane: lane.to_string(),
292 detail,
293 });
294 }
295 Err(_) => {
296 drop(routes);
297 drop(senders);
298 stop_lanes(lanes);
299 return Err(ReplicatedRunnerError::LaneUnavailable {
300 lane: lane.to_string(),
301 });
302 }
303 }
304 }
305
306 Ok(Self {
307 plan,
308 lanes,
309 diagnostics,
310 epoch,
311 })
312 }
313
314 pub fn lane_count(&self) -> usize {
316 self.lanes.len()
317 }
318
319 pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
321 self.diagnostics.snapshot()
322 }
323
324 pub async fn invoke<C: RequestCapability>(
326 &self,
327 caller_instance: &str,
328 operation: &str,
329 request: C::Request,
330 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
331 where
332 C::Request: Send,
333 C::Response: Send,
334 C::DomainError: Send,
335 {
336 self.invoke_with_options::<C>(
337 caller_instance,
338 operation,
339 request,
340 LaneInvocationOptions::new(),
341 )
342 .await
343 }
344
345 pub async fn invoke_with_options<C: RequestCapability>(
347 &self,
348 caller_instance: &str,
349 operation: &str,
350 request: C::Request,
351 options: LaneInvocationOptions,
352 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
353 where
354 C::Request: Send,
355 C::Response: Send,
356 C::DomainError: Send,
357 {
358 let _ = singular_binding::<C>(&self.plan, caller_instance)?;
359 let consumer = self.plan.module_instance(caller_instance).ok_or_else(|| {
360 RuntimeFailure::InvalidResolvedPlan {
361 detail: format!("binding consumer `{caller_instance}` is absent from the Plan"),
362 }
363 })?;
364 let lane =
365 self.lanes
366 .get(consumer.execution_lane())
367 .ok_or_else(|| RuntimeFailure::Internal {
368 detail: format!(
369 "Execution Lane `{}` is unavailable",
370 consumer.execution_lane()
371 ),
372 })?;
373 let caller_instance = caller_instance.to_owned();
374 let operation = operation.to_owned();
375 let deadline = options
376 .timeout
377 .map(|timeout| self.epoch.elapsed().saturating_add(timeout));
378 let (completed, completion) = oneshot::channel();
379 lane.commands
380 .send(LaneCommand::Run(Box::new(move |app| {
381 Box::pin(async move {
382 let cancellation = CancellationToken::new();
383 let completed_signal = Arc::new(AtomicBool::new(false));
384 if let Some(external) = options.cancellation.clone() {
385 let local = cancellation.clone();
386 let watcher_completed = Arc::clone(&completed_signal);
387 tokio::task::spawn_local(async move {
388 while !external.is_cancelled()
389 && !watcher_completed.load(Ordering::Acquire)
390 {
391 tokio::task::yield_now().await;
392 }
393 if external.is_cancelled() {
394 local.cancel();
395 }
396 });
397 }
398 let result = if deadline.is_some() || options.cancellation.is_some() {
399 let context = app.invocation_context(deadline, cancellation);
400 app.invoke_with_context::<C>(&caller_instance, &operation, context, request)
401 .await
402 } else {
403 app.invoke::<C>(&caller_instance, &operation, request).await
404 };
405 completed_signal.store(true, Ordering::Release);
406 let _ = completed.send(result);
407 })
408 })))
409 .await
410 .map_err(|_| RuntimeFailure::Internal {
411 detail: format!("Execution Lane `{}` is unavailable", lane.id),
412 })?;
413 completion.await.map_err(|_| RuntimeFailure::Internal {
414 detail: format!("Execution Lane `{}` dropped an invocation", lane.id),
415 })?
416 }
417
418 pub async fn shutdown(self, timeout: Duration) -> Result<(), ReplicatedRunnerError> {
420 let mut completions = Vec::new();
421 let mut threads = Vec::new();
422 let mut first_error = None;
423 for (_, lane) in self.lanes {
424 let LaneHandle {
425 id,
426 commands,
427 thread,
428 } = lane;
429 let (completed, completion) = oneshot::channel();
430 if commands
431 .send(LaneCommand::Shutdown { timeout, completed })
432 .await
433 .is_ok()
434 {
435 completions.push((id.clone(), completion));
436 } else if first_error.is_none() {
437 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
438 lane: id.to_string(),
439 });
440 }
441 drop(commands);
442 threads.push((id, thread));
443 }
444
445 for (lane, completion) in completions {
446 match completion.await {
447 Ok(ShutdownOutcome::Clean) => {}
448 Ok(outcome) if first_error.is_none() => {
449 first_error = Some(ReplicatedRunnerError::LaneShutdown {
450 lane: lane.to_string(),
451 outcome,
452 });
453 }
454 Err(_) if first_error.is_none() => {
455 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
456 lane: lane.to_string(),
457 });
458 }
459 _ => {}
460 }
461 }
462 for (lane, thread) in threads {
463 if thread.join().is_err() && first_error.is_none() {
464 first_error = Some(ReplicatedRunnerError::LanePanicked {
465 lane: lane.to_string(),
466 });
467 }
468 }
469 match first_error {
470 Some(error) => Err(error),
471 None => Ok(()),
472 }
473 }
474}
475
476fn stop_lanes(lanes: BTreeMap<ExecutionLaneId, LaneHandle>) {
477 let mut threads = Vec::new();
478 for (_, lane) in lanes {
479 let (completed, _) = oneshot::channel();
480 let _ = lane.commands.try_send(LaneCommand::Shutdown {
481 timeout: Duration::from_secs(1),
482 completed,
483 });
484 threads.push(lane.thread);
485 }
486 for thread in threads {
487 let _ = thread.join();
488 }
489}
490
491fn singular_binding<'a, C: RequestCapability>(
492 plan: &'a ResolvedAppPlan,
493 caller_instance: &str,
494) -> Result<&'a CapabilityBinding, RuntimeFailure> {
495 let bindings = plan
496 .capability_bindings()
497 .iter()
498 .filter(|binding| {
499 binding.consumer_instance() == caller_instance && binding.capability_id() == C::ID
500 })
501 .collect::<Vec<_>>();
502 match bindings.as_slice() {
503 [binding] => Ok(*binding),
504 [] => Err(RuntimeFailure::Unavailable { capability: C::ID }),
505 bindings => Err(RuntimeFailure::AmbiguousBinding {
506 capability: C::ID,
507 providers: bindings.len(),
508 }),
509 }
510}
511
512fn run_lane<F>(
513 lane: ExecutionLaneId,
514 plan: ResolvedAppPlan,
515 mut commands: mpsc::Receiver<LaneCommand>,
516 started: std_mpsc::SyncSender<Result<(), String>>,
517 adapters: Arc<F>,
518 proxy_adapter: LaneProxyAdapter,
519 diagnostics: Arc<LaneDiagnosticsState>,
520 epoch: Instant,
521) where
522 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
523{
524 let runtime = match tokio::runtime::Builder::new_current_thread()
525 .enable_all()
526 .build()
527 {
528 Ok(runtime) => runtime,
529 Err(error) => {
530 let _ = started.send(Err(error.to_string()));
531 return;
532 }
533 };
534 let local = tokio::task::LocalSet::new();
535 local.block_on(&runtime, async move {
536 let cpu_started = ThreadTime::now();
537 let catalog = match adapters(&lane).with_adapter(proxy_adapter) {
538 Ok(catalog) => catalog,
539 Err(error) => {
540 let _ = started.send(Err(error.to_string()));
541 return;
542 }
543 };
544 let driver = TokioDriver::with_epoch(epoch);
545 let runtime_diagnostics = RuntimeDiagnostics::new();
546 let observer = runtime_diagnostics
547 .subscribe(DiagnosticFilter::only(DiagnosticSource::Invocation), 2048)
548 .expect("diagnostics capacity is positive");
549 let app = match lenso_kernel::Kernel::start_with_diagnostics(
550 plan,
551 driver,
552 catalog,
553 runtime_diagnostics,
554 )
555 .await
556 {
557 Ok(app) => app,
558 Err(error) => {
559 let _ = started.send(Err(format!("{error:?}")));
560 return;
561 }
562 };
563 let _ = started.send(Ok(()));
564 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
565 let mut sample_interval = tokio::time::interval(Duration::from_millis(10));
566
567 loop {
568 tokio::select! {
569 command = commands.recv() => match command {
570 Some(LaneCommand::Run(task)) => {
571 tokio::task::spawn_local(task(app.clone()));
572 }
573 Some(LaneCommand::Shutdown { timeout, completed }) => {
574 let outcome = app.shutdown(timeout).await;
575 let _ = completed.send(outcome);
576 break;
577 }
578 None => {
579 let _ = app.shutdown(Duration::from_secs(1)).await;
580 break;
581 }
582 },
583 _ = sample_interval.tick() => {
584 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
585 }
586 }
587 while let Some(record) = observer.try_recv() {
588 if let DiagnosticEvent::InvocationStarted {
589 caller_instance: Some(caller),
590 provider_instance: Some(provider),
591 ..
592 } = record.event
593 {
594 diagnostics.record_invocation(&lane, &caller, &provider);
595 }
596 }
597 }
598 });
599}