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