1use crate::headers::HeaderMap;
2use crate::service_protocol::messages::{
3 attach_invocation_command_message, complete_awakeable_command_message,
4 complete_promise_command_message, get_invocation_output_command_message,
5 output_command_message, send_signal_command_message, AttachInvocationCommandMessage,
6 CallCommandMessage, CompleteAwakeableCommandMessage, CompletePromiseCommandMessage,
7 ErrorBehavior, GetInvocationOutputCommandMessage, GetPromiseCommandMessage,
8 IdempotentRequestTarget, OneWayCallCommandMessage, OutputCommandMessage,
9 PeekPromiseCommandMessage, SendSignalCommandMessage, SleepCommandMessage, WorkflowTarget,
10};
11use crate::service_protocol::{Decoder, NotificationId, RawMessage, Version, CANCEL_SIGNAL_ID};
12use crate::vm::errors::{
13 ClosedError, OutOfBoundsDuration, UnexpectedStateError, UnsupportedFeatureForNegotiatedVersion,
14 EMPTY_IDEMPOTENCY_KEY, EMPTY_LIMIT_KEY, EMPTY_SCOPE, SUSPENDED,
15};
16use crate::vm::run_state::RunState;
17use crate::vm::transitions::*;
18use crate::{
19 AttachInvocationTarget, AwaitResponse, AwakeableHandle, CallHandle, CommandRelationship,
20 EphemeralCompletionId, Error, Header, ImplicitCancellationOption, Input, JournalReplay,
21 NonDeterministicChecksOption, NonEmptyValue, NotificationHandle, PayloadOptions, ResponseHead,
22 RetryPolicy, RunExitResult, RunHandle, SendHandle, Target, TerminalFailure, UnresolvedFuture,
23 VMOptions, VMResult, Value, CANCEL_NOTIFICATION_HANDLE,
24};
25use async_results_state::AsyncResultsState;
26use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig};
27use base64::{alphabet, Engine};
28use bytes::{Buf, BufMut, Bytes, BytesMut};
29use context::{Context, EagerState, Output};
30use ephemeral_commands::EphemeralCommands;
31use std::borrow::Cow;
32use std::collections::{HashMap, VecDeque};
33use std::mem::size_of;
34use std::time::Duration;
35use std::{fmt, mem};
36use strum::IntoStaticStr;
37use tracing::{debug, enabled, instrument, Level};
38
39mod async_results_state;
40mod context;
41mod ephemeral_commands;
42pub(crate) mod errors;
43mod run_state;
44mod transitions;
45
46const CONTENT_TYPE: &str = "content-type";
47
48#[derive(Debug, IntoStaticStr)]
49pub(crate) enum State {
50 WaitingStart,
51 WaitingReplayEntries {
52 received_entries: u32,
53 commands: VecDeque<RawMessage>,
54 async_results: AsyncResultsState,
55 eager_state: EagerState,
56 },
57 Replaying {
58 commands: VecDeque<RawMessage>,
59 run_state: RunState,
60 async_results: AsyncResultsState,
61 eager_state: EagerState,
62 },
63 Processing {
64 processing_first_entry: bool,
65 run_state: RunState,
66 async_results: AsyncResultsState,
67 eager_state: EagerState,
68 ephemeral_commands: EphemeralCommands,
69 },
70 Closed,
71}
72
73impl State {
74 fn as_unexpected_state(&self, event: impl ToString) -> Error {
75 if matches!(self, State::Closed) {
76 return ClosedError::new(event.to_string()).into();
77 }
78 UnexpectedStateError::new(self.into(), event.to_string()).into()
79 }
80
81 #[inline]
83 fn try_transition_to_processing(self) -> Self {
84 match self {
85 State::Replaying {
86 commands,
87 async_results,
88 eager_state,
89 run_state,
90 } if commands.is_empty() => State::Processing {
91 processing_first_entry: true,
92 run_state,
93 async_results,
94 eager_state,
95 ephemeral_commands: Default::default(),
96 },
97 s => s,
98 }
99 }
100
101 #[inline]
102 fn eager_state_mut(&mut self) -> Option<&mut EagerState> {
103 match self {
104 State::WaitingReplayEntries { eager_state, .. }
105 | State::Replaying { eager_state, .. }
106 | State::Processing { eager_state, .. } => Some(eager_state),
107 _ => None,
108 }
109 }
110}
111
112struct TrackedInvocationId {
113 handle: NotificationHandle,
114 invocation_id: Option<String>,
115}
116
117impl TrackedInvocationId {
118 fn is_resolved(&self) -> bool {
119 self.invocation_id.is_some()
120 }
121}
122
123pub struct CoreVM {
124 options: VMOptions,
125
126 decoder: Decoder,
128
129 context: Context,
131 last_transition: Result<State, Error>,
132
133 tracked_invocation_ids: Vec<TrackedInvocationId>,
135
136 sys_run_names: HashMap<NotificationHandle, String>,
138}
139
140impl CoreVM {
141 fn debug_invocation_id(&self) -> &str {
143 if let Some(start_info) = self.context.start_info() {
144 &start_info.debug_id
145 } else {
146 ""
147 }
148 }
149
150 fn debug_state(&self) -> &'static str {
151 match &self.last_transition {
152 Ok(s) => s.into(),
153 Err(_) => "Failed",
154 }
155 }
156
157 fn verify_error_metadata_feature_support(&mut self, value: &NonEmptyValue) -> VMResult<()> {
158 if let NonEmptyValue::Failure(f) = value {
159 if !f.metadata.is_empty() {
160 self.verify_feature_support("terminal error metadata", Version::V6)?;
161 }
162 }
163 Ok(())
164 }
165
166 #[allow(dead_code)]
167 fn verify_feature_support(
168 &mut self,
169 feature: &'static str,
170 minimum_required_protocol: Version,
171 ) -> VMResult<()> {
172 if self.context.negotiated_protocol_version < minimum_required_protocol {
173 return self.do_transition(HitError(
174 UnsupportedFeatureForNegotiatedVersion::new(
175 feature,
176 self.context.negotiated_protocol_version,
177 minimum_required_protocol,
178 )
179 .into(),
180 ));
181 }
182 Ok(())
183 }
184
185 fn _is_completed(&self, handle: NotificationHandle) -> bool {
186 match &self.last_transition {
187 Ok(State::Replaying { async_results, .. })
188 | Ok(State::Processing { async_results, .. }) => {
189 async_results.is_handle_completed(handle)
190 }
191 _ => false,
192 }
193 }
194
195 fn _do_progress(
196 &mut self,
197 unresolved_future: UnresolvedFuture,
198 ) -> Result<AwaitResponse, Error> {
199 match self.do_transition(DoProgress(unresolved_future)) {
200 Ok(Ok(do_progress_response)) => Ok(do_progress_response),
201 Ok(Err(_)) => Err(SUSPENDED),
202 Err(e) => Err(e),
203 }
204 }
205
206 fn is_implicit_cancellation_enabled(&self) -> bool {
207 matches!(
208 self.options.implicit_cancellation,
209 ImplicitCancellationOption::Enabled { .. }
210 )
211 }
212
213 fn is_processing(&self) -> bool {
214 matches!(&self.last_transition, Ok(State::Processing { .. }))
215 }
216}
217
218impl fmt::Debug for CoreVM {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 let mut s = f.debug_struct("CoreVM");
221 s.field("version", &self.context.negotiated_protocol_version);
222
223 if let Some(start_info) = self.context.start_info() {
224 s.field("invocation_id", &start_info.debug_id);
225 }
226
227 match &self.last_transition {
228 Ok(state) => s.field("last_transition", &<&'static str>::from(state)),
229 Err(_) => s.field("last_transition", &"Errored"),
230 };
231
232 s.field("command_index", &self.context.journal.command_index())
233 .field(
234 "notification_index",
235 &self.context.journal.notification_index(),
236 )
237 .finish()
238 }
239}
240
241#[allow(unused)]
243const fn is_send<T: Send>() {}
244const _: () = is_send::<CoreVM>();
245
246macro_rules! invocation_debug_logs {
248 ($this:expr, $($arg:tt)*) => {
249 if ($this.is_processing()) {
250 tracing::debug!($($arg)*)
251 }
252 };
253}
254
255impl super::VM for CoreVM {
256 #[instrument(level = "trace", skip(request_headers), ret)]
257 fn new(request_headers: impl HeaderMap, options: VMOptions) -> Result<Self, Error> {
258 let version = request_headers
259 .extract(CONTENT_TYPE)
260 .map_err(|e| {
261 Error::new(
262 errors::codes::BAD_REQUEST,
263 format!("cannot read '{CONTENT_TYPE}' header: {e:?}"),
264 )
265 })?
266 .ok_or(errors::MISSING_CONTENT_TYPE)?
267 .parse::<Version>()?;
268
269 if version < Version::minimum_supported_version()
270 || version > Version::maximum_supported_version()
271 {
272 return Err(Error::new(
273 errors::codes::UNSUPPORTED_MEDIA_TYPE,
274 format!(
275 "Unsupported protocol version {:?}, not within [{:?} to {:?}]. \
276 You might need to rediscover the service, check https://docs.restate.dev/references/errors/#RT0015",
277 version,
278 Version::minimum_supported_version(),
279 Version::maximum_supported_version()
280 ),
281 ));
282 }
283 let non_deterministic_checks_ignore_payload_equality = matches!(
284 options.non_determinism_checks,
285 NonDeterministicChecksOption::PayloadChecksDisabled
286 );
287 let awaiting_on_policy = options.awaiting_on_policy;
288 let journal_replay = options.journal_replay;
289 let signal_delivery = options.signal_delivery;
290
291 Ok(Self {
292 options,
293 decoder: Decoder::new(version),
294 context: Context {
295 input_is_closed: false,
296 output: Output::new(version),
297 start_info: None,
298 journal: Default::default(),
299 non_deterministic_checks_ignore_payload_equality,
300 negotiated_protocol_version: version,
301 awaiting_on_policy,
302 journal_replay,
303 signal_delivery,
304 },
305 last_transition: Ok(State::WaitingStart),
306 tracked_invocation_ids: vec![],
307 sys_run_names: HashMap::with_capacity(0),
308 })
309 }
310
311 #[instrument(
312 level = "trace",
313 skip(self),
314 fields(
315 restate.invocation.id = self.debug_invocation_id(),
316 restate.protocol.state = self.debug_state(),
317 restate.journal.command_index = self.context.journal.command_index(),
318 restate.protocol.version = %self.context.negotiated_protocol_version
319 ),
320 ret
321 )]
322 fn get_response_head(&self) -> ResponseHead {
323 ResponseHead {
324 status_code: 200,
325 headers: vec![Header {
326 key: Cow::Borrowed(CONTENT_TYPE),
327 value: Cow::Borrowed(self.context.negotiated_protocol_version.content_type()),
328 }],
329 version: self.context.negotiated_protocol_version,
330 }
331 }
332
333 #[instrument(
334 level = "trace",
335 skip(self),
336 fields(
337 restate.invocation.id = self.debug_invocation_id(),
338 restate.protocol.state = self.debug_state(),
339 restate.journal.command_index = self.context.journal.command_index(),
340 restate.protocol.version = %self.context.negotiated_protocol_version
341 ),
342 ret
343 )]
344 fn notify_input(&mut self, buffer: Bytes) {
345 self.decoder.push(buffer);
346 loop {
347 match self.decoder.consume_next() {
348 Ok(Some(msg)) => {
349 if self.do_transition(NewMessage(msg)).is_err() {
350 return;
351 }
352 }
353 Ok(None) => {
354 return;
355 }
356 Err(e) => {
357 if self.do_transition(HitError(e.into())).is_err() {
358 return;
359 }
360 }
361 }
362 }
363 }
364
365 #[instrument(
366 level = "trace",
367 skip(self),
368 fields(
369 restate.invocation.id = self.debug_invocation_id(),
370 restate.protocol.state = self.debug_state(),
371 restate.journal.command_index = self.context.journal.command_index(),
372 restate.protocol.version = %self.context.negotiated_protocol_version
373 ),
374 ret
375 )]
376 fn notify_input_closed(&mut self) {
377 self.context.input_is_closed = true;
378 let _ = self.do_transition(NotifyInputClosed);
379 }
380
381 #[instrument(
382 level = "trace",
383 skip(self),
384 fields(
385 restate.invocation.id = self.debug_invocation_id(),
386 restate.protocol.state = self.debug_state(),
387 restate.journal.command_index = self.context.journal.command_index(),
388 restate.protocol.version = %self.context.negotiated_protocol_version
389 ),
390 ret
391 )]
392 fn notify_error(
393 &mut self,
394 mut error: Error,
395 command_relationship: Option<CommandRelationship>,
396 ) {
397 if error.behavior != ErrorBehavior::Retry
398 && self
399 .verify_feature_support("error behavior", Version::V7)
400 .is_err()
401 {
402 return;
403 }
404
405 if let Some(command_relationship) = command_relationship {
406 error = error.with_related_command_metadata(
407 self.context
408 .journal
409 .resolve_related_command(command_relationship),
410 );
411 }
412
413 let _ = self.do_transition(HitError(error));
414 }
415
416 #[instrument(
417 level = "trace",
418 skip(self),
419 fields(
420 restate.invocation.id = self.debug_invocation_id(),
421 restate.protocol.state = self.debug_state(),
422 restate.journal.command_index = self.context.journal.command_index(),
423 restate.protocol.version = %self.context.negotiated_protocol_version
424 ),
425 ret
426 )]
427 fn take_output(&mut self) -> Bytes {
428 self.context
429 .output
430 .buffer
431 .copy_to_bytes(self.context.output.buffer.remaining())
432 }
433
434 #[instrument(
435 level = "trace",
436 skip(self),
437 fields(
438 restate.invocation.id = self.debug_invocation_id(),
439 restate.protocol.state = self.debug_state(),
440 restate.journal.command_index = self.context.journal.command_index(),
441 restate.protocol.version = %self.context.negotiated_protocol_version
442 ),
443 ret
444 )]
445 fn is_ready_to_execute(&self) -> Result<bool, Error> {
446 match &self.last_transition {
447 Ok(State::WaitingStart) | Ok(State::WaitingReplayEntries { .. }) => Ok(false),
448 Ok(State::Processing { .. }) | Ok(State::Replaying { .. }) => Ok(true),
449 Ok(s) => Err(s.as_unexpected_state("IsReadyToExecute")),
450 Err(e) => Err(e.clone()),
451 }
452 }
453
454 #[instrument(
455 level = "trace",
456 skip(self),
457 fields(
458 restate.invocation.id = self.debug_invocation_id(),
459 restate.protocol.state = self.debug_state(),
460 restate.journal.command_index = self.context.journal.command_index(),
461 restate.protocol.version = %self.context.negotiated_protocol_version
462 ),
463 ret
464 )]
465 fn is_completed(&self, handle: NotificationHandle) -> bool {
466 self._is_completed(handle)
467 }
468
469 #[instrument(
470 level = "trace",
471 skip(self),
472 fields(
473 restate.invocation.id = self.debug_invocation_id(),
474 restate.protocol.state = self.debug_state(),
475 restate.journal.command_index = self.context.journal.command_index(),
476 restate.protocol.version = %self.context.negotiated_protocol_version
477 ),
478 ret
479 )]
480 fn do_await(&mut self, unresolved_future: UnresolvedFuture) -> VMResult<AwaitResponse> {
481 if let Ok(State::Processing {
483 ephemeral_commands, ..
484 }) = &self.last_transition
485 {
486 if let Some(completion_id) = ephemeral_commands.any_ready() {
487 return Ok(AwaitResponse::EphemeralNotificationReady(completion_id));
488 }
489 }
490
491 if self.is_implicit_cancellation_enabled() {
494 let cancellation = UnresolvedFuture::Single(CANCEL_NOTIFICATION_HANDLE);
497 let unresolved_future_with_cancellation = if matches!(&unresolved_future, UnresolvedFuture::FirstCompleted(f) if f.is_empty())
498 {
499 UnresolvedFuture::FirstCompleted(vec![cancellation])
500 } else {
501 UnresolvedFuture::FirstCompleted(vec![unresolved_future, cancellation])
502 };
503
504 match self._do_progress(unresolved_future_with_cancellation) {
505 Ok(AwaitResponse::AnyCompleted) => {
506 if self._is_completed(CANCEL_NOTIFICATION_HANDLE) {
508 for i in 0..self.tracked_invocation_ids.len() {
510 if self.tracked_invocation_ids[i].is_resolved() {
511 continue;
512 }
513
514 let handle = self.tracked_invocation_ids[i].handle;
515
516 match self._do_progress(UnresolvedFuture::Single(handle)) {
518 Ok(AwaitResponse::AnyCompleted) => {
519 let invocation_id = match self.do_transition(CopyNotification(handle)) {
520 Ok(Ok(Some(Value::InvocationId(invocation_id)))) => Ok(invocation_id),
521 Ok(Err(_)) => Err(SUSPENDED),
522 _ => panic!("Unexpected variant! If the id handle is completed, it must be an invocation id handle!")
523 }?;
524
525 self.tracked_invocation_ids[i].invocation_id =
527 Some(invocation_id);
528 }
529 res => return res,
530 }
531 }
532
533 for tracked_invocation_id in mem::take(&mut self.tracked_invocation_ids) {
535 self.sys_cancel_invocation(
536 tracked_invocation_id
537 .invocation_id
538 .expect("We resolved before all the invocation ids"),
539 )?;
540 }
541
542 let _ = self.take_notification(CANCEL_NOTIFICATION_HANDLE);
544
545 Ok(AwaitResponse::CancelSignalReceived)
547 } else {
548 Ok(AwaitResponse::AnyCompleted)
549 }
550 }
551 res => res,
552 }
553 } else {
554 self._do_progress(unresolved_future)
555 }
556 }
557
558 #[instrument(
559 level = "trace",
560 skip(self),
561 fields(
562 restate.invocation.id = self.debug_invocation_id(),
563 restate.protocol.state = self.debug_state(),
564 restate.journal.command_index = self.context.journal.command_index(),
565 restate.protocol.version = %self.context.negotiated_protocol_version
566 ),
567 ret
568 )]
569 fn take_notification(&mut self, handle: NotificationHandle) -> VMResult<Option<Value>> {
570 match self.do_transition(TakeNotification(handle)) {
571 Ok(Ok(Some(value))) => {
572 if self.is_implicit_cancellation_enabled() {
573 if let Ok(found) = self
576 .tracked_invocation_ids
577 .binary_search_by(|tracked| tracked.handle.cmp(&handle))
578 {
579 let Value::InvocationId(invocation_id) = &value else {
580 panic!("Expecting an invocation id here, but got {value:?}");
581 };
582 self.tracked_invocation_ids
584 .get_mut(found)
585 .unwrap()
586 .invocation_id = Some(invocation_id.clone());
587 }
588 }
589
590 Ok(Some(value))
591 }
592 Ok(Ok(None)) => Ok(None),
593 Ok(Err(_)) => Err(SUSPENDED),
594 Err(e) => Err(e),
595 }
596 }
597
598 #[instrument(
599 level = "trace",
600 skip(self),
601 fields(
602 restate.invocation.id = self.debug_invocation_id(),
603 restate.protocol.state = self.debug_state(),
604 restate.journal.command_index = self.context.journal.command_index(),
605 restate.protocol.version = %self.context.negotiated_protocol_version
606 ),
607 ret
608 )]
609 fn sys_input(&mut self) -> Result<Input, Error> {
610 self.do_transition(SysInput)
611 }
612
613 #[instrument(
614 level = "trace",
615 skip(self),
616 fields(
617 restate.invocation.id = self.debug_invocation_id(),
618 restate.protocol.state = self.debug_state(),
619 restate.journal.command_index = self.context.journal.command_index(),
620 restate.protocol.version = %self.context.negotiated_protocol_version
621 ),
622 ret
623 )]
624 fn sys_state_get(
625 &mut self,
626 key: String,
627 name: Option<String>,
628 options: PayloadOptions,
629 ) -> Result<NotificationHandle, Error> {
630 invocation_debug_logs!(self, "Executing 'Get state {key}'");
631 self.do_transition(SysStateGet(key, name, options))
632 }
633
634 #[instrument(
635 level = "trace",
636 skip(self),
637 fields(
638 restate.invocation.id = self.debug_invocation_id(),
639 restate.protocol.state = self.debug_state(),
640 restate.journal.command_index = self.context.journal.command_index(),
641 restate.protocol.version = %self.context.negotiated_protocol_version
642 ),
643 ret
644 )]
645 fn sys_state_get_keys(&mut self, name: Option<String>) -> VMResult<NotificationHandle> {
646 invocation_debug_logs!(self, "Executing 'Get state keys'");
647 self.do_transition(SysStateGetKeys(name))
648 }
649
650 #[instrument(
651 level = "trace",
652 skip(self),
653 fields(
654 restate.invocation.id = self.debug_invocation_id(),
655 restate.protocol.state = self.debug_state(),
656 restate.journal.command_index = self.context.journal.command_index(),
657 restate.protocol.version = %self.context.negotiated_protocol_version
658 ),
659 ret
660 )]
661 fn ephemeral_state_get(&mut self, key: String) -> VMResult<EphemeralCompletionId> {
662 self.verify_feature_support("ephemeral state get", Version::V8)?;
663 invocation_debug_logs!(self, "Executing 'Ephemeral state get {key}'");
664 self.do_transition(EphemeralStateGet(key))
665 }
666
667 #[instrument(
668 level = "trace",
669 skip(self),
670 fields(
671 restate.invocation.id = self.debug_invocation_id(),
672 restate.protocol.state = self.debug_state(),
673 restate.journal.command_index = self.context.journal.command_index(),
674 restate.protocol.version = %self.context.negotiated_protocol_version
675 ),
676 ret
677 )]
678 fn take_ephemeral_notification(
679 &mut self,
680 completion_id: EphemeralCompletionId,
681 ) -> VMResult<Option<Value>> {
682 self.do_transition(TakeEphemeralNotification(completion_id))
683 }
684
685 #[instrument(
686 level = "trace",
687 skip(self, value),
688 fields(
689 restate.invocation.id = self.debug_invocation_id(),
690 restate.protocol.state = self.debug_state(),
691 restate.journal.command_index = self.context.journal.command_index(),
692 restate.protocol.version = %self.context.negotiated_protocol_version
693 ),
694 ret
695 )]
696 fn sys_state_set(
697 &mut self,
698 key: String,
699 value: Bytes,
700 name: Option<String>,
701 options: PayloadOptions,
702 ) -> VMResult<()> {
703 invocation_debug_logs!(self, "Executing 'Set state {key}'");
704 self.do_transition(SysStateSet(key, value, name, options))
705 }
706
707 #[instrument(
708 level = "trace",
709 skip(self),
710 fields(
711 restate.invocation.id = self.debug_invocation_id(),
712 restate.protocol.state = self.debug_state(),
713 restate.journal.command_index = self.context.journal.command_index(),
714 restate.protocol.version = %self.context.negotiated_protocol_version
715 ),
716 ret
717 )]
718 fn sys_state_clear(&mut self, key: String, name: Option<String>) -> Result<(), Error> {
719 invocation_debug_logs!(self, "Executing 'Clear state {key}'");
720 self.do_transition(SysStateClear(key, name))
721 }
722
723 #[instrument(
724 level = "trace",
725 skip(self),
726 fields(
727 restate.invocation.id = self.debug_invocation_id(),
728 restate.protocol.state = self.debug_state(),
729 restate.journal.command_index = self.context.journal.command_index(),
730 restate.protocol.version = %self.context.negotiated_protocol_version
731 ),
732 ret
733 )]
734 fn sys_state_clear_all(&mut self, name: Option<String>) -> Result<(), Error> {
735 invocation_debug_logs!(self, "Executing 'Clear all state'");
736 self.do_transition(SysStateClearAll(name))
737 }
738
739 #[instrument(
740 level = "trace",
741 skip(self),
742 fields(
743 restate.invocation.id = self.debug_invocation_id(),
744 restate.protocol.state = self.debug_state(),
745 restate.journal.command_index = self.context.journal.command_index(),
746 restate.protocol.version = %self.context.negotiated_protocol_version
747 ),
748 ret
749 )]
750 fn sys_sleep(
751 &mut self,
752 name: String,
753 wake_up_time_since_unix_epoch: Duration,
754 now_since_unix_epoch: Option<Duration>,
755 ) -> VMResult<NotificationHandle> {
756 if self.is_processing() {
757 match (&name, now_since_unix_epoch) {
758 (name, Some(now_since_unix_epoch)) if name.is_empty() => {
759 debug!(
760 "Executing 'Timer with duration {:?}'",
761 wake_up_time_since_unix_epoch.saturating_sub(now_since_unix_epoch)
762 );
763 }
764 (name, Some(now_since_unix_epoch)) => {
765 debug!(
766 "Executing 'Timer {name} with duration {:?}'",
767 wake_up_time_since_unix_epoch.saturating_sub(now_since_unix_epoch)
768 );
769 }
770 (name, None) if name.is_empty() => {
771 debug!("Executing 'Timer'");
772 }
773 (name, None) => {
774 debug!("Executing 'Timer named {name}'");
775 }
776 }
777 }
778 let wake_up_time = match u64::try_from(wake_up_time_since_unix_epoch.as_millis()) {
779 Ok(d) => d,
780 Err(e) => {
781 self.do_transition(HitError(OutOfBoundsDuration("sleep duration", e).into()))?;
782 unreachable!();
783 }
784 };
785 let completion_id = self.context.journal.next_completion_notification_id();
786
787 self.do_transition(SysSimpleCompletableEntry(
788 SleepCommandMessage {
789 wake_up_time,
790 result_completion_id: completion_id,
791 name,
792 },
793 completion_id,
794 PayloadOptions::default(),
795 ))
796 }
797
798 #[instrument(
799 level = "trace",
800 skip(self, input),
801 fields(
802 restate.invocation.id = self.debug_invocation_id(),
803 restate.protocol.state = self.debug_state(),
804 restate.journal.command_index = self.context.journal.command_index(),
805 restate.protocol.version = %self.context.negotiated_protocol_version
806 ),
807 ret
808 )]
809 fn sys_call(
810 &mut self,
811 target: Target,
812 input: Bytes,
813 name: Option<String>,
814 options: PayloadOptions,
815 ) -> VMResult<CallHandle> {
816 invocation_debug_logs!(
817 self,
818 "Executing 'Call {}/{}'",
819 target.service,
820 target.handler
821 );
822 if let Some(idempotency_key) = &target.idempotency_key {
823 if idempotency_key.is_empty() {
824 self.do_transition(HitError(EMPTY_IDEMPOTENCY_KEY))?;
825 unreachable!();
826 }
827 }
828 if let Some(scope) = &target.scope {
829 if scope.is_empty() {
830 self.do_transition(HitError(EMPTY_SCOPE))?;
831 unreachable!();
832 }
833 }
834 if let Some(limit_key) = &target.limit_key {
835 if limit_key.is_empty() {
836 self.do_transition(HitError(EMPTY_LIMIT_KEY))?;
837 unreachable!();
838 }
839 }
840 if target.scope.is_some() {
841 self.verify_feature_support("scope", Version::V7)?;
842 }
843 if target.limit_key.is_some() {
844 self.verify_feature_support("limit key", Version::V7)?;
845 }
846
847 let call_invocation_id_completion_id =
848 self.context.journal.next_completion_notification_id();
849 let result_completion_id = self.context.journal.next_completion_notification_id();
850
851 let handles = self.do_transition(SysCompletableEntryWithMultipleCompletions(
852 CallCommandMessage {
853 service_name: target.service,
854 handler_name: target.handler,
855 key: target.key.unwrap_or_default(),
856 idempotency_key: target.idempotency_key,
857 scope: target.scope,
858 limit_key: target.limit_key,
859 headers: target
860 .headers
861 .into_iter()
862 .map(crate::service_protocol::messages::Header::from)
863 .collect(),
864 parameter: input,
865 invocation_id_notification_idx: call_invocation_id_completion_id,
866 name: name.unwrap_or_default(),
867 result_completion_id,
868 },
869 vec![call_invocation_id_completion_id, result_completion_id],
870 options,
871 ))?;
872
873 if matches!(
874 self.options.implicit_cancellation,
875 ImplicitCancellationOption::Enabled {
876 cancel_children_calls: true,
877 ..
878 }
879 ) {
880 self.tracked_invocation_ids.push(TrackedInvocationId {
881 handle: handles[0],
882 invocation_id: None,
883 })
884 }
885
886 Ok(CallHandle {
887 invocation_id_notification_handle: handles[0],
888 call_notification_handle: handles[1],
889 })
890 }
891
892 #[instrument(
893 level = "trace",
894 skip(self, input),
895 fields(
896 restate.invocation.id = self.debug_invocation_id(),
897 restate.protocol.state = self.debug_state(),
898 restate.journal.command_index = self.context.journal.command_index(),
899 restate.protocol.version = %self.context.negotiated_protocol_version
900 ),
901 ret
902 )]
903 fn sys_send(
904 &mut self,
905 target: Target,
906 input: Bytes,
907 delay: Option<Duration>,
908 name: Option<String>,
909 options: PayloadOptions,
910 ) -> VMResult<SendHandle> {
911 invocation_debug_logs!(
912 self,
913 "Executing 'Send to {}/{}'",
914 target.service,
915 target.handler
916 );
917 if let Some(idempotency_key) = &target.idempotency_key {
918 if idempotency_key.is_empty() {
919 self.do_transition(HitError(EMPTY_IDEMPOTENCY_KEY))?;
920 unreachable!();
921 }
922 }
923 if let Some(scope) = &target.scope {
924 if scope.is_empty() {
925 self.do_transition(HitError(EMPTY_SCOPE))?;
926 unreachable!();
927 }
928 }
929 if let Some(limit_key) = &target.limit_key {
930 if limit_key.is_empty() {
931 self.do_transition(HitError(EMPTY_LIMIT_KEY))?;
932 unreachable!();
933 }
934 }
935 if target.scope.is_some() {
936 self.verify_feature_support("scope", Version::V7)?;
937 }
938 if target.limit_key.is_some() {
939 self.verify_feature_support("limit key", Version::V7)?;
940 }
941 let invoke_time = match u64::try_from(delay.unwrap_or_default().as_millis()) {
942 Ok(d) => d,
943 Err(e) => {
944 self.do_transition(HitError(OutOfBoundsDuration("send delay", e).into()))?;
945 unreachable!();
946 }
947 };
948 let call_invocation_id_completion_id =
949 self.context.journal.next_completion_notification_id();
950 let invocation_id_notification_handle = self.do_transition(SysSimpleCompletableEntry(
951 OneWayCallCommandMessage {
952 service_name: target.service,
953 handler_name: target.handler,
954 key: target.key.unwrap_or_default(),
955 idempotency_key: target.idempotency_key,
956 scope: target.scope,
957 limit_key: target.limit_key,
958 headers: target
959 .headers
960 .into_iter()
961 .map(crate::service_protocol::messages::Header::from)
962 .collect(),
963 parameter: input,
964 invoke_time,
965 invocation_id_notification_idx: call_invocation_id_completion_id,
966 name: name.unwrap_or_default(),
967 },
968 call_invocation_id_completion_id,
969 options,
970 ))?;
971
972 if matches!(
973 self.options.implicit_cancellation,
974 ImplicitCancellationOption::Enabled {
975 cancel_children_one_way_calls: true,
976 ..
977 }
978 ) {
979 self.tracked_invocation_ids.push(TrackedInvocationId {
980 handle: invocation_id_notification_handle,
981 invocation_id: None,
982 })
983 }
984
985 Ok(SendHandle {
986 invocation_id_notification_handle,
987 })
988 }
989
990 #[instrument(
991 level = "trace",
992 skip(self),
993 fields(
994 restate.invocation.id = self.debug_invocation_id(),
995 restate.protocol.state = self.debug_state(),
996 restate.journal.command_index = self.context.journal.command_index(),
997 restate.protocol.version = %self.context.negotiated_protocol_version
998 ),
999 ret
1000 )]
1001 fn sys_awakeable(&mut self) -> VMResult<AwakeableHandle> {
1002 invocation_debug_logs!(self, "Executing 'Create awakeable'");
1003 if self.options.journal_replay == JournalReplay::Disabled {
1004 self.do_transition(HitError(Error::new(
1006 errors::codes::UNSUPPORTED_FEATURE,
1007 "awakeables need journal replay",
1008 )))?;
1009 unreachable!();
1010 }
1011
1012 let signal_id = self.context.journal.next_signal_notification_id();
1013
1014 let handle = self.do_transition(CreateSignalHandle(
1015 "awakeable",
1016 NotificationId::SignalId(signal_id),
1017 ))?;
1018
1019 Ok(AwakeableHandle {
1020 id: awakeable_id_str(&self.context.expect_start_info().id, signal_id),
1021 handle,
1022 })
1023 }
1024
1025 #[instrument(
1026 level = "trace",
1027 skip(self, value),
1028 fields(
1029 restate.invocation.id = self.debug_invocation_id(),
1030 restate.protocol.state = self.debug_state(),
1031 restate.journal.command_index = self.context.journal.command_index(),
1032 restate.protocol.version = %self.context.negotiated_protocol_version
1033 ),
1034 ret
1035 )]
1036 fn sys_complete_awakeable(
1037 &mut self,
1038 id: String,
1039 value: NonEmptyValue,
1040 options: PayloadOptions,
1041 ) -> VMResult<()> {
1042 invocation_debug_logs!(self, "Executing 'Complete awakeable {id}'");
1043 self.verify_error_metadata_feature_support(&value)?;
1044 self.do_transition(SysNonCompletableEntry(
1045 CompleteAwakeableCommandMessage {
1046 awakeable_id: id,
1047 result: Some(match value {
1048 NonEmptyValue::Success(s) => {
1049 complete_awakeable_command_message::Result::Value(s.into())
1050 }
1051 NonEmptyValue::Failure(f) => {
1052 complete_awakeable_command_message::Result::Failure(f.into())
1053 }
1054 }),
1055 ..Default::default()
1056 },
1057 options,
1058 ))
1059 }
1060
1061 #[instrument(
1062 level = "trace",
1063 skip(self),
1064 fields(
1065 restate.invocation.id = self.debug_invocation_id(),
1066 restate.protocol.state = self.debug_state(),
1067 restate.journal.command_index = self.context.journal.command_index(),
1068 restate.protocol.version = %self.context.negotiated_protocol_version
1069 ),
1070 ret
1071 )]
1072 fn create_signal_handle(&mut self, signal_name: String) -> VMResult<NotificationHandle> {
1073 invocation_debug_logs!(self, "Executing 'Create named signal'");
1074
1075 self.do_transition(CreateSignalHandle(
1076 "named awakeable",
1077 NotificationId::SignalName(signal_name),
1078 ))
1079 }
1080
1081 #[instrument(
1082 level = "trace",
1083 skip(self, value),
1084 fields(
1085 restate.invocation.id = self.debug_invocation_id(),
1086 restate.protocol.state = self.debug_state(),
1087 restate.journal.command_index = self.context.journal.command_index(),
1088 restate.protocol.version = %self.context.negotiated_protocol_version
1089 ),
1090 ret
1091 )]
1092 fn sys_complete_signal(
1093 &mut self,
1094 target_invocation_id: String,
1095 signal_name: String,
1096 value: NonEmptyValue,
1097 ) -> VMResult<()> {
1098 invocation_debug_logs!(self, "Executing 'Complete named signal {signal_name}'");
1099 self.verify_error_metadata_feature_support(&value)?;
1100 self.do_transition(SysNonCompletableEntry(
1101 SendSignalCommandMessage {
1102 target_invocation_id,
1103 signal_id: Some(send_signal_command_message::SignalId::Name(signal_name)),
1104 result: Some(match value {
1105 NonEmptyValue::Success(s) => {
1106 send_signal_command_message::Result::Value(s.into())
1107 }
1108 NonEmptyValue::Failure(f) => {
1109 send_signal_command_message::Result::Failure(f.into())
1110 }
1111 }),
1112 ..Default::default()
1113 },
1114 PayloadOptions::default(),
1115 ))
1116 }
1117
1118 #[instrument(
1119 level = "trace",
1120 skip(self),
1121 fields(
1122 restate.invocation.id = self.debug_invocation_id(),
1123 restate.protocol.state = self.debug_state(),
1124 restate.journal.command_index = self.context.journal.command_index(),
1125 restate.protocol.version = %self.context.negotiated_protocol_version
1126 ),
1127 ret
1128 )]
1129 fn sys_get_promise(&mut self, key: String) -> VMResult<NotificationHandle> {
1130 invocation_debug_logs!(self, "Executing 'Await promise {key}'");
1131
1132 let result_completion_id = self.context.journal.next_completion_notification_id();
1133 self.do_transition(SysSimpleCompletableEntry(
1134 GetPromiseCommandMessage {
1135 key,
1136 result_completion_id,
1137 ..Default::default()
1138 },
1139 result_completion_id,
1140 PayloadOptions::default(),
1141 ))
1142 }
1143
1144 #[instrument(
1145 level = "trace",
1146 skip(self),
1147 fields(
1148 restate.invocation.id = self.debug_invocation_id(),
1149 restate.protocol.state = self.debug_state(),
1150 restate.journal.command_index = self.context.journal.command_index(),
1151 restate.protocol.version = %self.context.negotiated_protocol_version
1152 ),
1153 ret
1154 )]
1155 fn sys_peek_promise(&mut self, key: String) -> VMResult<NotificationHandle> {
1156 invocation_debug_logs!(self, "Executing 'Peek promise {key}'");
1157
1158 let result_completion_id = self.context.journal.next_completion_notification_id();
1159 self.do_transition(SysSimpleCompletableEntry(
1160 PeekPromiseCommandMessage {
1161 key,
1162 result_completion_id,
1163 ..Default::default()
1164 },
1165 result_completion_id,
1166 PayloadOptions::default(),
1167 ))
1168 }
1169
1170 #[instrument(
1171 level = "trace",
1172 skip(self, value),
1173 fields(
1174 restate.invocation.id = self.debug_invocation_id(),
1175 restate.protocol.state = self.debug_state(),
1176 restate.journal.command_index = self.context.journal.command_index(),
1177 restate.protocol.version = %self.context.negotiated_protocol_version
1178 ),
1179 ret
1180 )]
1181 fn sys_complete_promise(
1182 &mut self,
1183 key: String,
1184 value: NonEmptyValue,
1185 options: PayloadOptions,
1186 ) -> VMResult<NotificationHandle> {
1187 invocation_debug_logs!(self, "Executing 'Complete promise {key}'");
1188 self.verify_error_metadata_feature_support(&value)?;
1189
1190 let result_completion_id = self.context.journal.next_completion_notification_id();
1191 self.do_transition(SysSimpleCompletableEntry(
1192 CompletePromiseCommandMessage {
1193 key,
1194 completion: Some(match value {
1195 NonEmptyValue::Success(s) => {
1196 complete_promise_command_message::Completion::CompletionValue(s.into())
1197 }
1198 NonEmptyValue::Failure(f) => {
1199 complete_promise_command_message::Completion::CompletionFailure(f.into())
1200 }
1201 }),
1202 result_completion_id,
1203 ..Default::default()
1204 },
1205 result_completion_id,
1206 options,
1207 ))
1208 }
1209
1210 #[instrument(
1211 level = "trace",
1212 skip(self),
1213 fields(
1214 restate.invocation.id = self.debug_invocation_id(),
1215 restate.protocol.state = self.debug_state(),
1216 restate.journal.command_index = self.context.journal.command_index(),
1217 restate.protocol.version = %self.context.negotiated_protocol_version
1218 ),
1219 ret
1220 )]
1221 fn sys_run(&mut self, name: String) -> VMResult<RunHandle> {
1222 match self.do_transition(SysRun(name.clone())) {
1223 Ok(handle) => {
1224 if enabled!(Level::DEBUG) && !handle.replayed {
1225 self.sys_run_names.insert(handle.handle, name);
1227 }
1228 Ok(handle)
1229 }
1230 Err(e) => Err(e),
1231 }
1232 }
1233
1234 #[instrument(
1235 level = "trace",
1236 skip(self, value, retry_policy),
1237 fields(
1238 restate.invocation.id = self.debug_invocation_id(),
1239 restate.protocol.state = self.debug_state(),
1240 restate.journal.command_index = self.context.journal.command_index(),
1241 restate.protocol.version = %self.context.negotiated_protocol_version
1242 ),
1243 ret
1244 )]
1245 fn propose_run_completion(
1246 &mut self,
1247 notification_handle: NotificationHandle,
1248 value: RunExitResult,
1249 retry_policy: RetryPolicy,
1250 ) -> VMResult<()> {
1251 if enabled!(Level::DEBUG) {
1252 let name = self
1253 .sys_run_names
1254 .remove(¬ification_handle)
1255 .unwrap_or_default();
1256 match &value {
1257 RunExitResult::Success(_) => {
1258 invocation_debug_logs!(self, "Journaling run '{name}' success result");
1259 }
1260 RunExitResult::TerminalFailure(TerminalFailure { code, .. }) => {
1261 invocation_debug_logs!(
1262 self,
1263 "Journaling run '{name}' terminal failure {code} result"
1264 );
1265 }
1266 RunExitResult::RetryableFailure { .. } => {
1267 invocation_debug_logs!(self, "Propagating run '{name}' retryable failure");
1268 }
1269 }
1270 }
1271 if let RunExitResult::TerminalFailure(f) = &value {
1272 if !f.metadata.is_empty() {
1273 self.verify_feature_support("terminal error metadata", Version::V6)?;
1274 }
1275 }
1276 if retry_policy.should_pause_on_max_attempts() {
1277 self.verify_feature_support("pause", Version::V7)?;
1278 }
1279
1280 self.do_transition(ProposeRunCompletion(
1281 notification_handle,
1282 value,
1283 retry_policy,
1284 ))
1285 }
1286
1287 #[instrument(
1288 level = "trace",
1289 skip(self),
1290 fields(
1291 restate.invocation.id = self.debug_invocation_id(),
1292 restate.protocol.state = self.debug_state(),
1293 restate.journal.command_index = self.context.journal.command_index(),
1294 restate.protocol.version = %self.context.negotiated_protocol_version
1295 ),
1296 ret
1297 )]
1298 fn sys_cancel_invocation(&mut self, target_invocation_id: String) -> VMResult<()> {
1299 invocation_debug_logs!(
1300 self,
1301 "Executing 'Cancel invocation' of {target_invocation_id}"
1302 );
1303 self.do_transition(SysNonCompletableEntry(
1304 SendSignalCommandMessage {
1305 target_invocation_id,
1306 signal_id: Some(send_signal_command_message::SignalId::Idx(CANCEL_SIGNAL_ID)),
1307 result: Some(send_signal_command_message::Result::Void(Default::default())),
1308 ..Default::default()
1309 },
1310 PayloadOptions::default(),
1311 ))
1312 }
1313
1314 #[instrument(
1315 level = "trace",
1316 skip(self),
1317 fields(
1318 restate.invocation.id = self.debug_invocation_id(),
1319 restate.protocol.state = self.debug_state(),
1320 restate.journal.command_index = self.context.journal.command_index(),
1321 restate.protocol.version = %self.context.negotiated_protocol_version
1322 ),
1323 ret
1324 )]
1325 fn sys_attach_invocation(
1326 &mut self,
1327 target: AttachInvocationTarget,
1328 ) -> VMResult<NotificationHandle> {
1329 invocation_debug_logs!(self, "Executing 'Attach invocation'");
1330
1331 match &target {
1332 AttachInvocationTarget::WorkflowId {
1333 scope: Some(scope), ..
1334 }
1335 | AttachInvocationTarget::IdempotencyId {
1336 scope: Some(scope), ..
1337 } => {
1338 if scope.is_empty() {
1339 self.do_transition(HitError(EMPTY_SCOPE))?;
1340 unreachable!();
1341 }
1342 self.verify_feature_support("scope", Version::V7)?;
1343 }
1344 _ => {}
1345 };
1346
1347 let result_completion_id = self.context.journal.next_completion_notification_id();
1348 self.do_transition(SysSimpleCompletableEntry(
1349 AttachInvocationCommandMessage {
1350 target: Some(match target {
1351 AttachInvocationTarget::InvocationId(id) => {
1352 attach_invocation_command_message::Target::InvocationId(id)
1353 }
1354 AttachInvocationTarget::WorkflowId { name, key, scope } => {
1355 attach_invocation_command_message::Target::WorkflowTarget(WorkflowTarget {
1356 workflow_name: name,
1357 workflow_key: key,
1358 scope,
1359 })
1360 }
1361 AttachInvocationTarget::IdempotencyId {
1362 service_name,
1363 service_key,
1364 handler_name,
1365 idempotency_key,
1366 scope,
1367 } => attach_invocation_command_message::Target::IdempotentRequestTarget(
1368 IdempotentRequestTarget {
1369 service_name,
1370 service_key,
1371 handler_name,
1372 idempotency_key,
1373 scope,
1374 },
1375 ),
1376 }),
1377 result_completion_id,
1378 ..Default::default()
1379 },
1380 result_completion_id,
1381 PayloadOptions::default(),
1382 ))
1383 }
1384
1385 #[instrument(
1386 level = "trace",
1387 skip(self),
1388 fields(
1389 restate.invocation.id = self.debug_invocation_id(),
1390 restate.protocol.state = self.debug_state(),
1391 restate.journal.command_index = self.context.journal.command_index(),
1392 restate.protocol.version = %self.context.negotiated_protocol_version
1393 ),
1394 ret
1395 )]
1396 fn sys_get_invocation_output(
1397 &mut self,
1398 target: AttachInvocationTarget,
1399 ) -> VMResult<NotificationHandle> {
1400 invocation_debug_logs!(self, "Executing 'Get invocation output'");
1401
1402 match &target {
1403 AttachInvocationTarget::WorkflowId {
1404 scope: Some(scope), ..
1405 }
1406 | AttachInvocationTarget::IdempotencyId {
1407 scope: Some(scope), ..
1408 } => {
1409 if scope.is_empty() {
1410 self.do_transition(HitError(EMPTY_SCOPE))?;
1411 unreachable!();
1412 }
1413 self.verify_feature_support("scope", Version::V7)?;
1414 }
1415 _ => {}
1416 };
1417
1418 let result_completion_id = self.context.journal.next_completion_notification_id();
1419 self.do_transition(SysSimpleCompletableEntry(
1420 GetInvocationOutputCommandMessage {
1421 target: Some(match target {
1422 AttachInvocationTarget::InvocationId(id) => {
1423 get_invocation_output_command_message::Target::InvocationId(id)
1424 }
1425 AttachInvocationTarget::WorkflowId { name, key, scope } => {
1426 get_invocation_output_command_message::Target::WorkflowTarget(
1427 WorkflowTarget {
1428 workflow_name: name,
1429 workflow_key: key,
1430 scope,
1431 },
1432 )
1433 }
1434 AttachInvocationTarget::IdempotencyId {
1435 service_name,
1436 service_key,
1437 handler_name,
1438 idempotency_key,
1439 scope,
1440 } => get_invocation_output_command_message::Target::IdempotentRequestTarget(
1441 IdempotentRequestTarget {
1442 service_name,
1443 service_key,
1444 handler_name,
1445 idempotency_key,
1446 scope,
1447 },
1448 ),
1449 }),
1450 result_completion_id,
1451 ..Default::default()
1452 },
1453 result_completion_id,
1454 PayloadOptions::default(),
1455 ))
1456 }
1457
1458 #[instrument(
1459 level = "trace",
1460 skip(self, value),
1461 fields(
1462 restate.invocation.id = self.debug_invocation_id(),
1463 restate.protocol.state = self.debug_state(),
1464 restate.journal.command_index = self.context.journal.command_index(),
1465 restate.protocol.version = %self.context.negotiated_protocol_version
1466 ),
1467 ret
1468 )]
1469 fn sys_write_output(&mut self, value: NonEmptyValue, options: PayloadOptions) -> VMResult<()> {
1470 match &value {
1471 NonEmptyValue::Success(_) => {
1472 invocation_debug_logs!(self, "Writing invocation result success value");
1473 }
1474 NonEmptyValue::Failure(_) => {
1475 invocation_debug_logs!(self, "Writing invocation result failure value");
1476 }
1477 }
1478 self.verify_error_metadata_feature_support(&value)?;
1479 self.do_transition(SysNonCompletableEntry(
1480 OutputCommandMessage {
1481 result: Some(match value {
1482 NonEmptyValue::Success(b) => output_command_message::Result::Value(b.into()),
1483 NonEmptyValue::Failure(f) => output_command_message::Result::Failure(f.into()),
1484 }),
1485 ..OutputCommandMessage::default()
1486 },
1487 options,
1488 ))
1489 }
1490
1491 #[instrument(
1492 level = "trace",
1493 skip(self),
1494 fields(
1495 restate.invocation.id = self.debug_invocation_id(),
1496 restate.protocol.state = self.debug_state(),
1497 restate.journal.command_index = self.context.journal.command_index(),
1498 restate.protocol.version = %self.context.negotiated_protocol_version
1499 ),
1500 ret
1501 )]
1502 fn sys_end(&mut self) -> Result<(), Error> {
1503 invocation_debug_logs!(self, "End of the invocation");
1504 self.do_transition(SysEnd)
1505 }
1506
1507 #[inline]
1508 fn state(&self) -> crate::State {
1509 match &self.last_transition {
1510 Ok(State::WaitingStart) | Ok(State::WaitingReplayEntries { .. }) => {
1511 crate::State::WaitingPreFlight
1512 }
1513 Ok(State::Replaying { .. }) => crate::State::Replaying,
1514 Ok(State::Processing { .. }) => crate::State::Processing,
1515 Ok(State::Closed) | Err(_) => crate::State::Closed,
1516 }
1517 }
1518
1519 fn last_command_index(&self) -> i64 {
1520 self.context.journal.command_index()
1521 }
1522}
1523
1524const INDIFFERENT_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
1525 .with_decode_padding_mode(DecodePaddingMode::Indifferent)
1526 .with_encode_padding(false);
1527const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, INDIFFERENT_PAD);
1528
1529const AWAKEABLE_PREFIX: &str = "sign_1";
1530
1531pub(super) fn awakeable_id_str(id: &[u8], completion_index: u32) -> String {
1532 let mut input_buf = BytesMut::with_capacity(id.len() + size_of::<u32>());
1533 input_buf.put_slice(id);
1534 input_buf.put_u32(completion_index);
1535 format!("{AWAKEABLE_PREFIX}{}", URL_SAFE.encode(input_buf.freeze()))
1536}
1537
1538#[cfg(test)]
1539mod tests {
1540 use super::*;
1541
1542 use crate::service_protocol::messages::Future;
1543
1544 impl CoreVM {
1545 pub(crate) fn resolve_unresolved_future(
1546 &self,
1547 unresolved_future: UnresolvedFuture,
1548 ) -> Future {
1549 match &self.last_transition {
1550 Ok(
1551 State::Replaying { async_results, .. }
1552 | State::Processing { async_results, .. },
1553 ) => async_results.resolve_unresolved_future(unresolved_future),
1554 _ => panic!("Could not resolve unresolved future"),
1555 }
1556 }
1557 }
1558}