1use crate::bindings::golem::durability::durability::{
16 CustomDurableInvocation, CustomInvocationContext,
17 DurableFunctionType as RawDurableFunctionType, LiveCustomDurableInvocation,
18 OplogEntryVersion as RawOplogEntryVersion, PersistedDurableFunctionInvocation,
19 begin_custom_durable_invocation, begin_custom_durable_invocation_async,
20 exchange_custom_invocation_context, observe_function_call,
21};
22use crate::schema::{FromSchema, IntoSchema, IntoTypedSchemaValue};
23use std::fmt::Display;
24use std::future::Future;
25use std::marker::PhantomData;
26use std::pin::Pin;
27use std::task::{Context, Poll};
28
29struct ScopedDurableFuture<F> {
30 inner: Option<Pin<Box<F>>>,
31 context: Option<CustomInvocationContext>,
32}
33
34impl<F> ScopedDurableFuture<F> {
35 fn new(inner: F) -> Self {
36 Self {
37 inner: Some(Box::pin(inner)),
38 context: None,
39 }
40 }
41}
42
43struct RestoreCustomContext<'a> {
46 caller: Option<CustomInvocationContext>,
47 suspended: &'a mut Option<CustomInvocationContext>,
48}
49
50impl Drop for RestoreCustomContext<'_> {
51 fn drop(&mut self) {
52 *self.suspended = Some(exchange_custom_invocation_context(self.caller.take()));
53 }
54}
55
56impl<F: Future> Future for ScopedDurableFuture<F> {
57 type Output = F::Output;
58
59 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
60 let this = self.get_mut();
61 let caller = exchange_custom_invocation_context(this.context.take());
62 let restore = RestoreCustomContext {
63 caller: Some(caller),
64 suspended: &mut this.context,
65 };
66 let result = this
67 .inner
68 .as_mut()
69 .expect("polled after completion")
70 .as_mut()
71 .poll(cx);
72 if result.is_ready() {
73 drop(this.inner.take());
74 }
75 drop(restore);
76 if result.is_ready() {
77 drop(this.context.take());
78 }
79 result
80 }
81}
82
83impl<F> Drop for ScopedDurableFuture<F> {
84 fn drop(&mut self) {
85 if let Some(context) = self.context.take() {
86 let caller = exchange_custom_invocation_context(Some(context));
87 let mut discarded = None;
88 let restore = RestoreCustomContext {
89 caller: Some(caller),
90 suspended: &mut discarded,
91 };
92 drop(self.inner.take());
93 drop(restore);
94 drop(discarded);
95 } else {
96 drop(self.inner.take());
97 }
98 }
99}
100
101struct LiveDurableBody<F: Future> {
102 future: Option<Pin<Box<F>>>,
103 result: Option<F::Output>,
104 invocation: Option<LiveCustomDurableInvocation>,
105}
106
107impl<F: Future> Drop for LiveDurableBody<F> {
108 fn drop(&mut self) {
109 drop(self.future.take());
111 drop(self.result.take());
112 drop(self.invocation.take());
113 }
114}
115
116#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoSchema, FromSchema)]
117pub enum DurableFunctionType {
118 ReadLocal,
119 WriteLocal,
120 ReadRemote,
121 WriteRemote,
122}
123
124impl From<DurableFunctionType> for RawDurableFunctionType {
125 fn from(value: DurableFunctionType) -> Self {
126 match value {
127 DurableFunctionType::ReadLocal => Self::ReadLocal,
128 DurableFunctionType::WriteLocal => Self::WriteLocal,
129 DurableFunctionType::ReadRemote => Self::ReadRemote,
130 DurableFunctionType::WriteRemote => Self::WriteRemote,
131 }
132 }
133}
134
135#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoSchema, FromSchema)]
136pub enum OplogEntryVersion {
137 V1,
138 V2,
139}
140
141impl From<RawOplogEntryVersion> for OplogEntryVersion {
142 fn from(value: RawOplogEntryVersion) -> Self {
143 match value {
144 RawOplogEntryVersion::V1 => Self::V1,
145 RawOplogEntryVersion::V2 => Self::V2,
146 }
147 }
148}
149
150#[must_use = "a durability invocation must be run to completion"]
157pub struct Durability<SOk, SErr> {
158 interface: &'static str,
159 function: &'static str,
160 request: Option<crate::schema::wit::wire::TypedSchemaValue>,
161 function_type: RawDurableFunctionType,
162 forced_commit: bool,
163 _sok: PhantomData<SOk>,
164 _serr: PhantomData<SErr>,
165}
166
167impl<SOk, SErr> Durability<SOk, SErr> {
168 pub fn new<SIn>(
169 interface: &'static str,
170 function: &'static str,
171 function_type: DurableFunctionType,
172 input: &SIn,
173 ) -> Self
174 where
175 SIn: Clone + IntoSchema,
176 {
177 let function_type = RawDurableFunctionType::from(function_type);
178 let request = input
179 .clone()
180 .into_typed_schema_value()
181 .unwrap_or_else(|err| panic!("Failed serializing durable function input: {err}"));
182 let request = crate::encode_typed_schema_value(&request)
183 .unwrap_or_else(|err| panic!("Failed encoding durable function input: {err}"));
184 Self {
185 interface,
186 function,
187 request: Some(request),
188 function_type,
189 forced_commit: false,
190 _sok: PhantomData,
191 _serr: PhantomData,
192 }
193 }
194
195 pub fn enable_forced_commit(&mut self) {
196 self.forced_commit = true;
197 }
198
199 pub fn with_forced_commit(mut self) -> Self {
200 self.forced_commit = true;
201 self
202 }
203
204 pub fn run<Ok, Err, F>(mut self, body: F) -> Result<Ok, Err>
205 where
206 F: FnOnce() -> Result<Ok, Err>,
207 Ok: Clone + From<SOk>,
208 Err: From<SErr>,
209 SErr: FromSchema + for<'a> From<&'a Err>,
210 SOk: FromSchema + From<Ok>,
211 Result<SOk, SErr>: IntoSchema,
212 {
213 match self.begin() {
214 CustomDurableInvocation::Live(invocation) => {
215 let result = body();
216 let serializable_result = result
217 .as_ref()
218 .map(|result| result.clone().into())
219 .map_err(|err| err.into());
220 Self::finish(invocation, serializable_result, self.forced_commit);
221 result
222 }
223 CustomDurableInvocation::Replayed(ref invocation) => self
224 .replay_serializable(invocation)
225 .map(Into::into)
226 .map_err(Into::into),
227 }
228 }
229
230 pub async fn run_async<Ok, Err, F, Fut>(mut self, body: F) -> Result<Ok, Err>
231 where
232 F: FnOnce() -> Fut,
233 Fut: Future<Output = Result<Ok, Err>>,
234 Ok: Clone + From<SOk>,
235 Err: From<SErr>,
236 SErr: FromSchema + for<'a> From<&'a Err>,
237 SOk: FromSchema + From<Ok>,
238 Result<SOk, SErr>: IntoSchema,
239 {
240 match self.begin_async().await {
241 CustomDurableInvocation::Live(invocation) => {
242 ScopedDurableFuture::new(async move {
243 invocation.enter();
244 let mut live = LiveDurableBody {
245 future: None,
246 result: None,
247 invocation: Some(invocation),
248 };
249 live.future = Some(Box::pin(body()));
250 live.result = Some(live.future.as_mut().unwrap().as_mut().await);
251 drop(live.future.take());
252 let serializable_result = live
253 .result
254 .as_ref()
255 .unwrap()
256 .as_ref()
257 .map(|result| result.clone().into())
258 .map_err(|err| err.into());
259 let response = Self::encode_response(serializable_result);
260 LiveCustomDurableInvocation::finish_async(
261 live.invocation.as_ref().unwrap(),
262 response,
263 self.forced_commit,
264 )
265 .await;
266 live.result.take().unwrap()
267 })
268 .await
269 }
270 CustomDurableInvocation::Replayed(ref invocation) => self
271 .replay_serializable(invocation)
272 .map(Into::into)
273 .map_err(Into::into),
274 }
275 }
276
277 pub fn run_infallible<Ok, F>(mut self, body: F) -> Ok
278 where
279 F: FnOnce() -> Ok,
280 Ok: Clone + From<SOk>,
281 SOk: FromSchema + From<Ok>,
282 SErr: FromSchema + Display,
283 Result<SOk, SErr>: IntoSchema,
284 {
285 match self.begin() {
286 CustomDurableInvocation::Live(invocation) => {
287 let result = body();
288 let serializable_result = Ok(result.clone().into());
289 Self::finish(invocation, serializable_result, self.forced_commit);
290 result
291 }
292 CustomDurableInvocation::Replayed(ref invocation) => {
293 match self.replay_serializable(invocation) {
294 Ok(result) => result.into(),
295 Err(err) => panic!(
296 "Function {} previously failed with {}",
297 self.function_name(),
298 err
299 ),
300 }
301 }
302 }
303 }
304
305 pub async fn run_infallible_async<Ok, F, Fut>(mut self, body: F) -> Ok
306 where
307 F: FnOnce() -> Fut,
308 Fut: Future<Output = Ok>,
309 Ok: Clone + From<SOk>,
310 SOk: FromSchema + From<Ok>,
311 SErr: FromSchema + Display,
312 Result<SOk, SErr>: IntoSchema,
313 {
314 match self.begin_async().await {
315 CustomDurableInvocation::Live(invocation) => {
316 ScopedDurableFuture::new(async move {
317 invocation.enter();
318 let mut live = LiveDurableBody {
319 future: None,
320 result: None,
321 invocation: Some(invocation),
322 };
323 live.future = Some(Box::pin(body()));
324 live.result = Some(live.future.as_mut().unwrap().as_mut().await);
325 drop(live.future.take());
326 let response =
327 Self::encode_response(Ok(live.result.as_ref().unwrap().clone().into()));
328 LiveCustomDurableInvocation::finish_async(
329 live.invocation.as_ref().unwrap(),
330 response,
331 self.forced_commit,
332 )
333 .await;
334 live.result.take().unwrap()
335 })
336 .await
337 }
338 CustomDurableInvocation::Replayed(ref invocation) => {
339 match self.replay_serializable(invocation) {
340 Ok(result) => result.into(),
341 Err(err) => panic!(
342 "Function {} previously failed with {}",
343 self.function_name(),
344 err
345 ),
346 }
347 }
348 }
349 }
350
351 fn begin(&mut self) -> CustomDurableInvocation {
352 observe_function_call(self.interface, self.function);
353 begin_custom_durable_invocation(
354 &self.function_name(),
355 self.request.take().unwrap(),
356 self.function_type,
357 )
358 }
359
360 async fn begin_async(&mut self) -> CustomDurableInvocation {
361 observe_function_call(self.interface, self.function);
362 begin_custom_durable_invocation_async(
363 self.function_name(),
364 self.request.take().unwrap(),
365 self.function_type,
366 )
367 .await
368 }
369
370 fn encode_response(result: Result<SOk, SErr>) -> crate::schema::wit::wire::TypedSchemaValue
371 where
372 Result<SOk, SErr>: IntoSchema,
373 {
374 let response = result
375 .into_typed_schema_value()
376 .unwrap_or_else(|err| panic!("Failed serializing durable function result: {err}"));
377 crate::encode_typed_schema_value(&response)
378 .unwrap_or_else(|err| panic!("Failed encoding durable function result: {err}"))
379 }
380
381 fn finish(
382 invocation: LiveCustomDurableInvocation,
383 result: Result<SOk, SErr>,
384 forced_commit: bool,
385 ) where
386 Result<SOk, SErr>: IntoSchema,
387 {
388 let response = Self::encode_response(result);
389 LiveCustomDurableInvocation::finish(invocation, response, forced_commit);
390 }
391
392 fn replay_serializable(
393 &self,
394 invocation: &PersistedDurableFunctionInvocation,
395 ) -> Result<SOk, SErr>
396 where
397 SOk: FromSchema,
398 SErr: FromSchema,
399 {
400 Self::validate_oplog_entry(invocation, &self.function_name());
401 let response = crate::decode_typed_schema_value(&invocation.response)
402 .unwrap_or_else(|err| panic!("Failed decoding durable function response: {err}"));
403 FromSchema::from_value(response.value())
404 .unwrap_or_else(|err| panic!("Unexpected durable function payload: {err}"))
405 }
406
407 fn function_name(&self) -> String {
408 if self.interface.is_empty() {
409 self.function.to_string()
411 } else {
412 format!("{}::{}", self.interface, self.function)
413 }
414 }
415
416 fn validate_oplog_entry(
417 oplog_entry: &PersistedDurableFunctionInvocation,
418 expected_function_name: &str,
419 ) {
420 if oplog_entry.function_name != expected_function_name {
421 panic!(
422 "Unexpected imported function call entry in oplog: expected {}, got {}",
423 expected_function_name, oplog_entry.function_name
424 );
425 }
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use crate::durability::DurableFunctionType;
432 use crate::{FromSchema, IntoSchema};
433 use std::io::Error;
434
435 #[allow(dead_code)]
438 fn durability_interface_test() {
439 #[derive(Debug, IntoSchema, FromSchema)]
440 enum CustomError {
441 Error1,
442 Error2,
443 }
444
445 impl From<&std::io::Error> for CustomError {
446 fn from(_value: &Error) -> Self {
447 Self::Error1
448 }
449 }
450
451 impl From<CustomError> for std::io::Error {
452 fn from(value: CustomError) -> Self {
453 Error::other(format!("{value:?}"))
454 }
455 }
456
457 fn durable_fn() -> Result<u64, std::io::Error> {
458 let input = "input".to_string();
459 super::Durability::<u64, CustomError>::new(
460 "custom",
461 "random-number-generator",
462 DurableFunctionType::ReadLocal,
463 &input,
464 )
465 .run(|| Ok(1234))
466 }
467 }
468}