Skip to main content

restate_sdk_shared_core/vm/
mod.rs

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    /// Tries to transition to Processing when the condition is met, in all the other cases returns the current state.
82    #[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    // Input decoder
127    decoder: Decoder,
128
129    // State machine
130    context: Context,
131    last_transition: Result<State, Error>,
132
133    // Implicit cancellation tracking
134    tracked_invocation_ids: Vec<TrackedInvocationId>,
135
136    // Run names, useful for debugging
137    sys_run_names: HashMap<NotificationHandle, String>,
138}
139
140impl CoreVM {
141    // Returns empty string if the invocation id is not present
142    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// --- Bound checks
242#[allow(unused)]
243const fn is_send<T: Send>() {}
244const _: () = is_send::<CoreVM>();
245
246// Macro used for informative debug logs
247macro_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        // Ephemeral notifications take precedence over anything else
482        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        // TODO(slinkydeveloper) this is getting complex over time,
492        //  need to figure out how to fit this implicit cancellation and co back into the typestate pattern.
493        if self.is_implicit_cancellation_enabled() {
494            // We want the runtime to wake us up in case cancel notification comes in.
495            // An empty future (only ephemeral commands in flight) never completes: leave it out.
496            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 it's cancel signal, then let's go on with the cancellation logic
507                    if self._is_completed(CANCEL_NOTIFICATION_HANDLE) {
508                        // Loop once over the tracked invocation ids to resolve the unresolved ones
509                        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                            // Try to resolve it
517                            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                                    // This handle is resolved
526                                    self.tracked_invocation_ids[i].invocation_id =
527                                        Some(invocation_id);
528                                }
529                                res => return res,
530                            }
531                        }
532
533                        // Now we got all the invocation IDs, let's cancel!
534                        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                        // Flip the cancellation
543                        let _ = self.take_notification(CANCEL_NOTIFICATION_HANDLE);
544
545                        // Done
546                        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                    // Let's check if that's one of the tracked invocation ids
574                    // We can do binary search here because we assume tracked_invocation_ids is ordered, as handles are incremental numbers
575                    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                        // Keep track of this invocation id
583                        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            // Signal ids of skipped attempts are unknown, so a new one could collide.
1005            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                    // Store the name, we need it later when completing
1226                    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(&notification_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}