submilli_engine/runtime/
finalizers.rs1use wasmtime::{Linker, Store, Val};
5
6use crate::backtrace::ThrownError;
7use crate::runtime::host::Denial;
8use crate::runtime::json::JsonUnknownAllocator;
9use crate::runtime::{LinkedPackageModule, StoreData, exec, install_package_modules_async};
10
11#[derive(Clone, Copy, Debug)]
15pub struct FinalizerCall<'a> {
16 pub package: &'a str,
17 pub key: &'a [u16],
18 pub value: &'a [u16],
20}
21
22#[derive(Debug)]
23pub enum FinalizerError {
24 Unavailable(String),
27 MalformedArgument(String),
29 IncompatibleArgument(String),
32 Denied(Denial),
34 BudgetExhausted(wasmtime::Error),
36 Failed(wasmtime::Error),
38}
39
40impl std::fmt::Display for FinalizerError {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 Self::Unavailable(message) => write!(f, "finalizer unavailable: {message}"),
44 Self::MalformedArgument(message) => {
45 write!(f, "malformed finalizer argument: {message}")
46 }
47 Self::IncompatibleArgument(message) => {
48 write!(f, "incompatible finalizer argument: {message}")
49 }
50 Self::Denied(denial) => write!(
51 f,
52 "finalizer denied `{}` for `{}`",
53 denial.capability, denial.caller
54 ),
55 Self::BudgetExhausted(error) => write!(f, "finalizer budget exhausted: {error}"),
56 Self::Failed(error) => write!(f, "finalizer failed: {error}"),
57 }
58 }
59}
60
61impl std::error::Error for FinalizerError {}
62
63pub async fn invoke_finalizer_async(
82 store: &mut Store<StoreData>,
83 packages: &[LinkedPackageModule<'_>],
84 call: FinalizerCall<'_>,
85) -> Result<(), FinalizerError> {
86 store.data_mut().finalizing = true;
87 let declaration = packages
90 .iter()
91 .map(|package| package.declaration)
92 .find(|declaration| declaration.package_name == call.package)
93 .ok_or_else(|| {
94 FinalizerError::Unavailable(format!("package `{}` is not linked", call.package))
95 })?;
96 if !declaration.values.contains_key("finalizer") {
97 return Err(FinalizerError::Unavailable(format!(
98 "package `{}` exports no finalizer",
99 call.package
100 )));
101 }
102 let value = crate::runtime::json_text::parse(call.value)
103 .map_err(|error| FinalizerError::MalformedArgument(error.message().to_owned()))?;
104
105 let mut linker = Linker::new(store.engine());
106 crate::runtime::install_runtime_async(&mut linker, store)
107 .await
108 .map_err(classify)?;
109 install_package_modules_async(&mut linker, store, packages)
110 .await
111 .map_err(classify)?;
112
113 let export = |store: &mut Store<StoreData>, name: &str| match linker.get(
114 &mut *store,
115 call.package,
116 name,
117 ) {
118 Ok(wasmtime::Extern::Func(function)) => Ok(function),
119 _ => Err(FinalizerError::Unavailable(format!(
120 "package `{}` does not export `{name}`; rebuild it",
121 call.package
122 ))),
123 };
124 let check = export(
125 store,
126 crate::mangle::finalizer_argument(call.package).as_str(),
127 )?;
128 let finalizer = export(
129 store,
130 crate::mangle::package_symbol(call.package, "finalizer").as_str(),
131 )?;
132
133 let allocator = JsonUnknownAllocator::new(store).map_err(classify)?;
134 let unknown = allocator.allocate(store, &value).map_err(classify)?;
135 let key = allocator
136 .allocate(
137 store,
138 &crate::runtime::json_text::JsonValue::String(call.key.to_vec()),
139 )
140 .map_err(classify)?;
141
142 let mut argument = [Val::null_any_ref()];
143 let checked = check
144 .call_async(&mut *store, &[unknown], &mut argument)
145 .await;
146 checked.map_err(|error| match classify(exec::uncaught_error(store, error)) {
147 FinalizerError::Failed(error) if error.is::<ThrownError>() => {
148 FinalizerError::IncompatibleArgument(error.to_string())
149 }
150 other => other,
151 })?;
152
153 let [argument] = argument;
154 let mut returned = vec![Val::null_any_ref(); finalizer.ty(&*store).results().len()];
157 let ran = finalizer
158 .call_async(&mut *store, &[key, argument], &mut returned)
159 .await;
160 ran.map_err(|error| classify(exec::uncaught_error(store, error)))
161}
162
163fn classify(error: wasmtime::Error) -> FinalizerError {
164 if matches!(
165 error.downcast_ref::<wasmtime::Trap>(),
166 Some(wasmtime::Trap::OutOfFuel | wasmtime::Trap::Interrupt)
167 ) || crate::runtime::is_memory_exhausted(&error)
168 {
169 return FinalizerError::BudgetExhausted(error);
170 }
171 if let Some(denial) = error
172 .downcast_ref::<ThrownError>()
173 .and_then(|thrown| thrown.denial.clone())
174 {
175 return FinalizerError::Denied(denial);
176 }
177 FinalizerError::Failed(error)
178}