1use std::{
2 any::Any,
3 collections::BTreeMap,
4 fmt,
5 future::Future,
6 marker::PhantomData,
7 pin::Pin,
8 rc::Rc,
9 sync::{
10 Arc,
11 atomic::{AtomicBool, Ordering},
12 },
13 task::{Context, Poll},
14 time::Instant,
15};
16
17use super::{LaneRoute, LaneTask};
18use futures::{future::LocalBoxFuture, task::AtomicWaker};
19use lenso_app_plan::ResolvedAppPlan;
20use lenso_kernel::{
21 CancellationToken, InvocationContext, NativeRequestEndpoint, NativeRequestFuture,
22 RequestCapability, RuntimeFailure, TypedNativeRequestEndpoint,
23};
24
25trait RequestTransferFactory: fmt::Debug + Send + Sync {
26 fn endpoint(&self, provider_lane: LaneRoute, epoch: Instant) -> Rc<dyn NativeRequestEndpoint>;
27}
28
29struct TypedRequestTransferFactory<C: RequestCapability> {
30 operations: &'static [&'static str],
31 capability: PhantomData<fn() -> C>,
32}
33
34impl<C: RequestCapability> fmt::Debug for TypedRequestTransferFactory<C> {
35 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36 formatter
37 .debug_struct("TypedRequestTransferFactory")
38 .field("capability", &C::ID)
39 .finish_non_exhaustive()
40 }
41}
42
43impl<C> RequestTransferFactory for TypedRequestTransferFactory<C>
44where
45 C: RequestCapability,
46 C::Request: Send,
47 C::Response: Send,
48 C::DomainError: Send,
49{
50 fn endpoint(&self, provider_lane: LaneRoute, epoch: Instant) -> Rc<dyn NativeRequestEndpoint> {
51 let typed_provider_lane = provider_lane.clone();
52 let operations = self.operations;
53 Rc::new(CrossLaneRequestEndpoint::<C> {
54 operations: self.operations,
55 typed: TypedNativeRequestEndpoint::new(move |operation, request, context| {
56 let Some(operation) = operations
57 .iter()
58 .copied()
59 .find(|candidate| *candidate == operation)
60 else {
61 return Box::pin(futures::future::ready(Err(
62 RuntimeFailure::UnknownOperation {
63 capability: C::ID,
64 operation: operation.to_owned(),
65 },
66 )));
67 };
68 invoke_cross_lane::<C>(
69 typed_provider_lane.clone(),
70 epoch,
71 operation,
72 request,
73 context,
74 )
75 }),
76 })
77 }
78}
79
80#[derive(Clone, Debug, Default)]
82pub struct CrossLaneRequestCatalog {
83 factories: BTreeMap<&'static str, Arc<dyn RequestTransferFactory>>,
84}
85
86impl CrossLaneRequestCatalog {
87 pub fn new() -> Self {
89 Self::default()
90 }
91
92 #[must_use]
94 pub fn with_request<C>(mut self, operations: &'static [&'static str]) -> Self
95 where
96 C: RequestCapability,
97 C::Request: Send,
98 C::Response: Send,
99 C::DomainError: Send,
100 {
101 self.factories.insert(
102 C::ID,
103 Arc::new(TypedRequestTransferFactory::<C> {
104 operations,
105 capability: PhantomData,
106 }),
107 );
108 self
109 }
110
111 pub(super) fn contains(&self, capability_id: &str) -> bool {
112 self.factories.contains_key(capability_id)
113 }
114
115 pub(super) fn validate_plan(
116 &self,
117 plan: &ResolvedAppPlan,
118 ) -> Result<(), super::ReplicatedRunnerError> {
119 for binding in plan.capability_bindings() {
120 let consumer = plan
121 .module_instance(binding.consumer_instance())
122 .expect("validated binding consumer should exist");
123 let provider = plan
124 .module_instance(binding.provider_instance())
125 .expect("validated binding provider should exist");
126 let endpoint = provider
127 .provided_capabilities()
128 .iter()
129 .find(|endpoint| endpoint.capability_id() == binding.capability_id())
130 .expect("validated provider endpoint should exist");
131 if consumer.execution_lane() != provider.execution_lane()
132 && !endpoint.request_operations().is_empty()
133 && !self.contains(binding.capability_id())
134 {
135 return Err(
136 super::ReplicatedRunnerError::MissingCrossLaneRequestTransfer {
137 capability: binding.capability_id().to_owned(),
138 },
139 );
140 }
141 }
142 Ok(())
143 }
144
145 pub(super) fn endpoint(
146 &self,
147 capability_id: &str,
148 provider_lane: LaneRoute,
149 epoch: Instant,
150 ) -> Option<Rc<dyn NativeRequestEndpoint>> {
151 self.factories
152 .get(capability_id)
153 .map(|factory| factory.endpoint(provider_lane, epoch))
154 }
155}
156
157struct CrossLaneRequestEndpoint<C: RequestCapability> {
158 operations: &'static [&'static str],
159 typed: TypedNativeRequestEndpoint<C>,
160}
161
162impl<C: RequestCapability> fmt::Debug for CrossLaneRequestEndpoint<C> {
163 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
164 formatter
165 .debug_struct("CrossLaneRequestEndpoint")
166 .field("capability", &C::ID)
167 .finish_non_exhaustive()
168 }
169}
170
171impl<C> NativeRequestEndpoint for CrossLaneRequestEndpoint<C>
172where
173 C: RequestCapability,
174 C::Request: Send,
175 C::Response: Send,
176 C::DomainError: Send,
177{
178 fn capability_id(&self) -> &'static str {
179 C::ID
180 }
181
182 fn descriptor_version(&self) -> &'static str {
183 C::DESCRIPTOR_VERSION
184 }
185
186 fn operations(&self) -> &'static [&'static str] {
187 self.operations
188 }
189
190 fn typed_endpoint(&self) -> Option<&dyn Any> {
191 Some(&self.typed)
192 }
193
194 fn invoke(
195 &self,
196 operation: &str,
197 request: Box<dyn Any>,
198 context: InvocationContext,
199 ) -> LocalBoxFuture<'static, Result<Result<Box<dyn Any>, Box<dyn Any>>, RuntimeFailure>> {
200 let Ok(request) = request.downcast::<C::Request>() else {
201 return Box::pin(futures::future::ready(Err(
202 RuntimeFailure::ProtocolViolation { capability: C::ID },
203 )));
204 };
205 let invocation = self.typed.invoke(operation, *request, context);
206 Box::pin(async move {
207 let result = invocation.await?;
208 Ok(result
209 .map(|response| Box::new(response) as Box<dyn Any>)
210 .map_err(|error| Box::new(error) as Box<dyn Any>))
211 })
212 }
213}
214
215fn invoke_cross_lane<C>(
216 provider_lane: LaneRoute,
217 epoch: Instant,
218 operation: &'static str,
219 request: C::Request,
220 context: InvocationContext,
221) -> NativeRequestFuture<C>
222where
223 C: RequestCapability,
224 C::Request: Send,
225 C::Response: Send,
226 C::DomainError: Send,
227{
228 Box::pin(async move {
229 let provider_lane = provider_lane.upgrade().ok_or_else(lane_unavailable::<C>)?;
230 let caller_instance = context
231 .caller_instance()
232 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
233 detail: format!("cross-lane invocation of `{}` has no planned caller", C::ID),
234 })?
235 .to_owned();
236 let cancellation = context.cancellation();
237 let deadline = context.deadline();
238 let request_id = context.request_id();
239 let (transferred, cancellation_signal) = TransferredInvocationContext::capture(&context);
240 let mut cancellation_guard = TransferredCancellationGuard::new(cancellation_signal);
241 let (completed, completion) = futures::channel::oneshot::channel();
242 let command: LaneTask = Box::new(move |lane| {
243 tokio::task::spawn_local(async move {
244 let local_cancellation = CancellationToken::new();
245 let (context, transferred_cancellation) =
246 transferred.restore(local_cancellation.clone());
247 if transferred_cancellation.is_cancelled() {
248 local_cancellation.cancel();
249 }
250 let handle = match lane.request_handle::<C>(&caller_instance) {
251 Ok(handle) => handle,
252 Err(error) => {
253 let _ = completed.send(Err(error));
254 return;
255 }
256 };
257 let invocation = handle.invoke_with_context(operation, context, request);
258 tokio::pin!(invocation);
259 let result = if local_cancellation.is_cancelled() {
260 invocation.await
261 } else {
262 tokio::select! {
263 result = &mut invocation => result,
264 () = transferred_cancellation.cancelled() => {
265 local_cancellation.cancel();
266 invocation.await
267 }
268 }
269 };
270 let _ = completed.send(result);
271 });
272 });
273
274 let send = provider_lane.send(command);
275 tokio::pin!(send);
276 let cancelled = cancellation.cancelled();
277 tokio::pin!(cancelled);
278 let result = if let Some(deadline) = deadline {
279 let sleep = tokio::time::sleep_until((epoch + deadline).into());
280 tokio::pin!(sleep);
281 tokio::select! {
282 result = &mut send => result.map_err(|_| lane_unavailable::<C>())?,
283 () = &mut cancelled => {
284 cancellation_guard.cancel();
285 return Err(RuntimeFailure::Cancelled { request_id });
286 }
287 () = &mut sleep => {
288 cancellation_guard.cancel();
289 return Err(RuntimeFailure::DeadlineExceeded { request_id });
290 }
291 }
292 tokio::select! {
293 biased;
294 result = completion => result.map_err(|_| lane_unavailable::<C>())?,
295 () = &mut cancelled => {
296 cancellation_guard.cancel();
297 Err(RuntimeFailure::Cancelled { request_id })
298 }
299 () = &mut sleep => {
300 cancellation_guard.cancel();
301 Err(RuntimeFailure::DeadlineExceeded { request_id })
302 }
303 }
304 } else {
305 tokio::select! {
306 result = &mut send => result.map_err(|_| lane_unavailable::<C>())?,
307 () = &mut cancelled => {
308 cancellation_guard.cancel();
309 return Err(RuntimeFailure::Cancelled { request_id });
310 }
311 }
312 tokio::select! {
313 biased;
314 result = completion => result.map_err(|_| lane_unavailable::<C>())?,
315 () = &mut cancelled => {
316 cancellation_guard.cancel();
317 Err(RuntimeFailure::Cancelled { request_id })
318 }
319 }
320 };
321 cancellation_guard.disarm();
322 result
323 })
324}
325
326fn lane_unavailable<C: RequestCapability>() -> RuntimeFailure {
327 RuntimeFailure::Internal {
328 detail: format!("provider lane for `{}` is unavailable", C::ID),
329 }
330}
331
332#[derive(Debug)]
333pub(super) struct TransferredInvocationContext {
334 request_id: u64,
335 deadline: Option<std::time::Duration>,
336 cancellation: Arc<TransferredCancellation>,
337 extensions: Vec<lenso_kernel::InvocationExtension>,
338 sealed_extensions: Vec<lenso_kernel::SealedInvocationExtension>,
339}
340
341impl TransferredInvocationContext {
342 pub(super) fn capture(context: &InvocationContext) -> (Self, Arc<TransferredCancellation>) {
343 let cancellation = Arc::new(TransferredCancellation::new(context.is_cancelled()));
344 (
345 Self {
346 request_id: context.request_id(),
347 deadline: context.deadline(),
348 cancellation: Arc::clone(&cancellation),
349 extensions: context.extensions().cloned().collect(),
350 sealed_extensions: context.sealed_extensions().cloned().collect(),
351 },
352 cancellation,
353 )
354 }
355
356 pub(super) fn restore(
357 self,
358 cancellation: CancellationToken,
359 ) -> (InvocationContext, Arc<TransferredCancellation>) {
360 let mut context = InvocationContext::new(self.request_id, self.deadline, cancellation);
361 for extension in self.extensions {
362 context = context
363 .with_extension(extension.key(), extension.value().to_vec())
364 .expect("captured ordinary Invocation Context extension remains valid");
365 }
366 for extension in self.sealed_extensions {
367 context = context
368 .with_sealed_extension(extension)
369 .expect("captured sealed Invocation Context extension remains valid");
370 }
371 (context, self.cancellation)
372 }
373}
374
375#[derive(Debug)]
376pub(super) struct TransferredCancellation {
377 cancelled: AtomicBool,
379 waker: AtomicWaker,
380}
381
382#[derive(Debug)]
384pub(super) struct TransferredCancellationGuard {
385 cancellation: Arc<TransferredCancellation>,
386 armed: bool,
387}
388
389impl TransferredCancellationGuard {
390 pub(super) fn new(cancellation: Arc<TransferredCancellation>) -> Self {
391 Self {
392 cancellation,
393 armed: true,
394 }
395 }
396
397 pub(super) fn cancellation(&self) -> Arc<TransferredCancellation> {
398 Arc::clone(&self.cancellation)
399 }
400
401 pub(super) fn cancel(&self) {
402 self.cancellation.cancel();
403 }
404
405 pub(super) fn disarm(&mut self) {
406 self.armed = false;
407 }
408}
409
410impl Drop for TransferredCancellationGuard {
411 fn drop(&mut self) {
412 if self.armed {
413 self.cancellation.cancel();
414 }
415 }
416}
417
418impl TransferredCancellation {
419 fn new(cancelled: bool) -> Self {
420 Self {
421 cancelled: AtomicBool::new(cancelled),
422 waker: AtomicWaker::new(),
423 }
424 }
425
426 pub(super) fn is_cancelled(&self) -> bool {
427 self.cancelled.load(Ordering::Acquire)
428 }
429
430 pub(super) fn cancel(&self) {
431 if !self.cancelled.swap(true, Ordering::AcqRel) {
432 self.waker.wake();
433 }
434 }
435
436 pub(super) fn cancelled(&self) -> TransferredCancellationFuture<'_> {
437 TransferredCancellationFuture { cancellation: self }
438 }
439}
440
441pub(super) struct TransferredCancellationFuture<'a> {
442 cancellation: &'a TransferredCancellation,
443}
444
445impl Future for TransferredCancellationFuture<'_> {
446 type Output = ();
447
448 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
449 if self.cancellation.is_cancelled() {
450 return Poll::Ready(());
451 }
452 self.cancellation.waker.register(context.waker());
453 if self.cancellation.is_cancelled() {
454 Poll::Ready(())
455 } else {
456 Poll::Pending
457 }
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use std::sync::Arc;
464
465 use super::TransferredCancellation;
466
467 #[tokio::test(flavor = "current_thread")]
468 async fn transferred_cancellation_observes_an_initial_signal() {
469 let cancellation = TransferredCancellation::new(true);
470
471 cancellation.cancelled().await;
472
473 assert!(cancellation.is_cancelled());
474 }
475
476 #[tokio::test(flavor = "current_thread")]
477 async fn transferred_cancellation_wakes_the_provider_waiter() {
478 let cancellation = Arc::new(TransferredCancellation::new(false));
479 let canceller = Arc::clone(&cancellation);
480
481 tokio::join!(cancellation.cancelled(), async move {
482 tokio::task::yield_now().await;
483 canceller.cancel();
484 });
485
486 assert!(cancellation.is_cancelled());
487 }
488}