Skip to main content

fakecloud_lambda/runtime/
backend.rs

1//! Pluggable Lambda execution backend abstraction.
2//!
3//! The facade in [`super::facade::LambdaRuntime`] owns the warm-pool
4//! bookkeeping, per-function startup serialization, and the HTTP
5//! invocation logic that's identical across backends. Backends only
6//! implement how to bring up a fresh runtime instance for a function
7//! (Docker container, Kubernetes pod, ...), how to tear it down, and
8//! optionally how to sweep instances left behind by a previous
9//! fakecloud process.
10
11use async_trait::async_trait;
12
13use crate::state::LambdaFunction;
14
15#[derive(Debug, thiserror::Error)]
16pub enum RuntimeError {
17    #[error("no code ZIP provided for function {0}")]
18    NoCodeZip(String),
19    #[error("unsupported runtime: {0}")]
20    UnsupportedRuntime(String),
21    #[error("container failed to start: {0}")]
22    ContainerStartFailed(String),
23    #[error("invocation failed: {0}")]
24    InvocationFailed(String),
25    #[error("ZIP extraction failed: {0}")]
26    ZipExtractionFailed(String),
27}
28
29/// Opaque per-backend identifier for a launched runtime instance. The
30/// facade hands this back to the backend on teardown so the backend can
31/// find the right resource to delete.
32#[derive(Debug, Clone)]
33pub enum BackendHandle {
34    Container { id: String },
35    Pod { namespace: String, name: String },
36}
37
38/// What [`LambdaBackend::launch`] returns. `endpoint` is the `host:port`
39/// the facade POSTs invocation payloads to; `handle` is the
40/// backend-specific identifier handed back on `terminate`.
41#[derive(Debug, Clone)]
42pub struct WarmInstance {
43    pub endpoint: String,
44    pub handle: BackendHandle,
45}
46
47/// Wrapper around an in-flight streaming invocation. Yields raw body
48/// chunks via [`Self::next_chunk`] until the RIE closes the response,
49/// at which point the final `Ok(None)` signals the caller to emit the
50/// terminal `InvokeComplete` frame.
51pub struct StreamingInvocation {
52    pub(crate) resp: reqwest::Response,
53}
54
55impl StreamingInvocation {
56    /// Read the next chunk of the function's response body. Returns
57    /// `Ok(None)` once the RIE has finished streaming. Buffered
58    /// handlers tend to deliver a single chunk; streaming handlers
59    /// deliver one chunk per `responseStream.write(...)` call.
60    pub async fn next_chunk(&mut self) -> Result<Option<bytes::Bytes>, RuntimeError> {
61        match self.resp.chunk().await {
62            Ok(Some(b)) => Ok(Some(b)),
63            Ok(None) => Ok(None),
64            Err(e) => Err(RuntimeError::InvocationFailed(e.to_string())),
65        }
66    }
67}
68
69#[async_trait]
70pub trait LambdaBackend: Send + Sync + 'static {
71    /// Short identifier surfaced via logs and introspection (e.g.
72    /// `"docker"`, `"podman"`, `"kubernetes"`).
73    fn name(&self) -> &str;
74
75    /// Launch a fresh runtime instance for `func` using the supplied
76    /// code (for zip-package functions) or `func.image_uri` (for image
77    /// package functions). `layers` are the attached layer ZIPs in
78    /// attach order. `deploy_id` is the facade-computed fingerprint
79    /// used to label resources so reaper logic can correlate.
80    async fn launch(
81        &self,
82        func: &LambdaFunction,
83        code_zip: Option<&[u8]>,
84        layers: &[Vec<u8>],
85        deploy_id: &str,
86    ) -> Result<WarmInstance, RuntimeError>;
87
88    /// Tear down one instance. Must be idempotent — the facade may call
89    /// this against an already-gone instance during cleanup races.
90    async fn terminate(&self, handle: &BackendHandle);
91
92    /// Sweep instances that belong to a previous fakecloud process so
93    /// their function names don't leak across restarts. Default no-op;
94    /// backends with out-of-process state should override.
95    async fn reap_stale(&self) {}
96
97    /// Optional hook: pre-warm the runtime image so the first `launch()`
98    /// for a function doesn't pay the cold-pull cost. Called in the
99    /// background after `CreateFunction` persists; backends that don't
100    /// benefit from pre-pulling (e.g. Kubernetes, which pulls images on
101    /// the scheduling node anyway) leave this as a no-op.
102    ///
103    /// `image` is the registry URI fetched from `runtime_to_image` for
104    /// Zip-package functions, or the user-supplied `ImageUri` (already
105    /// translated to the local registry if applicable) for Image-package
106    /// functions.
107    async fn prepull_image(&self, _image: &str) -> Result<(), RuntimeError> {
108        Ok(())
109    }
110}