Skip to main content

lenso_runner/replicated/
transfer.rs

1use std::{
2    any::Any, collections::BTreeMap, fmt, marker::PhantomData, rc::Rc, sync::Arc, time::Instant,
3};
4
5use futures::future::LocalBoxFuture;
6use lenso_app_plan::ResolvedAppPlan;
7use lenso_kernel::{
8    CancellationToken, InvocationContext, NativeRequestEndpoint, NativeRequestFuture,
9    RequestCapability, RuntimeFailure, TypedNativeRequestEndpoint,
10};
11use tokio::sync::watch;
12
13use super::{LaneRoute, LaneTask};
14
15trait RequestTransferFactory: fmt::Debug + Send + Sync {
16    fn endpoint(&self, provider_lane: LaneRoute, epoch: Instant) -> Rc<dyn NativeRequestEndpoint>;
17}
18
19struct TypedRequestTransferFactory<C: RequestCapability> {
20    operations: &'static [&'static str],
21    capability: PhantomData<fn() -> C>,
22}
23
24impl<C: RequestCapability> fmt::Debug for TypedRequestTransferFactory<C> {
25    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26        formatter
27            .debug_struct("TypedRequestTransferFactory")
28            .field("capability", &C::ID)
29            .finish_non_exhaustive()
30    }
31}
32
33impl<C> RequestTransferFactory for TypedRequestTransferFactory<C>
34where
35    C: RequestCapability,
36    C::Request: Send,
37    C::Response: Send,
38    C::DomainError: Send,
39{
40    fn endpoint(&self, provider_lane: LaneRoute, epoch: Instant) -> Rc<dyn NativeRequestEndpoint> {
41        let typed_provider_lane = provider_lane.clone();
42        let operations = self.operations;
43        Rc::new(CrossLaneRequestEndpoint::<C> {
44            operations: self.operations,
45            typed: TypedNativeRequestEndpoint::new(move |operation, request, context| {
46                let Some(operation) = operations
47                    .iter()
48                    .copied()
49                    .find(|candidate| *candidate == operation)
50                else {
51                    return Box::pin(futures::future::ready(Err(
52                        RuntimeFailure::UnknownOperation {
53                            capability: C::ID,
54                            operation: operation.to_owned(),
55                        },
56                    )));
57                };
58                invoke_cross_lane::<C>(
59                    typed_provider_lane.clone(),
60                    epoch,
61                    operation,
62                    request,
63                    context,
64                )
65            }),
66        })
67    }
68}
69
70/// Native request types registered for zero-serialization cross-lane transfer.
71#[derive(Clone, Debug, Default)]
72pub struct CrossLaneRequestCatalog {
73    factories: BTreeMap<&'static str, Arc<dyn RequestTransferFactory>>,
74}
75
76impl CrossLaneRequestCatalog {
77    /// Creates an empty catalog.
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    /// Registers one generated request Capability whose values are `Send`.
83    #[must_use]
84    pub fn with_request<C>(mut self, operations: &'static [&'static str]) -> Self
85    where
86        C: RequestCapability,
87        C::Request: Send,
88        C::Response: Send,
89        C::DomainError: Send,
90    {
91        self.factories.insert(
92            C::ID,
93            Arc::new(TypedRequestTransferFactory::<C> {
94                operations,
95                capability: PhantomData,
96            }),
97        );
98        self
99    }
100
101    pub(super) fn contains(&self, capability_id: &str) -> bool {
102        self.factories.contains_key(capability_id)
103    }
104
105    pub(super) fn validate_plan(
106        &self,
107        plan: &ResolvedAppPlan,
108    ) -> Result<(), super::ReplicatedRunnerError> {
109        for binding in plan.capability_bindings() {
110            let consumer = plan
111                .module_instance(binding.consumer_instance())
112                .expect("validated binding consumer should exist");
113            let provider = plan
114                .module_instance(binding.provider_instance())
115                .expect("validated binding provider should exist");
116            let endpoint = provider
117                .provided_capabilities()
118                .iter()
119                .find(|endpoint| endpoint.capability_id() == binding.capability_id())
120                .expect("validated provider endpoint should exist");
121            if consumer.execution_lane() != provider.execution_lane()
122                && !endpoint.request_operations().is_empty()
123                && !self.contains(binding.capability_id())
124            {
125                return Err(
126                    super::ReplicatedRunnerError::MissingCrossLaneRequestTransfer {
127                        capability: binding.capability_id().to_owned(),
128                    },
129                );
130            }
131        }
132        Ok(())
133    }
134
135    pub(super) fn endpoint(
136        &self,
137        capability_id: &str,
138        provider_lane: LaneRoute,
139        epoch: Instant,
140    ) -> Option<Rc<dyn NativeRequestEndpoint>> {
141        self.factories
142            .get(capability_id)
143            .map(|factory| factory.endpoint(provider_lane, epoch))
144    }
145}
146
147struct CrossLaneRequestEndpoint<C: RequestCapability> {
148    operations: &'static [&'static str],
149    typed: TypedNativeRequestEndpoint<C>,
150}
151
152impl<C: RequestCapability> fmt::Debug for CrossLaneRequestEndpoint<C> {
153    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
154        formatter
155            .debug_struct("CrossLaneRequestEndpoint")
156            .field("capability", &C::ID)
157            .finish_non_exhaustive()
158    }
159}
160
161impl<C> NativeRequestEndpoint for CrossLaneRequestEndpoint<C>
162where
163    C: RequestCapability,
164    C::Request: Send,
165    C::Response: Send,
166    C::DomainError: Send,
167{
168    fn capability_id(&self) -> &'static str {
169        C::ID
170    }
171
172    fn descriptor_version(&self) -> &'static str {
173        C::DESCRIPTOR_VERSION
174    }
175
176    fn operations(&self) -> &'static [&'static str] {
177        self.operations
178    }
179
180    fn typed_endpoint(&self) -> Option<&dyn Any> {
181        Some(&self.typed)
182    }
183
184    fn invoke(
185        &self,
186        operation: &str,
187        request: Box<dyn Any>,
188        context: InvocationContext,
189    ) -> LocalBoxFuture<'static, Result<Result<Box<dyn Any>, Box<dyn Any>>, RuntimeFailure>> {
190        let Ok(request) = request.downcast::<C::Request>() else {
191            return Box::pin(futures::future::ready(Err(
192                RuntimeFailure::ProtocolViolation { capability: C::ID },
193            )));
194        };
195        let invocation = self.typed.invoke(operation, *request, context);
196        Box::pin(async move {
197            let result = invocation.await?;
198            Ok(result
199                .map(|response| Box::new(response) as Box<dyn Any>)
200                .map_err(|error| Box::new(error) as Box<dyn Any>))
201        })
202    }
203}
204
205fn invoke_cross_lane<C>(
206    provider_lane: LaneRoute,
207    epoch: Instant,
208    operation: &'static str,
209    request: C::Request,
210    context: InvocationContext,
211) -> NativeRequestFuture<C>
212where
213    C: RequestCapability,
214    C::Request: Send,
215    C::Response: Send,
216    C::DomainError: Send,
217{
218    Box::pin(async move {
219        let provider_lane = provider_lane.upgrade().ok_or_else(lane_unavailable::<C>)?;
220        let caller_instance = context
221            .caller_instance()
222            .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
223                detail: format!("cross-lane invocation of `{}` has no planned caller", C::ID),
224            })?
225            .to_owned();
226        let cancellation = context.cancellation();
227        let deadline = context.deadline();
228        let request_id = context.request_id();
229        let (transferred, cancellation_signal) = TransferredInvocationContext::capture(&context);
230        let (completed, completion) = futures::channel::oneshot::channel();
231        let command: LaneTask = Box::new(move |lane| {
232            tokio::task::spawn_local(async move {
233                let local_cancellation = CancellationToken::new();
234                let (context, mut transferred_cancellation) =
235                    transferred.restore(local_cancellation.clone());
236                if *transferred_cancellation.borrow() {
237                    local_cancellation.cancel();
238                }
239                let handle = match lane.request_handle::<C>(&caller_instance) {
240                    Ok(handle) => handle,
241                    Err(error) => {
242                        let _ = completed.send(Err(error));
243                        return;
244                    }
245                };
246                let invocation = handle.invoke_with_context(operation, context, request);
247                tokio::pin!(invocation);
248                let result = if local_cancellation.is_cancelled() {
249                    invocation.await
250                } else {
251                    tokio::select! {
252                        result = &mut invocation => result,
253                        () = wait_for_cancellation(&mut transferred_cancellation) => {
254                            local_cancellation.cancel();
255                            invocation.await
256                        }
257                    }
258                };
259                let _ = completed.send(result);
260            });
261        });
262
263        let send = provider_lane.send(command);
264        tokio::pin!(send);
265        let cancelled = cancellation.cancelled();
266        tokio::pin!(cancelled);
267        if let Some(deadline) = deadline {
268            let sleep = tokio::time::sleep_until((epoch + deadline).into());
269            tokio::pin!(sleep);
270            tokio::select! {
271                result = &mut send => result.map_err(|_| lane_unavailable::<C>())?,
272                () = &mut cancelled => {
273                    cancellation_signal.send_replace(true);
274                    return Err(RuntimeFailure::Cancelled { request_id });
275                }
276                () = &mut sleep => {
277                    cancellation_signal.send_replace(true);
278                    return Err(RuntimeFailure::DeadlineExceeded { request_id });
279                }
280            }
281            tokio::select! {
282                biased;
283                result = completion => result.map_err(|_| lane_unavailable::<C>())?,
284                () = &mut cancelled => {
285                    cancellation_signal.send_replace(true);
286                    Err(RuntimeFailure::Cancelled { request_id })
287                }
288                () = &mut sleep => {
289                    cancellation_signal.send_replace(true);
290                    Err(RuntimeFailure::DeadlineExceeded { request_id })
291                }
292            }
293        } else {
294            tokio::select! {
295                result = &mut send => result.map_err(|_| lane_unavailable::<C>())?,
296                () = &mut cancelled => {
297                    cancellation_signal.send_replace(true);
298                    return Err(RuntimeFailure::Cancelled { request_id });
299                }
300            }
301            tokio::select! {
302                biased;
303                result = completion => result.map_err(|_| lane_unavailable::<C>())?,
304                () = &mut cancelled => {
305                    cancellation_signal.send_replace(true);
306                    Err(RuntimeFailure::Cancelled { request_id })
307                }
308            }
309        }
310    })
311}
312
313fn lane_unavailable<C: RequestCapability>() -> RuntimeFailure {
314    RuntimeFailure::Internal {
315        detail: format!("provider lane for `{}` is unavailable", C::ID),
316    }
317}
318
319#[derive(Debug)]
320struct TransferredInvocationContext {
321    request_id: u64,
322    deadline: Option<std::time::Duration>,
323    cancellation: watch::Receiver<bool>,
324    extensions: Vec<lenso_kernel::InvocationExtension>,
325    sealed_extensions: Vec<lenso_kernel::SealedInvocationExtension>,
326}
327
328impl TransferredInvocationContext {
329    fn capture(context: &InvocationContext) -> (Self, watch::Sender<bool>) {
330        let (cancellation_signal, cancellation) = watch::channel(context.is_cancelled());
331        (
332            Self {
333                request_id: context.request_id(),
334                deadline: context.deadline(),
335                cancellation,
336                extensions: context.extensions().cloned().collect(),
337                sealed_extensions: context.sealed_extensions().cloned().collect(),
338            },
339            cancellation_signal,
340        )
341    }
342
343    fn restore(
344        self,
345        cancellation: CancellationToken,
346    ) -> (InvocationContext, watch::Receiver<bool>) {
347        let mut context = InvocationContext::new(self.request_id, self.deadline, cancellation);
348        for extension in self.extensions {
349            context = context
350                .with_extension(extension.key(), extension.value().to_vec())
351                .expect("captured ordinary Invocation Context extension remains valid");
352        }
353        for extension in self.sealed_extensions {
354            context = context
355                .with_sealed_extension(extension)
356                .expect("captured sealed Invocation Context extension remains valid");
357        }
358        (context, self.cancellation)
359    }
360}
361
362async fn wait_for_cancellation(cancellation: &mut watch::Receiver<bool>) {
363    loop {
364        if *cancellation.borrow_and_update() {
365            return;
366        }
367        if cancellation.changed().await.is_err() {
368            return;
369        }
370    }
371}