aardvark_core/
config.rs

1//! Runtime configuration options.
2
3use crate::engine::OverlayExport;
4use crate::error::Result;
5use crate::invocation::InvocationLimits;
6use crate::pyodide::PYODIDE_VERSION;
7use crate::runtime::PyRuntime;
8use crate::runtime_language::RuntimeLanguage;
9use std::fmt;
10use std::path::PathBuf;
11use std::sync::{Arc, Mutex};
12
13/// Controls how the runtime loads and captures Pyodide snapshots.
14#[derive(Clone)]
15pub struct SnapshotConfig {
16    /// Optional path to load a prebuilt snapshot from.
17    pub load_from: Option<std::path::PathBuf>,
18    /// Optional path to write a freshly captured snapshot to.
19    pub save_to: Option<std::path::PathBuf>,
20    cache: Arc<SnapshotCache>,
21}
22
23impl SnapshotConfig {
24    /// Clears any cached snapshot bytes.
25    pub fn clear_cache(&self) {
26        let mut guard = self.cache.bytes.lock().unwrap();
27        *guard = None;
28    }
29
30    pub(crate) fn cached_bytes(&self) -> Option<Arc<[u8]>> {
31        self.cache.bytes.lock().unwrap().clone()
32    }
33
34    pub(crate) fn store_cached_bytes(&self, bytes: Arc<[u8]>) {
35        let mut guard = self.cache.bytes.lock().unwrap();
36        *guard = Some(bytes);
37    }
38}
39
40impl Default for SnapshotConfig {
41    fn default() -> Self {
42        Self {
43            load_from: None,
44            save_to: None,
45            cache: Arc::new(SnapshotCache::default()),
46        }
47    }
48}
49
50impl fmt::Debug for SnapshotConfig {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        f.debug_struct("SnapshotConfig")
53            .field("load_from", &self.load_from)
54            .field("save_to", &self.save_to)
55            .field("cached", &self.cache)
56            .finish()
57    }
58}
59
60#[derive(Default)]
61struct SnapshotCache {
62    bytes: Mutex<Option<Arc<[u8]>>>,
63}
64
65impl fmt::Debug for SnapshotCache {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        let cached = self.bytes.lock().unwrap().as_ref().map(|arc| arc.len());
68        f.debug_struct("SnapshotCache")
69            .field("bytes", &cached)
70            .finish()
71    }
72}
73
74/// Type alias for host-provided warm snapshot hooks.
75pub type WarmHook = dyn Fn(&mut PyRuntime) -> Result<()> + Send + Sync;
76
77/// Host-configurable hooks for the warm snapshot lifecycle.
78#[derive(Clone, Default)]
79pub struct HostHooks {
80    /// Invoked immediately before a warm snapshot is captured.
81    pub before_warm_snapshot: Option<Arc<WarmHook>>,
82    /// Invoked after a warm snapshot has been applied to a runtime.
83    pub after_warm_restore: Option<Arc<WarmHook>>,
84}
85
86impl fmt::Debug for HostHooks {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        f.debug_struct("HostHooks")
89            .field(
90                "before_warm_snapshot",
91                &self.before_warm_snapshot.as_ref().map(|_| "Some"),
92            )
93            .field(
94                "after_warm_restore",
95                &self.after_warm_restore.as_ref().map(|_| "Some"),
96            )
97            .finish()
98    }
99}
100
101/// Captured warm state containing a Pyodide snapshot and its overlay assets.
102#[derive(Clone)]
103pub struct WarmState {
104    snapshot: Arc<[u8]>,
105    overlay: Arc<OverlayExport>,
106    overlay_preloaded: bool,
107}
108
109impl WarmState {
110    /// Constructs a warm state from raw components.
111    pub fn new(snapshot: Arc<[u8]>, overlay: OverlayExport) -> Self {
112        Self {
113            snapshot,
114            overlay: Arc::new(overlay),
115            overlay_preloaded: false,
116        }
117    }
118
119    /// Constructs a warm state that already includes the overlay in the snapshot image.
120    pub fn with_overlay_preloaded(snapshot: Arc<[u8]>, overlay: OverlayExport) -> Self {
121        Self {
122            snapshot,
123            overlay: Arc::new(overlay),
124            overlay_preloaded: true,
125        }
126    }
127
128    /// Flags the warm state as already containing the overlay contents inside the snapshot.
129    ///
130    /// Hosts that assemble a warm state manually can call this to skip the overlay import
131    /// step during `prepare_environment`.
132    pub fn into_overlay_preloaded(mut self) -> Self {
133        self.overlay_preloaded = true;
134        self
135    }
136
137    /// Returns the snapshot bytes.
138    pub fn snapshot(&self) -> Arc<[u8]> {
139        self.snapshot.clone()
140    }
141
142    /// Returns the overlay export.
143    pub fn overlay(&self) -> Arc<OverlayExport> {
144        self.overlay.clone()
145    }
146
147    /// Indicates whether the overlay contents were baked into the snapshot, allowing
148    /// the runtime to skip `import_overlay` when restoring the warm state.
149    pub fn overlay_preloaded(&self) -> bool {
150        self.overlay_preloaded
151    }
152}
153
154impl fmt::Debug for WarmState {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        f.debug_struct("WarmState")
157            .field("snapshot_len", &self.snapshot.len())
158            .field("overlay_blobs", &self.overlay.blobs.len())
159            .field("overlay_preloaded", &self.overlay_preloaded)
160            .finish()
161    }
162}
163
164/// Configuration applied when constructing [`PyRuntime`][crate::PyRuntime] or pool members.
165#[derive(Debug, Clone)]
166pub struct PyRuntimeConfig {
167    /// Bundled Pyodide version string (usually derived from build-time assets).
168    pub pyodide_version: String,
169    /// Filesystem directory used to resolve Pyodide wheel and metadata requests.
170    ///
171    /// When the crate is compiled with the `full-pyodide-packages` feature this defaults to the
172    /// build-script managed cache extracted into `OUT_DIR`. Hosts can override it programmatically
173    /// to avoid relying on process-wide environment variables.
174    pub pyodide_package_dir: Option<PathBuf>,
175    /// Default guest language selected when manifests/descriptors omit one.
176    pub default_language: RuntimeLanguage,
177    /// Snapshot-related configuration.
178    pub snapshot: SnapshotConfig,
179    /// Host lifecycle hooks executed around warm snapshot capture/restore.
180    pub hooks: HostHooks,
181    /// Optional global budget override applied to every session.
182    pub budget_override: Option<InvocationLimits>,
183    /// Runtime reset behaviour after each invocation.
184    pub reset_policy: ResetPolicy,
185    /// Host capabilities enabled for exposed native APIs.
186    pub host_capabilities: Vec<String>,
187    /// Optional prebuilt warm state (snapshot + overlay).
188    pub warm_state: Option<WarmState>,
189}
190
191impl Default for PyRuntimeConfig {
192    fn default() -> Self {
193        let default_package_dir =
194            option_env!("AARDVARK_PYODIDE_DEFAULT_PACKAGES").and_then(|value| {
195                let trimmed = value.trim();
196                if trimmed.is_empty() {
197                    None
198                } else {
199                    Some(PathBuf::from(trimmed))
200                }
201            });
202        Self {
203            pyodide_version: PYODIDE_VERSION.to_owned(),
204            pyodide_package_dir: default_package_dir,
205            default_language: RuntimeLanguage::Python,
206            snapshot: SnapshotConfig::default(),
207            hooks: HostHooks::default(),
208            budget_override: None,
209            reset_policy: ResetPolicy::Manual,
210            host_capabilities: vec!["rawctx_buffers".to_string()],
211            warm_state: None,
212        }
213    }
214}
215
216impl PyRuntimeConfig {
217    /// Returns the configured Pyodide package directory, if any.
218    pub fn pyodide_package_dir(&self) -> Option<&PathBuf> {
219        self.pyodide_package_dir.as_ref()
220    }
221
222    /// Sets the Pyodide package directory override.
223    pub fn set_pyodide_package_dir<P: Into<PathBuf>>(&mut self, path: P) {
224        self.pyodide_package_dir = Some(path.into());
225    }
226
227    /// Clears any explicit Pyodide package directory override.
228    pub fn clear_pyodide_package_dir(&mut self) {
229        self.pyodide_package_dir = None;
230    }
231
232    /// Returns a new configuration with the provided Pyodide package directory.
233    pub fn with_pyodide_package_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
234        self.set_pyodide_package_dir(path);
235        self
236    }
237
238    /// Returns a new configuration without an explicit Pyodide package directory override.
239    pub fn without_pyodide_package_dir(mut self) -> Self {
240        self.clear_pyodide_package_dir();
241        self
242    }
243}
244
245/// Determines how the runtime resets between invocations.
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
247pub enum ResetPolicy {
248    /// Host is responsible for calling [`PyRuntime::reset_to_snapshot`](crate::PyRuntime::reset_to_snapshot).
249    #[default]
250    Manual,
251    /// Runtime automatically resets to its baseline snapshot after each invocation.
252    AfterInvocation,
253}