harmos_runtime/operations/
invocation.rs1use std::any::Any;
20use std::sync::{Arc, Mutex};
21
22use super::HostContext;
26use harmos_domain::{MemorySize, Message, MessageError, OperationDeclaration};
27use harmos_sdk::__private::ErasedResponse;
28use harmos_sdk::operations::{CallFuture, Caller, InvocationError, Target};
29
30macro_rules! native_payload {
35 ($ty:ty) => {
36 impl Message for $ty {
37 const CODEC: &'static str = "harmos.native";
38 fn encode(&self) -> Vec<u8> {
39 panic!("native payload cannot be encoded")
40 }
41 fn encoded_size(&self) -> usize {
42 0
43 }
44 fn decode(_: &[u8]) -> Result<Self, MessageError> {
45 Err(MessageError::borrowed("native payload cannot be decoded"))
46 }
47 fn decoding_memory(_: &[u8]) -> Result<usize, MessageError> {
48 Err(MessageError::borrowed("native payload cannot be decoded"))
49 }
50 }
51 impl harmos_domain::schema::Described for $ty {
52 const NAME: &'static str = "harmos::native";
53 const FIELDS: &'static [harmos_domain::schema::declaration::Field] = &[];
54 const MARKER: &'static [u8] = &[];
55 }
56 };
57}
58
59native_payload!(Request);
60
61native_payload!(Answer);
62
63struct Request {
68 payload: Mutex<Option<Box<dyn Any + Send>>>,
69 memory: usize,
70}
71
72struct Answer(ErasedResponse);
73
74struct Invocation;
75
76impl Caller for HostContext {
83 fn call(&self, target: Target, request: Box<dyn Any + Send>) -> CallFuture<'_> {
84 Box::pin(async move {
85 let result = async {
86 let context = self.borrow();
87 let origin = context.source.check()?;
88 let entry = context.operations.entry_as(target.id, &origin)?;
89 if entry.declared().fingerprint != (target.definition)().fingerprint {
90 return Err(crate::Error::InvalidInput {
91 field: "declared operation",
92 reason: "callee fingerprint differs",
93 });
94 }
95 let Some(factory) = entry.binding.downcast_ref::<harmos_sdk::operations::OperationFactory>().cloned() else {
96 let payload = (target.encode)(request.as_ref()).ok_or(crate::Error::InvalidInput {
97 field: "declared operation",
98 reason: "callee payload differs",
99 })?;
100 let bytes = context.call_encoded(target.id, payload).await?;
101 return (target.decode)(&bytes).map_err(|source| crate::Error::OperationFailed {
102 operation: target.id.to_owned(),
103 source: Arc::new(source),
104 });
105 };
106 if !factory.accepts_target(&target) {
107 return Err(crate::Error::InvalidInput {
108 field: "declared operation",
109 reason: "callee payload types differ",
110 });
111 }
112 let grant = (target.grant)(request.as_ref());
113 let key = (target.key)(request.as_ref());
114 let memory = (target.memory)(request.as_ref());
115 let resources = context.operations.resources_for(&entry);
116 let binding = super::authored::factory_projected::<Invocation, ()>(
117 &context.operations,
118 move |request, ()| {
119 let payload = request
120 .payload
121 .into_inner()
122 .map_err(|_| harmos_sdk::ServingError::Catalog)?
123 .ok_or(harmos_sdk::ServingError::Catalog)?;
124 factory.prepare_native(resources.clone(), payload, Box::new(()))
125 },
126 |response| Some(harmos_sdk::Response::new(Answer(response))),
127 |response| response.0.clone(),
128 );
129 let mut input = super::binding::Request::from(Request {
130 payload: Mutex::new(Some(request)),
131 memory,
132 });
133 input.binding = Some(Arc::new(binding));
134 input.grant = grant;
135 input.key = key;
136 let running = context.operations.admit_at::<Invocation, ()>(
137 target.id,
138 (input, ()),
139 Some(&context.scope),
140 context.admission.held(),
141 &context.source,
142 None,
143 )?;
144 let response = running.await?;
145 Ok(response.0.clone())
146 }
147 .await;
148 result.map_err(|error: crate::Error| InvocationError::failed(target.id, error))
149 })
150 }
151}
152
153impl OperationDeclaration for Invocation {
154 type Request = Request;
155 type Response = Answer;
156 type Input = harmos_domain::operations::NoStream;
157 type Output = harmos_domain::operations::NoStream;
158 const ID: &'static str = "native.invocation";
159 const POLICY: harmos_domain::OperationPolicy = harmos_operations::host::POLICY;
160}
161
162impl MemorySize for Request {
163 fn memory_size(&self) -> usize {
164 self.memory
165 }
166}
167
168impl MemorySize for Answer {
169 fn memory_size(&self) -> usize {
170 std::mem::size_of::<Self>()
171 }
172}
173
174#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::operations::{Lane, RegistrationExt};
184 use harmos_sdk::{Context, Operation, Response};
185
186 #[crate::lanes]
187 enum Lanes {
188 #[default]
189 Open,
190 }
191 struct Model;
192 impl crate::Lifecycle for Model {
193 type Config = ();
194 type Error = std::convert::Infallible;
195 type Context = crate::Startup<Self>;
196
197 async fn prepare(_: (), _: &mut crate::Setup<Self>) -> std::result::Result<Self, Self::Error> {
198 Ok(Self)
199 }
200
201 async fn initialize(&mut self, _: &Self::Context) -> std::result::Result<(), Self::Error> {
202 Ok(())
203 }
204
205 async fn stop(&mut self, _: &Self::Context) -> std::result::Result<(), Self::Error> {
206 Ok(())
207 }
208
209 async fn cleanup(&mut self) -> std::result::Result<(), Self::Error> {
210 Ok(())
211 }
212 }
213
214 impl crate::Application for Model {
215 type Lanes = Lanes;
216 type State = ();
217 type Transaction = crate::state::Never;
218 type Record = ();
219 type Streams = ();
220 }
221
222 const POLICY: crate::OperationPolicy = crate::OperationPolicy {
223 timeout: std::time::Duration::from_secs(2),
224 attempt_timeout: std::time::Duration::from_secs(2),
225 retries: crate::RetryPolicy::Never,
226 };
227
228 #[crate::operation(harmos_path = crate, resources = [u64], id = "test.leaf", response = u64, policy = POLICY,
229 key = |request: &Leaf| crate::operations::CacheKey::new(request.value.to_le_bytes()).ok())]
230 struct Leaf {
231 value: u64,
232 }
233 impl Operation for Leaf {
234 type Error = std::convert::Infallible;
235 async fn run(self, ctx: Context<'_, Self>) -> Result<Response<u64>, Self::Error> {
236 Ok((self.value + *ctx.resource::<u64>()).into())
237 }
238 }
239
240 #[crate::operation(harmos_path = crate, id = "test.parent", response = u64, policy = POLICY, operations = [Leaf])]
241 struct Parent {
242 value: u64,
243 }
244 impl Operation for Parent {
245 type Error = InvocationError;
246 async fn run(self, ctx: Context<'_, Self>) -> Result<Response<u64>, Self::Error> {
247 ctx.operation(Leaf { value: self.value }).await
248 }
249 }
250
251 #[tokio::test]
252 async fn declared_calls_verify_activation_and_run_with_resources() {
253 let runtime = crate::Runtime::<Model>::launch((), ()).await.unwrap();
254 let missing = harmos_sdk::__private::RegistrationBatch::new().operation::<Parent>();
255 assert!(runtime.register_batch(missing.lane(<Lanes as crate::operations::Lane>::default_lane())).await.is_err());
256 let missing = harmos_sdk::__private::RegistrationBatch::new().operation::<Leaf>();
257 assert!(runtime.register_batch(missing.lane(<Lanes as crate::operations::Lane>::default_lane())).await.is_err());
258 runtime.shutdown().await.unwrap();
259 let runtime = crate::Runtime::<Model>::launch_with((), (), |setup| {
260 setup.resource(7_u64);
261 })
262 .await
263 .unwrap();
264 let batch = harmos_sdk::__private::RegistrationBatch::new().operation::<Parent>().operation::<Leaf>();
265 runtime.register_batch(batch.lane(<Lanes as crate::operations::Lane>::default_lane())).await.unwrap();
266 runtime.operations.set_cache_capacity(2);
267 let direct = runtime.operation(Leaf { value: 5 }).run().await.unwrap();
268 let child = runtime.operation(Parent { value: 5 }).run().await.unwrap();
269 assert_eq!(*child, 12);
270 assert!(direct.ptr_eq(&child));
271 runtime.shutdown().await.unwrap();
272 }
273
274 #[tokio::test]
275 async fn typed_calls_keep_carrier_and_runtime_resources() {
276 struct Carrier(u32);
277
278 impl harmos_sdk::operations::Carrier for Carrier {
279 type Declaration = Leaf;
280 type Error = harmos_sdk::ResourceError;
281 type StreamEndpoints = ();
282
283 async fn bind(context: harmos_sdk::ExecutionContext) -> Result<Self, Self::Error> {
284 let handle = context.resource::<u32>()?;
285 Ok(Self(*handle))
286 }
287
288 async fn attempt(
289 &mut self, context: harmos_sdk::ExecutionContext, request: &mut Leaf, _: &mut (),
290 ) -> Result<Response<u64>, Self::Error> {
291 let resource = context.resource::<u64>()?;
292 let response = request.value + u64::from(self.0) + *resource;
293 Ok(response.into())
294 }
295 }
296
297 let runtime = crate::Runtime::<Model>::launch_with((), (), |setup| {
298 setup.resource(7_u64);
299 })
300 .await
301 .unwrap();
302
303 let mut batch = harmos_sdk::__private::RegistrationBatch::new().carrier::<Leaf, Carrier>();
304 let factory = batch.operations.pop().unwrap();
305 let binding = Arc::new(factory);
306 let lane = <Lanes as Lane>::default_lane().name();
307 let definition = Leaf::definition();
308 let mut entry = runtime
309 .operations
310 .binding_erased(
311 "host",
312 lane,
313 binding,
314 definition,
315 None,
316 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
317 None,
318 )
319 .unwrap();
320
321 let handle = Arc::new(11_u32);
322 let staged = Arc::get_mut(&mut entry).unwrap();
323 staged.carrier.insert(handle).unwrap();
324 runtime.operations.reserve(&[entry]).unwrap();
325
326 let response = runtime.operation(Leaf { value: 5 }).run().await.unwrap();
327 assert_eq!(*response, 23);
328 let resources = runtime.operations.resources();
329 assert!(resources.resource::<u32>().is_err());
330 let owner = resources.resource::<u64>().unwrap();
331 assert_eq!(*owner, 7);
332 runtime.shutdown().await.unwrap();
333 }
334}