From ff06ba8645cc9883b113a75735de69b93179b4c2 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Mon, 6 Jan 2025 09:06:02 +0100 Subject: [PATCH] Service Protocol V4 support --- service-protocol-ext/combinators.proto | 22 - .../dev/restate/service/protocol.proto | 507 +++++--- src/lib.rs | 169 +-- src/service_protocol/encoding.rs | 85 +- ...dev.restate.service.protocol.extensions.rs | 10 - .../generated/dev.restate.service.protocol.rs | 754 +++++++----- src/service_protocol/header.rs | 360 ++---- src/service_protocol/messages.rs | 476 ++------ src/service_protocol/mod.rs | 21 +- src/service_protocol/version.rs | 8 +- src/tests/async_result.rs | 341 +++--- src/tests/calls.rs | 131 +-- src/tests/failures.rs | 31 +- src/tests/get_state.rs | 192 --- src/tests/input_output.rs | 52 +- src/tests/mod.rs | 65 +- src/tests/promise.rs | 319 +++-- src/tests/run.rs | 688 +++++------ src/tests/sleep.rs | 124 +- src/tests/state.rs | 1036 ++++++++--------- src/tests/suspensions.rs | 144 +-- src/vm/context.rs | 325 +++--- src/vm/errors.rs | 56 +- src/vm/mod.rs | 630 ++++++---- src/vm/transitions/async_results.rs | 168 +-- src/vm/transitions/combinators.rs | 204 ---- src/vm/transitions/input.rs | 123 +- src/vm/transitions/journal.rs | 627 ++++++++-- src/vm/transitions/mod.rs | 8 +- src/vm/transitions/terminal.rs | 28 +- tests/bootstrap.rs | 11 +- 31 files changed, 3690 insertions(+), 4025 deletions(-) delete mode 100644 service-protocol-ext/combinators.proto delete mode 100644 src/service_protocol/generated/dev.restate.service.protocol.extensions.rs delete mode 100644 src/tests/get_state.rs delete mode 100644 src/vm/transitions/combinators.rs diff --git a/service-protocol-ext/combinators.proto b/service-protocol-ext/combinators.proto deleted file mode 100644 index 01df28f..0000000 --- a/service-protocol-ext/combinators.proto +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate SDK for Node.js/TypeScript, - * which is released under the MIT license. - * - * You can find a copy of the license in file LICENSE in the root - * directory of this repository or package, or at - * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE - */ - -syntax = "proto3"; - -package dev.restate.service.protocol.extensions; - -// Type: 0xFC00 + 2 -message CombinatorEntryMessage { - repeated uint32 completed_entries_order = 1; - - // Entry name - string name = 12; -} \ No newline at end of file diff --git a/service-protocol/dev/restate/service/protocol.proto b/service-protocol/dev/restate/service/protocol.proto index 5df29b4..21cf4e3 100644 --- a/service-protocol/dev/restate/service/protocol.proto +++ b/service-protocol/dev/restate/service/protocol.proto @@ -29,6 +29,8 @@ enum ServiceProtocolVersion { // * New entry to attach to existing invocation: AttachInvocationEntryMessage // * New entry to get output of existing invocation: GetInvocationOutputEntryMessage V3 = 3; + // Immutable journal. + V4 = 4; } // --- Core frames --- @@ -49,6 +51,7 @@ message StartMessage { // The user can use this id to address this invocation in admin and status introspection apis. string debug_id = 2; + // This is the sum of known commands + notifications uint32 known_entries = 3; // protolint:disable:next REPEATED_FIELD_NAMES_PLURALIZED @@ -72,28 +75,18 @@ message StartMessage { } // Type: 0x0000 + 1 -message CompletionMessage { - uint32 entry_index = 1; - - oneof result { - Empty empty = 13; - bytes value = 14; - Failure failure = 15; - }; -} - -// Type: 0x0000 + 2 // Implementations MUST send this message when suspending an invocation. +// +// These lists represent any of the notification_idx and/or notification_name the invocation is waiting on to progress. +// The runtime will resume the invocation as soon as either one of the given notification_idx or notification_name is completed. +// Between the two lists there MUST be at least one element. message SuspensionMessage { - // This list represents any of the entry_index the invocation is waiting on to progress. - // The runtime will resume the invocation as soon as one of the given entry_index is completed. - // This list MUST not be empty. - // False positive, entry_indexes is a valid plural of entry_indices. - // https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/i/index-indexes-indices - repeated uint32 entry_indexes = 1; // protolint:disable:this REPEATED_FIELD_NAMES_PLURALIZED + repeated uint32 waiting_completions = 1; + repeated uint32 waiting_signals = 2; + repeated string waiting_named_signals = 3; } -// Type: 0x0000 + 3 +// Type: 0x0000 + 2 message ErrorMessage { // The code can be any HTTP status code, as described https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. // In addition, we define the following error codes that MAY be used by the SDK for better error reporting: @@ -105,52 +98,82 @@ message ErrorMessage { // Contains a verbose error description, e.g. the exception stacktrace. string description = 3; - // Entry that caused the failure. This may be outside the current stored journal size. + // Command that caused the failure. This may be outside the current stored journal size. // If no specific entry caused the failure, the current replayed/processed entry can be used. - optional uint32 related_entry_index = 4; + optional uint32 related_command_index = 4; // Name of the entry that caused the failure. - optional string related_entry_name = 5; - // Entry type. - optional uint32 related_entry_type = 6; + optional string related_command_name = 5; + // Command type. + optional uint32 related_command_type = 6; // Delay before executing the next retry, specified as duration in milliseconds. // If provided, it will override the default retry policy used by Restate's invoker ONLY for the next retry attempt. optional uint64 next_retry_delay = 8; } +// Type: 0x0000 + 3 +// Implementations MUST send this message when the invocation lifecycle ends. +message EndMessage { +} + // Type: 0x0000 + 4 -message EntryAckMessage { - uint32 entry_index = 1; +message CommandAckMessage { + uint32 command_index = 1; } +// This is a special control message to propose ctx.run completions to the runtime. +// This won't be written to the journal immediately, but will appear later as a new notification (meaning the result was stored). +// // Type: 0x0000 + 5 -// Implementations MUST send this message when the invocation lifecycle ends. -message EndMessage { +message ProposeRunCompletionMessage { + uint32 result_completion_id = 1; + oneof result { + bytes value = 14; + Failure failure = 15; + }; } -// --- Journal Entries --- +// --- Commands and Notifications --- -// Every Completable JournalEntry has a result field, filled only and only if the entry is in DONE state. -// -// For every journal entry, fields 12, 13, 14 and 15 are reserved. +// The Journal is modelled as commands and notifications. +// Commands define the operations executed, while notifications can be: +// * Completions to commands +// * Unnamed signals +// * Named signals // -// The field 12 is used for name. The name is used by introspection/observability tools. -// -// Depending on the semantics of the corresponding syscall, the entry can represent the completion result field with any of these three types: +// An individual command can produce 0 or more completions, where the respective completion id(s) are defined in the command message. + +// A notification message follows the following duck-type: // -// * google.protobuf.Empty empty = 13 for cases when we need to propagate to user code the distinction between default value or no value. -// * bytes value = 14 for carrying the result value -// * Failure failure = 15 for carrying a failure +message NotificationTemplate { + reserved 12; + + oneof id { + uint32 completion_id = 1; + uint32 signal_id = 2; + string signal_name = 3; + } + + oneof result { + Void void = 4; + Value value = 5; + Failure failure = 6; + + // Used by specific commands + string invocation_id = 16; + StateKeys state_keys = 17; + }; +} // ------ Input and output ------ // Completable: No // Fallible: No // Type: 0x0400 + 0 -message InputEntryMessage { +message InputCommandMessage { repeated Header headers = 1; - bytes value = 14; + Value value = 14; // Entry name string name = 12; @@ -159,9 +182,9 @@ message InputEntryMessage { // Completable: No // Fallible: No // Type: 0x0400 + 1 -message OutputEntryMessage { +message OutputCommandMessage { oneof result { - bytes value = 14; + Value value = 14; Failure failure = 15; }; @@ -173,26 +196,34 @@ message OutputEntryMessage { // Completable: Yes // Fallible: No -// Type: 0x0800 + 0 -message GetStateEntryMessage { +// Type: 0x0400 + 2 +message GetLazyStateCommandMessage { bytes key = 1; + uint32 result_completion_id = 11; + string name = 12; +} + +// Notification for GetLazyStateCommandMessage +// Type: 0x8000 + 2 +message GetLazyStateCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 6, 7, 8, 12; + + uint32 completion_id = 1; + oneof result { - Empty empty = 13; - bytes value = 14; - Failure failure = 15; + Void void = 4; + Value value = 5; }; - - // Entry name - string name = 12; } // Completable: No // Fallible: No -// Type: 0x0800 + 1 -message SetStateEntryMessage { +// Type: 0x0400 + 3 +message SetStateCommandMessage { bytes key = 1; - bytes value = 3; + Value value = 3; // Entry name string name = 12; @@ -200,8 +231,8 @@ message SetStateEntryMessage { // Completable: No // Fallible: No -// Type: 0x0800 + 2 -message ClearStateEntryMessage { +// Type: 0x0400 + 4 +message ClearStateCommandMessage { bytes key = 1; // Entry name @@ -210,39 +241,50 @@ message ClearStateEntryMessage { // Completable: No // Fallible: No -// Type: 0x0800 + 3 -message ClearAllStateEntryMessage { +// Type: 0x0400 + 5 +message ClearAllStateCommandMessage { // Entry name string name = 12; } // Completable: Yes // Fallible: No -// Type: 0x0800 + 4 -message GetStateKeysEntryMessage { - message StateKeys { - repeated bytes keys = 1; - } +// Type: 0x0400 + 6 +message GetLazyStateKeysCommandMessage { + uint32 result_completion_id = 11; + string name = 12; +} + +// Notification for GetLazyStateKeysCommandMessage +// Type: 0x8000 + 6 +message GetLazyStateKeysCompletionNotificationMessage { + // See NotificationMessage above + reserved 2 to 8, 12, 16; + + uint32 completion_id = 1; + StateKeys state_keys = 17; +} + +// Completable: No +// Fallible: No +// Type: 0x0400 + 7 +message GetEagerStateCommandMessage { + bytes key = 1; oneof result { - StateKeys value = 14; - Failure failure = 15; + Void void = 13; + Value value = 14; }; // Entry name string name = 12; } -// Completable: Yes +// Completable: No // Fallible: No -// Type: 0x0800 + 8 -message GetPromiseEntryMessage { - string key = 1; - - oneof result { - bytes value = 14; - Failure failure = 15; - }; +// Type: 0x0400 + 8 +message GetEagerStateKeysCommandMessage { + StateKeys value = 14; // Entry name string name = 12; @@ -250,66 +292,111 @@ message GetPromiseEntryMessage { // Completable: Yes // Fallible: No -// Type: 0x0800 + 9 -message PeekPromiseEntryMessage { +// Type: 0x0400 + 9 +message GetPromiseCommandMessage { string key = 1; + uint32 result_completion_id = 11; + string name = 12; +} + +// Notification for GetPromiseCommandMessage +// Type: 0x8000 + 9 +message GetPromiseCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 4, 12, 16, 17; + + uint32 completion_id = 1; + oneof result { - Empty empty = 13; - bytes value = 14; - Failure failure = 15; + Value value = 5; + Failure failure = 6; }; +} - // Entry name +// Completable: Yes +// Fallible: No +// Type: 0x0400 + A +message PeekPromiseCommandMessage { + string key = 1; + + uint32 result_completion_id = 11; string name = 12; } +// Notification for PeekPromiseCommandMessage +// Type: 0x8000 + A +message PeekPromiseCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 12, 16, 17; + + uint32 completion_id = 1; + + oneof result { + Void void = 4; + Value value = 5; + Failure failure = 6; + }; +} + // Completable: Yes // Fallible: No -// Type: 0x0800 + A -message CompletePromiseEntryMessage { +// Type: 0x0400 + B +message CompletePromiseCommandMessage { string key = 1; // The value to use to complete the promise oneof completion { - bytes completion_value = 2; + Value completion_value = 2; Failure completion_failure = 3; }; - oneof result { - // Returns empty if value was set successfully - Empty empty = 13; - // Returns a failure if the promise was already completed - Failure failure = 15; - } - - // Entry name + uint32 result_completion_id = 11; string name = 12; } +// Notification for CompletePromiseCommandMessage +// Type: 0x8000 + B +message CompletePromiseCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 5, 7, 8, 12, 16, 17; + + uint32 completion_id = 1; + + oneof result { + Void void = 4; + Failure failure = 6; + }; +} + // ------ Syscalls ------ // Completable: Yes // Fallible: No -// Type: 0x0C00 + 0 -message SleepEntryMessage { +// Type: 0x0400 + C +message SleepCommandMessage { // Wake up time. // The time is set as duration since UNIX Epoch. uint64 wake_up_time = 1; - oneof result { - Empty empty = 13; - Failure failure = 15; - } - - // Entry name + uint32 result_completion_id = 11; string name = 12; } -// Completable: Yes +// Notification for SleepCommandMessage +// Type: 0x8000 + C +message SleepCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 5, 6, 7, 8, 12, 16, 17; + + uint32 completion_id = 1; + Void void = 4; +} + +// Completable: Yes (two notifications: one with invocation id, then one with the actual result) // Fallible: Yes -// Type: 0x0C00 + 1 -message CallEntryMessage { +// Type: 0x0400 + D +message CallCommandMessage { string service_name = 1; string handler_name = 2; @@ -323,19 +410,39 @@ message CallEntryMessage { // If present, it must be non empty. optional string idempotency_key = 6; + uint32 invocation_id_notification_idx = 10; + uint32 result_completion_id = 11; + string name = 12; +} + +// Notification for CallCommandMessage and OneWayCallCommandMessage +// Type: 0x8000 + E +message CallInvocationIdCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 4, 5, 6, 7, 8, 12, 17; + + uint32 completion_id = 1; + string invocation_id = 16; +} + +// Notification for CallCommandMessage +// Type: 0x8000 + D +message CallCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 4, 12, 16, 17; + + uint32 completion_id = 1; + oneof result { - bytes value = 14; - Failure failure = 15; + Value value = 5; + Failure failure = 6; }; - - // Entry name - string name = 12; } -// Completable: No +// Completable: Yes (only one notification with invocation id) // Fallible: Yes -// Type: 0x0C00 + 2 -message OneWayCallEntryMessage { +// Type: 0x0400 + E +message OneWayCallCommandMessage { string service_name = 1; string handler_name = 2; @@ -355,136 +462,164 @@ message OneWayCallEntryMessage { // If present, it must be non empty. optional string idempotency_key = 7; - // Entry name - string name = 12; -} - -// Completable: Yes -// Fallible: No -// Type: 0x0C00 + 3 -// Awakeables are addressed by an identifier exposed to the user. See the spec for more details. -message AwakeableEntryMessage { - oneof result { - bytes value = 14; - Failure failure = 15; - }; - - // Entry name + uint32 invocation_id_notification_idx = 10; string name = 12; } // Completable: No // Fallible: Yes -// Type: 0x0C00 + 4 -message CompleteAwakeableEntryMessage { - // Identifier of the awakeable. See the spec for more details. - string id = 1; +// Type: 0x04000 + 10 +message SendSignalCommandMessage { + string target_invocation_id = 1; + + oneof signal_id { + uint32 idx = 2; + string name = 3; + } oneof result { - bytes value = 14; - Failure failure = 15; + Void void = 4; + Value value = 5; + Failure failure = 6; }; - // Entry name - string name = 12; + // Cannot use the field 'name' here because used above + string entry_name = 12; } -// Completable: No +// Proposals for Run completions are sent through ProposeRunCompletionMessage +// +// Completable: Yes // Fallible: No -// Type: 0x0C00 + 5 -// Flag: RequiresRuntimeAck -message RunEntryMessage { - oneof result { - bytes value = 14; - Failure failure = 15; - }; - - // Entry name +// Type: 0x0400 + 11 +message RunCommandMessage { + uint32 result_completion_id = 11; string name = 12; } -// Completable: No -// Fallible: Yes -// Type: 0x0C00 + 6 -message CancelInvocationEntryMessage { - oneof target { - // Target invocation id to cancel - string invocation_id = 1; - // Target index of the call/one way call journal entry in this journal. - uint32 call_entry_index = 2; - } - - // Entry name - string name = 12; -} +// Notification for RunCommandMessage +// Type: 0x8000 + 11 +message RunCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 4, 12, 16, 17; -// Completable: Yes -// Fallible: Yes -// Type: 0x0C00 + 7 -message GetCallInvocationIdEntryMessage { - // Index of the call/one way call journal entry in this journal. - uint32 call_entry_index = 1; + uint32 completion_id = 1; oneof result { - string value = 14; - Failure failure = 15; + Value value = 5; + Failure failure = 6; }; - - string name = 12; } // Completable: Yes // Fallible: Yes -// Type: 0x0C00 + 8 -message AttachInvocationEntryMessage { +// Type: 0x0400 + 12 +message AttachInvocationCommandMessage { oneof target { // Target invocation id string invocation_id = 1; - // Target index of the call/one way call journal entry in this journal. - uint32 call_entry_index = 2; // Target idempotent request IdempotentRequestTarget idempotent_request_target = 3; // Target workflow target WorkflowTarget workflow_target = 4; } + uint32 result_completion_id = 11; + string name = 12; +} + +// Notification for AttachInvocationCommandMessage +// Type: 0x8000 + 12 +message AttachInvocationCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 4, 12, 16, 17; + + uint32 completion_id = 1; + oneof result { - bytes value = 14; - Failure failure = 15; + Value value = 5; + Failure failure = 6; }; - - string name = 12; } // Completable: Yes // Fallible: Yes -// Type: 0x0C00 + 9 -message GetInvocationOutputEntryMessage { +// Type: 0x0400 + 13 +message GetInvocationOutputCommandMessage { oneof target { // Target invocation id string invocation_id = 1; - // Target index of the call/one way call journal entry in this journal. - uint32 call_entry_index = 2; // Target idempotent request IdempotentRequestTarget idempotent_request_target = 3; // Target workflow target WorkflowTarget workflow_target = 4; } + uint32 result_completion_id = 11; + string name = 12; +} + +// Notification for GetInvocationOutputCommandMessage +// Type: 0x8000 + 13 +message GetInvocationOutputCompletionNotificationMessage { + // See NotificationMessage above + reserved 2, 3, 12, 16, 17; + + uint32 completion_id = 1; + oneof result { - // Empty if no result is still available - Empty empty = 13; - bytes value = 14; - Failure failure = 15; + Void void = 4; + Value value = 5; + Failure failure = 6; + }; +} + +// We have this for backward compatibility, because we need to parse both old and new awakeable id. +// Completable: No +// Fallible: Yes +// Type: 0x0400 + 14 +message CompleteAwakeableCommandMessage { + string awakeable_id = 1; + + oneof result { + Value value = 2; + Failure failure = 3; }; + // Cannot use the field 'name' here because used above string name = 12; } +// Notification message for signals +// Type: 0xFBFF +message SignalNotificationMessage { + // See NotificationMessage above + reserved 1, 12, 16, 17; + + oneof signal_id { + uint32 idx = 2; + string name = 3; + } + + oneof result { + Void void = 4; + Value value = 5; + Failure failure = 6; + }; +} + // --- Nested messages +message StateKeys { + repeated bytes keys = 1; +} + +message Value { + bytes content = 1; +} + // This failure object carries user visible errors, -// e.g. invocation failure return value or failure result of an InvokeEntryMessage. +// e.g. invocation failure return value or failure result of an InvokeCommandMessage. message Failure { // The code can be any HTTP status code, as described https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. uint32 code = 1; @@ -509,5 +644,11 @@ message IdempotentRequestTarget { string idempotency_key = 4; } -message Empty { +message Void { } + +enum BuiltInSignal { + UNKNOWN = 0; + CANCEL = 1; + reserved 2 to 15; +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e41d020..e63e6b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ mod vm; use bytes::Bytes; use std::borrow::Cow; -use std::fmt; use std::time::Duration; pub use crate::retries::RetryPolicy; @@ -23,8 +22,6 @@ pub mod error { pub use crate::vm::errors::InvocationErrorCode; } -use crate::vm::AsyncResultAccessTrackerInner; - #[derive(Debug, Eq, PartialEq)] pub struct Header { pub key: Cow<'static, str>, @@ -126,34 +123,32 @@ pub struct Target { pub headers: Vec
, } +pub const CANCEL_NOTIFICATION_HANDLE: NotificationHandle = NotificationHandle(1); + #[derive(Debug, Hash, Clone, Copy, Eq, PartialEq)] -pub struct AsyncResultHandle(u32); +pub struct NotificationHandle(u32); -impl From for AsyncResultHandle { +impl From for NotificationHandle { fn from(value: u32) -> Self { - AsyncResultHandle(value) + NotificationHandle(value) } } -impl From for u32 { - fn from(value: AsyncResultHandle) -> Self { +impl From for u32 { + fn from(value: NotificationHandle) -> Self { value.0 } } #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct SendHandle(u32); - -impl From for SendHandle { - fn from(value: u32) -> Self { - SendHandle(value) - } +pub struct CallHandle { + pub invocation_id_notification_handle: NotificationHandle, + pub call_notification_handle: NotificationHandle, } -impl From for u32 { - fn from(value: SendHandle) -> Self { - value.0 - } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct SendHandle { + pub invocation_id_notification_handle: NotificationHandle, } #[derive(Debug, Eq, PartialEq)] @@ -166,7 +161,6 @@ pub enum Value { StateKeys(Vec), /// Only returned for get_call_invocation_id InvocationId(String), - CombinatorResult(Vec), } /// Terminal failure @@ -184,12 +178,6 @@ pub struct EntryRetryInfo { pub retry_loop_duration: Duration, } -#[derive(Debug)] -pub enum RunEnterResult { - Executed(NonEmptyValue), - NotExecuted(EntryRetryInfo), -} - #[derive(Debug, Clone)] pub enum RunExitResult { Success(Bytes), @@ -215,24 +203,9 @@ impl From for Value { } } -#[derive(Debug, Eq, PartialEq)] -pub enum GetInvocationIdTarget { - CallEntry(AsyncResultHandle), - SendEntry(SendHandle), -} - -#[derive(Debug, Eq, PartialEq)] -pub enum CancelInvocationTarget { - InvocationId(String), - CallEntry(AsyncResultHandle), - SendEntry(SendHandle), -} - #[derive(Debug, Eq, PartialEq)] pub enum AttachInvocationTarget { InvocationId(String), - CallEntry(AsyncResultHandle), - SendEntry(SendHandle), WorkflowId { name: String, key: String, @@ -253,17 +226,19 @@ pub enum TakeOutputResult { pub type VMResult = Result; -pub struct VMOptions { - /// If true, false when two concurrent async results are awaited at the same time. If false, just log it. - pub fail_on_wait_concurrent_async_result: bool, -} - -impl Default for VMOptions { - fn default() -> Self { - Self { - fail_on_wait_concurrent_async_result: true, - } - } +#[derive(Default)] +pub struct VMOptions {} + +#[derive(Debug, PartialEq, Eq)] +pub enum DoProgressResponse { + /// Any of the given AsyncResultHandle completed + AnyCompleted, + /// The SDK should read from input at this point + ReadFromInput, + /// The SDK should execute a pending run + ExecuteRun(NotificationHandle), + /// Any of the run given before with ExecuteRun is waiting for completion + WaitingPendingRun, } pub trait VM: Sized { @@ -291,21 +266,25 @@ pub trait VM: Sized { // --- Async results - fn notify_await_point(&mut self, handle: AsyncResultHandle); + fn is_completed(&self, handle: NotificationHandle) -> bool; - /// Ok(None) means the result is not ready. - fn take_async_result( + fn do_progress( &mut self, - handle: AsyncResultHandle, + any_handle: Vec, + ) -> Result; + + fn take_notification( + &mut self, + handle: NotificationHandle, ) -> Result, SuspendedOrVMError>; // --- Syscall(s) fn sys_input(&mut self) -> VMResult; - fn sys_state_get(&mut self, key: String) -> VMResult; + fn sys_state_get(&mut self, key: String) -> VMResult; - fn sys_state_get_keys(&mut self) -> VMResult; + fn sys_state_get_keys(&mut self) -> VMResult; fn sys_state_set(&mut self, key: String, value: Bytes) -> VMResult<()>; @@ -318,9 +297,9 @@ pub trait VM: Sized { &mut self, wake_up_time_since_unix_epoch: Duration, now_since_unix_epoch: Option, - ) -> VMResult; + ) -> VMResult; - fn sys_call(&mut self, target: Target, input: Bytes) -> VMResult; + fn sys_call(&mut self, target: Target, input: Bytes) -> VMResult; fn sys_send( &mut self, @@ -329,38 +308,49 @@ pub trait VM: Sized { execution_time_since_unix_epoch: Option, ) -> VMResult; - fn sys_awakeable(&mut self) -> VMResult<(String, AsyncResultHandle)>; + fn sys_awakeable(&mut self) -> VMResult<(String, NotificationHandle)>; fn sys_complete_awakeable(&mut self, id: String, value: NonEmptyValue) -> VMResult<()>; - fn sys_get_promise(&mut self, key: String) -> VMResult; + fn create_signal_handle(&mut self, signal_name: String) -> VMResult; + + fn sys_complete_signal( + &mut self, + target_invocation_id: String, + signal_name: String, + value: NonEmptyValue, + ) -> VMResult<()>; + + fn sys_get_promise(&mut self, key: String) -> VMResult; - fn sys_peek_promise(&mut self, key: String) -> VMResult; + fn sys_peek_promise(&mut self, key: String) -> VMResult; fn sys_complete_promise( &mut self, key: String, value: NonEmptyValue, - ) -> VMResult; + ) -> VMResult; - fn sys_run_enter(&mut self, name: String) -> VMResult; + fn sys_run(&mut self, name: String) -> VMResult; - fn sys_run_exit( + fn propose_run_completion( &mut self, + notification_handle: NotificationHandle, value: RunExitResult, retry_policy: RetryPolicy, - ) -> VMResult; + ) -> VMResult<()>; - fn sys_get_call_invocation_id( - &mut self, - call: GetInvocationIdTarget, - ) -> VMResult; - - fn sys_cancel_invocation(&mut self, target: CancelInvocationTarget) -> VMResult<()>; + fn sys_cancel_invocation(&mut self, target_invocation_id: String) -> VMResult<()>; - fn sys_attach_invocation(&mut self, target: AttachInvocationTarget) -> VMResult<()>; + fn sys_attach_invocation( + &mut self, + target: AttachInvocationTarget, + ) -> VMResult; - fn sys_get_invocation_output(&mut self, target: AttachInvocationTarget) -> VMResult<()>; + fn sys_get_invocation_output( + &mut self, + target: AttachInvocationTarget, + ) -> VMResult; fn sys_write_output(&mut self, value: NonEmptyValue) -> VMResult<()>; @@ -368,15 +358,6 @@ pub trait VM: Sized { /// Returns true if the state machine is in processing state fn is_processing(&self) -> bool; - - /// Returns true if the state machine is between a sys_run_enter and sys_run_exit - fn is_inside_run(&self) -> bool; - - /// Returns false if the combinator can't be completed yet. - fn sys_try_complete_combinator( - &mut self, - combinator: impl AsyncResultCombinator + fmt::Debug, - ) -> VMResult>; } // HOW TO USE THIS API @@ -420,27 +401,5 @@ pub trait VM: Sized { // } // io.close() -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AsyncResultState { - Success, - Failure, - NotReady, -} - -pub struct AsyncResultAccessTracker(AsyncResultAccessTrackerInner); - -impl AsyncResultAccessTracker { - pub fn get_state(&mut self, handle: AsyncResultHandle) -> AsyncResultState { - self.0.get_state(handle) - } -} - -pub trait AsyncResultCombinator { - fn try_complete( - &self, - tracker: &mut AsyncResultAccessTracker, - ) -> Option>; -} - #[cfg(test)] mod tests; diff --git a/src/service_protocol/encoding.rs b/src/service_protocol/encoding.rs index fdb1547..0c0b5be 100644 --- a/src/service_protocol/encoding.rs +++ b/src/service_protocol/encoding.rs @@ -9,13 +9,14 @@ // by the Apache License, Version 2.0. use super::header::UnknownMessageType; -use super::messages::{RestateMessage, WriteableRestateMessage}; +use super::messages::RestateMessage; use super::*; use std::mem; use bytes::{Buf, BufMut, Bytes, BytesMut}; use bytes_utils::SegmentedBuf; +use prost::Message; #[derive(Debug, thiserror::Error)] pub enum DecodingError { @@ -26,6 +27,11 @@ pub enum DecodingError { expected: MessageType, actual: MessageType, }, + #[error("expected message type {expected:?} to have field {field}")] + MissingField { + expected: MessageType, + field: &'static str, + }, #[error(transparent)] UnknownMessageType(#[from] UnknownMessageType), } @@ -46,7 +52,7 @@ impl Encoder { } /// Encodes a protocol message to bytes - pub fn encode(&self, msg: &M) -> Bytes { + pub fn encode(&self, msg: &M) -> Bytes { let mut buf = BytesMut::with_capacity(self.encoded_len(msg)); self.encode_to_buf_mut(&mut buf, msg).expect( "Encoding messages should be infallible, \ @@ -57,16 +63,16 @@ impl Encoder { } /// Includes header len - pub fn encoded_len(&self, msg: &M) -> usize { + pub fn encoded_len(&self, msg: &M) -> usize { 8 + msg.encoded_len() } - pub fn encode_to_buf_mut( + pub fn encode_to_buf_mut( &self, mut buf: impl BufMut, msg: &M, ) -> Result<(), prost::EncodeError> { - let header = msg.generate_header(false); + let header = msg.generate_header(); buf.put_u64(header.into()); // Note: // prost::EncodeError can be triggered only by a buffer smaller than required, @@ -95,6 +101,26 @@ impl RawMessage { } M::decode(self.1).map_err(|e| DecodingError::DecodeMessage(self.0.message_type(), e)) } + + pub fn decode_as_notification(self) -> Result { + let ty = self.ty(); + assert!(ty.is_notification(), "Expected a notification"); + + let messages::NotificationTemplate { id, result } = + messages::NotificationTemplate::decode(self.1) + .map_err(|e| DecodingError::DecodeMessage(self.0.message_type(), e))?; + + Ok(Notification { + id: id.ok_or(DecodingError::MissingField { + expected: ty, + field: "id", + })?, + result: result.ok_or(DecodingError::MissingField { + expected: ty, + field: "result", + })?, + }) + } } /// Stateful decoder to decode [`RestateMessage`] @@ -149,7 +175,7 @@ impl DecoderState { fn needs_bytes(&self) -> usize { match self { DecoderState::WaitingHeader => 8, - DecoderState::WaitingPayload(h) => h.frame_length() as usize, + DecoderState::WaitingPayload(h) => h.message_length() as usize, } } @@ -162,7 +188,7 @@ impl DecoderState { DecoderState::WaitingPayload(header) } DecoderState::WaitingPayload(h) => { - let msg = RawMessage(h, buf.copy_to_bytes(h.frame_length() as usize)); + let msg = RawMessage(h, buf.copy_to_bytes(h.message_length() as usize)); res = Some(msg); DecoderState::WaitingHeader } @@ -199,15 +225,19 @@ mod tests { duration_since_last_stored_entry: 0, }; - let expected_msg_1 = messages::InputEntryMessage { - value: Bytes::from_static("input".as_bytes()), - ..messages::InputEntryMessage::default() + let expected_msg_1 = messages::InputCommandMessage { + value: Some(messages::Value { + content: Bytes::from_static("input".as_bytes()), + }), + ..messages::InputCommandMessage::default() }; - let expected_msg_2 = messages::CompletionMessage { - entry_index: 1, - result: Some(messages::completion_message::Result::Empty( - messages::Empty::default(), - )), + let expected_msg_2 = messages::GetLazyStateCompletionNotificationMessage { + completion_id: 1, + result: Some( + messages::get_lazy_state_completion_notification_message::Result::Void( + messages::Void::default(), + ), + ), }; decoder.push(encoder.encode(&expected_msg_0)); @@ -224,12 +254,11 @@ mod tests { let actual_msg_1 = decoder.consume_next().unwrap().unwrap(); assert_eq!( actual_msg_1.header().message_type(), - MessageType::InputEntry + MessageType::InputCommand ); - assert_eq!(actual_msg_1.header().completed(), None); assert_eq!( actual_msg_1 - .decode_to::() + .decode_to::() .unwrap(), expected_msg_1 ); @@ -237,11 +266,11 @@ mod tests { let actual_msg_2 = decoder.consume_next().unwrap().unwrap(); assert_eq!( actual_msg_2.header().message_type(), - MessageType::Completion + MessageType::GetLazyStateCompletionNotification ); assert_eq!( actual_msg_2 - .decode_to::() + .decode_to::() .unwrap(), expected_msg_2 ); @@ -263,9 +292,11 @@ mod tests { let encoder = Encoder::new(Version::maximum_supported_version()); let mut decoder = Decoder::new(Version::maximum_supported_version()); - let expected_msg = messages::InputEntryMessage { - value: Bytes::from_static("input".as_bytes()), - ..messages::InputEntryMessage::default() + let expected_msg = messages::InputCommandMessage { + value: Some(messages::Value { + content: Bytes::from_static("input".as_bytes()), + }), + ..messages::InputCommandMessage::default() }; let expected_msg_encoded = encoder.encode(&expected_msg); @@ -275,11 +306,13 @@ mod tests { decoder.push(expected_msg_encoded.slice(split_index..)); let actual_msg = decoder.consume_next().unwrap().unwrap(); - assert_eq!(actual_msg.header().message_type(), MessageType::InputEntry); - assert_eq!(actual_msg.header().completed(), None); + assert_eq!( + actual_msg.header().message_type(), + MessageType::InputCommand + ); assert_eq!( actual_msg - .decode_to::() + .decode_to::() .unwrap(), expected_msg ); diff --git a/src/service_protocol/generated/dev.restate.service.protocol.extensions.rs b/src/service_protocol/generated/dev.restate.service.protocol.extensions.rs deleted file mode 100644 index c347398..0000000 --- a/src/service_protocol/generated/dev.restate.service.protocol.extensions.rs +++ /dev/null @@ -1,10 +0,0 @@ -// This file is @generated by prost-build. -/// Type: 0xFC00 + 2 -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct CombinatorEntryMessage { - #[prost(uint32, repeated, tag = "1")] - pub completed_entries_order: ::prost::alloc::vec::Vec, - /// Entry name - #[prost(string, tag = "12")] - pub name: ::prost::alloc::string::String, -} diff --git a/src/service_protocol/generated/dev.restate.service.protocol.rs b/src/service_protocol/generated/dev.restate.service.protocol.rs index 5eca093..4b5e8e1 100644 --- a/src/service_protocol/generated/dev.restate.service.protocol.rs +++ b/src/service_protocol/generated/dev.restate.service.protocol.rs @@ -9,6 +9,7 @@ pub struct StartMessage { /// The user can use this id to address this invocation in admin and status introspection apis. #[prost(string, tag = "2")] pub debug_id: ::prost::alloc::string::String, + /// This is the sum of known commands + notifications #[prost(uint32, tag = "3")] pub known_entries: u32, /// protolint:disable:next REPEATED_FIELD_NAMES_PLURALIZED @@ -45,41 +46,21 @@ pub mod start_message { } } /// Type: 0x0000 + 1 -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct CompletionMessage { - #[prost(uint32, tag = "1")] - pub entry_index: u32, - #[prost(oneof = "completion_message::Result", tags = "13, 14, 15")] - pub result: ::core::option::Option, -} -/// Nested message and enum types in `CompletionMessage`. -pub mod completion_message { - #[allow(clippy::enum_variant_names)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Result { - #[prost(message, tag = "13")] - Empty(super::Empty), - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] - Failure(super::Failure), - } -} -/// Type: 0x0000 + 2 /// Implementations MUST send this message when suspending an invocation. +/// +/// These lists represent any of the notification_idx and/or notification_name the invocation is waiting on to progress. +/// The runtime will resume the invocation as soon as either one of the given notification_idx or notification_name is completed. +/// Between the two lists there MUST be at least one element. #[derive(Clone, PartialEq, ::prost::Message)] pub struct SuspensionMessage { - /// This list represents any of the entry_index the invocation is waiting on to progress. - /// The runtime will resume the invocation as soon as one of the given entry_index is completed. - /// This list MUST not be empty. - /// False positive, entry_indexes is a valid plural of entry_indices. - /// - /// - /// protolint:disable:this REPEATED_FIELD_NAMES_PLURALIZED #[prost(uint32, repeated, tag = "1")] - pub entry_indexes: ::prost::alloc::vec::Vec, + pub waiting_completions: ::prost::alloc::vec::Vec, + #[prost(uint32, repeated, tag = "2")] + pub waiting_signals: ::prost::alloc::vec::Vec, + #[prost(string, repeated, tag = "3")] + pub waiting_named_signals: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -/// Type: 0x0000 + 3 +/// Type: 0x0000 + 2 #[derive(Clone, PartialEq, ::prost::Message)] pub struct ErrorMessage { /// The code can be any HTTP status code, as described @@ -94,40 +75,100 @@ pub struct ErrorMessage { /// Contains a verbose error description, e.g. the exception stacktrace. #[prost(string, tag = "3")] pub description: ::prost::alloc::string::String, - /// Entry that caused the failure. This may be outside the current stored journal size. + /// Command that caused the failure. This may be outside the current stored journal size. /// If no specific entry caused the failure, the current replayed/processed entry can be used. #[prost(uint32, optional, tag = "4")] - pub related_entry_index: ::core::option::Option, + pub related_command_index: ::core::option::Option, /// Name of the entry that caused the failure. #[prost(string, optional, tag = "5")] - pub related_entry_name: ::core::option::Option<::prost::alloc::string::String>, - /// Entry type. + pub related_command_name: ::core::option::Option<::prost::alloc::string::String>, + /// Command type. #[prost(uint32, optional, tag = "6")] - pub related_entry_type: ::core::option::Option, + pub related_command_type: ::core::option::Option, /// Delay before executing the next retry, specified as duration in milliseconds. /// If provided, it will override the default retry policy used by Restate's invoker ONLY for the next retry attempt. #[prost(uint64, optional, tag = "8")] pub next_retry_delay: ::core::option::Option, } +/// Type: 0x0000 + 3 +/// Implementations MUST send this message when the invocation lifecycle ends. +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct EndMessage {} /// Type: 0x0000 + 4 #[derive(Clone, Copy, PartialEq, ::prost::Message)] -pub struct EntryAckMessage { +pub struct CommandAckMessage { #[prost(uint32, tag = "1")] - pub entry_index: u32, + pub command_index: u32, } +/// This is a special control message to propose ctx.run completions to the runtime. +/// This won't be written to the journal immediately, but will appear later as a new notification (meaning the result was stored). +/// /// Type: 0x0000 + 5 -/// Implementations MUST send this message when the invocation lifecycle ends. -#[derive(Clone, Copy, PartialEq, ::prost::Message)] -pub struct EndMessage {} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProposeRunCompletionMessage { + #[prost(uint32, tag = "1")] + pub result_completion_id: u32, + #[prost(oneof = "propose_run_completion_message::Result", tags = "14, 15")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `ProposeRunCompletionMessage`. +pub mod propose_run_completion_message { + #[allow(clippy::enum_variant_names)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(bytes, tag = "14")] + Value(::prost::bytes::Bytes), + #[prost(message, tag = "15")] + Failure(super::Failure), + } +} +/// A notification message follows the following duck-type: +/// +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NotificationTemplate { + #[prost(oneof = "notification_template::Id", tags = "1, 2, 3")] + pub id: ::core::option::Option, + #[prost(oneof = "notification_template::Result", tags = "4, 5, 6, 16, 17")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `NotificationTemplate`. +pub mod notification_template { + #[allow(clippy::enum_variant_names)] + #[derive(Eq, Hash)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Id { + #[prost(uint32, tag = "1")] + CompletionId(u32), + #[prost(uint32, tag = "2")] + SignalId(u32), + #[prost(string, tag = "3")] + SignalName(::prost::alloc::string::String), + } + #[allow(clippy::enum_variant_names)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(message, tag = "4")] + Void(super::Void), + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] + Failure(super::Failure), + /// Used by specific commands + #[prost(string, tag = "16")] + InvocationId(::prost::alloc::string::String), + #[prost(message, tag = "17")] + StateKeys(super::StateKeys), + } +} /// Completable: No /// Fallible: No /// Type: 0x0400 + 0 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct InputEntryMessage { +pub struct InputCommandMessage { #[prost(message, repeated, tag = "1")] pub headers: ::prost::alloc::vec::Vec
, - #[prost(bytes = "bytes", tag = "14")] - pub value: ::prost::bytes::Bytes, + #[prost(message, optional, tag = "14")] + pub value: ::core::option::Option, /// Entry name #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, @@ -136,68 +177,79 @@ pub struct InputEntryMessage { /// Fallible: No /// Type: 0x0400 + 1 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct OutputEntryMessage { +pub struct OutputCommandMessage { /// Entry name #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "output_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, + #[prost(oneof = "output_command_message::Result", tags = "14, 15")] + pub result: ::core::option::Option, } -/// Nested message and enum types in `OutputEntryMessage`. -pub mod output_entry_message { +/// Nested message and enum types in `OutputCommandMessage`. +pub mod output_command_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), + #[prost(message, tag = "14")] + Value(super::Value), #[prost(message, tag = "15")] Failure(super::Failure), } } /// Completable: Yes /// Fallible: No -/// Type: 0x0800 + 0 +/// Type: 0x0400 + 2 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetStateEntryMessage { +pub struct GetLazyStateCommandMessage { #[prost(bytes = "bytes", tag = "1")] pub key: ::prost::bytes::Bytes, - /// Entry name + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "get_state_entry_message::Result", tags = "13, 14, 15")] - pub result: ::core::option::Option, } -/// Nested message and enum types in `GetStateEntryMessage`. -pub mod get_state_entry_message { +/// Notification for GetLazyStateCommandMessage +/// Type: 0x8000 + 2 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetLazyStateCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost( + oneof = "get_lazy_state_completion_notification_message::Result", + tags = "4, 5" + )] + pub result: ::core::option::Option< + get_lazy_state_completion_notification_message::Result, + >, +} +/// Nested message and enum types in `GetLazyStateCompletionNotificationMessage`. +pub mod get_lazy_state_completion_notification_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(message, tag = "13")] - Empty(super::Empty), - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] - Failure(super::Failure), + #[prost(message, tag = "4")] + Void(super::Void), + #[prost(message, tag = "5")] + Value(super::Value), } } /// Completable: No /// Fallible: No -/// Type: 0x0800 + 1 +/// Type: 0x0400 + 3 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct SetStateEntryMessage { +pub struct SetStateCommandMessage { #[prost(bytes = "bytes", tag = "1")] pub key: ::prost::bytes::Bytes, - #[prost(bytes = "bytes", tag = "3")] - pub value: ::prost::bytes::Bytes, + #[prost(message, optional, tag = "3")] + pub value: ::core::option::Option, /// Entry name #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, } /// Completable: No /// Fallible: No -/// Type: 0x0800 + 2 +/// Type: 0x0400 + 4 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct ClearStateEntryMessage { +pub struct ClearStateCommandMessage { #[prost(bytes = "bytes", tag = "1")] pub key: ::prost::bytes::Bytes, /// Entry name @@ -206,159 +258,223 @@ pub struct ClearStateEntryMessage { } /// Completable: No /// Fallible: No -/// Type: 0x0800 + 3 +/// Type: 0x0400 + 5 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct ClearAllStateEntryMessage { +pub struct ClearAllStateCommandMessage { /// Entry name #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, } /// Completable: Yes /// Fallible: No -/// Type: 0x0800 + 4 +/// Type: 0x0400 + 6 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetLazyStateKeysCommandMessage { + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, + #[prost(string, tag = "12")] + pub name: ::prost::alloc::string::String, +} +/// Notification for GetLazyStateKeysCommandMessage +/// Type: 0x8000 + 6 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetStateKeysEntryMessage { +pub struct GetLazyStateKeysCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost(message, optional, tag = "17")] + pub state_keys: ::core::option::Option, +} +/// Completable: No +/// Fallible: No +/// Type: 0x0400 + 7 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetEagerStateCommandMessage { + #[prost(bytes = "bytes", tag = "1")] + pub key: ::prost::bytes::Bytes, /// Entry name #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "get_state_keys_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, + #[prost(oneof = "get_eager_state_command_message::Result", tags = "13, 14")] + pub result: ::core::option::Option, } -/// Nested message and enum types in `GetStateKeysEntryMessage`. -pub mod get_state_keys_entry_message { - #[derive(Clone, PartialEq, ::prost::Message)] - pub struct StateKeys { - #[prost(bytes = "bytes", repeated, tag = "1")] - pub keys: ::prost::alloc::vec::Vec<::prost::bytes::Bytes>, - } +/// Nested message and enum types in `GetEagerStateCommandMessage`. +pub mod get_eager_state_command_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { + #[prost(message, tag = "13")] + Void(super::Void), #[prost(message, tag = "14")] - Value(StateKeys), - #[prost(message, tag = "15")] - Failure(super::Failure), + Value(super::Value), } } +/// Completable: No +/// Fallible: No +/// Type: 0x0400 + 8 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetEagerStateKeysCommandMessage { + #[prost(message, optional, tag = "14")] + pub value: ::core::option::Option, + /// Entry name + #[prost(string, tag = "12")] + pub name: ::prost::alloc::string::String, +} /// Completable: Yes /// Fallible: No -/// Type: 0x0800 + 8 +/// Type: 0x0400 + 9 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetPromiseEntryMessage { +pub struct GetPromiseCommandMessage { #[prost(string, tag = "1")] pub key: ::prost::alloc::string::String, - /// Entry name + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "get_promise_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, } -/// Nested message and enum types in `GetPromiseEntryMessage`. -pub mod get_promise_entry_message { +/// Notification for GetPromiseCommandMessage +/// Type: 0x8000 + 9 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetPromiseCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost( + oneof = "get_promise_completion_notification_message::Result", + tags = "5, 6" + )] + pub result: ::core::option::Option< + get_promise_completion_notification_message::Result, + >, +} +/// Nested message and enum types in `GetPromiseCompletionNotificationMessage`. +pub mod get_promise_completion_notification_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] Failure(super::Failure), } } /// Completable: Yes /// Fallible: No -/// Type: 0x0800 + 9 +/// Type: 0x0400 + A #[derive(Clone, PartialEq, ::prost::Message)] -pub struct PeekPromiseEntryMessage { +pub struct PeekPromiseCommandMessage { #[prost(string, tag = "1")] pub key: ::prost::alloc::string::String, - /// Entry name + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "peek_promise_entry_message::Result", tags = "13, 14, 15")] - pub result: ::core::option::Option, } -/// Nested message and enum types in `PeekPromiseEntryMessage`. -pub mod peek_promise_entry_message { +/// Notification for PeekPromiseCommandMessage +/// Type: 0x8000 + A +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PeekPromiseCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost( + oneof = "peek_promise_completion_notification_message::Result", + tags = "4, 5, 6" + )] + pub result: ::core::option::Option< + peek_promise_completion_notification_message::Result, + >, +} +/// Nested message and enum types in `PeekPromiseCompletionNotificationMessage`. +pub mod peek_promise_completion_notification_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(message, tag = "13")] - Empty(super::Empty), - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] + #[prost(message, tag = "4")] + Void(super::Void), + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] Failure(super::Failure), } } /// Completable: Yes /// Fallible: No -/// Type: 0x0800 + A +/// Type: 0x0400 + B #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CompletePromiseEntryMessage { +pub struct CompletePromiseCommandMessage { #[prost(string, tag = "1")] pub key: ::prost::alloc::string::String, - /// Entry name + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, /// The value to use to complete the promise - #[prost(oneof = "complete_promise_entry_message::Completion", tags = "2, 3")] - pub completion: ::core::option::Option, - #[prost(oneof = "complete_promise_entry_message::Result", tags = "13, 15")] - pub result: ::core::option::Option, + #[prost(oneof = "complete_promise_command_message::Completion", tags = "2, 3")] + pub completion: ::core::option::Option, } -/// Nested message and enum types in `CompletePromiseEntryMessage`. -pub mod complete_promise_entry_message { +/// Nested message and enum types in `CompletePromiseCommandMessage`. +pub mod complete_promise_command_message { /// The value to use to complete the promise #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Completion { - #[prost(bytes, tag = "2")] - CompletionValue(::prost::bytes::Bytes), + #[prost(message, tag = "2")] + CompletionValue(super::Value), #[prost(message, tag = "3")] CompletionFailure(super::Failure), } +} +/// Notification for CompletePromiseCommandMessage +/// Type: 0x8000 + B +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CompletePromiseCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost( + oneof = "complete_promise_completion_notification_message::Result", + tags = "4, 6" + )] + pub result: ::core::option::Option< + complete_promise_completion_notification_message::Result, + >, +} +/// Nested message and enum types in `CompletePromiseCompletionNotificationMessage`. +pub mod complete_promise_completion_notification_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - /// Returns empty if value was set successfully - #[prost(message, tag = "13")] - Empty(super::Empty), - /// Returns a failure if the promise was already completed - #[prost(message, tag = "15")] + #[prost(message, tag = "4")] + Void(super::Void), + #[prost(message, tag = "6")] Failure(super::Failure), } } /// Completable: Yes /// Fallible: No -/// Type: 0x0C00 + 0 +/// Type: 0x0400 + C #[derive(Clone, PartialEq, ::prost::Message)] -pub struct SleepEntryMessage { +pub struct SleepCommandMessage { /// Wake up time. /// The time is set as duration since UNIX Epoch. #[prost(uint64, tag = "1")] pub wake_up_time: u64, - /// Entry name + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "sleep_entry_message::Result", tags = "13, 15")] - pub result: ::core::option::Option, } -/// Nested message and enum types in `SleepEntryMessage`. -pub mod sleep_entry_message { - #[allow(clippy::enum_variant_names)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Result { - #[prost(message, tag = "13")] - Empty(super::Empty), - #[prost(message, tag = "15")] - Failure(super::Failure), - } +/// Notification for SleepCommandMessage +/// Type: 0x8000 + C +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct SleepCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost(message, optional, tag = "4")] + pub void: ::core::option::Option, } -/// Completable: Yes +/// Completable: Yes (two notifications: one with invocation id, then one with the actual result) /// Fallible: Yes -/// Type: 0x0C00 + 1 +/// Type: 0x0400 + D #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CallEntryMessage { +pub struct CallCommandMessage { #[prost(string, tag = "1")] pub service_name: ::prost::alloc::string::String, #[prost(string, tag = "2")] @@ -373,28 +489,47 @@ pub struct CallEntryMessage { /// If present, it must be non empty. #[prost(string, optional, tag = "6")] pub idempotency_key: ::core::option::Option<::prost::alloc::string::String>, - /// Entry name + #[prost(uint32, tag = "10")] + pub invocation_id_notification_idx: u32, + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "call_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, } -/// Nested message and enum types in `CallEntryMessage`. -pub mod call_entry_message { +/// Notification for CallCommandMessage and OneWayCallCommandMessage +/// Type: 0x8000 + E +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CallInvocationIdCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost(string, tag = "16")] + pub invocation_id: ::prost::alloc::string::String, +} +/// Notification for CallCommandMessage +/// Type: 0x8000 + D +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CallCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost(oneof = "call_completion_notification_message::Result", tags = "5, 6")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `CallCompletionNotificationMessage`. +pub mod call_completion_notification_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] Failure(super::Failure), } } -/// Completable: No +/// Completable: Yes (only one notification with invocation id) /// Fallible: Yes -/// Type: 0x0C00 + 2 +/// Type: 0x0400 + E #[derive(Clone, PartialEq, ::prost::Message)] -pub struct OneWayCallEntryMessage { +pub struct OneWayCallCommandMessage { #[prost(string, tag = "1")] pub service_name: ::prost::alloc::string::String, #[prost(string, tag = "2")] @@ -415,152 +550,152 @@ pub struct OneWayCallEntryMessage { /// If present, it must be non empty. #[prost(string, optional, tag = "7")] pub idempotency_key: ::core::option::Option<::prost::alloc::string::String>, - /// Entry name - #[prost(string, tag = "12")] - pub name: ::prost::alloc::string::String, -} -/// Completable: Yes -/// Fallible: No -/// Type: 0x0C00 + 3 -/// Awakeables are addressed by an identifier exposed to the user. See the spec for more details. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct AwakeableEntryMessage { - /// Entry name + #[prost(uint32, tag = "10")] + pub invocation_id_notification_idx: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "awakeable_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, -} -/// Nested message and enum types in `AwakeableEntryMessage`. -pub mod awakeable_entry_message { - #[allow(clippy::enum_variant_names)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Result { - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] - Failure(super::Failure), - } } /// Completable: No /// Fallible: Yes -/// Type: 0x0C00 + 4 +/// Type: 0x04000 + 10 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CompleteAwakeableEntryMessage { - /// Identifier of the awakeable. See the spec for more details. +pub struct SendSignalCommandMessage { #[prost(string, tag = "1")] - pub id: ::prost::alloc::string::String, - /// Entry name + pub target_invocation_id: ::prost::alloc::string::String, + /// Cannot use the field 'name' here because used above #[prost(string, tag = "12")] - pub name: ::prost::alloc::string::String, - #[prost(oneof = "complete_awakeable_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, -} -/// Nested message and enum types in `CompleteAwakeableEntryMessage`. -pub mod complete_awakeable_entry_message { + pub entry_name: ::prost::alloc::string::String, + #[prost(oneof = "send_signal_command_message::SignalId", tags = "2, 3")] + pub signal_id: ::core::option::Option, + #[prost(oneof = "send_signal_command_message::Result", tags = "4, 5, 6")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `SendSignalCommandMessage`. +pub mod send_signal_command_message { + #[allow(clippy::enum_variant_names)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum SignalId { + #[prost(uint32, tag = "2")] + Idx(u32), + #[prost(string, tag = "3")] + Name(::prost::alloc::string::String), + } #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] + #[prost(message, tag = "4")] + Void(super::Void), + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] Failure(super::Failure), } } -/// Completable: No +/// Proposals for Run completions are sent through ProposeRunCompletionMessage +/// +/// Completable: Yes /// Fallible: No -/// Type: 0x0C00 + 5 -/// Flag: RequiresRuntimeAck +/// Type: 0x0400 + 11 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct RunEntryMessage { - /// Entry name +pub struct RunCommandMessage { + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "run_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, } -/// Nested message and enum types in `RunEntryMessage`. -pub mod run_entry_message { +/// Notification for RunCommandMessage +/// Type: 0x8000 + 11 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RunCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost(oneof = "run_completion_notification_message::Result", tags = "5, 6")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `RunCompletionNotificationMessage`. +pub mod run_completion_notification_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] Failure(super::Failure), } } -/// Completable: No +/// Completable: Yes /// Fallible: Yes -/// Type: 0x0C00 + 6 +/// Type: 0x0400 + 12 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CancelInvocationEntryMessage { - /// Entry name +pub struct AttachInvocationCommandMessage { + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "cancel_invocation_entry_message::Target", tags = "1, 2")] - pub target: ::core::option::Option, + #[prost(oneof = "attach_invocation_command_message::Target", tags = "1, 3, 4")] + pub target: ::core::option::Option, } -/// Nested message and enum types in `CancelInvocationEntryMessage`. -pub mod cancel_invocation_entry_message { +/// Nested message and enum types in `AttachInvocationCommandMessage`. +pub mod attach_invocation_command_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Target { - /// Target invocation id to cancel + /// Target invocation id #[prost(string, tag = "1")] InvocationId(::prost::alloc::string::String), - /// Target index of the call/one way call journal entry in this journal. - #[prost(uint32, tag = "2")] - CallEntryIndex(u32), + /// Target idempotent request + #[prost(message, tag = "3")] + IdempotentRequestTarget(super::IdempotentRequestTarget), + /// Target workflow target + #[prost(message, tag = "4")] + WorkflowTarget(super::WorkflowTarget), } } -/// Completable: Yes -/// Fallible: Yes -/// Type: 0x0C00 + 7 +/// Notification for AttachInvocationCommandMessage +/// Type: 0x8000 + 12 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetCallInvocationIdEntryMessage { - /// Index of the call/one way call journal entry in this journal. +pub struct AttachInvocationCompletionNotificationMessage { #[prost(uint32, tag = "1")] - pub call_entry_index: u32, - #[prost(string, tag = "12")] - pub name: ::prost::alloc::string::String, - #[prost(oneof = "get_call_invocation_id_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, -} -/// Nested message and enum types in `GetCallInvocationIdEntryMessage`. -pub mod get_call_invocation_id_entry_message { + pub completion_id: u32, + #[prost( + oneof = "attach_invocation_completion_notification_message::Result", + tags = "5, 6" + )] + pub result: ::core::option::Option< + attach_invocation_completion_notification_message::Result, + >, +} +/// Nested message and enum types in `AttachInvocationCompletionNotificationMessage`. +pub mod attach_invocation_completion_notification_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(string, tag = "14")] - Value(::prost::alloc::string::String), - #[prost(message, tag = "15")] + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] Failure(super::Failure), } } /// Completable: Yes /// Fallible: Yes -/// Type: 0x0C00 + 8 +/// Type: 0x0400 + 13 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct AttachInvocationEntryMessage { +pub struct GetInvocationOutputCommandMessage { + #[prost(uint32, tag = "11")] + pub result_completion_id: u32, #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "attach_invocation_entry_message::Target", tags = "1, 2, 3, 4")] - pub target: ::core::option::Option, - #[prost(oneof = "attach_invocation_entry_message::Result", tags = "14, 15")] - pub result: ::core::option::Option, + #[prost(oneof = "get_invocation_output_command_message::Target", tags = "1, 3, 4")] + pub target: ::core::option::Option, } -/// Nested message and enum types in `AttachInvocationEntryMessage`. -pub mod attach_invocation_entry_message { +/// Nested message and enum types in `GetInvocationOutputCommandMessage`. +pub mod get_invocation_output_command_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Target { /// Target invocation id #[prost(string, tag = "1")] InvocationId(::prost::alloc::string::String), - /// Target index of the call/one way call journal entry in this journal. - #[prost(uint32, tag = "2")] - CallEntryIndex(u32), /// Target idempotent request #[prost(message, tag = "3")] IdempotentRequestTarget(super::IdempotentRequestTarget), @@ -568,59 +703,101 @@ pub mod attach_invocation_entry_message { #[prost(message, tag = "4")] WorkflowTarget(super::WorkflowTarget), } +} +/// Notification for GetInvocationOutputCommandMessage +/// Type: 0x8000 + 13 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetInvocationOutputCompletionNotificationMessage { + #[prost(uint32, tag = "1")] + pub completion_id: u32, + #[prost( + oneof = "get_invocation_output_completion_notification_message::Result", + tags = "4, 5, 6" + )] + pub result: ::core::option::Option< + get_invocation_output_completion_notification_message::Result, + >, +} +/// Nested message and enum types in `GetInvocationOutputCompletionNotificationMessage`. +pub mod get_invocation_output_completion_notification_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] + #[prost(message, tag = "4")] + Void(super::Void), + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] Failure(super::Failure), } } -/// Completable: Yes +/// We have this for backward compatibility, because we need to parse both old and new awakeable id. +/// Completable: No /// Fallible: Yes -/// Type: 0x0C00 + 9 +/// Type: 0x0400 + 14 #[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetInvocationOutputEntryMessage { +pub struct CompleteAwakeableCommandMessage { + #[prost(string, tag = "1")] + pub awakeable_id: ::prost::alloc::string::String, + /// Cannot use the field 'name' here because used above #[prost(string, tag = "12")] pub name: ::prost::alloc::string::String, - #[prost(oneof = "get_invocation_output_entry_message::Target", tags = "1, 2, 3, 4")] - pub target: ::core::option::Option, - #[prost(oneof = "get_invocation_output_entry_message::Result", tags = "13, 14, 15")] - pub result: ::core::option::Option, + #[prost(oneof = "complete_awakeable_command_message::Result", tags = "2, 3")] + pub result: ::core::option::Option, } -/// Nested message and enum types in `GetInvocationOutputEntryMessage`. -pub mod get_invocation_output_entry_message { +/// Nested message and enum types in `CompleteAwakeableCommandMessage`. +pub mod complete_awakeable_command_message { #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Target { - /// Target invocation id - #[prost(string, tag = "1")] - InvocationId(::prost::alloc::string::String), - /// Target index of the call/one way call journal entry in this journal. - #[prost(uint32, tag = "2")] - CallEntryIndex(u32), - /// Target idempotent request + pub enum Result { + #[prost(message, tag = "2")] + Value(super::Value), #[prost(message, tag = "3")] - IdempotentRequestTarget(super::IdempotentRequestTarget), - /// Target workflow target - #[prost(message, tag = "4")] - WorkflowTarget(super::WorkflowTarget), + Failure(super::Failure), + } +} +/// Notification message for signals +/// Type: 0xFBFF +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SignalNotificationMessage { + #[prost(oneof = "signal_notification_message::SignalId", tags = "2, 3")] + pub signal_id: ::core::option::Option, + #[prost(oneof = "signal_notification_message::Result", tags = "4, 5, 6")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `SignalNotificationMessage`. +pub mod signal_notification_message { + #[allow(clippy::enum_variant_names)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum SignalId { + #[prost(uint32, tag = "2")] + Idx(u32), + #[prost(string, tag = "3")] + Name(::prost::alloc::string::String), } #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { - /// Empty if no result is still available - #[prost(message, tag = "13")] - Empty(super::Empty), - #[prost(bytes, tag = "14")] - Value(::prost::bytes::Bytes), - #[prost(message, tag = "15")] + #[prost(message, tag = "4")] + Void(super::Void), + #[prost(message, tag = "5")] + Value(super::Value), + #[prost(message, tag = "6")] Failure(super::Failure), } } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StateKeys { + #[prost(bytes = "bytes", repeated, tag = "1")] + pub keys: ::prost::alloc::vec::Vec<::prost::bytes::Bytes>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Value { + #[prost(bytes = "bytes", tag = "1")] + pub content: ::prost::bytes::Bytes, +} /// This failure object carries user visible errors, -/// e.g. invocation failure return value or failure result of an InvokeEntryMessage. +/// e.g. invocation failure return value or failure result of an InvokeCommandMessage. #[derive(Clone, PartialEq, ::prost::Message)] pub struct Failure { /// The code can be any HTTP status code, as described @@ -656,7 +833,7 @@ pub struct IdempotentRequestTarget { pub idempotency_key: ::prost::alloc::string::String, } #[derive(Clone, Copy, PartialEq, ::prost::Message)] -pub struct Empty {} +pub struct Void {} /// Service protocol version. #[allow(clippy::enum_variant_names)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] @@ -675,6 +852,8 @@ pub enum ServiceProtocolVersion { /// * New entry to attach to existing invocation: AttachInvocationEntryMessage /// * New entry to get output of existing invocation: GetInvocationOutputEntryMessage V3 = 3, + /// Immutable journal. + V4 = 4, } impl ServiceProtocolVersion { /// String value of the enum field names used in the ProtoBuf definition. @@ -687,6 +866,7 @@ impl ServiceProtocolVersion { Self::V1 => "V1", Self::V2 => "V2", Self::V3 => "V3", + Self::V4 => "V4", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -696,6 +876,34 @@ impl ServiceProtocolVersion { "V1" => Some(Self::V1), "V2" => Some(Self::V2), "V3" => Some(Self::V3), + "V4" => Some(Self::V4), + _ => None, + } + } +} +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum BuiltInSignal { + Unknown = 0, + Cancel = 1, +} +impl BuiltInSignal { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unknown => "UNKNOWN", + Self::Cancel => "CANCEL", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "UNKNOWN" => Some(Self::Unknown), + "CANCEL" => Some(Self::Cancel), _ => None, } } diff --git a/src/service_protocol/header.rs b/src/service_protocol/header.rs index d42ac6b..549f884 100644 --- a/src/service_protocol/header.rs +++ b/src/service_protocol/header.rs @@ -8,9 +8,9 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0. +const COMMAND_ENTRY_MASK: u16 = 0x0400; +const NOTIFICATION_ENTRY_MASK: u16 = 0x8000; const CUSTOM_ENTRY_MASK: u16 = 0xFC00; -const COMPLETED_MASK: u64 = 0x0001_0000_0000; -const REQUIRES_ACK_MASK: u64 = 0x8000_0000_0000; type MessageTypeId = u16; @@ -27,31 +27,23 @@ macro_rules! gen_message_type_enum { CustomEntry(u16) } }; - (@gen_enum [$variant:ident Entry = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { - paste::paste! { gen_message_type_enum!(@gen_enum [$($tail)*] -> [[<$variant Entry>], $($body)*]); } - }; (@gen_enum [$variant:ident = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { gen_message_type_enum!(@gen_enum [$($tail)*] -> [$variant, $($body)*]); }; - (@gen_is_entry_impl [] -> [$($variant:ident, $is_entry:literal,)*]) => { - impl MessageType { - pub fn is_entry(&self) -> bool { - match self { - $(MessageType::$variant => $is_entry,)* - MessageType::CustomEntry(_) => true + (@gen_id [] -> [$($variant:ident, $id:literal,)*]) => { + impl TryFrom for MessageType { + type Error = UnknownMessageType; + + fn try_from(value: MessageTypeId) -> Result { + match value { + $($id => Ok(MessageType::$variant),)* + v if (v & CUSTOM_ENTRY_MASK) != 0 => Ok(MessageType::CustomEntry(v)), + v => Err(UnknownMessageType(v)), } } } - }; - (@gen_is_entry_impl [$variant:ident Entry = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { - paste::paste! { gen_message_type_enum!(@gen_is_entry_impl [$($tail)*] -> [[<$variant Entry>], true, $($body)*]); } - }; - (@gen_is_entry_impl [$variant:ident = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { - gen_message_type_enum!(@gen_is_entry_impl [$($tail)*] -> [$variant, false, $($body)*]); - }; - (@gen_to_id [] -> [$($variant:ident, $id:literal,)*]) => { impl From for MessageTypeId { fn from(mt: MessageType) -> Self { match mt { @@ -61,88 +53,68 @@ macro_rules! gen_message_type_enum { } } }; - (@gen_to_id [$variant:ident Entry = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { - paste::paste! { gen_message_type_enum!(@gen_to_id [$($tail)*] -> [[<$variant Entry>], $id, $($body)*]); } - }; - (@gen_to_id [$variant:ident = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { - gen_message_type_enum!(@gen_to_id [$($tail)*] -> [$variant, $id, $($body)*]); - }; - - (@gen_from_id [] -> [$($variant:ident, $id:literal,)*]) => { - impl TryFrom for MessageType { - type Error = UnknownMessageType; - - fn try_from(value: MessageTypeId) -> Result { - match value { - $($id => Ok(MessageType::$variant),)* - v if (v & CUSTOM_ENTRY_MASK) != 0 => Ok(MessageType::CustomEntry(v)), - v => Err(UnknownMessageType(v)), - } - } - } - }; - (@gen_from_id [$variant:ident Entry = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { - paste::paste! { gen_message_type_enum!(@gen_from_id [$($tail)*] -> [[<$variant Entry>], $id, $($body)*]); } - }; - (@gen_from_id [$variant:ident = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { - gen_message_type_enum!(@gen_from_id [$($tail)*] -> [$variant, $id, $($body)*]); + (@gen_id [$variant:ident = $id:literal, $($tail:tt)*] -> [$($body:tt)*]) => { + gen_message_type_enum!(@gen_id [$($tail)*] -> [$variant, $id, $($body)*]); }; // Entrypoint of the macro ($($tokens:tt)*) => { gen_message_type_enum!(@gen_enum [$($tokens)*] -> []); - gen_message_type_enum!(@gen_is_entry_impl [$($tokens)*] -> []); - gen_message_type_enum!(@gen_to_id [$($tokens)*] -> []); - gen_message_type_enum!(@gen_from_id [$($tokens)*] -> []); + gen_message_type_enum!(@gen_id [$($tokens)*] -> []); }; } gen_message_type_enum!( Start = 0x0000, - Completion = 0x0001, - Suspension = 0x0002, - Error = 0x0003, - End = 0x0005, - EntryAck = 0x0004, - Input Entry = 0x0400, - Output Entry = 0x0401, - GetState Entry = 0x0800, - SetState Entry = 0x0801, - ClearState Entry = 0x0802, - GetStateKeys Entry = 0x0804, - ClearAllState Entry = 0x0803, - GetPromise Entry = 0x0808, - PeekPromise Entry = 0x0809, - CompletePromise Entry = 0x080A, - Sleep Entry = 0x0C00, - Call Entry = 0x0C01, - OneWayCall Entry = 0x0C02, - Awakeable Entry = 0x0C03, - CompleteAwakeable Entry = 0x0C04, - Run Entry = 0x0C05, - CancelInvocation Entry = 0x0C06, - GetCallInvocationId Entry = 0x0C07, - AttachInvocation Entry = 0x0C08, - GetInvocationOutput Entry = 0x0C09, - Combinator Entry = 0xFC02, + Suspension = 0x0001, + Error = 0x0002, + End = 0x0003, + ProposeRunCompletion = 0x0005, + InputCommand = 0x0400, + OutputCommand = 0x0401, + GetLazyStateCommand = 0x0402, + GetLazyStateCompletionNotification = 0x8002, + SetStateCommand = 0x0403, + ClearStateCommand = 0x0404, + ClearAllStateCommand = 0x0405, + GetLazyStateKeysCommand = 0x0406, + GetLazyStateKeysCompletionNotification = 0x8006, + GetEagerStateCommand = 0x0407, + GetEagerStateKeysCommand = 0x0408, + GetPromiseCommand = 0x0409, + GetPromiseCompletionNotification = 0x8009, + PeekPromiseCommand = 0x040A, + PeekPromiseCompletionNotification = 0x800A, + CompletePromiseCommand = 0x040B, + CompletePromiseCompletionNotification = 0x800B, + SleepCommand = 0x040C, + SleepCompletionNotification = 0x800C, + CallCommand = 0x040D, + CallInvocationIdCompletionNotification = 0x800E, + CallCompletionNotification = 0x800D, + OneWayCallCommand = 0x040E, + SendSignalCommand = 0x0410, + RunCommand = 0x0411, + RunCompletionNotification = 0x8011, + AttachInvocationCommand = 0x0412, + AttachInvocationCompletionNotification = 0x8012, + GetInvocationOutputCommand = 0x0413, + GetInvocationOutputCompletionNotification = 0x8013, + CompleteAwakeableCommand = 0x0414, + SignalNotification = 0xFBFF, ); impl MessageType { - fn has_completed_flag(&self) -> bool { - matches!( - self, - MessageType::GetStateEntry - | MessageType::GetStateKeysEntry - | MessageType::SleepEntry - | MessageType::CallEntry - | MessageType::AwakeableEntry - | MessageType::GetPromiseEntry - | MessageType::PeekPromiseEntry - | MessageType::CompletePromiseEntry - | MessageType::GetCallInvocationIdEntry - | MessageType::AttachInvocationEntry - | MessageType::GetInvocationOutputEntry - ) + fn id(&self) -> MessageTypeId { + MessageTypeId::from(*self) + } + + pub fn is_command(&self) -> bool { + (COMMAND_ENTRY_MASK..NOTIFICATION_ENTRY_MASK).contains(&self.id()) + } + + pub fn is_notification(&self) -> bool { + (NOTIFICATION_ENTRY_MASK..CUSTOM_ENTRY_MASK).contains(&self.id()) } } @@ -150,62 +122,12 @@ impl MessageType { pub struct MessageHeader { ty: MessageType, length: u32, - - // --- Flags - /// Only `CompletableEntries` have completed flag. See [`MessageType#allows_completed_flag`]. - completed_flag: Option, - /// All Entry messages may have requires ack flag. - requires_ack_flag: Option, } impl MessageHeader { #[inline] pub fn new(ty: MessageType, length: u32) -> Self { - Self::_new(ty, None, None, length) - } - - #[inline] - pub fn new_entry_header(ty: MessageType, completed_flag: Option, length: u32) -> Self { - debug_assert!(completed_flag.is_some() == ty.has_completed_flag()); - - MessageHeader { - ty, - length, - completed_flag, - requires_ack_flag: None, - } - } - - #[inline] - pub fn new_ackable_entry_header( - ty: MessageType, - completed_flag: Option, - requires_ack_flag: Option, - length: u32, - ) -> Self { - debug_assert!(completed_flag.is_some() == ty.has_completed_flag()); - - MessageHeader { - ty, - length, - completed_flag, - requires_ack_flag, - } - } - - #[inline] - fn _new( - ty: MessageType, - completed_flag: Option, - requires_ack_flag: Option, - length: u32, - ) -> Self { - MessageHeader { - ty, - length, - completed_flag, - requires_ack_flag, - } + Self { ty, length } } #[inline] @@ -214,31 +136,11 @@ impl MessageHeader { } #[inline] - pub fn completed(&self) -> Option { - self.completed_flag - } - - #[inline] - pub fn requires_ack(&self) -> Option { - self.requires_ack_flag - } - - #[inline] - pub fn frame_length(&self) -> u32 { + pub fn message_length(&self) -> u32 { self.length } } -macro_rules! read_flag_if { - ($cond:expr, $value:expr, $mask:expr) => { - if $cond { - Some(($value & $mask) != 0) - } else { - None - } - }; -} - impl TryFrom for MessageHeader { type Error = UnknownMessageType; @@ -247,43 +149,17 @@ impl TryFrom for MessageHeader { fn try_from(value: u64) -> Result { let ty_code = (value >> 48) as u16; let ty: MessageType = ty_code.try_into()?; - - let completed_flag = read_flag_if!(ty.has_completed_flag(), value, COMPLETED_MASK); - let requires_ack_flag = read_flag_if!(ty.is_entry(), value, REQUIRES_ACK_MASK); let length = value as u32; - Ok(MessageHeader::_new( - ty, - completed_flag, - requires_ack_flag, - length, - )) + Ok(MessageHeader::new(ty, length)) } } -macro_rules! write_flag { - ($flag:expr, $value:expr, $mask:expr) => { - if let Some(true) = $flag { - *$value |= $mask; - } - }; -} - impl From for u64 { /// Serialize the protocol header. /// See https://github.com/restatedev/service-protocol/blob/main/service-invocation-protocol.md#message-header fn from(message_header: MessageHeader) -> Self { - let mut res = - ((u16::from(message_header.ty) as u64) << 48) | (message_header.length as u64); - - write_flag!(message_header.completed_flag, &mut res, COMPLETED_MASK); - write_flag!( - message_header.requires_ack_flag, - &mut res, - REQUIRES_ACK_MASK - ); - - res + ((u16::from(message_header.ty) as u64) << 48) | (message_header.length as u64) } } @@ -292,121 +168,37 @@ mod tests { use super::{MessageType::*, *}; - impl MessageHeader { - fn new_completable_entry(ty: MessageType, completed: bool, length: u32) -> Self { - Self::new_entry_header(ty, Some(completed), length) - } - } - macro_rules! roundtrip_test { ($test_name:ident, $header:expr, $ty:expr, $len:expr) => { - roundtrip_test!($test_name, $header, $ty, $len, None, None, None); - }; - ($test_name:ident, $header:expr, $ty:expr, $len:expr, version: $protocol_version:expr) => { - roundtrip_test!( - $test_name, - $header, - $ty, - $len, - None, - Some($protocol_version), - None - ); - }; - ($test_name:ident, $header:expr, $ty:expr, $len:expr, completed: $completed:expr) => { - roundtrip_test!($test_name, $header, $ty, $len, Some($completed), None, None); - }; - ($test_name:ident, $header:expr, $ty:expr, $len:expr, requires_ack: $requires_ack:expr) => { - roundtrip_test!( - $test_name, - $header, - $ty, - $len, - None, - None, - Some($requires_ack) - ); - }; - ($test_name:ident, $header:expr, $ty:expr, $len:expr, requires_ack: $requires_ack:expr, completed: $completed:expr) => { - roundtrip_test!( - $test_name, - $header, - $ty, - $len, - Some($completed), - None, - Some($requires_ack) - ); - }; - ($test_name:ident, $header:expr, $ty:expr, $len:expr, $completed:expr, $protocol_version:expr, $requires_ack:expr) => { #[test] fn $test_name() { let serialized: u64 = $header.into(); let header: MessageHeader = serialized.try_into().unwrap(); assert_eq!(header.message_type(), $ty); - assert_eq!(header.completed(), $completed); - assert_eq!(header.requires_ack(), $requires_ack); - assert_eq!(header.frame_length(), $len); + assert_eq!(header.message_length(), $len); } }; } roundtrip_test!( - completion, - MessageHeader::new(Completion, 22), - Completion, - 22 - ); - - roundtrip_test!( - completed_get_state, - MessageHeader::new_completable_entry(GetStateEntry, true, 0), - GetStateEntry, - 0, - requires_ack: false, - completed: true + get_state_empty, + MessageHeader::new(GetLazyStateCommand, 0), + GetLazyStateCommand, + 0 ); roundtrip_test!( - not_completed_get_state, - MessageHeader::new_completable_entry(GetStateEntry, false, 0), - GetStateEntry, - 0, - requires_ack: false, - completed: false - ); - - roundtrip_test!( - completed_get_state_with_len, - MessageHeader::new_completable_entry(GetStateEntry, true, 10341), - GetStateEntry, - 10341, - requires_ack: false, - completed: true - ); - - roundtrip_test!( - set_state_with_requires_ack, - MessageHeader::_new(SetStateEntry, None, Some(true), 10341), - SetStateEntry, - 10341, - requires_ack: true + get_state_with_length, + MessageHeader::new(GetLazyStateCommand, 22), + GetLazyStateCommand, + 22 ); roundtrip_test!( custom_entry, - MessageHeader::new(MessageType::CustomEntry(0xFC00), 10341), - MessageType::CustomEntry(0xFC00), - 10341, - requires_ack: false - ); - - roundtrip_test!( - custom_entry_with_requires_ack, - MessageHeader::_new(MessageType::CustomEntry(0xFC00), None, Some(true), 10341), - MessageType::CustomEntry(0xFC00), - 10341, - requires_ack: true + MessageHeader::new(CustomEntry(0xFC01), 10341), + CustomEntry(0xFC01), + 10341 ); } diff --git a/src/service_protocol/messages.rs b/src/service_protocol/messages.rs index 3ce2464..c75e26b 100644 --- a/src/service_protocol/messages.rs +++ b/src/service_protocol/messages.rs @@ -1,67 +1,37 @@ -use std::borrow::Cow; - -use crate::service_protocol::messages::get_state_keys_entry_message::StateKeys; -use crate::service_protocol::{MessageHeader, MessageType}; -use crate::vm::errors::{ - DecodeGetCallInvocationIdUtf8, DecodeStateKeysProst, DecodeStateKeysUtf8, - EmptyGetCallInvocationId, EmptyStateKeys, -}; -use crate::{Error, NonEmptyValue, Value}; +use crate::service_protocol::{MessageHeader, MessageType, NotificationResult}; +use bytes::Bytes; use paste::paste; -use prost::Message; +use std::borrow::Cow; pub trait RestateMessage: prost::Message + Default { fn ty() -> MessageType; -} -pub trait WriteableRestateMessage: RestateMessage { - fn generate_header(&self, _never_ack: bool) -> MessageHeader { + fn generate_header(&self) -> MessageHeader { MessageHeader::new(Self::ty(), self.encoded_len() as u32) } } -pub trait EntryMessage { +pub trait NamedCommandMessage { fn name(&self) -> String; } -pub trait EntryMessageHeaderEq { +pub trait CommandMessageHeaderEq { fn header_eq(&self, other: &Self) -> bool; } -pub trait CompletableEntryMessage: RestateMessage + EntryMessage + EntryMessageHeaderEq { - fn is_completed(&self) -> bool; - fn into_completion(self) -> Result, Error>; - fn completion_parsing_hint() -> CompletionParsingHint; -} - -impl WriteableRestateMessage for M { - fn generate_header(&self, _never_ack: bool) -> MessageHeader { - MessageHeader::new_entry_header( - Self::ty(), - Some(self.is_completed()), - self.encoded_len() as u32, - ) - } -} - include!("./generated/dev.restate.service.protocol.rs"); -include!("./generated/dev.restate.service.protocol.extensions.rs"); macro_rules! impl_message_traits { ($name:ident: core) => { impl_message_traits!($name: message); - impl_message_traits!($name: writeable); }; - ($name:ident: non_completable_entry) => { + ($name:ident: notification) => { impl_message_traits!($name: message); - impl_message_traits!($name: writeable); - impl_message_traits!($name: entry); - impl_message_traits!($name: entry_header_eq); }; - ($name:ident: completable_entry) => { + ($name:ident: command) => { impl_message_traits!($name: message); - impl_message_traits!($name: entry); - impl_message_traits!($name: completable); + impl_message_traits!($name: named_command); + impl_message_traits!($name: command_header_eq); }; ($name:ident: message) => { impl RestateMessage for paste! { [<$name Message>] } { @@ -70,33 +40,15 @@ macro_rules! impl_message_traits { } } }; - ($name:ident: writeable) => { - impl WriteableRestateMessage for paste! { [<$name Message>] } {} - }; - ($name:ident: completable) => { - impl CompletableEntryMessage for paste! { [<$name Message>] } { - fn is_completed(&self) -> bool { - self.result.is_some() - } - - fn into_completion(self) -> Result, Error> { - self.result.map(TryInto::try_into).transpose() - } - - fn completion_parsing_hint() -> CompletionParsingHint { - CompletionParsingHint::EmptyOrSuccessOrValue - } - } - }; - ($name:ident: entry) => { - impl EntryMessage for paste! { [<$name Message>] } { + ($name:ident: named_command) => { + impl NamedCommandMessage for paste! { [<$name Message>] } { fn name(&self) -> String { self.name.clone() } } }; - ($name:ident: entry_header_eq) => { - impl EntryMessageHeaderEq for paste! { [<$name Message>] } { + ($name:ident: command_header_eq) => { + impl CommandMessageHeaderEq for paste! { [<$name Message>] } { fn header_eq(&self, other: &Self) -> bool { self.eq(other) } @@ -106,81 +58,53 @@ macro_rules! impl_message_traits { // --- Core messages impl_message_traits!(Start: core); -impl_message_traits!(Completion: core); impl_message_traits!(Suspension: core); impl_message_traits!(Error: core); impl_message_traits!(End: core); -impl_message_traits!(EntryAck: core); +impl_message_traits!(ProposeRunCompletion: core); // -- Entries -impl_message_traits!(InputEntry: message); -impl_message_traits!(InputEntry: entry); -impl_message_traits!(InputEntry: writeable); -impl EntryMessageHeaderEq for InputEntryMessage { - fn header_eq(&self, _: &Self) -> bool { - true - } -} - -impl_message_traits!(OutputEntry: non_completable_entry); - -impl_message_traits!(GetStateEntry: completable_entry); -impl EntryMessageHeaderEq for GetStateEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.key.eq(&other.key) - } -} - -impl_message_traits!(GetStateKeysEntry: message); -impl_message_traits!(GetStateKeysEntry: entry); -impl CompletableEntryMessage for GetStateKeysEntryMessage { - fn is_completed(&self) -> bool { - self.result.is_some() - } - - fn into_completion(self) -> Result, Error> { - self.result.map(TryInto::try_into).transpose() - } - - fn completion_parsing_hint() -> CompletionParsingHint { - CompletionParsingHint::StateKeys - } -} -impl EntryMessageHeaderEq for GetStateKeysEntryMessage { +impl_message_traits!(InputCommand: message); +impl_message_traits!(InputCommand: named_command); +impl CommandMessageHeaderEq for InputCommandMessage { fn header_eq(&self, _: &Self) -> bool { true } } -impl_message_traits!(SetStateEntry: non_completable_entry); - -impl_message_traits!(ClearStateEntry: non_completable_entry); - -impl_message_traits!(ClearAllStateEntry: non_completable_entry); - -impl_message_traits!(SleepEntry: completable_entry); -impl EntryMessageHeaderEq for SleepEntryMessage { +impl_message_traits!(OutputCommand: command); +impl_message_traits!(GetLazyStateCommand: command); +impl_message_traits!(GetLazyStateCompletionNotification: notification); +impl_message_traits!(SetStateCommand: command); +impl_message_traits!(ClearStateCommand: command); +impl_message_traits!(ClearAllStateCommand: command); +impl_message_traits!(GetLazyStateKeysCommand: command); +impl_message_traits!(GetLazyStateKeysCompletionNotification: notification); +impl_message_traits!(GetEagerStateCommand: command); +impl_message_traits!(GetEagerStateKeysCommand: command); +impl_message_traits!(GetPromiseCommand: command); +impl_message_traits!(GetPromiseCompletionNotification: notification); +impl_message_traits!(PeekPromiseCommand: command); +impl_message_traits!(PeekPromiseCompletionNotification: notification); +impl_message_traits!(CompletePromiseCommand: command); +impl_message_traits!(CompletePromiseCompletionNotification: notification); + +impl_message_traits!(SleepCommand: message); +impl_message_traits!(SleepCommand: named_command); +impl CommandMessageHeaderEq for SleepCommandMessage { fn header_eq(&self, other: &Self) -> bool { self.name == other.name } } -impl_message_traits!(CallEntry: completable_entry); -impl EntryMessageHeaderEq for CallEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.service_name == other.service_name - && self.handler_name == other.handler_name - && self.key == other.key - && self.headers == other.headers - && self.parameter == other.parameter - && self.name == other.name - } -} +impl_message_traits!(SleepCompletionNotification: notification); +impl_message_traits!(CallCommand: command); +impl_message_traits!(CallInvocationIdCompletionNotification: notification); +impl_message_traits!(CallCompletionNotification: notification); -impl_message_traits!(OneWayCallEntry: message); -impl_message_traits!(OneWayCallEntry: writeable); -impl_message_traits!(OneWayCallEntry: entry); -impl EntryMessageHeaderEq for OneWayCallEntryMessage { +impl_message_traits!(OneWayCallCommand: message); +impl_message_traits!(OneWayCallCommand: named_command); +impl CommandMessageHeaderEq for OneWayCallCommandMessage { fn header_eq(&self, other: &Self) -> bool { self.service_name == other.service_name && self.handler_name == other.handler_name @@ -191,251 +115,22 @@ impl EntryMessageHeaderEq for OneWayCallEntryMessage { } } -impl_message_traits!(AwakeableEntry: completable_entry); -impl EntryMessageHeaderEq for AwakeableEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.name == other.name +impl_message_traits!(SendSignalCommand: message); +impl NamedCommandMessage for SendSignalCommandMessage { + fn name(&self) -> String { + self.entry_name.clone() } } +impl_message_traits!(SendSignalCommand: command_header_eq); -impl_message_traits!(CompleteAwakeableEntry: non_completable_entry); - -impl_message_traits!(GetPromiseEntry: completable_entry); -impl EntryMessageHeaderEq for GetPromiseEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.key == other.key && self.name == other.name - } -} - -impl_message_traits!(PeekPromiseEntry: completable_entry); -impl EntryMessageHeaderEq for PeekPromiseEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.key == other.key && self.name == other.name - } -} - -impl_message_traits!(CompletePromiseEntry: completable_entry); -impl EntryMessageHeaderEq for CompletePromiseEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.key == other.key && self.completion == other.completion && self.name == other.name - } -} - -impl_message_traits!(RunEntry: message); -impl_message_traits!(RunEntry: entry); -impl WriteableRestateMessage for RunEntryMessage { - fn generate_header(&self, never_ack: bool) -> MessageHeader { - MessageHeader::new_ackable_entry_header( - MessageType::RunEntry, - None, - if never_ack { Some(false) } else { Some(true) }, - self.encoded_len() as u32, - ) - } -} -impl EntryMessageHeaderEq for RunEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.name == other.name - } -} - -impl_message_traits!(CancelInvocationEntry: non_completable_entry); - -impl_message_traits!(GetCallInvocationIdEntry: message); -impl_message_traits!(GetCallInvocationIdEntry: entry); -impl CompletableEntryMessage for GetCallInvocationIdEntryMessage { - fn is_completed(&self) -> bool { - self.result.is_some() - } - - fn into_completion(self) -> Result, Error> { - self.result.map(TryInto::try_into).transpose() - } - - fn completion_parsing_hint() -> CompletionParsingHint { - CompletionParsingHint::GetCompletionId - } -} -impl EntryMessageHeaderEq for GetCallInvocationIdEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.call_entry_index == other.call_entry_index - } -} - -impl_message_traits!(AttachInvocationEntry: completable_entry); -impl EntryMessageHeaderEq for AttachInvocationEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.target == other.target && self.name == other.name - } -} - -impl_message_traits!(GetInvocationOutputEntry: completable_entry); -impl EntryMessageHeaderEq for GetInvocationOutputEntryMessage { - fn header_eq(&self, other: &Self) -> bool { - self.target == other.target && self.name == other.name - } -} - -impl_message_traits!(CombinatorEntry: message); -impl_message_traits!(CombinatorEntry: entry); -impl WriteableRestateMessage for CombinatorEntryMessage { - fn generate_header(&self, never_ack: bool) -> MessageHeader { - MessageHeader::new_ackable_entry_header( - MessageType::CombinatorEntry, - None, - if never_ack { Some(false) } else { Some(true) }, - self.encoded_len() as u32, - ) - } -} -impl EntryMessageHeaderEq for CombinatorEntryMessage { - fn header_eq(&self, _: &Self) -> bool { - true - } -} - -// --- Completion extraction - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: get_state_entry_message::Result) -> Result { - Ok(match value { - get_state_entry_message::Result::Empty(_) => Value::Void, - get_state_entry_message::Result::Value(b) => Value::Success(b), - get_state_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: get_state_keys_entry_message::Result) -> Result { - match value { - get_state_keys_entry_message::Result::Value(state_keys) => { - let mut state_keys = state_keys - .keys - .into_iter() - .map(|b| String::from_utf8(b.to_vec()).map_err(DecodeStateKeysUtf8)) - .collect::, _>>()?; - state_keys.sort(); - Ok(Value::StateKeys(state_keys)) - } - get_state_keys_entry_message::Result::Failure(f) => Ok(Value::Failure(f.into())), - } - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: sleep_entry_message::Result) -> Result { - Ok(match value { - sleep_entry_message::Result::Empty(_) => Value::Void, - sleep_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: call_entry_message::Result) -> Result { - Ok(match value { - call_entry_message::Result::Value(b) => Value::Success(b), - call_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: awakeable_entry_message::Result) -> Result { - Ok(match value { - awakeable_entry_message::Result::Value(b) => Value::Success(b), - awakeable_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: get_promise_entry_message::Result) -> Result { - Ok(match value { - get_promise_entry_message::Result::Value(b) => Value::Success(b), - get_promise_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: peek_promise_entry_message::Result) -> Result { - Ok(match value { - peek_promise_entry_message::Result::Empty(_) => Value::Void, - peek_promise_entry_message::Result::Value(b) => Value::Success(b), - peek_promise_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: complete_promise_entry_message::Result) -> Result { - Ok(match value { - complete_promise_entry_message::Result::Empty(_) => Value::Void, - complete_promise_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl From for NonEmptyValue { - fn from(value: run_entry_message::Result) -> Self { - match value { - run_entry_message::Result::Value(b) => NonEmptyValue::Success(b), - run_entry_message::Result::Failure(f) => NonEmptyValue::Failure(f.into()), - } - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: get_call_invocation_id_entry_message::Result) -> Result { - Ok(match value { - get_call_invocation_id_entry_message::Result::Value(id) => Value::InvocationId(id), - get_call_invocation_id_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: attach_invocation_entry_message::Result) -> Result { - Ok(match value { - attach_invocation_entry_message::Result::Value(b) => Value::Success(b), - attach_invocation_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} - -impl TryFrom for Value { - type Error = Error; - - fn try_from(value: get_invocation_output_entry_message::Result) -> Result { - Ok(match value { - get_invocation_output_entry_message::Result::Empty(_) => Value::Void, - get_invocation_output_entry_message::Result::Value(b) => Value::Success(b), - get_invocation_output_entry_message::Result::Failure(f) => Value::Failure(f.into()), - }) - } -} +impl_message_traits!(RunCommand: command); +impl_message_traits!(RunCompletionNotification: notification); +impl_message_traits!(AttachInvocationCommand: command); +impl_message_traits!(AttachInvocationCompletionNotification: notification); +impl_message_traits!(GetInvocationOutputCommand: command); +impl_message_traits!(GetInvocationOutputCompletionNotification: notification); +impl_message_traits!(CompleteAwakeableCommand: command); +impl_message_traits!(SignalNotification: notification); // --- Other conversions @@ -475,46 +170,25 @@ impl From for Header { } } -// --- Completion parsing - -#[derive(Debug)] -pub(crate) enum CompletionParsingHint { - StateKeys, - GetCompletionId, - /// The normal case - EmptyOrSuccessOrValue, +impl From for Value { + fn from(content: Bytes) -> Self { + Self { content } + } } -impl CompletionParsingHint { - pub(crate) fn parse(self, result: completion_message::Result) -> Result { - match self { - CompletionParsingHint::StateKeys => match result { - completion_message::Result::Empty(_) => Err(EmptyStateKeys.into()), - completion_message::Result::Value(b) => { - let mut state_keys = StateKeys::decode(b) - .map_err(DecodeStateKeysProst)? - .keys - .into_iter() - .map(|b| String::from_utf8(b.to_vec()).map_err(DecodeStateKeysUtf8)) - .collect::, _>>()?; - state_keys.sort(); - - Ok(Value::StateKeys(state_keys)) - } - completion_message::Result::Failure(f) => Ok(Value::Failure(f.into())), - }, - CompletionParsingHint::GetCompletionId => match result { - completion_message::Result::Empty(_) => Err(EmptyGetCallInvocationId.into()), - completion_message::Result::Value(b) => Ok(Value::InvocationId( - String::from_utf8(b.to_vec()).map_err(DecodeGetCallInvocationIdUtf8)?, - )), - completion_message::Result::Failure(f) => Ok(Value::Failure(f.into())), - }, - CompletionParsingHint::EmptyOrSuccessOrValue => Ok(match result { - completion_message::Result::Empty(_) => Value::Void, - completion_message::Result::Value(b) => Value::Success(b), - completion_message::Result::Failure(f) => Value::Failure(f.into()), - }), +impl From for crate::Value { + fn from(value: NotificationResult) -> Self { + match value { + NotificationResult::Void(_) => crate::Value::Void, + NotificationResult::Value(v) => crate::Value::Success(v.content), + NotificationResult::Failure(f) => crate::Value::Failure(f.into()), + NotificationResult::InvocationId(id) => crate::Value::InvocationId(id), + NotificationResult::StateKeys(sk) => crate::Value::StateKeys( + sk.keys + .iter() + .map(|b| String::from_utf8_lossy(b).to_string()) + .collect(), + ), } } } diff --git a/src/service_protocol/mod.rs b/src/service_protocol/mod.rs index 31489d5..2d2870a 100644 --- a/src/service_protocol/mod.rs +++ b/src/service_protocol/mod.rs @@ -10,9 +10,22 @@ mod encoding; mod header; -pub mod messages; +pub(crate) mod messages; mod version; -pub use encoding::{Decoder, DecodingError, Encoder, RawMessage}; -pub use header::{MessageHeader, MessageType}; -pub use version::{UnsupportedVersionError, Version}; +pub(crate) use encoding::{Decoder, DecodingError, Encoder, RawMessage}; +pub(crate) use header::{MessageHeader, MessageType}; +pub(crate) use version::UnsupportedVersionError; +pub use version::Version; + +pub(crate) type NotificationId = messages::notification_template::Id; +pub(crate) type NotificationResult = messages::notification_template::Result; +pub(crate) type CompletionId = u32; + +#[derive(Debug)] +pub(crate) struct Notification { + pub(crate) id: NotificationId, + pub(crate) result: NotificationResult, +} + +pub(crate) const CANCEL_SIGNAL_ID: u32 = 1; diff --git a/src/service_protocol/version.rs b/src/service_protocol/version.rs index 1f71e36..511b284 100644 --- a/src/service_protocol/version.rs +++ b/src/service_protocol/version.rs @@ -6,11 +6,13 @@ pub enum Version { V1 = 1, V2 = 2, V3 = 3, + V4 = 4, } const CONTENT_TYPE_V1: &str = "application/vnd.restate.invocation.v1"; const CONTENT_TYPE_V2: &str = "application/vnd.restate.invocation.v2"; const CONTENT_TYPE_V3: &str = "application/vnd.restate.invocation.v3"; +const CONTENT_TYPE_V4: &str = "application/vnd.restate.invocation.v4"; impl Version { pub const fn content_type(&self) -> &'static str { @@ -18,15 +20,16 @@ impl Version { Version::V1 => CONTENT_TYPE_V1, Version::V2 => CONTENT_TYPE_V2, Version::V3 => CONTENT_TYPE_V3, + Version::V4 => CONTENT_TYPE_V4, } } pub const fn minimum_supported_version() -> Self { - Version::V2 + Version::V4 } pub const fn maximum_supported_version() -> Self { - Version::V3 + Version::V4 } } @@ -48,6 +51,7 @@ impl FromStr for Version { CONTENT_TYPE_V1 => Ok(Version::V1), CONTENT_TYPE_V2 => Ok(Version::V2), CONTENT_TYPE_V3 => Ok(Version::V3), + CONTENT_TYPE_V4 => Ok(Version::V4), s => Err(UnsupportedVersionError(s.to_owned())), } } diff --git a/src/tests/async_result.rs b/src/tests/async_result.rs index 97b634d..dd8a389 100644 --- a/src/tests/async_result.rs +++ b/src/tests/async_result.rs @@ -1,6 +1,7 @@ use super::*; use crate::service_protocol::messages::*; +use crate::Value; use assert2::let_assert; use test_log::test; @@ -24,7 +25,7 @@ fn dont_await_call() { known_entries: 1, ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run(|vm| { vm.sys_input().unwrap(); @@ -37,22 +38,19 @@ fn dont_await_call() { }); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Francesco"), + invocation_id_notification_idx: 1, + result_completion_id: 2, ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Whatever" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Whatever") ); assert_eq!( output.next_decoded::().unwrap(), @@ -70,7 +68,7 @@ fn dont_await_call_dont_notify_input_closed() { known_entries: 1, ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run_without_closing_input(|vm, _| { vm.sys_input().unwrap(); let _ = vm @@ -82,22 +80,19 @@ fn dont_await_call_dont_notify_input_closed() { }); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Francesco"), + invocation_id_notification_idx: 1, + result_completion_id: 2, ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Whatever" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Whatever") ); assert_eq!( output.next_decoded::().unwrap(), @@ -106,7 +101,7 @@ fn dont_await_call_dont_notify_input_closed() { assert_eq!(output.next(), None); } -mod notify_await_point { +mod do_progress { use super::*; use test_log::test; @@ -120,79 +115,29 @@ mod notify_await_point { known_entries: 1, ..Default::default() }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) + .input(input_entry_message(b"my-data")) .run_without_closing_input(|vm, _| { vm.sys_input().unwrap(); let (_, h) = vm.sys_awakeable().unwrap(); - vm.notify_await_point(h); - vm.notify_await_point(h); + assert_eq!( + vm.do_progress(vec![h]).unwrap(), + DoProgressResponse::ReadFromInput + ); + assert_eq!( + vm.do_progress(vec![h]).unwrap(), + DoProgressResponse::ReadFromInput + ); vm.notify_input_closed(); - }); - - assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() - ); - assert_eq!( - output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } - ); - assert_eq!(output.next(), None); - } - - #[test] - fn await_two_handles_at_same_time() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"123"), - debug_id: "123".to_string(), - known_entries: 1, - ..Default::default() - }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) - .run_without_closing_input(|vm, _| { - vm.sys_input().unwrap(); - let (_, h1) = vm.sys_awakeable().unwrap(); - let (_, h2) = vm.sys_awakeable().unwrap(); - - vm.notify_await_point(h1); - // This should transition the state machine to error - vm.notify_await_point(h2); - - vm.notify_input_closed(); + let_assert!(Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h])); }); - assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() - ); - assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() - ); assert_that!( - output.next_decoded::().unwrap(), - error_message_as_vm_error( - vm::errors::AwaitingTwoAsyncResultError { - previous: 1, - current: 2, - } - .into() - ) + output.next_decoded::().unwrap(), + suspended_waiting_signal(17) ); assert_eq!(output.next(), None); } @@ -213,21 +158,35 @@ mod reverse_await_order { .sys_call(greeter_target(), Bytes::from_static(b"Till")) .unwrap(); - vm.notify_await_point(h2); - let h2_result = vm.take_async_result(h2); - if let Err(SuspendedOrVMError::Suspended(_)) = &h2_result { + if let Err(SuspendedOrVMError::Suspended(_)) = + vm.do_progress(vec![h2.call_notification_handle]) + { + assert_that!( + vm.take_notification(h2.call_notification_handle), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let_assert!(Some(Value::Success(h2_value)) = h2_result.unwrap()); + let_assert!( + Some(Value::Success(h2_value)) = + vm.take_notification(h2.call_notification_handle).unwrap() + ); vm.sys_state_set("A2".to_owned(), h2_value.clone()).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + if let Err(SuspendedOrVMError::Suspended(_)) = + vm.do_progress(vec![h1.call_notification_handle]) + { + assert_that!( + vm.take_notification(h1.call_notification_handle), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let_assert!(Some(Value::Success(h1_value)) = h1_result.unwrap()); + let_assert!( + Some(Value::Success(h1_value)) = + vm.take_notification(h1.call_notification_handle).unwrap() + ); vm.sys_write_output(NonEmptyValue::Success(Bytes::from( [&h1_value[..], b"-", &h2_value[..]].concat(), @@ -246,32 +205,34 @@ mod reverse_await_order { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Francesco"), + invocation_id_notification_idx: 1, + result_completion_id: 2, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Till"), + invocation_id_notification_idx: 3, + result_completion_id: 4, ..Default::default() } ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![2], - } + suspended_waiting_completion(4) ); assert_eq!(output.next(), None); @@ -287,55 +248,62 @@ mod reverse_await_order { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"FRANCESCO", - ))), + .input(InputCommandMessage::default()) + .input(CallInvocationIdCompletionNotificationMessage { + completion_id: 1, + invocation_id: "a1".to_string(), + }) + .input(CallInvocationIdCompletionNotificationMessage { + completion_id: 3, + invocation_id: "a2".to_string(), + }) + .input(CallCompletionNotificationMessage { + completion_id: 2, + result: Some(call_completion_notification_message::Result::Value( + Bytes::from_static(b"FRANCESCO").into(), + )), }) - .input(CompletionMessage { - entry_index: 2, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"TILL", - ))), + .input(CallCompletionNotificationMessage { + completion_id: 4, + result: Some(call_completion_notification_message::Result::Value( + Bytes::from_static(b"TILL").into(), + )), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Francesco"), + invocation_id_notification_idx: 1, + result_completion_id: 2, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Till"), + invocation_id_notification_idx: 3, + result_completion_id: 4, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - SetStateEntryMessage { + output.next_decoded::().unwrap(), + SetStateCommandMessage { key: Bytes::from_static(b"A2"), - value: Bytes::from_static(b"TILL"), + value: Some(Bytes::from_static(b"TILL").into()), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"FRANCESCO-TILL" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"FRANCESCO-TILL") ); assert_eq!( output.next_decoded::().unwrap(), @@ -355,55 +323,54 @@ mod reverse_await_order { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 2, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"TILL", - ))), + .input(InputCommandMessage::default()) + .input(CallCompletionNotificationMessage { + completion_id: 4, + result: Some(call_completion_notification_message::Result::Value( + Bytes::from_static(b"TILL").into(), + )), }) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"FRANCESCO", - ))), + .input(CallCompletionNotificationMessage { + completion_id: 2, + result: Some(call_completion_notification_message::Result::Value( + Bytes::from_static(b"FRANCESCO").into(), + )), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Francesco"), + invocation_id_notification_idx: 1, + result_completion_id: 2, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Till"), + invocation_id_notification_idx: 3, + result_completion_id: 4, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - SetStateEntryMessage { + output.next_decoded::().unwrap(), + SetStateCommandMessage { key: Bytes::from_static(b"A2"), - value: Bytes::from_static(b"TILL"), + value: Some(Bytes::from_static(b"TILL").into()), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"FRANCESCO-TILL" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"FRANCESCO-TILL") ); assert_eq!( output.next_decoded::().unwrap(), @@ -423,46 +390,48 @@ mod reverse_await_order { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 2, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"TILL", - ))), + .input(InputCommandMessage::default()) + .input(CallCompletionNotificationMessage { + completion_id: 4, + result: Some(call_completion_notification_message::Result::Value( + Bytes::from_static(b"TILL").into(), + )), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Francesco"), + invocation_id_notification_idx: 1, + result_completion_id: 2, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Till"), + invocation_id_notification_idx: 3, + result_completion_id: 4, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - SetStateEntryMessage { + output.next_decoded::().unwrap(), + SetStateCommandMessage { key: Bytes::from_static(b"A2"), - value: Bytes::from_static(b"TILL"), + value: Some(Bytes::from_static(b"TILL").into()), ..Default::default() } ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(2) ); assert_eq!(output.next(), None); @@ -478,38 +447,40 @@ mod reverse_await_order { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"FRANCESCO", - ))), + .input(InputCommandMessage::default()) + .input(CallCompletionNotificationMessage { + completion_id: 2, + result: Some(call_completion_notification_message::Result::Value( + Bytes::from_static(b"FRANCESCO").into(), + )), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Francesco"), + invocation_id_notification_idx: 1, + result_completion_id: 2, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - CallEntryMessage { + output.next_decoded::().unwrap(), + CallCommandMessage { service_name: "Greeter".to_owned(), handler_name: "greeter".to_owned(), parameter: Bytes::from_static(b"Till"), + invocation_id_notification_idx: 3, + result_completion_id: 4, ..Default::default() } ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![2], - } + suspended_waiting_completion(4) ); assert_eq!(output.next(), None); diff --git a/src/tests/calls.rs b/src/tests/calls.rs index f062f7a..d3c8e9b 100644 --- a/src/tests/calls.rs +++ b/src/tests/calls.rs @@ -1,6 +1,9 @@ use super::*; +use crate::service_protocol::messages::send_signal_command_message::SignalId; use crate::service_protocol::messages::*; +use crate::service_protocol::CANCEL_SIGNAL_ID; +use crate::Value; use assert2::let_assert; use googletest::prelude::*; use test_log::test; @@ -10,11 +13,9 @@ fn call_then_get_invocation_id_then_cancel_invocation() { let mut output = VMTestCase::new() .input(start_message(1)) .input(input_entry_message(b"my-data")) - .input(CompletionMessage { - entry_index: 2, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"my-id", - ))), + .input(CallInvocationIdCompletionNotificationMessage { + completion_id: 1, + invocation_id: "my-id".to_string(), }) .run(|vm| { vm.sys_input().unwrap(); @@ -32,57 +33,37 @@ fn call_then_get_invocation_id_then_cancel_invocation() { ) .unwrap(); - let invocation_id_handle = vm - .sys_get_call_invocation_id(GetInvocationIdTarget::CallEntry(call_handle)) - .unwrap(); - vm.notify_await_point(invocation_id_handle); + assert_eq!( + vm.do_progress(vec![call_handle.invocation_id_notification_handle]) + .unwrap(), + DoProgressResponse::AnyCompleted + ); let_assert!( - Some(Value::InvocationId(invocation_id)) = - vm.take_async_result(invocation_id_handle).unwrap() + Some(Value::InvocationId(invocation_id)) = vm + .take_notification(call_handle.invocation_id_notification_handle) + .unwrap() ); assert_eq!(invocation_id, "my-id"); - vm.sys_cancel_invocation(CancelInvocationTarget::CallEntry(call_handle)) - .unwrap(); - vm.sys_cancel_invocation(CancelInvocationTarget::InvocationId(invocation_id.clone())) - .unwrap(); + vm.sys_cancel_invocation(invocation_id).unwrap(); vm.sys_end().unwrap(); }); assert_that!( - output.next_decoded::().unwrap(), - pat!(CallEntryMessage { + output.next_decoded::().unwrap(), + pat!(CallCommandMessage { service_name: eq("MySvc"), - handler_name: eq("MyHandler") + handler_name: eq("MyHandler"), + invocation_id_notification_idx: eq(1), }) ); assert_eq!( - output - .next_decoded::() - .unwrap(), - GetCallInvocationIdEntryMessage { - call_entry_index: 1, - ..Default::default() - } - ); - assert_eq!( - output - .next_decoded::() - .unwrap(), - CancelInvocationEntryMessage { - target: Some(cancel_invocation_entry_message::Target::CallEntryIndex(1)), - ..Default::default() - } - ); - assert_eq!( - output - .next_decoded::() - .unwrap(), - CancelInvocationEntryMessage { - target: Some(cancel_invocation_entry_message::Target::InvocationId( - "my-id".to_string() - )), + output.next_decoded::().unwrap(), + SendSignalCommandMessage { + target_invocation_id: "my-id".to_string(), + signal_id: Some(SignalId::Idx(CANCEL_SIGNAL_ID)), + result: Some(send_signal_command_message::Result::Void(Default::default())), ..Default::default() } ); @@ -98,11 +79,9 @@ fn send_then_get_invocation_id_then_cancel_invocation() { let mut output = VMTestCase::new() .input(start_message(1)) .input(input_entry_message(b"my-data")) - .input(CompletionMessage { - entry_index: 2, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"my-id", - ))), + .input(CallInvocationIdCompletionNotificationMessage { + completion_id: 1, + invocation_id: "my-id".to_string(), }) .run(|vm| { vm.sys_input().unwrap(); @@ -121,57 +100,37 @@ fn send_then_get_invocation_id_then_cancel_invocation() { ) .unwrap(); - let invocation_id_handle = vm - .sys_get_call_invocation_id(GetInvocationIdTarget::SendEntry(send_handle)) - .unwrap(); - vm.notify_await_point(invocation_id_handle); + assert_eq!( + vm.do_progress(vec![send_handle.invocation_id_notification_handle]) + .unwrap(), + DoProgressResponse::AnyCompleted + ); let_assert!( - Some(Value::InvocationId(invocation_id)) = - vm.take_async_result(invocation_id_handle).unwrap() + Some(Value::InvocationId(invocation_id)) = vm + .take_notification(send_handle.invocation_id_notification_handle) + .unwrap() ); assert_eq!(invocation_id, "my-id"); - vm.sys_cancel_invocation(CancelInvocationTarget::SendEntry(send_handle)) - .unwrap(); - vm.sys_cancel_invocation(CancelInvocationTarget::InvocationId(invocation_id.clone())) - .unwrap(); + vm.sys_cancel_invocation(invocation_id).unwrap(); vm.sys_end().unwrap(); }); assert_that!( - output.next_decoded::().unwrap(), - pat!(OneWayCallEntryMessage { + output.next_decoded::().unwrap(), + pat!(OneWayCallCommandMessage { service_name: eq("MySvc"), - handler_name: eq("MyHandler") + handler_name: eq("MyHandler"), + invocation_id_notification_idx: eq(1), }) ); assert_eq!( - output - .next_decoded::() - .unwrap(), - GetCallInvocationIdEntryMessage { - call_entry_index: 1, - ..Default::default() - } - ); - assert_eq!( - output - .next_decoded::() - .unwrap(), - CancelInvocationEntryMessage { - target: Some(cancel_invocation_entry_message::Target::CallEntryIndex(1)), - ..Default::default() - } - ); - assert_eq!( - output - .next_decoded::() - .unwrap(), - CancelInvocationEntryMessage { - target: Some(cancel_invocation_entry_message::Target::InvocationId( - "my-id".to_string() - )), + output.next_decoded::().unwrap(), + SendSignalCommandMessage { + target_invocation_id: "my-id".to_string(), + signal_id: Some(SignalId::Idx(CANCEL_SIGNAL_ID)), + result: Some(send_signal_command_message::Result::Void(Default::default())), ..Default::default() } ); diff --git a/src/tests/failures.rs b/src/tests/failures.rs index 2ca12d4..e1a5e99 100644 --- a/src/tests/failures.rs +++ b/src/tests/failures.rs @@ -1,9 +1,10 @@ use super::*; use crate::service_protocol::messages::{ - ErrorMessage, GetStateEntryMessage, InputEntryMessage, OneWayCallEntryMessage, StartMessage, + ErrorMessage, GetLazyStateCommandMessage, OneWayCallCommandMessage, StartMessage, }; use std::fmt; +use std::result::Result; use test_log::test; #[test] @@ -18,7 +19,7 @@ fn got_closed_stream_before_end_of_replay() { known_entries: 2, ..Default::default() })); - vm.notify_input(encoder.encode(&InputEntryMessage::default())); + vm.notify_input(encoder.encode(&InputCommandMessage::default())); // Now notify input closed vm.notify_input_closed(); @@ -38,14 +39,16 @@ fn got_closed_stream_before_end_of_replay() { } #[test] -fn get_state_entry_mismatch() { - test_entry_mismatch( - GetStateEntryMessage { +fn get_lazy_state_entry_mismatch() { + test_entry_mismatch_on_replay( + GetLazyStateCommandMessage { key: Bytes::from_static(b"my-key"), + result_completion_id: 1, ..Default::default() }, - GetStateEntryMessage { + GetLazyStateCommandMessage { key: Bytes::from_static(b"another-key"), + result_completion_id: 1, ..Default::default() }, |vm| vm.sys_state_get("another-key".to_owned()), @@ -54,19 +57,21 @@ fn get_state_entry_mismatch() { #[test] fn one_way_call_entry_mismatch() { - test_entry_mismatch( - OneWayCallEntryMessage { + test_entry_mismatch_on_replay( + OneWayCallCommandMessage { service_name: "greeter".to_owned(), handler_name: "greet".to_owned(), key: "my-key".to_owned(), parameter: Bytes::from_static(b"123"), + invocation_id_notification_idx: 1, ..Default::default() }, - OneWayCallEntryMessage { + OneWayCallCommandMessage { service_name: "greeter".to_owned(), handler_name: "greet".to_owned(), key: "my-key".to_owned(), parameter: Bytes::from_static(b"456"), + invocation_id_notification_idx: 1, ..Default::default() }, |vm| { @@ -85,7 +90,7 @@ fn one_way_call_entry_mismatch() { ); } -fn test_entry_mismatch( +fn test_entry_mismatch_on_replay( expected: M, actual: M, user_code: impl FnOnce(&mut CoreVM) -> Result, @@ -98,11 +103,7 @@ fn test_entry_mismatch( partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) + .input(input_entry_message(b"my-data")) .input(expected.clone()) .run(|vm| { vm.sys_input().unwrap(); diff --git a/src/tests/get_state.rs b/src/tests/get_state.rs deleted file mode 100644 index 27c75d3..0000000 --- a/src/tests/get_state.rs +++ /dev/null @@ -1,192 +0,0 @@ -use super::*; - -use crate::service_protocol::messages::start_message::StateEntry; -use crate::service_protocol::messages::{ - completion_message, get_state_entry_message, output_entry_message, CompletionMessage, - EndMessage, GetStateEntryMessage, InputEntryMessage, OutputEntryMessage, StartMessage, - SuspensionMessage, -}; -use assert2::let_assert; -use test_log::test; - -#[test] -fn just_replay() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"123"), - debug_id: "123".to_string(), - known_entries: 2, - ..Default::default() - }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) - .input(GetStateEntryMessage { - key: Bytes::from_static(b"Personaggio"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"Pippo", - ))), - ..Default::default() - }) - .run(|vm| { - vm.sys_input().unwrap(); - - let handle = vm.sys_state_get("Personaggio".to_owned()).unwrap(); - let_assert!(Some(Value::Success(b)) = vm.take_async_result(handle).unwrap()); - assert_eq!(b, b"Pippo".to_vec()); - - vm.sys_write_output(NonEmptyValue::Success(b)).unwrap(); - vm.sys_end().unwrap(); - }); - - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Pippo" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - EndMessage::default() - ); - assert_eq!(output.next(), None); -} - -#[test] -fn suspend() { - let mut output = VMTestCase::new() - .input(start_message(1)) - .input(input_entry_message(b"my-data")) - .run(|vm| { - vm.sys_input().unwrap(); - - let handle = vm.sys_state_get("Personaggio".to_owned()).unwrap(); - - // The callback should be completed immediately - vm.notify_await_point(handle); - assert_that!( - vm.take_async_result(handle), - err(pat!(SuspendedOrVMError::Suspended(_))) - ); - }); - - assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { - key: Bytes::from_static(b"Personaggio"), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } - ); - assert_eq!(output.next(), None); -} - -#[test] -fn completed() { - let mut output = VMTestCase::new() - .input(start_message(1)) - .input(input_entry_message(b"my-data")) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"Pippo", - ))), - }) - .run(|vm| { - vm.sys_input().unwrap(); - - let handle = vm.sys_state_get("Personaggio".to_owned()).unwrap(); - - vm.notify_await_point(handle); - let_assert!(Some(Value::Success(b)) = vm.take_async_result(handle).unwrap()); - assert_eq!(b, b"Pippo".to_vec()); - - vm.sys_write_output(NonEmptyValue::Success(b)).unwrap(); - vm.sys_end().unwrap(); - }); - - assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { - key: Bytes::from_static(b"Personaggio"), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Pippo" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - EndMessage::default() - ); - assert_eq!(output.next(), None); -} - -#[test] -fn completed_with_eager_state() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"123"), - debug_id: "123".to_string(), - known_entries: 1, - state_map: vec![StateEntry { - key: Bytes::from_static(b"Personaggio"), - value: Bytes::from_static(b"Francesco"), - }], - ..Default::default() - }) - .input(input_entry_message(b"my-data")) - .run(|vm| { - vm.sys_input().unwrap(); - - let handle = vm.sys_state_get("Personaggio".to_owned()).unwrap(); - - vm.notify_await_point(handle); - let_assert!(Some(Value::Success(b)) = vm.take_async_result(handle).unwrap()); - assert_eq!(b, b"Francesco".to_vec()); - - vm.sys_write_output(NonEmptyValue::Success(b)).unwrap(); - vm.sys_end().unwrap(); - }); - - assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { - key: Bytes::from_static(b"Personaggio"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - EndMessage::default() - ); - assert_eq!(output.next(), None); -} diff --git a/src/tests/input_output.rs b/src/tests/input_output.rs index 96e6c46..eed197e 100644 --- a/src/tests/input_output.rs +++ b/src/tests/input_output.rs @@ -1,8 +1,6 @@ use super::*; -use crate::service_protocol::messages::{ - output_entry_message, EndMessage, InputEntryMessage, OutputEntryMessage, StartMessage, -}; +use crate::service_protocol::messages::{EndMessage, OutputCommandMessage, StartMessage}; use assert2::let_assert; use test_log::test; @@ -23,21 +21,12 @@ fn echo() { known_entries: 1, ..Default::default() }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) + .input(input_entry_message(b"my-data")) .run(echo_handler); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"my-data" - ))), - ..OutputEntryMessage::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"my-data") ); assert_eq!( output.next_decoded::().unwrap(), @@ -55,13 +44,13 @@ fn headers() { known_entries: 1, ..Default::default() }) - .input(InputEntryMessage { + .input(InputCommandMessage { headers: vec![service_protocol::messages::Header { key: "x-my-header".to_owned(), value: "my-value".to_owned(), }], - value: Bytes::from_static(b"other-value"), - ..InputEntryMessage::default() + value: Some(Bytes::from_static(b"other-value").into()), + ..InputCommandMessage::default() }) .run(|vm| { let_assert!(Input { headers, .. } = vm.sys_input().unwrap()); @@ -79,12 +68,9 @@ fn headers() { vm.sys_end().unwrap(); }); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::default())), - ..OutputEntryMessage::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success("") ); assert_eq!( output.next_decoded::().unwrap(), @@ -102,16 +88,12 @@ fn replay_output_too() { known_entries: 2, ..Default::default() }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) - .input(OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"my-data", - ))), - ..OutputEntryMessage::default() + .input(input_entry_message(b"my-data")) + .input(OutputCommandMessage { + result: Some(output_command_message::Result::Value( + Bytes::from_static(b"my-data").into(), + )), + ..OutputCommandMessage::default() }) .run(echo_handler); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index bff1575..c4de724 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,7 +1,6 @@ mod async_result; mod calls; mod failures; -mod get_state; mod input_output; mod promise; mod run; @@ -12,13 +11,12 @@ mod suspensions; use super::*; use crate::service_protocol::messages::{ - output_entry_message, run_entry_message, ErrorMessage, InputEntryMessage, OutputEntryMessage, - RestateMessage, RunEntryMessage, StartMessage, SuspensionMessage, WriteableRestateMessage, + output_command_message, ErrorMessage, InputCommandMessage, OutputCommandMessage, + RestateMessage, StartMessage, SuspensionMessage, }; -use crate::service_protocol::{messages, Decoder, Encoder, RawMessage, Version}; +use crate::service_protocol::{messages, CompletionId, Decoder, Encoder, RawMessage, Version}; use bytes::Bytes; use googletest::prelude::*; -use std::result::Result; use test_log::test; // --- Test infra @@ -56,7 +54,7 @@ impl VMTestCase { } } - fn input(mut self, m: M) -> Self { + fn input(mut self, m: M) -> Self { self.vm.notify_input(self.encoder.encode(&m)); self } @@ -131,43 +129,28 @@ pub fn error_message_as_vm_error(vm_error: Error) -> impl Matcher impl Matcher { +pub fn suspended_waiting_completion( + completion_id: CompletionId, +) -> impl Matcher { pat!(SuspensionMessage { - entry_indexes: eq(vec![index]) + waiting_completions: eq(vec![completion_id]) }) } -pub fn is_suspended() -> impl Matcher { - pat!(SuspendedOrVMError::Suspended(_)) -} - -#[allow(dead_code)] -pub fn is_run_with_success(b: impl AsRef<[u8]>) -> impl Matcher { - pat!(RunEntryMessage { - result: some(pat!(run_entry_message::Result::Value(eq( - Bytes::copy_from_slice(b.as_ref()) - )))) +pub fn suspended_waiting_signal(signal_idx: u32) -> impl Matcher { + pat!(SuspensionMessage { + waiting_signals: eq(vec![signal_idx]) }) } -pub fn is_run_with_failure( - code: u16, - message: impl Into, -) -> impl Matcher { - pat!(RunEntryMessage { - result: some(pat!(run_entry_message::Result::Failure(eq( - messages::Failure { - code: code as u32, - message: message.into(), - } - )))) - }) +pub fn is_suspended() -> impl Matcher { + pat!(SuspendedOrVMError::Suspended(_)) } -pub fn is_output_with_success(b: impl AsRef<[u8]>) -> impl Matcher { - pat!(OutputEntryMessage { - result: some(pat!(output_entry_message::Result::Value(eq( - Bytes::copy_from_slice(b.as_ref()) +pub fn is_output_with_success(b: impl AsRef<[u8]>) -> impl Matcher { + pat!(OutputCommandMessage { + result: some(pat!(output_command_message::Result::Value(eq( + Bytes::copy_from_slice(b.as_ref()).into() )))) }) } @@ -175,9 +158,9 @@ pub fn is_output_with_success(b: impl AsRef<[u8]>) -> impl Matcher, -) -> impl Matcher { - pat!(OutputEntryMessage { - result: some(pat!(output_entry_message::Result::Failure(eq( +) -> impl Matcher { + pat!(OutputCommandMessage { + result: some(pat!(output_command_message::Result::Failure(eq( messages::Failure { code: code as u32, message: message.into(), @@ -201,11 +184,11 @@ pub fn start_message(known_entries: u32) -> StartMessage { } } -pub fn input_entry_message(b: impl AsRef<[u8]>) -> InputEntryMessage { - InputEntryMessage { +pub fn input_entry_message(b: impl AsRef<[u8]>) -> InputCommandMessage { + InputCommandMessage { headers: vec![], - value: Bytes::copy_from_slice(b.as_ref()), - ..InputEntryMessage::default() + value: Some(Bytes::copy_from_slice(b.as_ref()).into()), + ..InputCommandMessage::default() } } diff --git a/src/tests/promise.rs b/src/tests/promise.rs index a7d4fdc..74f70c0 100644 --- a/src/tests/promise.rs +++ b/src/tests/promise.rs @@ -1,6 +1,7 @@ use super::*; use crate::service_protocol::messages::{Failure, *}; +use crate::Value; mod get_promise { use super::*; @@ -11,13 +12,15 @@ mod get_promise { vm.sys_input().unwrap(); let h1 = vm.sys_get_promise("my-prom".to_owned()).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - - let output = match h1_result.unwrap().expect("Should be ready") { + let output = match vm.take_notification(h1).unwrap().expect("Should be ready") { Value::Void => { panic!("Got void result, unexpected for get promise") } @@ -40,31 +43,26 @@ mod get_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"\"my value\"", - ))), + .input(InputCommandMessage::default()) + .input(GetPromiseCompletionNotificationMessage { + completion_id: 1, + result: Some(get_promise_completion_notification_message::Result::Value( + Bytes::from_static(b"\"my value\"").into(), + )), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - GetPromiseEntryMessage { + output.next_decoded::().unwrap(), + GetPromiseCommandMessage { key: "my-prom".to_owned(), + result_completion_id: 1, ..Default::default() } ); - - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"\"my value\"" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"\"my value\"") ); assert_eq!( output.next_decoded::().unwrap(), @@ -82,34 +80,30 @@ mod get_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Failure(Failure { - code: 500, - message: "myerror".to_owned(), - })), + .input(InputCommandMessage::default()) + .input(GetPromiseCompletionNotificationMessage { + completion_id: 1, + result: Some( + get_promise_completion_notification_message::Result::Failure(Failure { + code: 500, + message: "myerror".to_owned(), + }), + ), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - GetPromiseEntryMessage { + output.next_decoded::().unwrap(), + GetPromiseCommandMessage { key: "my-prom".to_owned(), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Failure(Failure { - code: 500, - message: "myerror".to_owned(), - })), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_failure(500, "myerror") ); - assert_eq!( output.next_decoded::().unwrap(), EndMessage::default() @@ -127,13 +121,15 @@ mod peek_promise { vm.sys_input().unwrap(); let h1 = vm.sys_peek_promise("my-prom".to_owned()).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - - let output = match h1_result.unwrap().expect("Should be ready") { + let output = match vm.take_notification(h1).unwrap().expect("Should be ready") { Value::Void => NonEmptyValue::Success("null".into()), Value::Success(s) => NonEmptyValue::Success(s), Value::Failure(f) => NonEmptyValue::Failure(f), @@ -154,30 +150,26 @@ mod peek_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"\"my value\"", - ))), + .input(InputCommandMessage::default()) + .input(PeekPromiseCompletionNotificationMessage { + completion_id: 1, + result: Some(peek_promise_completion_notification_message::Result::Value( + Bytes::from_static(b"\"my value\"").into(), + )), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - PeekPromiseEntryMessage { + output.next_decoded::().unwrap(), + PeekPromiseCommandMessage { key: "my-prom".to_owned(), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"\"my value\"" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"\"my value\"") ); assert_eq!( output.next_decoded::().unwrap(), @@ -196,32 +188,29 @@ mod peek_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Failure(Failure { - code: 500, - message: "myerror".to_owned(), - })), + .input(InputCommandMessage::default()) + .input(PeekPromiseCompletionNotificationMessage { + completion_id: 1, + result: Some( + peek_promise_completion_notification_message::Result::Failure(Failure { + code: 500, + message: "myerror".to_owned(), + }), + ), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - PeekPromiseEntryMessage { + output.next_decoded::().unwrap(), + PeekPromiseCommandMessage { key: "my-prom".to_owned(), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Failure(Failure { - code: 500, - message: "myerror".to_owned(), - })), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_failure(500, "myerror") ); assert_eq!( output.next_decoded::().unwrap(), @@ -240,28 +229,26 @@ mod peek_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Empty(Empty::default())), + .input(InputCommandMessage::default()) + .input(PeekPromiseCompletionNotificationMessage { + completion_id: 1, + result: Some(peek_promise_completion_notification_message::Result::Void( + Default::default(), + )), }) .run(handler); assert_eq!( - output.next_decoded::().unwrap(), - PeekPromiseEntryMessage { + output.next_decoded::().unwrap(), + PeekPromiseCommandMessage { key: "my-prom".to_owned(), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"null" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"null") ); assert_eq!( output.next_decoded::().unwrap(), @@ -286,13 +273,15 @@ mod complete_promise { let h1 = vm .sys_complete_promise("my-prom".to_owned(), result) .unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - - let output = match h1_result.unwrap().expect("Should be ready") { + let output = match vm.take_notification(h1).unwrap().expect("Should be ready") { Value::Void => RESOLVED, Value::Success(_) => panic!("Unexpected success completion"), Value::Failure(_) => REJECTED, @@ -314,10 +303,14 @@ mod complete_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Empty(Empty::default())), + .input(InputCommandMessage::default()) + .input(CompletePromiseCompletionNotificationMessage { + completion_id: 1, + result: Some( + complete_promise_completion_notification_message::Result::Void( + Default::default(), + ), + ), }) .run(handler(NonEmptyValue::Success(Bytes::from_static( b"my val", @@ -325,22 +318,22 @@ mod complete_promise { assert_eq!( output - .next_decoded::() + .next_decoded::() .unwrap(), - CompletePromiseEntryMessage { + CompletePromiseCommandMessage { key: "my-prom".to_owned(), - completion: Some(complete_promise_entry_message::Completion::CompletionValue( - Bytes::from_static(b"my val") - )), + result_completion_id: 1, + completion: Some( + complete_promise_command_message::Completion::CompletionValue( + Bytes::from_static(b"my val").into() + ) + ), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(RESOLVED)), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(RESOLVED) ); assert_eq!( output.next_decoded::().unwrap(), @@ -359,13 +352,15 @@ mod complete_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Failure(Failure { - code: 500, - message: "cannot write promise".to_owned(), - })), + .input(InputCommandMessage::default()) + .input(CompletePromiseCompletionNotificationMessage { + completion_id: 1, + result: Some( + complete_promise_completion_notification_message::Result::Failure(Failure { + code: 500, + message: "cannot write promise".to_owned(), + }), + ), }) .run(handler(NonEmptyValue::Success(Bytes::from_static( b"my val", @@ -373,22 +368,22 @@ mod complete_promise { assert_eq!( output - .next_decoded::() + .next_decoded::() .unwrap(), - CompletePromiseEntryMessage { + CompletePromiseCommandMessage { key: "my-prom".to_owned(), - completion: Some(complete_promise_entry_message::Completion::CompletionValue( - Bytes::from_static(b"my val") - )), + result_completion_id: 1, + completion: Some( + complete_promise_command_message::Completion::CompletionValue( + Bytes::from_static(b"my val").into() + ) + ), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(REJECTED)), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(REJECTED) ); assert_eq!( output.next_decoded::().unwrap(), @@ -407,10 +402,14 @@ mod complete_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Empty(Empty::default())), + .input(InputCommandMessage::default()) + .input(CompletePromiseCompletionNotificationMessage { + completion_id: 1, + result: Some( + complete_promise_completion_notification_message::Result::Void( + Default::default(), + ), + ), }) .run(handler(NonEmptyValue::Failure(TerminalFailure { code: 500, @@ -419,12 +418,13 @@ mod complete_promise { assert_eq!( output - .next_decoded::() + .next_decoded::() .unwrap(), - CompletePromiseEntryMessage { + CompletePromiseCommandMessage { key: "my-prom".to_owned(), + result_completion_id: 1, completion: Some( - complete_promise_entry_message::Completion::CompletionFailure(Failure { + complete_promise_command_message::Completion::CompletionFailure(Failure { code: 500, message: "my failure".to_owned(), }) @@ -432,12 +432,9 @@ mod complete_promise { ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(RESOLVED)), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(RESOLVED) ); assert_eq!( output.next_decoded::().unwrap(), @@ -456,13 +453,15 @@ mod complete_promise { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Failure(Failure { - code: 500, - message: "cannot write promise".to_owned(), - })), + .input(InputCommandMessage::default()) + .input(CompletePromiseCompletionNotificationMessage { + completion_id: 1, + result: Some( + complete_promise_completion_notification_message::Result::Failure(Failure { + code: 500, + message: "cannot write promise".to_owned(), + }), + ), }) .run(handler(NonEmptyValue::Failure(TerminalFailure { code: 500, @@ -471,12 +470,13 @@ mod complete_promise { assert_eq!( output - .next_decoded::() + .next_decoded::() .unwrap(), - CompletePromiseEntryMessage { + CompletePromiseCommandMessage { key: "my-prom".to_owned(), + result_completion_id: 1, completion: Some( - complete_promise_entry_message::Completion::CompletionFailure(Failure { + complete_promise_command_message::Completion::CompletionFailure(Failure { code: 500, message: "my failure".to_owned(), }) @@ -484,12 +484,9 @@ mod complete_promise { ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(REJECTED)), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(REJECTED) ); assert_eq!( output.next_decoded::().unwrap(), diff --git a/src/tests/run.rs b/src/tests/run.rs index 3f27f54..6a87076 100644 --- a/src/tests/run.rs +++ b/src/tests/run.rs @@ -1,14 +1,15 @@ use super::*; use crate::service_protocol::messages::{ - run_entry_message, EndMessage, EntryAckMessage, ErrorMessage, InputEntryMessage, - OutputEntryMessage, RunEntryMessage, StartMessage, SuspensionMessage, + propose_run_completion_message, run_completion_notification_message, EndMessage, ErrorMessage, + Failure, OutputCommandMessage, ProposeRunCompletionMessage, RunCommandMessage, + RunCompletionNotificationMessage, StartMessage, SuspensionMessage, }; use assert2::let_assert; use test_log::test; #[test] -fn run_guard() { +fn enter_then_propose_completion_then_suspend() { let mut output = VMTestCase::new() .input(StartMessage { id: Bytes::from_static(b"123"), @@ -17,66 +18,62 @@ fn run_guard() { partial_state: false, ..Default::default() }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) - .run(|vm| { + .input(input_entry_message(b"my-data")) + .run_without_closing_input(|vm, _| { vm.sys_input().unwrap(); - let_assert!( - RunEnterResult::NotExecuted { .. } = vm.sys_run_enter("".to_owned()).unwrap() + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); + + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::ExecuteRun(handle) ); - assert_that!( - vm.sys_state_get("Personaggio".to_owned()), - err(eq_vm_error(vm::errors::INSIDE_RUN)) + + vm.propose_run_completion( + handle, + RunExitResult::Success(Bytes::from_static(b"123")), + RetryPolicy::default(), + ) + .unwrap(); + + // Not yet closed, we could still receive the completion here + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::ReadFromInput ); + + // Input closed, we won't receive the ack anymore + vm.notify_input_closed(); + assert_that!(vm.do_progress(vec![handle]), err(is_suspended())); }); assert_that!( - output.next_decoded::().unwrap(), - error_message_as_vm_error(vm::errors::INSIDE_RUN) - ); - assert_eq!(output.next(), None); -} - -#[test] -fn exit_without_enter() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"123"), - debug_id: "123".to_string(), - known_entries: 1, - partial_state: false, - ..Default::default() + output.next_decoded::().unwrap(), + eq(RunCommandMessage { + result_completion_id: 1, + name: "my-side-effect".to_owned(), }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() + ); + assert_that!( + output + .next_decoded::() + .unwrap(), + eq(ProposeRunCompletionMessage { + result_completion_id: 1, + result: Some(propose_run_completion_message::Result::Value( + Bytes::from_static(b"123") + )), }) - .run(|vm| { - vm.sys_input().unwrap(); - - assert_that!( - vm.sys_run_exit( - RunExitResult::Success(vec![1, 2, 3].into()), - RetryPolicy::default() - ), - err(eq_vm_error(vm::errors::INVOKED_RUN_EXIT_WITHOUT_ENTER)) - ); - }); - + ); assert_that!( - output.next_decoded::().unwrap(), - error_message_as_vm_error(vm::errors::INVOKED_RUN_EXIT_WITHOUT_ENTER) + output.next_decoded::().unwrap(), + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); } #[test] -fn enter_then_exit_then_suspend() { +fn enter_then_propose_completion_then_complete() { let mut output = VMTestCase::new() .input(StartMessage { id: Bytes::from_static(b"123"), @@ -85,50 +82,72 @@ fn enter_then_exit_then_suspend() { partial_state: false, ..Default::default() }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) - .run_without_closing_input(|vm, _| { + .input(input_entry_message(b"my-data")) + .run_without_closing_input(|vm, encoder| { vm.sys_input().unwrap(); - let_assert!( - RunEnterResult::NotExecuted { .. } = - vm.sys_run_enter("my-side-effect".to_owned()).unwrap() + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::ExecuteRun(handle) ); - let handle = vm - .sys_run_exit( - RunExitResult::Success(Bytes::from_static(b"123")), - RetryPolicy::default(), - ) - .unwrap(); - vm.notify_await_point(handle); - // Not yet closed, we could still receive the ack here - assert_that!(vm.take_async_result(handle), ok(none())); + vm.propose_run_completion( + handle, + RunExitResult::Success(Bytes::from_static(b"123")), + RetryPolicy::default(), + ) + .unwrap(); - // Input closed, we won't receive the ack anymore + vm.notify_input(encoder.encode(&RunCompletionNotificationMessage { + completion_id: 1, + result: Some(run_completion_notification_message::Result::Value( + Bytes::from_static(b"123").into(), + )), + })); vm.notify_input_closed(); - assert_that!(vm.take_async_result(handle), err(is_suspended())); + + // We should now get the side effect result + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::AnyCompleted + ); + let result = vm.take_notification(handle).unwrap().unwrap(); + let_assert!(Value::Success(s) = result); + + // Write the result as output + vm.sys_write_output(NonEmptyValue::Success(s)).unwrap(); + vm.sys_end().unwrap(); }); assert_that!( - output.next_decoded::().unwrap(), - eq(RunEntryMessage { + output.next_decoded::().unwrap(), + eq(RunCommandMessage { name: "my-side-effect".to_owned(), - result: Some(run_entry_message::Result::Value(Bytes::from_static(b"123"))), + result_completion_id: 1 }) ); + assert_eq!( + output + .next_decoded::() + .unwrap(), + ProposeRunCompletionMessage { + result_completion_id: 1, + result: Some(propose_run_completion_message::Result::Value( + Bytes::from_static(b"123") + )), + } + ); assert_that!( - output.next_decoded::().unwrap(), - suspended_with_index(1) + output.next_decoded::().unwrap(), + is_output_with_success(b"123") ); + output.next_decoded::().unwrap(); assert_eq!(output.next(), None); } #[test] -fn enter_then_exit_then_ack() { +fn enter_then_propose_completion_then_complete_with_failure() { let mut output = VMTestCase::new() .input(StartMessage { id: Bytes::from_static(b"123"), @@ -137,56 +156,79 @@ fn enter_then_exit_then_ack() { partial_state: false, ..Default::default() }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) + .input(input_entry_message(b"my-data")) .run_without_closing_input(|vm, encoder| { vm.sys_input().unwrap(); - let_assert!( - RunEnterResult::NotExecuted { .. } = - vm.sys_run_enter("my-side-effect".to_owned()).unwrap() + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); + + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::ExecuteRun(handle) ); - let handle = vm - .sys_run_exit( - RunExitResult::Success(Bytes::from_static(b"123")), - RetryPolicy::default(), - ) - .unwrap(); - vm.notify_await_point(handle); + vm.propose_run_completion( + handle, + RunExitResult::TerminalFailure(TerminalFailure { + code: 500, + message: "my-failure".to_string(), + }), + RetryPolicy::default(), + ) + .unwrap(); - // Send the ack and close the input - vm.notify_input(encoder.encode(&EntryAckMessage { entry_index: 1 })); + vm.notify_input(encoder.encode(&RunCompletionNotificationMessage { + completion_id: 1, + result: Some(run_completion_notification_message::Result::Failure( + Failure { + code: 500, + message: "my-failure".to_string(), + }, + )), + })); vm.notify_input_closed(); // We should now get the side effect result - let result = vm.take_async_result(handle).unwrap().unwrap(); - let_assert!(Value::Success(s) = result); + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::AnyCompleted + ); + let result = vm.take_notification(handle).unwrap().unwrap(); + let_assert!(Value::Failure(f) = result); // Write the result as output - vm.sys_write_output(NonEmptyValue::Success(s)).unwrap(); + vm.sys_write_output(NonEmptyValue::Failure(f)).unwrap(); vm.sys_end().unwrap(); }); assert_that!( - output.next_decoded::().unwrap(), - eq(RunEntryMessage { + output.next_decoded::().unwrap(), + eq(RunCommandMessage { + result_completion_id: 1, name: "my-side-effect".to_owned(), - result: Some(run_entry_message::Result::Value(Bytes::from_static(b"123"))), }) ); + assert_eq!( + output + .next_decoded::() + .unwrap(), + ProposeRunCompletionMessage { + result_completion_id: 1, + result: Some(propose_run_completion_message::Result::Failure(Failure { + code: 500, + message: "my-failure".to_string(), + })), + } + ); assert_that!( - output.next_decoded::().unwrap(), - is_output_with_success(b"123") + output.next_decoded::().unwrap(), + is_output_with_failure(500, "my-failure") ); output.next_decoded::().unwrap(); assert_eq!(output.next(), None); } #[test] -fn enter_then_exit_then_ack_with_failure() { +fn enter_then_notify_input_closed_then_propose_completion() { let mut output = VMTestCase::new() .input(StartMessage { id: Bytes::from_static(b"123"), @@ -195,84 +237,141 @@ fn enter_then_exit_then_ack_with_failure() { partial_state: false, ..Default::default() }) - .input(InputEntryMessage { - headers: vec![], - value: Bytes::from_static(b"my-data"), - ..InputEntryMessage::default() - }) - .run_without_closing_input(|vm, encoder| { + .input(input_entry_message(b"my-data")) + .run_without_closing_input(|vm, _| { vm.sys_input().unwrap(); - let_assert!( - RunEnterResult::NotExecuted { .. } = - vm.sys_run_enter("my-side-effect".to_owned()).unwrap() + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::ExecuteRun(handle) ); - let handle = vm - .sys_run_exit( - RunExitResult::TerminalFailure(TerminalFailure { - code: 500, - message: "my-failure".to_string(), - }), - RetryPolicy::default(), - ) - .unwrap(); - vm.notify_await_point(handle); - // Send the ack and close the input - vm.notify_input(encoder.encode(&EntryAckMessage { entry_index: 1 })); + // Notify input closed here vm.notify_input_closed(); - // We should now get the side effect result - let result = vm.take_async_result(handle).unwrap().unwrap(); - let_assert!(Value::Failure(f) = result); + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::WaitingPendingRun + ); - // Write the result as output - vm.sys_write_output(NonEmptyValue::Failure(f)).unwrap(); - vm.sys_end().unwrap(); + // Propose run completion + vm.propose_run_completion( + handle, + RunExitResult::Success(Bytes::from_static(b"123")), + RetryPolicy::default(), + ) + .unwrap(); + + assert_that!(vm.do_progress(vec![handle]), err(is_suspended())); }); assert_that!( - output.next_decoded::().unwrap(), - eq(RunEntryMessage { + output.next_decoded::().unwrap(), + eq(RunCommandMessage { name: "my-side-effect".to_owned(), - result: Some(run_entry_message::Result::Failure(messages::Failure { - code: 500, - message: "my-failure".to_string(), - })), + result_completion_id: 1 }) ); + assert_eq!( + output + .next_decoded::() + .unwrap(), + ProposeRunCompletionMessage { + result_completion_id: 1, + result: Some(propose_run_completion_message::Result::Value( + Bytes::from_static(b"123") + )), + } + ); assert_that!( - output.next_decoded::().unwrap(), - is_output_with_failure(500, "my-failure") + output.next_decoded::().unwrap(), + suspended_waiting_completion(1) ); - output.next_decoded::().unwrap(); assert_eq!(output.next(), None); } #[test] -fn replay() { +fn replay_without_completion() { let mut output = VMTestCase::new() .input(start_message(2)) .input(input_entry_message(b"my-data")) - .input(RunEntryMessage { + .input(RunCommandMessage { + result_completion_id: 1, + name: "my-side-effect".to_owned(), + }) + .run(|vm| { + vm.sys_input().unwrap(); + + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); + + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::ExecuteRun(handle) + ); + vm.propose_run_completion( + handle, + RunExitResult::Success(Bytes::from_static(b"123")), + RetryPolicy::default(), + ) + .unwrap(); + + assert_that!(vm.do_progress(vec![handle]), err(is_suspended())); + }); + + assert_eq!( + output + .next_decoded::() + .unwrap(), + ProposeRunCompletionMessage { + result_completion_id: 1, + result: Some(propose_run_completion_message::Result::Value( + Bytes::from_static(b"123") + )), + } + ); + assert_that!( + output.next_decoded::().unwrap(), + suspended_waiting_completion(1) + ); + assert_eq!(output.next(), None); +} + +#[test] +fn replay_with_completion() { + let mut output = VMTestCase::new() + .input(start_message(3)) + .input(input_entry_message(b"my-data")) + .input(RunCommandMessage { + result_completion_id: 1, name: "my-side-effect".to_owned(), - result: Some(run_entry_message::Result::Value(Bytes::from_static(b"123"))), + }) + .input(RunCompletionNotificationMessage { + completion_id: 1, + result: Some(run_completion_notification_message::Result::Value( + Bytes::from_static(b"123").into(), + )), }) .run(|vm| { vm.sys_input().unwrap(); - let_assert!( - RunEnterResult::Executed(NonEmptyValue::Success(s)) = - vm.sys_run_enter("my-side-effect".to_owned()).unwrap() + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::AnyCompleted ); + // We should now get the side effect result + let result = vm.take_notification(handle).unwrap().unwrap(); + let_assert!(Value::Success(s) = result); + // Write the result as output vm.sys_write_output(NonEmptyValue::Success(s)).unwrap(); vm.sys_end().unwrap(); }); assert_that!( - output.next_decoded::().unwrap(), + output.next_decoded::().unwrap(), is_output_with_success(b"123") ); output.next_decoded::().unwrap(); @@ -286,10 +385,9 @@ fn enter_then_notify_error() { .input(input_entry_message(b"my-data")) .run(|vm| { vm.sys_input().unwrap(); - let_assert!( - RunEnterResult::NotExecuted { .. } = - vm.sys_run_enter("my-side-effect".to_owned()).unwrap() - ); + + let _ = vm.sys_run("my-side-effect".to_owned()).unwrap(); + vm.notify_error( Error::internal(Cow::Borrowed("my-error")) .with_description(Cow::Borrowed("my-error-description")), @@ -297,6 +395,13 @@ fn enter_then_notify_error() { ); }); + assert_that!( + output.next_decoded::().unwrap(), + eq(RunCommandMessage { + result_completion_id: 1, + name: "my-side-effect".to_owned(), + }) + ); assert_that!( output.next_decoded::().unwrap(), error_message_as_vm_error(Error { @@ -308,187 +413,10 @@ fn enter_then_notify_error() { assert_eq!(output.next(), None); } -mod consecutive_run { - use super::*; - - use test_log::test; - - fn handler(vm: &mut CoreVM) { - vm.sys_input().unwrap(); - - // First run - let_assert!(RunEnterResult::NotExecuted { .. } = vm.sys_run_enter("".to_owned()).unwrap()); - let h1 = vm - .sys_run_exit( - RunExitResult::Success(Bytes::from_static(b"Francesco")), - RetryPolicy::default(), - ) - .unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { - return; - } - let_assert!(Some(Value::Success(h1_value)) = h1_result.unwrap()); - - // Second run - let_assert!(RunEnterResult::NotExecuted { .. } = vm.sys_run_enter("".to_owned()).unwrap()); - let h2 = vm - .sys_run_exit( - RunExitResult::Success(Bytes::from( - String::from_utf8_lossy(&h1_value).to_uppercase(), - )), - RetryPolicy::default(), - ) - .unwrap(); - vm.notify_await_point(h2); - let h2_result = vm.take_async_result(h2); - if let Err(SuspendedOrVMError::Suspended(_)) = &h2_result { - return; - } - let_assert!(Some(Value::Success(h2_value)) = h2_result.unwrap()); - - // Write the result as output - vm.sys_write_output(NonEmptyValue::Success( - format!("Hello {}", String::from_utf8(h2_value.to_vec()).unwrap()) - .into_bytes() - .into(), - )) - .unwrap(); - vm.sys_end().unwrap(); - } - - #[test] - fn without_acks_suspends() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"abc"), - debug_id: "abc".to_owned(), - known_entries: 1, - partial_state: true, - ..Default::default() - }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .run(handler); - - assert_eq!( - output.next_decoded::().unwrap(), - RunEntryMessage { - result: Some(run_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } - ); - - assert_eq!(output.next(), None); - } - #[test] - fn ack_on_first_side_effect_will_suspend() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"abc"), - debug_id: "abc".to_owned(), - known_entries: 1, - partial_state: true, - ..Default::default() - }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(EntryAckMessage { entry_index: 1 }) - .run(handler); - - assert_eq!( - output.next_decoded::().unwrap(), - RunEntryMessage { - result: Some(run_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - RunEntryMessage { - result: Some(run_entry_message::Result::Value(Bytes::from_static( - b"FRANCESCO" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![2], - } - ); - - assert_eq!(output.next(), None); - } - #[test] - fn ack_on_first_and_second_side_effect_will_resume() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"abc"), - debug_id: "abc".to_owned(), - known_entries: 1, - partial_state: true, - ..Default::default() - }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(EntryAckMessage { entry_index: 1 }) - .input(EntryAckMessage { entry_index: 2 }) - .run(handler); - - assert_eq!( - output.next_decoded::().unwrap(), - RunEntryMessage { - result: Some(run_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - RunEntryMessage { - result: Some(run_entry_message::Result::Value(Bytes::from_static( - b"FRANCESCO" - ))), - ..Default::default() - } - ); - assert_that!( - output.next_decoded::().unwrap(), - is_output_with_success(b"Hello FRANCESCO"), - ); - assert_eq!( - output.next_decoded::().unwrap(), - EndMessage::default() - ); - - assert_eq!(output.next(), None); - } -} - mod retry_policy { use super::*; - use crate::service_protocol::messages::AwakeableEntryMessage; + use crate::service_protocol::messages::SleepCommandMessage; use test_log::test; fn test_should_stop_retrying( @@ -502,32 +430,41 @@ mod retry_policy { retry_count_since_last_stored_entry, duration_since_last_stored_entry: duration_since_last_stored_entry.as_millis() as u64, - ..start_message(1) + ..start_message(2) }) .input(input_entry_message(b"my-data")) - .input(EntryAckMessage { entry_index: 1 }) + .input(RunCommandMessage { + result_completion_id: 1, + name: "my-side-effect".to_string(), + }) + .input(RunCompletionNotificationMessage { + completion_id: 1, + result: Some(run_completion_notification_message::Result::Failure( + Failure { + code: 500, + message: "my-error".to_string(), + }, + )), + }) .run(|vm| { vm.sys_input().unwrap(); - let_assert!( - RunEnterResult::NotExecuted { .. } = - vm.sys_run_enter("my-side-effect".to_owned()).unwrap() - ); - let handle = vm - .sys_run_exit( - RunExitResult::RetryableFailure { - error: Error::internal("my-error"), - attempt_duration, - }, - retry_policy, - ) - .unwrap(); - vm.notify_await_point(handle); - let handle_result = vm.take_async_result(handle); - if let Err(SuspendedOrVMError::Suspended(_)) = &handle_result { - return; - } - let_assert!(Some(value) = handle_result.unwrap()); + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); + vm.propose_run_completion( + handle, + RunExitResult::RetryableFailure { + error: Error::internal("my-error"), + attempt_duration, + }, + retry_policy, + ) + .unwrap(); + + assert_eq!( + vm.do_progress(vec![handle]).unwrap(), + DoProgressResponse::AnyCompleted + ); + let value = vm.take_notification(handle).unwrap().unwrap(); // Write the result as output vm.sys_write_output(match value { @@ -539,12 +476,20 @@ mod retry_policy { vm.sys_end().unwrap(); }); - assert_that!( - output.next_decoded::().unwrap(), - is_run_with_failure(500, "my-error") + assert_eq!( + output + .next_decoded::() + .unwrap(), + ProposeRunCompletionMessage { + result_completion_id: 1, + result: Some(propose_run_completion_message::Result::Failure(Failure { + code: 500, + message: "my-error".to_string(), + },)), + } ); assert_that!( - output.next_decoded::().unwrap(), + output.next_decoded::().unwrap(), is_output_with_failure(500, "my-error"), ); assert_eq!( @@ -571,21 +516,26 @@ mod retry_policy { .input(input_entry_message(b"my-data")) .run(|vm| { vm.sys_input().unwrap(); - let_assert!( - RunEnterResult::NotExecuted { .. } = - vm.sys_run_enter("my-side-effect".to_owned()).unwrap() - ); + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); assert!(vm - .sys_run_exit( + .propose_run_completion( + handle, RunExitResult::RetryableFailure { error: Error::internal("my-error"), - attempt_duration + attempt_duration, }, - retry_policy + retry_policy, ) .is_err()); }); + assert_that!( + output.next_decoded::().unwrap(), + eq(RunCommandMessage { + result_completion_id: 1, + name: "my-side-effect".to_owned(), + }) + ); assert_that!( output.next_decoded::().unwrap(), pat!(ErrorMessage { @@ -669,21 +619,14 @@ mod retry_policy { vm.sys_input().unwrap(); // Just create another journal entry - vm.sys_awakeable().unwrap(); + vm.sys_sleep(Duration::from_secs(100), None).unwrap(); // Now try to enter run - let_assert!( - RunEnterResult::NotExecuted(retry_info) = - vm.sys_run_enter("my-side-effect".to_owned()).unwrap() - ); - - // This is not the first processed entry of this attempt, - // so the info in StartMessage should are invalid now - assert_eq!(retry_info.retry_count, 0); - assert_eq!(retry_info.retry_loop_duration, Duration::ZERO); + let handle = vm.sys_run("my-side-effect".to_owned()).unwrap(); assert!(vm - .sys_run_exit( + .propose_run_completion( + handle, RunExitResult::RetryableFailure { error: Error::internal("my-error"), attempt_duration: Duration::from_millis(99) @@ -697,7 +640,14 @@ mod retry_policy { .is_err()); }); - let _ = output.next_decoded::().unwrap(); + let _ = output.next_decoded::().unwrap(); + assert_that!( + output.next_decoded::().unwrap(), + eq(RunCommandMessage { + result_completion_id: 2, + name: "my-side-effect".to_owned(), + }) + ); assert_that!( output.next_decoded::().unwrap(), pat!(ErrorMessage { diff --git a/src/tests/sleep.rs b/src/tests/sleep.rs index d7ffb89..4f45d98 100644 --- a/src/tests/sleep.rs +++ b/src/tests/sleep.rs @@ -2,9 +2,29 @@ use super::*; use crate::service_protocol::messages::*; +use crate::Value; use assert2::let_assert; use test_log::test; +fn sleep_handler(vm: &mut CoreVM) { + vm.sys_input().unwrap(); + + let h1 = vm.sys_sleep(Duration::from_secs(1), None).unwrap(); + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); + return; + } + let_assert!(Some(Value::Void) = vm.take_notification(h1).unwrap()); + + vm.sys_write_output(NonEmptyValue::Success(Bytes::default())) + .unwrap(); + vm.sys_end().unwrap(); +} + #[test] fn sleep_suspends() { let mut output = VMTestCase::new() @@ -15,32 +35,18 @@ fn sleep_suspends() { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .run(|vm| { - vm.sys_input().unwrap(); + .input(input_entry_message(b"Till")) + .run(sleep_handler); - let h1 = vm.sys_sleep(Duration::from_secs(1), None).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { - return; - } - let_assert!(Some(Value::Void) = h1_result.unwrap()); - - vm.sys_write_output(NonEmptyValue::Success(Bytes::default())) - .unwrap(); - vm.sys_end().unwrap(); - }); - - let _ = output.next_decoded::().unwrap(); - assert_eq!( + assert_that!( + output.next_decoded::().unwrap(), + pat!(SleepCommandMessage { + result_completion_id: eq(1) + }) + ); + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); } @@ -51,41 +57,25 @@ fn sleep_completed() { .input(StartMessage { id: Bytes::from_static(b"abc"), debug_id: "abc".to_owned(), - known_entries: 2, + known_entries: 3, partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(SleepEntryMessage { + .input(input_entry_message(b"Till")) + .input(SleepCommandMessage { wake_up_time: 1721123699086, - result: Some(sleep_entry_message::Result::Empty(Empty::default())), + result_completion_id: 1, ..Default::default() }) - .run(|vm| { - vm.sys_input().unwrap(); - - let h1 = vm.sys_sleep(Duration::from_secs(1), None).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { - return; - } - let_assert!(Some(Value::Void) = h1_result.unwrap()); - - vm.sys_write_output(NonEmptyValue::Success(Bytes::default())) - .unwrap(); - vm.sys_end().unwrap(); - }); + .input(SleepCompletionNotificationMessage { + completion_id: 1, + void: Some(Default::default()), + }) + .run(sleep_handler); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::new())), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success("") ); assert_eq!( output.next_decoded::().unwrap(), @@ -104,35 +94,17 @@ fn sleep_still_sleeping() { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(SleepEntryMessage { + .input(input_entry_message(b"Till")) + .input(SleepCommandMessage { wake_up_time: 1721123699086, + result_completion_id: 1, ..Default::default() }) - .run(|vm| { - vm.sys_input().unwrap(); + .run(sleep_handler); - let h1 = vm.sys_sleep(Duration::from_secs(1), None).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { - return; - } - let_assert!(Some(Value::Void) = h1_result.unwrap()); - - vm.sys_write_output(NonEmptyValue::Success(Bytes::default())) - .unwrap(); - vm.sys_end().unwrap(); - }); - - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); } diff --git a/src/tests/state.rs b/src/tests/state.rs index bdca3fe..2af71fe 100644 --- a/src/tests/state.rs +++ b/src/tests/state.rs @@ -3,6 +3,9 @@ use crate::tests::VMTestCase; use crate::{CoreVM, NonEmptyValue, SuspendedOrVMError, Value, VM}; use assert2::let_assert; use bytes::Bytes; +use googletest::assert_that; +use googletest::matchers::pat; +use googletest::prelude::err; /// Normal state fn get_state_handler(vm: &mut CoreVM) { @@ -10,20 +13,17 @@ fn get_state_handler(vm: &mut CoreVM) { let h1 = vm.sys_state_get("STATE".to_owned()).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let str_result = match h1_result.unwrap().unwrap() { + let str_result = match vm.take_notification(h1).unwrap().unwrap() { Value::Void => "Unknown".to_owned(), Value::Success(s) => String::from_utf8(s.to_vec()).unwrap(), - Value::Failure(f) => { - vm.sys_write_output(NonEmptyValue::Failure(f)).unwrap(); - vm.sys_end().unwrap(); - return; - } _ => panic!("Unexpected variants"), }; @@ -36,7 +36,9 @@ fn get_state_handler(vm: &mut CoreVM) { mod only_lazy_state { use super::*; + use googletest::assert_that; + use crate::tests::{input_entry_message, is_output_with_success, suspended_waiting_completion}; use test_log::test; #[test] @@ -45,31 +47,29 @@ mod only_lazy_state { .input(StartMessage { id: Bytes::from_static(b"abc"), debug_id: "abc".to_owned(), - known_entries: 2, + known_entries: 3, partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(GetStateEntryMessage { + .input(input_entry_message(b"Till")) + .input(GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"Francesco", - ))), + result_completion_id: 1, ..Default::default() }) + .input(GetLazyStateCompletionNotificationMessage { + completion_id: 1, + result: Some( + get_lazy_state_completion_notification_message::Result::Value( + Bytes::from_static(b"Francesco").into(), + ), + ), + }) .run(get_state_handler); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -77,35 +77,37 @@ mod only_lazy_state { ); assert_eq!(output.next(), None); } + #[test] fn entry_already_completed_empty() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"abc"), - debug_id: "abc".to_owned(), - known_entries: 2, - partial_state: true, - ..Default::default() - }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(GetStateEntryMessage { - key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), - ..Default::default() - }) - .run(get_state_handler); + let mut output = + VMTestCase::new() + .input(StartMessage { + id: Bytes::from_static(b"abc"), + debug_id: "abc".to_owned(), + known_entries: 3, + partial_state: true, + ..Default::default() + }) + .input(input_entry_message(b"Till")) + .input(GetLazyStateCommandMessage { + key: Bytes::from_static(b"STATE"), + result_completion_id: 1, + ..Default::default() + }) + .input(GetLazyStateCompletionNotificationMessage { + completion_id: 1, + result: Some( + get_lazy_state_completion_notification_message::Result::Void( + Default::default(), + ), + ), + }) + .run(get_state_handler); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Unknown" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Unknown") ); assert_eq!( output.next_decoded::().unwrap(), @@ -113,6 +115,7 @@ mod only_lazy_state { ); assert_eq!(output.next(), None); } + #[test] fn new_entry() { let mut output = VMTestCase::new() @@ -123,28 +126,25 @@ mod only_lazy_state { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) + .input(input_entry_message(b"Till")) .run(get_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); } + #[test] fn entry_not_completed_on_replay() { let mut output = VMTestCase::new() @@ -155,25 +155,22 @@ mod only_lazy_state { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(GetStateEntryMessage { + .input(input_entry_message(b"Till")) + .input(GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() }) .run(get_state_handler); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); } + #[test] fn entry_on_replay_completed_later() { let mut output = VMTestCase::new() @@ -184,74 +181,25 @@ mod only_lazy_state { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(GetStateEntryMessage { + .input(input_entry_message(b"Till")) + .input(GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() }) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"Francesco", - ))), - }) - .run(get_state_handler); - - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - EndMessage::default() - ); - assert_eq!(output.next(), None); - } - #[test] - fn new_entry_completed_later() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"abc"), - debug_id: "abc".to_owned(), - known_entries: 1, - partial_state: true, - ..Default::default() - }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"Francesco", - ))), + .input(GetLazyStateCompletionNotificationMessage { + completion_id: 1, + result: Some( + get_lazy_state_completion_notification_message::Result::Value( + Bytes::from_static(b"Francesco").into(), + ), + ), }) .run(get_state_handler); - assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { - key: Bytes::from_static(b"STATE"), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -259,48 +207,9 @@ mod only_lazy_state { ); assert_eq!(output.next(), None); } - #[test] - fn replay_failed_get_state_entry() { - let mut output = VMTestCase::new() - .input(StartMessage { - id: Bytes::from_static(b"abc"), - debug_id: "abc".to_owned(), - known_entries: 2, - partial_state: true, - ..Default::default() - }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(GetStateEntryMessage { - key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Failure(Failure { - code: 409, - ..Default::default() - })), - ..Default::default() - }) - .run(get_state_handler); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Failure(Failure { - code: 409, - ..Default::default() - })), - ..Default::default() - } - ); - assert_eq!( - output.next_decoded::().unwrap(), - EndMessage::default() - ); - assert_eq!(output.next(), None); - } #[test] - fn complete_failing_get_state_entry() { + fn new_entry_completed_later() { let mut output = VMTestCase::new() .input(StartMessage { id: Bytes::from_static(b"abc"), @@ -309,35 +218,28 @@ mod only_lazy_state { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Failure(Failure { - code: 409, - ..Default::default() - })), + .input(input_entry_message(b"Till")) + .input(GetLazyStateCompletionNotificationMessage { + completion_id: 1, + result: Some( + get_lazy_state_completion_notification_message::Result::Value( + Bytes::from_static(b"Francesco").into(), + ), + ), }) .run(get_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Failure(Failure { - code: 409, - ..Default::default() - })), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -351,6 +253,7 @@ mod only_lazy_state { mod eager { use super::*; + use crate::tests::{input_entry_message, is_output_with_success, suspended_waiting_completion}; use test_log::test; fn get_empty_state_handler(vm: &mut CoreVM) { @@ -358,20 +261,17 @@ mod eager { let h1 = vm.sys_state_get("STATE".to_owned()).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let str_result = match h1_result.unwrap().unwrap() { + let str_result = match vm.take_notification(h1).unwrap().unwrap() { Value::Void => "true".to_owned(), Value::Success(_) => "false".to_owned(), - Value::Failure(f) => { - vm.sys_write_output(NonEmptyValue::Failure(f)).unwrap(); - vm.sys_end().unwrap(); - return; - } _ => panic!("Unexpected variants"), }; @@ -391,25 +291,24 @@ mod eager { known_entries: 1, ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run(get_empty_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"true" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"true") ); assert_eq!( output.next_decoded::().unwrap(), @@ -428,21 +327,20 @@ mod eager { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run(get_empty_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); @@ -458,22 +356,19 @@ mod eager { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(GetStateEntryMessage { + .input(InputCommandMessage::default()) + .input(GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default(), + )), ..Default::default() }) .run(get_empty_state_handler); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"true" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"true") ); assert_eq!( output.next_decoded::().unwrap(), @@ -496,27 +391,24 @@ mod eager { key: "my-greeter".to_owned(), ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run(get_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), + result: Some(get_eager_state_command_message::Result::Value( + Bytes::from_static(b"Francesco").into() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -540,27 +432,24 @@ mod eager { key: "my-greeter".to_owned(), ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run(get_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), + result: Some(get_eager_state_command_message::Result::Value( + Bytes::from_static(b"Francesco").into() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -579,21 +468,20 @@ mod eager { partial_state: true, ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run(get_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); } @@ -602,12 +490,16 @@ mod eager { let input = vm.sys_input().unwrap().input; let h1 = vm.sys_state_get("STATE".to_owned()).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let get_result = match h1_result.unwrap().unwrap() { + + let get_result = match vm.take_notification(h1).unwrap().unwrap() { Value::Void => { panic!("Unexpected empty get state") } @@ -627,12 +519,16 @@ mod eager { .unwrap(); let h2 = vm.sys_state_get("STATE".to_owned()).unwrap(); - vm.notify_await_point(h2); - let h2_result = vm.take_async_result(h2); - if let Err(SuspendedOrVMError::Suspended(_)) = &h2_result { + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h2]) { + assert_that!( + vm.take_notification(h2), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let second_get_result = match h2_result.unwrap().unwrap() { + + let second_get_result = match vm.take_notification(h2).unwrap().unwrap() { Value::Void => { panic!("Unexpected empty get state") } @@ -665,48 +561,44 @@ mod eager { key: "my-greeter".to_owned(), ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) + .input(input_entry_message(b"Till")) .run(append_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), + result: Some(get_eager_state_command_message::Result::Value( + Bytes::from_static(b"Francesco").into() + )), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - SetStateEntryMessage { + output.next_decoded::().unwrap(), + SetStateCommandMessage { key: Bytes::from_static(b"STATE"), - value: Bytes::from_static(b"FrancescoTill"), + value: Some(Bytes::from_static(b"FrancescoTill").into()), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"FrancescoTill" - ))), + result: Some(get_eager_state_command_message::Result::Value( + Bytes::from_static(b"FrancescoTill").into() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"FrancescoTill" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"FrancescoTill") ); assert_eq!( output.next_decoded::().unwrap(), @@ -725,51 +617,48 @@ mod eager { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"Francesco", - ))), + .input(input_entry_message(b"Till")) + .input(GetLazyStateCompletionNotificationMessage { + completion_id: 1, + result: Some( + get_lazy_state_completion_notification_message::Result::Value( + Bytes::from_static(b"Francesco").into(), + ), + ), }) .run(append_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - SetStateEntryMessage { + output.next_decoded::().unwrap(), + SetStateCommandMessage { key: Bytes::from_static(b"STATE"), - value: Bytes::from_static(b"FrancescoTill"), + value: Some(Bytes::from_static(b"FrancescoTill").into()), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"FrancescoTill" - ))), + result: Some(get_eager_state_command_message::Result::Value( + Bytes::from_static(b"FrancescoTill").into() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"FrancescoTill" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"FrancescoTill") ); assert_eq!( output.next_decoded::().unwrap(), @@ -782,33 +671,34 @@ mod eager { vm.sys_input().unwrap(); let h1 = vm.sys_state_get("STATE".to_owned()).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let first_get_result = match h1_result.unwrap().unwrap() { + let first_get_result = match vm.take_notification(h1).unwrap().unwrap() { Value::Void => { panic!("Unexpected empty get state") } Value::Success(s) => s, - Value::Failure(f) => { - vm.sys_write_output(NonEmptyValue::Failure(f)).unwrap(); - vm.sys_end().unwrap(); - return; - } _ => panic!("Unexpected variants"), }; vm.sys_state_clear("STATE".to_owned()).unwrap(); let h2 = vm.sys_state_get("STATE".to_owned()).unwrap(); - vm.notify_await_point(h2); - let h2_result = vm.take_async_result(h2); - if let Err(SuspendedOrVMError::Suspended(_)) = &h2_result { + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h2]) { + assert_that!( + vm.take_notification(h2), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let_assert!(Ok(Some(Value::Void)) = h2_result); + let_assert!(Ok(Some(Value::Void)) = vm.take_notification(h2)); vm.sys_write_output(NonEmptyValue::Success(first_get_result)) .unwrap(); @@ -830,45 +720,43 @@ mod eager { key: "my-greeter".to_owned(), ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) + .input(input_entry_message(b"Till")) .run(get_and_clear_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), + result: Some(get_eager_state_command_message::Result::Value( + Bytes::from_static(b"Francesco").into() + )), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - ClearStateEntryMessage { + output.next_decoded::().unwrap(), + ClearStateCommandMessage { key: Bytes::from_static(b"STATE"), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -887,48 +775,47 @@ mod eager { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"Francesco", - ))), + .input(input_entry_message(b"Till")) + .input(GetLazyStateCompletionNotificationMessage { + completion_id: 1, + result: Some( + get_lazy_state_completion_notification_message::Result::Value( + Bytes::from_static(b"Francesco").into(), + ), + ), }) .run(get_and_clear_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - ClearStateEntryMessage { + output.next_decoded::().unwrap(), + ClearStateCommandMessage { key: Bytes::from_static(b"STATE"), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -941,33 +828,31 @@ mod eager { vm.sys_input().unwrap(); let h1 = vm.sys_state_get("STATE".to_owned()).unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - let first_get_result = match h1_result.unwrap().unwrap() { + let first_get_result = match vm.take_notification(h1).unwrap().unwrap() { Value::Void => { panic!("Unexpected empty get state") } Value::Success(s) => s, - Value::Failure(f) => { - vm.sys_write_output(NonEmptyValue::Failure(f)).unwrap(); - vm.sys_end().unwrap(); - return; - } _ => panic!("Unexpected variants"), }; vm.sys_state_clear_all().unwrap(); let h2 = vm.sys_state_get("STATE".to_owned()).unwrap(); - vm.notify_await_point(h2); - let_assert!(Ok(Some(Value::Void)) = vm.take_async_result(h2)); + vm.do_progress(vec![h2]).unwrap(); + let_assert!(Ok(Some(Value::Void)) = vm.take_notification(h2)); let h3 = vm.sys_state_get("ANOTHER_STATE".to_owned()).unwrap(); - vm.notify_await_point(h3); - let_assert!(Ok(Some(Value::Void)) = vm.take_async_result(h3)); + vm.do_progress(vec![h3]).unwrap(); + let_assert!(Ok(Some(Value::Void)) = vm.take_notification(h3)); vm.sys_write_output(NonEmptyValue::Success(first_get_result)) .unwrap(); @@ -995,50 +880,54 @@ mod eager { key: "my-greeter".to_owned(), ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) + .input(input_entry_message(b"Till")) .run(get_and_clear_all_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), + result: Some(get_eager_state_command_message::Result::Value( + Bytes::from_static(b"Francesco").into() + )), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - ClearAllStateEntryMessage::default() + output + .next_decoded::() + .unwrap(), + ClearAllStateCommandMessage::default() ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"ANOTHER_STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -1057,53 +946,58 @@ mod eager { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(Bytes::from_static( - b"Francesco", - ))), + .input(input_entry_message(b"Till")) + .input(GetLazyStateCompletionNotificationMessage { + completion_id: 1, + result: Some( + get_lazy_state_completion_notification_message::Result::Value( + Bytes::from_static(b"Francesco").into(), + ), + ), }) .run(get_and_clear_all_state_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { key: Bytes::from_static(b"STATE"), + result_completion_id: 1, ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - ClearAllStateEntryMessage::default() + output + .next_decoded::() + .unwrap(), + ClearAllStateCommandMessage::default() ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"ANOTHER_STATE"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"Francesco" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"Francesco") ); assert_eq!( output.next_decoded::().unwrap(), @@ -1116,12 +1010,12 @@ mod eager { vm.sys_input().unwrap(); let h1 = vm.sys_state_get("key-0".to_owned()).unwrap(); - vm.notify_await_point(h1); - let_assert!(Ok(Some(Value::Void)) = vm.take_async_result(h1)); + vm.do_progress(vec![h1]).unwrap(); + let_assert!(Ok(Some(Value::Void)) = vm.take_notification(h1)); let h2 = vm.sys_state_get("key-0".to_owned()).unwrap(); - vm.notify_await_point(h2); - let_assert!(Ok(Some(Value::Void)) = vm.take_async_result(h2)); + vm.do_progress(vec![h2]).unwrap(); + let_assert!(Ok(Some(Value::Void)) = vm.take_notification(h2)); vm.sys_write_output(NonEmptyValue::Success(Bytes::default())) .unwrap(); @@ -1137,31 +1031,36 @@ mod eager { known_entries: 1, ..Default::default() }) - .input(InputEntryMessage::default()) + .input(InputCommandMessage::default()) .run(consecutive_get_with_empty_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"key-0"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"key-0"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static(b""))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"") ); assert_eq!( output.next_decoded::().unwrap(), @@ -1179,28 +1078,31 @@ mod eager { known_entries: 2, ..Default::default() }) - .input(InputEntryMessage::default()) - .input(GetStateEntryMessage { + .input(InputCommandMessage::default()) + .input(GetEagerStateCommandMessage { key: Bytes::from_static(b"key-0"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default(), + )), ..Default::default() }) .run(consecutive_get_with_empty_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output + .next_decoded::() + .unwrap(), + GetEagerStateCommandMessage { key: Bytes::from_static(b"key-0"), - result: Some(get_state_entry_message::Result::Empty(Empty::default())), + result: Some(get_eager_state_command_message::Result::Void( + Default::default() + )), ..Default::default() } ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static(b""))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"") ); assert_eq!( output.next_decoded::().unwrap(), @@ -1213,9 +1115,9 @@ mod eager { mod state_keys { use super::*; - use crate::service_protocol::messages::get_state_keys_entry_message::StateKeys; + use crate::service_protocol::messages::StateKeys; + use crate::tests::{input_entry_message, is_output_with_success, suspended_waiting_completion}; use googletest::prelude::*; - use prost::Message; use test_log::test; fn get_state_keys_handler(vm: &mut CoreVM) { @@ -1223,14 +1125,14 @@ mod state_keys { let h1 = vm.sys_state_get_keys().unwrap(); - vm.notify_await_point(h1); - let h1_result = vm.take_async_result(h1); - if let Err(SuspendedOrVMError::Suspended(_)) = &h1_result { + if let Err(SuspendedOrVMError::Suspended(_)) = vm.do_progress(vec![h1]) { + assert_that!( + vm.take_notification(h1), + err(pat!(SuspendedOrVMError::Suspended(_))) + ); return; } - - let output = match h1_result.unwrap().unwrap() { - Value::Failure(f) => NonEmptyValue::Failure(f), + let output = match vm.take_notification(h1).unwrap().unwrap() { Value::StateKeys(keys) => NonEmptyValue::Success(Bytes::from(keys.join(","))), _ => panic!("Unexpected variants"), }; @@ -1249,29 +1151,21 @@ mod state_keys { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(GetStateKeysEntryMessage { - result: Some(get_state_keys_entry_message::Result::Value(StateKeys { + .input(input_entry_message(b"Till")) + .input(GetEagerStateKeysCommandMessage { + value: Some(StateKeys { keys: vec![ - Bytes::from_static(b"MY-STATE"), Bytes::from_static(b"ANOTHER-STATE"), + Bytes::from_static(b"MY-STATE"), ], - })), + }), ..Default::default() }) .run(get_state_keys_handler); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"ANOTHER-STATE,MY-STATE" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"ANOTHER-STATE,MY-STATE") ); assert_eq!( output.next_decoded::().unwrap(), @@ -1279,6 +1173,7 @@ mod state_keys { ); assert_eq!(output.next(), None); } + #[test] fn new_entry() { let mut output = VMTestCase::new() @@ -1289,35 +1184,34 @@ mod state_keys { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) + .input(input_entry_message(b"Till")) .run(get_state_keys_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateKeysEntryMessage::default() + output + .next_decoded::() + .unwrap(), + GetLazyStateKeysCommandMessage { + result_completion_id: 1, + ..Default::default() + } ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); } + #[test] fn new_entry_completed_later() { - let state_keys: Bytes = StateKeys { + let state_keys = StateKeys { keys: vec![ Bytes::from_static(b"MY-STATE"), Bytes::from_static(b"ANOTHER-STATE"), ], - } - .encode_to_vec() - .into(); + }; let mut output = VMTestCase::new() .input(StartMessage { id: Bytes::from_static(b"abc"), @@ -1326,45 +1220,41 @@ mod state_keys { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(state_keys)), + .input(input_entry_message(b"Till")) + .input(GetLazyStateKeysCompletionNotificationMessage { + completion_id: 1, + state_keys: Some(state_keys.clone()), }) .run(get_state_keys_handler); assert_eq!( - output.next_decoded::().unwrap(), - GetStateKeysEntryMessage::default() - ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"ANOTHER-STATE,MY-STATE" - ))), + output + .next_decoded::() + .unwrap(), + GetLazyStateKeysCommandMessage { + result_completion_id: 1, ..Default::default() } ); + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"MY-STATE,ANOTHER-STATE") + ); assert_eq!( output.next_decoded::().unwrap(), EndMessage::default() ); assert_eq!(output.next(), None); } + #[test] fn entry_on_replay_completed_later() { - let state_keys: Bytes = StateKeys { + let state_keys = StateKeys { keys: vec![ Bytes::from_static(b"MY-STATE"), Bytes::from_static(b"ANOTHER-STATE"), ], - } - .encode_to_vec() - .into(); + }; let mut output = VMTestCase::new() .input(StartMessage { id: Bytes::from_static(b"abc"), @@ -1373,25 +1263,20 @@ mod state_keys { partial_state: true, ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), + .input(input_entry_message(b"Till")) + .input(GetLazyStateKeysCommandMessage { + result_completion_id: 1, ..Default::default() }) - .input(GetStateKeysEntryMessage::default()) - .input(CompletionMessage { - entry_index: 1, - result: Some(completion_message::Result::Value(state_keys)), + .input(GetLazyStateKeysCompletionNotificationMessage { + completion_id: 1, + state_keys: Some(state_keys.clone()), }) .run(get_state_keys_handler); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"ANOTHER-STATE,MY-STATE" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"MY-STATE,ANOTHER-STATE") ); assert_eq!( output.next_decoded::().unwrap(), @@ -1399,6 +1284,7 @@ mod state_keys { ); assert_eq!(output.next(), None); } + #[test] fn new_entry_completed_with_eager_state() { let mut output = VMTestCase::new() @@ -1408,44 +1294,36 @@ mod state_keys { known_entries: 1, partial_state: false, state_map: vec![ - StateEntry { - key: Bytes::from_static(b"ANOTHER-STATE"), - value: Bytes::from_static(b"Till"), - }, StateEntry { key: Bytes::from_static(b"MY-STATE"), value: Bytes::from_static(b"Francesco"), }, + StateEntry { + key: Bytes::from_static(b"ANOTHER-STATE"), + value: Bytes::from_static(b"Till"), + }, ], ..Default::default() }) - .input(InputEntryMessage { - value: Bytes::from_static(b"Till"), - ..Default::default() - }) + .input(input_entry_message(b"Till")) .run(get_state_keys_handler); assert_that!( - output.next_decoded::().unwrap(), - pat!(GetStateKeysEntryMessage { - result: some(pat!(get_state_keys_entry_message::Result::Value(pat!( - StateKeys { - keys: unordered_elements_are!( - eq(Bytes::from_static(b"ANOTHER-STATE")), - eq(Bytes::from_static(b"MY-STATE")) - ) - } - )))) + output + .next_decoded::() + .unwrap(), + pat!(GetEagerStateKeysCommandMessage { + value: some(pat!(StateKeys { + keys: eq(vec![ + Bytes::from_static(b"ANOTHER-STATE"), + Bytes::from_static(b"MY-STATE") + ]) + })) }) ); - assert_eq!( - output.next_decoded::().unwrap(), - OutputEntryMessage { - result: Some(output_entry_message::Result::Value(Bytes::from_static( - b"ANOTHER-STATE,MY-STATE" - ))), - ..Default::default() - } + assert_that!( + output.next_decoded::().unwrap(), + is_output_with_success(b"ANOTHER-STATE,MY-STATE") ); assert_eq!( output.next_decoded::().unwrap(), diff --git a/src/tests/suspensions.rs b/src/tests/suspensions.rs index 7d8c555..78d009d 100644 --- a/src/tests/suspensions.rs +++ b/src/tests/suspensions.rs @@ -1,13 +1,14 @@ use super::*; use crate::service_protocol::messages::{ - completion_message, AwakeableEntryMessage, CompletionMessage, EndMessage, GetStateEntryMessage, + signal_notification_message, EndMessage, GetLazyStateCommandMessage, SignalNotificationMessage, SuspensionMessage, }; +use crate::Value; use test_log::test; #[test] -fn suspension_should_be_triggered_in_notify_input_closed() { +fn trigger_suspension_with_get_state() { let mut output = VMTestCase::new() .input(start_message(1)) .input(input_entry_message(b"my-data")) @@ -17,36 +18,34 @@ fn suspension_should_be_triggered_in_notify_input_closed() { let handle = vm.sys_state_get("Personaggio".to_owned()).unwrap(); // Also take_async_result returns Ok(None) - assert_that!(vm.take_async_result(handle), ok(none())); + assert_that!(vm.take_notification(handle), ok(none())); // Let's notify_input_closed now vm.notify_input_closed(); - vm.notify_await_point(handle); assert_that!( - vm.take_async_result(handle), + vm.do_progress(vec![handle]), err(pat!(SuspendedOrVMError::Suspended(_))) ); }); // Assert output assert_eq!( - output.next_decoded::().unwrap(), - GetStateEntryMessage { + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { key: Bytes::from_static(b"Personaggio"), + result_completion_id: 1, ..Default::default() } ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![1], - } + suspended_waiting_completion(1) ); assert_eq!(output.next(), None); } #[test] -fn suspension_should_be_triggered_with_correct_entry() { +fn trigger_suspension_with_correct_awakeable() { let mut output = VMTestCase::new() .input(start_message(1)) .input(input_entry_message(b"my-data")) @@ -57,92 +56,66 @@ fn suspension_should_be_triggered_with_correct_entry() { let (_, h2) = vm.sys_awakeable().unwrap(); // Also take_async_result returns Ok(None) - assert_that!(vm.take_async_result(h2), ok(none())); + assert_that!(vm.take_notification(h2), ok(none())); // Let's notify_input_closed now - vm.notify_await_point(h2); vm.notify_input_closed(); assert_that!( - vm.take_async_result(h2), + vm.do_progress(vec![h2]), err(pat!(SuspendedOrVMError::Suspended(_))) ); }); - assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() - ); - assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() - ); - assert_eq!( + assert_that!( output.next_decoded::().unwrap(), - SuspensionMessage { - entry_indexes: vec![2], - } + pat!(SuspensionMessage { + waiting_signals: eq(vec![18]) + }) ); assert_eq!(output.next(), None); } #[test] -fn when_notify_completion_then_notify_await_point_then_notify_input_closed_then_no_suspension() { - let completion = Bytes::from_static(b"completion"); - +fn await_many_notifications() { let mut output = VMTestCase::new() .input(start_message(1)) .input(input_entry_message(b"my-data")) - .run_without_closing_input(|vm, encoder| { + .run_without_closing_input(|vm, _| { vm.sys_input().unwrap(); - let (_, _h1) = vm.sys_awakeable().unwrap(); - let (_, h2) = vm.sys_awakeable().unwrap(); - - // Also take_async_result returns Ok(None) - assert_that!(vm.take_async_result(h2), ok(none())); - - // Let's send Completion for h2 - vm.notify_input(encoder.encode(&CompletionMessage { - entry_index: 2, - result: Some(completion_message::Result::Value(completion.clone())), - })); + let (_, h1) = vm.sys_awakeable().unwrap(); + let h2 = vm.create_signal_handle("abc".into()).unwrap(); + let h3 = vm.sys_state_get("Personaggio".to_owned()).unwrap(); - // Let's await for input h2, then notify_input_closed - vm.notify_await_point(h2); + // Let's notify_input_closed now vm.notify_input_closed(); - - // This should not suspend assert_that!( - vm.take_async_result(h2), - ok(some(eq(Value::Success(completion.clone())))) + vm.do_progress(vec![h1, h2, h3]), + err(pat!(SuspendedOrVMError::Suspended(_))) ); - - vm.sys_write_output(NonEmptyValue::Success(completion.clone())) - .unwrap(); - vm.sys_end().unwrap(); }); assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() - ); - assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() + output.next_decoded::().unwrap(), + GetLazyStateCommandMessage { + key: Bytes::from_static(b"Personaggio"), + result_completion_id: 1, + ..Default::default() + } ); assert_that!( - output.next_decoded::().unwrap(), - is_output_with_success(completion) - ); - assert_eq!( - output.next_decoded::().unwrap(), - EndMessage::default() + output.next_decoded::().unwrap(), + pat!(SuspensionMessage { + waiting_completions: eq(&[1]), + waiting_signals: eq(&[17]), + waiting_named_signals: eq(&["abc".to_owned()]) + }) ); assert_eq!(output.next(), None); } #[test] -fn when_notify_await_point_then_notify_completion_then_notify_input_closed_then_no_suspension() { +fn when_notify_completion_then_notify_await_point_then_notify_input_closed_then_no_suspension() { let completion = Bytes::from_static(b"completion"); let mut output = VMTestCase::new() @@ -151,23 +124,34 @@ fn when_notify_await_point_then_notify_completion_then_notify_input_closed_then_ .run_without_closing_input(|vm, encoder| { vm.sys_input().unwrap(); - let (_, _h1) = vm.sys_awakeable().unwrap(); + let (_, h1) = vm.sys_awakeable().unwrap(); let (_, h2) = vm.sys_awakeable().unwrap(); - // Also take_async_result returns Ok(None) - assert_that!(vm.take_async_result(h2), ok(none())); + // Do progress will ask for more input + assert_that!( + vm.do_progress(vec![h1, h2]), + ok(eq(DoProgressResponse::ReadFromInput)) + ); - // Notify await point, then send completion, then close input - vm.notify_await_point(h2); - vm.notify_input(encoder.encode(&CompletionMessage { - entry_index: 2, - result: Some(completion_message::Result::Value(completion.clone())), + // Let's send Completion for h2 + vm.notify_input(encoder.encode(&SignalNotificationMessage { + signal_id: Some(signal_notification_message::SignalId::Idx(18)), + result: Some(signal_notification_message::Result::Value( + completion.clone().into(), + )), })); - vm.notify_input_closed(); // This should not suspend + vm.notify_input_closed(); assert_that!( - vm.take_async_result(h2), + vm.do_progress(vec![h1, h2]), + ok(eq(DoProgressResponse::AnyCompleted)) + ); + + // H2 should be completed and we can take it + assert!(vm.is_completed(h2)); + assert_that!( + vm.take_notification(h2), ok(some(eq(Value::Success(completion.clone())))) ); @@ -176,16 +160,8 @@ fn when_notify_await_point_then_notify_completion_then_notify_input_closed_then_ vm.sys_end().unwrap(); }); - assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() - ); - assert_eq!( - output.next_decoded::().unwrap(), - AwakeableEntryMessage::default() - ); assert_that!( - output.next_decoded::().unwrap(), + output.next_decoded::().unwrap(), is_output_with_success(completion) ); assert_eq!( diff --git a/src/vm/context.rs b/src/vm/context.rs index 9f9bb39..5f64373 100644 --- a/src/vm/context.rs +++ b/src/vm/context.rs @@ -1,13 +1,13 @@ -use crate::service_protocol::messages::{ - completion_message, CompletionParsingHint, EntryMessage, RestateMessage, - WriteableRestateMessage, +use crate::service_protocol::messages::{NamedCommandMessage, RestateMessage}; +use crate::service_protocol::{ + Encoder, MessageType, Notification, NotificationId, NotificationResult, Version, }; -use crate::service_protocol::{Encoder, MessageType, Version}; -use crate::{AsyncResultHandle, AsyncResultState, EntryRetryInfo, Error, VMOptions, Value}; +use crate::{EntryRetryInfo, NotificationHandle, VMOptions, CANCEL_NOTIFICATION_HANDLE}; use bytes::Bytes; use bytes_utils::SegmentedBuf; -use std::collections::{HashMap, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; use std::time::Duration; +use tracing::instrument; #[derive(Clone, Debug)] pub(crate) struct StartInfo { @@ -20,31 +20,56 @@ pub(crate) struct StartInfo { } pub(crate) struct Journal { - index: Option, + command_index: Option, + notification_index: Option, + completion_index: u32, + signal_index: u32, pub(crate) current_entry_ty: MessageType, pub(crate) current_entry_name: String, } impl Journal { - pub(crate) fn transition(&mut self, expected: &M) { - self.index = Some(self.index.take().map(|i| i + 1).unwrap_or(0)); + pub(crate) fn transition(&mut self, expected: &M) { + if M::ty().is_notification() { + self.notification_index = + Some(self.notification_index.take().map(|i| i + 1).unwrap_or(0)); + } else if M::ty().is_command() { + self.command_index = Some(self.command_index.take().map(|i| i + 1).unwrap_or(0)); + } self.current_entry_name = expected.name(); self.current_entry_ty = M::ty(); } - pub(crate) fn index(&self) -> i64 { - self.index.map(|u| u as i64).unwrap_or(-1) + pub(crate) fn command_index(&self) -> i64 { + self.command_index.map(|u| u as i64).unwrap_or(-1) + } + + pub(crate) fn notification_index(&self) -> i64 { + self.notification_index.map(|u| u as i64).unwrap_or(-1) + } + + pub(crate) fn next_completion_notification_id(&mut self) -> u32 { + let next = self.completion_index; + self.completion_index += 1; + next } - pub(crate) fn expect_index(&self) -> u32 { - self.index.expect("index was initialized") + pub(crate) fn next_signal_notification_id(&mut self) -> u32 { + let next = self.signal_index; + self.signal_index += 1; + next } } impl Default for Journal { fn default() -> Self { Journal { - index: None, + command_index: None, + notification_index: None, + // Clever trick for protobuf here + completion_index: 1, + // 1 to 16 are reserved! + signal_index: 17, current_entry_ty: MessageType::Start, current_entry_name: "".to_string(), } @@ -66,7 +91,7 @@ impl Output { } } - pub(crate) fn send(&mut self, msg: &M) { + pub(crate) fn send(&mut self, msg: &M) { if !self.is_closed { self.buffer.push(self.encoder.encode(msg)) } @@ -82,143 +107,168 @@ impl Output { } #[derive(Debug)] -enum UnparsedCompletionOrParsingHint { - UnparsedCompletion(completion_message::Result), - ParsingHint(CompletionParsingHint), +pub(crate) struct AsyncResultsState { + to_process: VecDeque, + ready: HashMap, + + handle_mapping: HashMap, + next_notification_handle: NotificationHandle, } -#[derive(Debug, Default)] -pub(crate) struct AsyncResultsState { - unparsed_completions_or_parsing_hints: HashMap, - ready_results: HashMap, - last_acked_entry: u32, - waiting_ack_results: VecDeque<(u32, Value)>, +impl Default for AsyncResultsState { + fn default() -> Self { + Self { + to_process: Default::default(), + ready: Default::default(), + + // First 15 are reserved for built-in signals! + handle_mapping: HashMap::from([( + CANCEL_NOTIFICATION_HANDLE, + NotificationId::SignalId(1), + )]), + next_notification_handle: NotificationHandle(17), + } + } } impl AsyncResultsState { - pub(crate) fn has_ready_result(&self, index: u32) -> bool { - self.ready_results.contains_key(&index) + #[instrument( + level = "trace", + skip_all, + fields( + restate.journal.notification.id = ?notification.id, + ), + ret + )] + pub(crate) fn enqueue(&mut self, notification: Notification) { + self.to_process.push_back(notification); } - pub(crate) fn take_ready_result(&mut self, index: u32) -> Option { - self.ready_results.remove(&index) + #[instrument( + level = "trace", + skip_all, + fields( + restate.journal.notification.id = ?notification.id, + ), + ret + )] + pub(crate) fn insert_ready(&mut self, notification: Notification) { + self.ready.insert(notification.id, notification.result); } - pub(crate) fn insert_completion_parsing_hint( + pub(crate) fn create_handle_mapping( &mut self, - index: u32, - completion_parsing_hint: CompletionParsingHint, - ) -> Result<(), Error> { - if let Some(unparsed_completion_or_parsing_hint) = - self.unparsed_completions_or_parsing_hints.remove(&index) - { - match unparsed_completion_or_parsing_hint { - UnparsedCompletionOrParsingHint::UnparsedCompletion(result) => { - self.ready_results - .insert(index, completion_parsing_hint.parse(result)?); - } - UnparsedCompletionOrParsingHint::ParsingHint(_) => { - panic!("Unexpected double call to insert_completion_parsing_hint for entry {index}") - } - } - } else { - self.unparsed_completions_or_parsing_hints.insert( - index, - UnparsedCompletionOrParsingHint::ParsingHint(completion_parsing_hint), - ); - } - Ok(()) + notification_id: NotificationId, + ) -> NotificationHandle { + let assigned_handle = self.next_notification_handle; + self.next_notification_handle.0 += 1; + self.handle_mapping.insert(assigned_handle, notification_id); + assigned_handle } - pub(crate) fn insert_unparsed_completion( - &mut self, - index: u32, - result: completion_message::Result, - ) -> Result<(), Error> { - if let Some(unparsed_completion_or_parsing_hint) = - self.unparsed_completions_or_parsing_hints.remove(&index) - { - match unparsed_completion_or_parsing_hint { - UnparsedCompletionOrParsingHint::UnparsedCompletion(_) => { - panic!("Unexpected double call to insert_unparsed_completion for entry {index}") - } - UnparsedCompletionOrParsingHint::ParsingHint(completion_parsing_hint) => { - self.ready_results - .insert(index, completion_parsing_hint.parse(result)?); - } + #[instrument(level = "trace", skip(self), ret)] + pub(crate) fn process_next_until_any_found(&mut self, ids: &HashSet) -> bool { + while let Some(notif) = self.to_process.pop_front() { + let any_found = ids.contains(¬if.id); + self.ready.insert(notif.id, notif.result); + if any_found { + return true; } - } else { - self.unparsed_completions_or_parsing_hints.insert( - index, - UnparsedCompletionOrParsingHint::UnparsedCompletion(result), - ); } - Ok(()) + false } - pub(crate) fn insert_ready_result(&mut self, index: u32, value: Value) { - self.ready_results.insert(index, value); + #[instrument( + level = "trace", + skip_all, + fields( + restate.shared_core.notification.handle = ?handle, + ), + ret + )] + pub(crate) fn is_handle_completed(&self, handle: NotificationHandle) -> bool { + self.handle_mapping + .get(&handle) + .is_some_and(|id| self.ready.contains_key(id)) } - pub(crate) fn insert_waiting_ack_result(&mut self, index: u32, value: Value) { - if index <= self.last_acked_entry { - self.ready_results.insert(index, value); - } else { - self.waiting_ack_results.push_back((index, value)); + pub(crate) fn non_deterministic_find_id(&self, id: &NotificationId) -> bool { + if self.ready.contains_key(id) { + return true; } + self.to_process.iter().any(|notif| notif.id == *id) } - pub(crate) fn notify_ack(&mut self, ack: u32) { - if ack <= self.last_acked_entry { - return; - } - self.last_acked_entry = ack; + pub(crate) fn resolve_notification_handles( + &self, + handles: Vec, + ) -> HashSet { + handles + .into_iter() + .filter_map(|h| self.handle_mapping.get(&h).cloned()) + .collect() + } - while let Some((idx, _)) = self.waiting_ack_results.front() { - if *idx > self.last_acked_entry { - return; - } - let (idx, value) = self.waiting_ack_results.pop_front().unwrap(); - self.ready_results.insert(idx, value); - } + pub(crate) fn must_resolve_notification_handle( + &self, + handle: &NotificationHandle, + ) -> NotificationId { + self.handle_mapping + .get(handle) + .expect("If there is an handle, there must be a corresponding id") + .clone() } - pub(crate) fn get_ready_results_state(&self) -> HashMap { - self.ready_results - .iter() - .map(|(idx, val)| { - ( - AsyncResultHandle(*idx), - match val { - Value::Void - | Value::Success(_) - | Value::StateKeys(_) - | Value::InvocationId(_) - | Value::CombinatorResult(_) => AsyncResultState::Success, - Value::Failure(_) => AsyncResultState::Failure, - }, - ) - }) - .collect() + #[instrument( + level = "trace", + skip_all, + fields( + restate.shared_core.notification.handle = ?handle, + ), + ret + )] + pub(crate) fn take_handle(&mut self, handle: NotificationHandle) -> Option { + let id = self.handle_mapping.get(&handle)?; + if let Some(res) = self.ready.remove(id) { + self.handle_mapping.remove(&handle); + Some(res) + } else { + None + } } } -#[derive(Debug)] -pub(crate) enum RunState { - Running(String), - NotRunning, +#[derive(Debug, Default)] +pub(crate) struct RunState { + to_execute: HashSet, + executing: HashSet, } impl RunState { - pub(crate) fn is_running(&self) -> bool { - matches!(self, RunState::Running(_)) + pub fn insert_run_to_execute(&mut self, handle: NotificationHandle) { + self.to_execute.insert(handle); } - pub(crate) fn name(&self) -> Option<&str> { - match self { - RunState::Running(n) => Some(n), - RunState::NotRunning => None, + pub fn try_execute_run( + &mut self, + any_handle: &HashSet, + ) -> Option { + if let Some(runnable) = self.to_execute.intersection(any_handle).next() { + let runnable = *runnable; + self.to_execute.remove(&runnable); + self.executing.insert(runnable); + return Some(runnable); } + None + } + + pub fn any_executing(&self, any_handle: &[NotificationHandle]) -> bool { + any_handle.iter().any(|h| self.executing.contains(h)) + } + + pub fn notify_executed(&mut self, executed: NotificationHandle) { + self.to_execute.remove(&executed); + self.executing.remove(&executed); } } @@ -281,7 +331,9 @@ impl EagerState { if self.is_partial { EagerGetStateKeys::Unknown } else { - EagerGetStateKeys::Keys(self.values.keys().cloned().collect()) + let mut keys: Vec<_> = self.values.keys().cloned().collect(); + keys.sort(); + EagerGetStateKeys::Keys(keys) } } @@ -313,6 +365,7 @@ pub(crate) struct Context { // Used by the error handler to set ErrorMessage.next_retry_delay pub(crate) next_retry_delay: Option, + #[allow(unused)] pub(crate) options: VMOptions, } @@ -327,25 +380,21 @@ impl Context { pub(crate) fn infer_entry_retry_info(&self) -> EntryRetryInfo { let start_info = self.expect_start_info(); - if self.journal.expect_index() == start_info.entries_to_replay { - // This is the first entry we try to commit after replay. - // ONLY in this case we re-use the StartInfo! - let retry_count = start_info.retry_count_since_last_stored_entry; - let retry_loop_duration = if start_info.retry_count_since_last_stored_entry == 0 { - // When the retry count is == 0, the duration_since_last_stored_entry might not be zero. - // - // In fact, in that case the duration is the interval between the previously stored entry and the time to start/resume the invocation. - // For the sake of entry retries though, we're not interested in that time elapsed, so we 0 it here for simplicity of the downstream consumer (the retry policy). - Duration::ZERO - } else { - Duration::from_millis(start_info.duration_since_last_stored_entry) - }; - EntryRetryInfo { - retry_count, - retry_loop_duration, - } + // This is the first entry we try to commit after replay. + // ONLY in this case we re-use the StartInfo! + let retry_count = start_info.retry_count_since_last_stored_entry; + let retry_loop_duration = if start_info.retry_count_since_last_stored_entry == 0 { + // When the retry count is == 0, the duration_since_last_stored_entry might not be zero. + // + // In fact, in that case the duration is the interval between the previously stored entry and the time to start/resume the invocation. + // For the sake of entry retries though, we're not interested in that time elapsed, so we 0 it here for simplicity of the downstream consumer (the retry policy). + Duration::ZERO } else { - EntryRetryInfo::default() + Duration::from_millis(start_info.duration_since_last_stored_entry) + }; + EntryRetryInfo { + retry_count, + retry_loop_duration, } } } diff --git a/src/vm/errors.rs b/src/vm/errors.rs index 39c6e7d..b62e562 100644 --- a/src/vm/errors.rs +++ b/src/vm/errors.rs @@ -95,36 +95,11 @@ pub const UNEXPECTED_ENTRY_MESSAGE: Error = Error::new_const( "Expected entry messages only when waiting replay entries", ); -pub const UNEXPECTED_NONE_RUN_RESULT: Error = Error::new_const( - codes::PROTOCOL_VIOLATION, - "Expected RunEntryMessage to contain a result", -); - -pub const EXPECTED_COMPLETION_RESULT: Error = Error::new_const( - codes::PROTOCOL_VIOLATION, - "The completion message MUST contain a result", -); - -pub const INSIDE_RUN: Error = Error::new_const( - codes::INTERNAL, - "A syscall was invoked from within a run operation", -); - -pub const INVOKED_RUN_EXIT_WITHOUT_ENTER: Error = Error::new_const( - codes::INTERNAL, - "Invoked sys_run_exit without invoking sys_run_enter before", -); - pub const INPUT_CLOSED_WHILE_WAITING_ENTRIES: Error = Error::new_const( codes::PROTOCOL_VIOLATION, "The input was closed while still waiting to receive all the `known_entries`", ); -pub const BAD_COMBINATOR_ENTRY: Error = Error::new_const( - codes::PROTOCOL_VIOLATION, - "The combinator cannot be replayed. This is most likely caused by non deterministic code.", -); - pub const EMPTY_IDEMPOTENCY_KEY: Error = Error::new_const( codes::INTERNAL, "Trying to execute an idempotent request with an empty idempotency key, this is not supported", @@ -199,16 +174,12 @@ pub struct DecodeStateKeysProst(#[from] pub(crate) prost::DecodeError); pub struct DecodeStateKeysUtf8(#[from] pub(crate) std::string::FromUtf8Error); #[derive(Debug, Clone, thiserror::Error)] -#[error("Unexpected empty value variant for state keys")] -pub struct EmptyStateKeys; - -#[derive(Debug, Clone, thiserror::Error)] -#[error("Unexpected empty variant for get call invocation id")] -pub struct EmptyGetCallInvocationId; +#[error("Unexpected empty value variant for get eager state")] +pub struct EmptyGetEagerState; #[derive(Debug, Clone, thiserror::Error)] -#[error("Cannot decode get call invocation id: {0}")] -pub struct DecodeGetCallInvocationIdUtf8(#[from] pub(crate) std::string::FromUtf8Error); +#[error("Unexpected empty value variant for state keys")] +pub struct EmptyGetEagerStateKeys; #[derive(Debug, thiserror::Error)] #[error("Feature {feature} is not supported by the negotiated protocol version '{current_version}', the minimum required version is '{minimum_required_version}'")] @@ -232,6 +203,18 @@ impl UnsupportedFeatureForNegotiatedVersion { } } +#[derive(Debug, Clone, thiserror::Error)] +#[error("Expecting entry to be either lazy or eager get state command, but was {actual:?}")] +pub struct UnexpectedGetState { + pub(crate) actual: MessageType, +} + +#[derive(Debug, Clone, thiserror::Error)] +#[error("Expecting entry to be either lazy or eager get state keys command, but was {actual:?}")] +pub struct UnexpectedGetStateKeys { + pub(crate) actual: MessageType, +} + // Conversions to VMError trait WithInvocationErrorCode { @@ -269,7 +252,8 @@ impl_error_code!(AwaitingTwoAsyncResultError, AWAITING_TWO_ASYNC_RESULTS); impl_error_code!(BadEagerStateKeyError, INTERNAL); impl_error_code!(DecodeStateKeysProst, PROTOCOL_VIOLATION); impl_error_code!(DecodeStateKeysUtf8, PROTOCOL_VIOLATION); -impl_error_code!(EmptyStateKeys, PROTOCOL_VIOLATION); -impl_error_code!(EmptyGetCallInvocationId, PROTOCOL_VIOLATION); -impl_error_code!(DecodeGetCallInvocationIdUtf8, PROTOCOL_VIOLATION); +impl_error_code!(EmptyGetEagerState, PROTOCOL_VIOLATION); +impl_error_code!(EmptyGetEagerStateKeys, PROTOCOL_VIOLATION); impl_error_code!(UnsupportedFeatureForNegotiatedVersion, UNSUPPORTED_FEATURE); +impl_error_code!(UnexpectedGetState, JOURNAL_MISMATCH); +impl_error_code!(UnexpectedGetStateKeys, JOURNAL_MISMATCH); diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 54665e1..606376f 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,28 +1,23 @@ use crate::headers::HeaderMap; -use crate::service_protocol::messages::get_state_keys_entry_message::StateKeys; use crate::service_protocol::messages::{ - attach_invocation_entry_message, cancel_invocation_entry_message, - complete_awakeable_entry_message, complete_promise_entry_message, - get_invocation_output_entry_message, get_state_entry_message, get_state_keys_entry_message, - output_entry_message, AttachInvocationEntryMessage, AwakeableEntryMessage, CallEntryMessage, - CancelInvocationEntryMessage, ClearAllStateEntryMessage, ClearStateEntryMessage, - CompleteAwakeableEntryMessage, CompletePromiseEntryMessage, Empty, - GetCallInvocationIdEntryMessage, GetInvocationOutputEntryMessage, GetPromiseEntryMessage, - GetStateEntryMessage, GetStateKeysEntryMessage, IdempotentRequestTarget, - OneWayCallEntryMessage, OutputEntryMessage, PeekPromiseEntryMessage, SetStateEntryMessage, - SleepEntryMessage, WorkflowTarget, + attach_invocation_command_message, complete_awakeable_command_message, + complete_promise_command_message, get_invocation_output_command_message, + output_command_message, send_signal_command_message, AttachInvocationCommandMessage, + CallCommandMessage, ClearAllStateCommandMessage, ClearStateCommandMessage, + CompleteAwakeableCommandMessage, CompletePromiseCommandMessage, + GetInvocationOutputCommandMessage, GetPromiseCommandMessage, IdempotentRequestTarget, + OneWayCallCommandMessage, OutputCommandMessage, PeekPromiseCommandMessage, + SendSignalCommandMessage, SetStateCommandMessage, SleepCommandMessage, WorkflowTarget, }; -use crate::service_protocol::{Decoder, RawMessage, Version}; -use crate::vm::context::{EagerGetState, EagerGetStateKeys}; +use crate::service_protocol::{Decoder, NotificationId, RawMessage, Version, CANCEL_SIGNAL_ID}; use crate::vm::errors::{ UnexpectedStateError, UnsupportedFeatureForNegotiatedVersion, EMPTY_IDEMPOTENCY_KEY, }; use crate::vm::transitions::*; use crate::{ - AsyncResultCombinator, AsyncResultHandle, AttachInvocationTarget, CancelInvocationTarget, - Error, GetInvocationIdTarget, Header, Input, NonEmptyValue, ResponseHead, RetryPolicy, - RunEnterResult, RunExitResult, SendHandle, SuspendedOrVMError, TakeOutputResult, Target, - VMOptions, VMResult, Value, + AttachInvocationTarget, CallHandle, DoProgressResponse, Error, Header, Input, NonEmptyValue, + NotificationHandle, ResponseHead, RetryPolicy, RunExitResult, SendHandle, SuspendedOrVMError, + TakeOutputResult, Target, VMOptions, VMResult, Value, }; use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}; use base64::{alphabet, Engine}; @@ -40,25 +35,24 @@ mod context; pub(crate) mod errors; mod transitions; -pub(crate) use transitions::AsyncResultAccessTrackerInner; - const CONTENT_TYPE: &str = "content-type"; #[derive(Debug, IntoStaticStr)] pub(crate) enum State { WaitingStart, WaitingReplayEntries { + received_entries: u32, entries: VecDeque, async_results: AsyncResultsState, }, Replaying { - current_await_point: Option, entries: VecDeque, + run_state: RunState, async_results: AsyncResultsState, }, Processing { + processing_first_entry: bool, run_state: RunState, - current_await_point: Option, async_results: AsyncResultsState, }, Ended, @@ -126,7 +120,11 @@ impl fmt::Debug for CoreVM { Err(_) => s.field("last_transition", &"Errored"), }; - s.field("execution_index", &self.context.journal.index()) + s.field("command_index", &self.context.journal.command_index()) + .field( + "notification_index", + &self.context.journal.notification_index(), + ) .finish() } } @@ -192,7 +190,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn get_response_head(&self) -> ResponseHead { @@ -209,7 +211,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn notify_input(&mut self, buffer: Bytes) { @@ -242,7 +248,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn notify_input_closed(&mut self) { @@ -253,7 +263,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn notify_error(&mut self, error: Error, next_retry_delay: Option) { @@ -266,7 +280,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn take_output(&mut self) -> TakeOutputResult { @@ -287,7 +305,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn is_ready_to_execute(&self) -> Result { @@ -302,24 +324,59 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn notify_await_point(&mut self, AsyncResultHandle(await_point): AsyncResultHandle) { - let _ = self.do_transition(NotifyAwaitPoint(await_point)); + fn is_completed(&self, handle: NotificationHandle) -> bool { + match &self.last_transition { + Ok(State::Replaying { async_results, .. }) + | Ok(State::Processing { async_results, .. }) => { + async_results.is_handle_completed(handle) + } + _ => false, + } + } + + #[instrument( + level = "trace", + skip(self), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), + ret + )] + fn do_progress( + &mut self, + any_handle: Vec, + ) -> Result { + match self.do_transition(DoProgress(any_handle)) { + Ok(Ok(do_progress_response)) => Ok(do_progress_response), + Ok(Err(suspended)) => Err(SuspendedOrVMError::Suspended(suspended)), + Err(e) => Err(SuspendedOrVMError::VM(e)), + } } #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn take_async_result( + fn take_notification( &mut self, - handle: AsyncResultHandle, + handle: NotificationHandle, ) -> Result, SuspendedOrVMError> { - match self.do_transition(TakeAsyncResult(handle.0)) { + match self.do_transition(TakeNotification(handle)) { Ok(Ok(opt_value)) => Ok(opt_value), Ok(Err(suspended)) => Err(SuspendedOrVMError::Suspended(suspended)), Err(e) => Err(SuspendedOrVMError::VM(e)), @@ -329,7 +386,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_input(&mut self) -> Result { @@ -339,55 +400,41 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_state_get(&mut self, key: String) -> Result { + fn sys_state_get(&mut self, key: String) -> Result { invocation_debug_logs!(self, "Executing 'Get state {key}'"); - let result = match self.context.eager_state.get(&key) { - EagerGetState::Unknown => None, - EagerGetState::Empty => Some(get_state_entry_message::Result::Empty(Empty::default())), - EagerGetState::Value(v) => Some(get_state_entry_message::Result::Value(v)), - }; - self.do_transition(SysCompletableEntry( - "SysStateGet", - GetStateEntryMessage { - key: Bytes::from(key), - result, - ..Default::default() - }, - )) + self.do_transition(SysStateGet(key)) } #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_state_get_keys(&mut self) -> VMResult { + fn sys_state_get_keys(&mut self) -> VMResult { invocation_debug_logs!(self, "Executing 'Get state keys'"); - let result = match self.context.eager_state.get_keys() { - EagerGetStateKeys::Unknown => None, - EagerGetStateKeys::Keys(keys) => { - Some(get_state_keys_entry_message::Result::Value(StateKeys { - keys: keys.into_iter().map(Bytes::from).collect(), - })) - } - }; - self.do_transition(SysCompletableEntry( - "SysStateGetKeys", - GetStateKeysEntryMessage { - result, - ..Default::default() - }, - )) + self.do_transition(SysStateGetKeys) } #[instrument( level = "trace", skip(self, value), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_state_set(&mut self, key: String, value: Bytes) -> Result<(), Error> { @@ -395,10 +442,10 @@ impl super::VM for CoreVM { self.context.eager_state.set(key.clone(), value.clone()); self.do_transition(SysNonCompletableEntry( "SysStateSet", - SetStateEntryMessage { + SetStateCommandMessage { key: Bytes::from(key.into_bytes()), - value, - ..SetStateEntryMessage::default() + value: Some(value.into()), + ..SetStateCommandMessage::default() }, )) } @@ -406,7 +453,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_state_clear(&mut self, key: String) -> Result<(), Error> { @@ -414,9 +465,9 @@ impl super::VM for CoreVM { self.context.eager_state.clear(key.clone()); self.do_transition(SysNonCompletableEntry( "SysStateClear", - ClearStateEntryMessage { + ClearStateCommandMessage { key: Bytes::from(key.into_bytes()), - ..ClearStateEntryMessage::default() + ..ClearStateCommandMessage::default() }, )) } @@ -424,7 +475,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_state_clear_all(&mut self) -> Result<(), Error> { @@ -432,21 +487,25 @@ impl super::VM for CoreVM { self.context.eager_state.clear_all(); self.do_transition(SysNonCompletableEntry( "SysStateClearAll", - ClearAllStateEntryMessage::default(), + ClearAllStateCommandMessage::default(), )) } #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_sleep( &mut self, wake_up_time_since_unix_epoch: Duration, now_since_unix_epoch: Option, - ) -> VMResult { + ) -> VMResult { if self.is_processing() { if let Some(now_since_unix_epoch) = now_since_unix_epoch { debug!( @@ -458,23 +517,31 @@ impl super::VM for CoreVM { } } - self.do_transition(SysCompletableEntry( + let completion_id = self.context.journal.next_completion_notification_id(); + + self.do_transition(SysSimpleCompletableEntry( "SysSleep", - SleepEntryMessage { + SleepCommandMessage { wake_up_time: u64::try_from(wake_up_time_since_unix_epoch.as_millis()) .expect("millis since Unix epoch should fit in u64"), + result_completion_id: completion_id, ..Default::default() }, + completion_id, )) } #[instrument( level = "trace", skip(self, input), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_call(&mut self, target: Target, input: Bytes) -> VMResult { + fn sys_call(&mut self, target: Target, input: Bytes) -> VMResult { invocation_debug_logs!( self, "Executing 'Call {}/{}'", @@ -491,9 +558,14 @@ impl super::VM for CoreVM { unreachable!(); } } - self.do_transition(SysCompletableEntry( + + let call_invocation_id_completion_id = + self.context.journal.next_completion_notification_id(); + let result_completion_id = self.context.journal.next_completion_notification_id(); + + let handles = self.do_transition(SysCompletableEntryWithMultipleCompletions( "SysCall", - CallEntryMessage { + CallCommandMessage { service_name: target.service, handler_name: target.handler, key: target.key.unwrap_or_default(), @@ -504,15 +576,27 @@ impl super::VM for CoreVM { .map(crate::service_protocol::messages::Header::from) .collect(), parameter: input, + invocation_id_notification_idx: call_invocation_id_completion_id, + result_completion_id, ..Default::default() }, - )) + vec![call_invocation_id_completion_id, result_completion_id], + ))?; + + Ok(CallHandle { + invocation_id_notification_handle: handles[0], + call_notification_handle: handles[1], + }) } #[instrument( level = "trace", skip(self, input), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_send( @@ -537,9 +621,11 @@ impl super::VM for CoreVM { unreachable!(); } } - self.do_transition(SysNonCompletableEntry( + let call_invocation_id_completion_id = + self.context.journal.next_completion_notification_id(); + let invocation_id_notification_handle = self.do_transition(SysSimpleCompletableEntry( "SysOneWayCall", - OneWayCallEntryMessage { + OneWayCallCommandMessage { service_name: target.service, handler_name: target.handler, key: target.key.unwrap_or_default(), @@ -556,51 +642,65 @@ impl super::VM for CoreVM { .expect("millis since Unix epoch should fit in u64") }) .unwrap_or_default(), + invocation_id_notification_idx: call_invocation_id_completion_id, ..Default::default() }, - )) - .map(|_| SendHandle(self.context.journal.expect_index())) + call_invocation_id_completion_id, + ))?; + + Ok(SendHandle { + invocation_id_notification_handle, + }) } #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_awakeable(&mut self) -> VMResult<(String, AsyncResultHandle)> { + fn sys_awakeable(&mut self) -> VMResult<(String, NotificationHandle)> { invocation_debug_logs!(self, "Executing 'Create awakeable'"); - self.do_transition(SysCompletableEntry( + + let signal_id = self.context.journal.next_signal_notification_id(); + + let handle = self.do_transition(CreateSignalHandle( "SysAwakeable", - AwakeableEntryMessage::default(), + NotificationId::SignalId(signal_id), + ))?; + + Ok(( + awakeable_id_str(&self.context.expect_start_info().id, signal_id), + handle, )) - .map(|h| { - ( - awakeable_id( - &self.context.expect_start_info().id, - self.context.journal.expect_index(), - ), - h, - ) - }) } #[instrument( level = "trace", skip(self, value), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_complete_awakeable(&mut self, id: String, value: NonEmptyValue) -> VMResult<()> { invocation_debug_logs!(self, "Executing 'Complete awakeable {id}'"); self.do_transition(SysNonCompletableEntry( "SysCompleteAwakeable", - CompleteAwakeableEntryMessage { - id, + CompleteAwakeableCommandMessage { + awakeable_id: id, result: Some(match value { - NonEmptyValue::Success(s) => complete_awakeable_entry_message::Result::Value(s), + NonEmptyValue::Success(s) => { + complete_awakeable_command_message::Result::Value(s.into()) + } NonEmptyValue::Failure(f) => { - complete_awakeable_entry_message::Result::Failure(f.into()) + complete_awakeable_command_message::Result::Failure(f.into()) } }), ..Default::default() @@ -611,160 +711,217 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), + ret + )] + fn create_signal_handle(&mut self, signal_name: String) -> VMResult { + invocation_debug_logs!(self, "Executing 'Create named signal'"); + + self.do_transition(CreateSignalHandle( + "SysCreateNamedSignal", + NotificationId::SignalName(signal_name), + )) + } + + #[instrument( + level = "trace", + skip(self, value), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_get_promise(&mut self, key: String) -> VMResult { + fn sys_complete_signal( + &mut self, + target_invocation_id: String, + signal_name: String, + value: NonEmptyValue, + ) -> VMResult<()> { + invocation_debug_logs!(self, "Executing 'Complete named signal {signal_name}'"); + self.do_transition(SysNonCompletableEntry( + "SysCompleteAwakeable", + SendSignalCommandMessage { + target_invocation_id, + signal_id: Some(send_signal_command_message::SignalId::Name(signal_name)), + result: Some(match value { + NonEmptyValue::Success(s) => { + send_signal_command_message::Result::Value(s.into()) + } + NonEmptyValue::Failure(f) => { + send_signal_command_message::Result::Failure(f.into()) + } + }), + ..Default::default() + }, + )) + } + + #[instrument( + level = "trace", + skip(self), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), + ret + )] + fn sys_get_promise(&mut self, key: String) -> VMResult { invocation_debug_logs!(self, "Executing 'Await promise {key}'"); - self.do_transition(SysCompletableEntry( + + let result_completion_id = self.context.journal.next_completion_notification_id(); + self.do_transition(SysSimpleCompletableEntry( "SysGetPromise", - GetPromiseEntryMessage { + GetPromiseCommandMessage { key, + result_completion_id, ..Default::default() }, + result_completion_id, )) } #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_peek_promise(&mut self, key: String) -> VMResult { + fn sys_peek_promise(&mut self, key: String) -> VMResult { invocation_debug_logs!(self, "Executing 'Peek promise {key}'"); - self.do_transition(SysCompletableEntry( + + let result_completion_id = self.context.journal.next_completion_notification_id(); + self.do_transition(SysSimpleCompletableEntry( "SysPeekPromise", - PeekPromiseEntryMessage { + PeekPromiseCommandMessage { key, + result_completion_id, ..Default::default() }, + result_completion_id, )) } #[instrument( level = "trace", skip(self, value), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_complete_promise( &mut self, key: String, value: NonEmptyValue, - ) -> VMResult { + ) -> VMResult { invocation_debug_logs!(self, "Executing 'Complete promise {key}'"); - self.do_transition(SysCompletableEntry( + + let result_completion_id = self.context.journal.next_completion_notification_id(); + self.do_transition(SysSimpleCompletableEntry( "SysCompletePromise", - CompletePromiseEntryMessage { + CompletePromiseCommandMessage { key, completion: Some(match value { NonEmptyValue::Success(s) => { - complete_promise_entry_message::Completion::CompletionValue(s) + complete_promise_command_message::Completion::CompletionValue(s.into()) } NonEmptyValue::Failure(f) => { - complete_promise_entry_message::Completion::CompletionFailure(f.into()) + complete_promise_command_message::Completion::CompletionFailure(f.into()) } }), + result_completion_id, ..Default::default() }, + result_completion_id, )) } #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_run_enter(&mut self, name: String) -> Result { - self.do_transition(SysRunEnter(name)) + fn sys_run(&mut self, name: String) -> VMResult { + self.do_transition(SysRun(name)) } #[instrument( level = "trace", skip(self, value, retry_policy), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_run_exit( + fn propose_run_completion( &mut self, + notification_handle: NotificationHandle, value: RunExitResult, retry_policy: RetryPolicy, - ) -> Result { + ) -> VMResult<()> { if enabled!(Level::DEBUG) { - let name = if let Ok(State::Processing { run_state, .. }) = &self.last_transition { - run_state.name() - } else { - None - } - .unwrap_or_default(); match &value { RunExitResult::Success(_) => { - invocation_debug_logs!(self, "Journaling 'run' success result named '{name}'"); + invocation_debug_logs!(self, "Journaling 'run' success result"); } RunExitResult::TerminalFailure(_) => { - invocation_debug_logs!( - self, - "Journaling 'run' terminal failure result named '{name}'" - ); + invocation_debug_logs!(self, "Journaling 'run' terminal failure result"); } RunExitResult::RetryableFailure { .. } => { - invocation_debug_logs!(self, "Propagating 'run' failure named '{name}'"); + invocation_debug_logs!(self, "Propagating 'run' failure"); } } } - self.do_transition(SysRunExit(value, retry_policy)) - } - - #[instrument( - level = "trace", - skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), - ret - )] - fn sys_get_call_invocation_id( - &mut self, - target: GetInvocationIdTarget, - ) -> VMResult { - invocation_debug_logs!(self, "Executing 'Get invocation id'"); - self.verify_feature_support("get call invocation id", Version::V3)?; - self.do_transition(SysCompletableEntry( - "SysGetCallInvocationId", - GetCallInvocationIdEntryMessage { - call_entry_index: match target { - GetInvocationIdTarget::CallEntry(h) => h.0, - GetInvocationIdTarget::SendEntry(h) => h.0, - }, - ..Default::default() - }, + self.do_transition(ProposeRunCompletion( + notification_handle, + value, + retry_policy, )) } #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_cancel_invocation(&mut self, target: CancelInvocationTarget) -> VMResult<()> { - invocation_debug_logs!(self, "Executing 'Cancel invocation'"); + fn sys_cancel_invocation(&mut self, target_invocation_id: String) -> VMResult<()> { + invocation_debug_logs!( + self, + "Executing 'Cancel invocation' of {target_invocation_id}" + ); self.verify_feature_support("cancel invocation", Version::V3)?; self.do_transition(SysNonCompletableEntry( "SysCancelInvocation", - CancelInvocationEntryMessage { - target: Some(match target { - CancelInvocationTarget::InvocationId(id) => { - cancel_invocation_entry_message::Target::InvocationId(id) - } - CancelInvocationTarget::CallEntry(handle) => { - cancel_invocation_entry_message::Target::CallEntryIndex(handle.0) - } - CancelInvocationTarget::SendEntry(handle) => { - cancel_invocation_entry_message::Target::CallEntryIndex(handle.0) - } - }), + SendSignalCommandMessage { + target_invocation_id, + signal_id: Some(send_signal_command_message::SignalId::Idx(CANCEL_SIGNAL_ID)), + result: Some(send_signal_command_message::Result::Void(Default::default())), ..Default::default() }, )) @@ -773,27 +930,30 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_attach_invocation(&mut self, target: AttachInvocationTarget) -> VMResult<()> { + fn sys_attach_invocation( + &mut self, + target: AttachInvocationTarget, + ) -> VMResult { invocation_debug_logs!(self, "Executing 'Attach invocation'"); self.verify_feature_support("attach invocation", Version::V3)?; - self.do_transition(SysNonCompletableEntry( + + let result_completion_id = self.context.journal.next_completion_notification_id(); + self.do_transition(SysSimpleCompletableEntry( "SysAttachInvocation", - AttachInvocationEntryMessage { + AttachInvocationCommandMessage { target: Some(match target { AttachInvocationTarget::InvocationId(id) => { - attach_invocation_entry_message::Target::InvocationId(id) - } - AttachInvocationTarget::CallEntry(handle) => { - attach_invocation_entry_message::Target::CallEntryIndex(handle.0) - } - AttachInvocationTarget::SendEntry(handle) => { - attach_invocation_entry_message::Target::CallEntryIndex(handle.0) + attach_invocation_command_message::Target::InvocationId(id) } AttachInvocationTarget::WorkflowId { name, key } => { - attach_invocation_entry_message::Target::WorkflowTarget(WorkflowTarget { + attach_invocation_command_message::Target::WorkflowTarget(WorkflowTarget { workflow_name: name, workflow_key: key, }) @@ -803,7 +963,7 @@ impl super::VM for CoreVM { service_key, handler_name, idempotency_key, - } => attach_invocation_entry_message::Target::IdempotentRequestTarget( + } => attach_invocation_command_message::Target::IdempotentRequestTarget( IdempotentRequestTarget { service_name, service_key, @@ -812,35 +972,40 @@ impl super::VM for CoreVM { }, ), }), + result_completion_id, ..Default::default() }, + result_completion_id, )) } #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] - fn sys_get_invocation_output(&mut self, target: AttachInvocationTarget) -> VMResult<()> { + fn sys_get_invocation_output( + &mut self, + target: AttachInvocationTarget, + ) -> VMResult { invocation_debug_logs!(self, "Executing 'Get invocation output'"); self.verify_feature_support("get invocation output", Version::V3)?; - self.do_transition(SysNonCompletableEntry( + + let result_completion_id = self.context.journal.next_completion_notification_id(); + self.do_transition(SysSimpleCompletableEntry( "SysGetInvocationOutput", - GetInvocationOutputEntryMessage { + GetInvocationOutputCommandMessage { target: Some(match target { AttachInvocationTarget::InvocationId(id) => { - get_invocation_output_entry_message::Target::InvocationId(id) - } - AttachInvocationTarget::CallEntry(handle) => { - get_invocation_output_entry_message::Target::CallEntryIndex(handle.0) - } - AttachInvocationTarget::SendEntry(handle) => { - get_invocation_output_entry_message::Target::CallEntryIndex(handle.0) + get_invocation_output_command_message::Target::InvocationId(id) } AttachInvocationTarget::WorkflowId { name, key } => { - get_invocation_output_entry_message::Target::WorkflowTarget( + get_invocation_output_command_message::Target::WorkflowTarget( WorkflowTarget { workflow_name: name, workflow_key: key, @@ -852,7 +1017,7 @@ impl super::VM for CoreVM { service_key, handler_name, idempotency_key, - } => get_invocation_output_entry_message::Target::IdempotentRequestTarget( + } => get_invocation_output_command_message::Target::IdempotentRequestTarget( IdempotentRequestTarget { service_name, service_key, @@ -861,15 +1026,21 @@ impl super::VM for CoreVM { }, ), }), + result_completion_id, ..Default::default() }, + result_completion_id, )) } #[instrument( level = "trace", skip(self, value), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_write_output(&mut self, value: NonEmptyValue) -> Result<(), Error> { @@ -883,12 +1054,12 @@ impl super::VM for CoreVM { } self.do_transition(SysNonCompletableEntry( "SysWriteOutput", - OutputEntryMessage { + OutputCommandMessage { result: Some(match value { - NonEmptyValue::Success(b) => output_entry_message::Result::Value(b), - NonEmptyValue::Failure(f) => output_entry_message::Result::Failure(f.into()), + NonEmptyValue::Success(b) => output_command_message::Result::Value(b.into()), + NonEmptyValue::Failure(f) => output_command_message::Result::Failure(f.into()), }), - ..OutputEntryMessage::default() + ..OutputCommandMessage::default() }, )) } @@ -896,7 +1067,11 @@ impl super::VM for CoreVM { #[instrument( level = "trace", skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), + fields( + restate.invocation.id = self.debug_invocation_id(), + restate.journal.command_index = self.context.journal.command_index(), + restate.protocol.version = %self.version + ), ret )] fn sys_end(&mut self) -> Result<(), Error> { @@ -907,29 +1082,6 @@ impl super::VM for CoreVM { fn is_processing(&self) -> bool { matches!(&self.last_transition, Ok(State::Processing { .. })) } - - fn is_inside_run(&self) -> bool { - matches!( - &self.last_transition, - Ok(State::Processing { - run_state: RunState::Running(_), - .. - }) - ) - } - - #[instrument( - level = "trace", - skip(self), - fields(restate.invocation.id = self.debug_invocation_id(), restate.journal.index = self.context.journal.index(), restate.protocol.version = %self.version), - ret - )] - fn sys_try_complete_combinator( - &mut self, - combinator: impl AsyncResultCombinator + fmt::Debug, - ) -> VMResult> { - self.do_transition(SysTryCompleteCombinator(combinator)) - } } const INDIFFERENT_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new() @@ -937,9 +1089,11 @@ const INDIFFERENT_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new() .with_encode_padding(false); const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, INDIFFERENT_PAD); -fn awakeable_id(id: &[u8], entry_index: u32) -> String { +const AWAKEABLE_PREFIX: &str = "sign_1"; + +fn awakeable_id_str(id: &[u8], completion_index: u32) -> String { let mut input_buf = BytesMut::with_capacity(id.len() + size_of::()); input_buf.put_slice(id); - input_buf.put_u32(entry_index); - format!("prom_1{}", URL_SAFE.encode(input_buf.freeze())) + input_buf.put_u32(completion_index); + format!("{AWAKEABLE_PREFIX}{}", URL_SAFE.encode(input_buf.freeze())) } diff --git a/src/vm/transitions/async_results.rs b/src/vm/transitions/async_results.rs index 5777d8e..eeafdcb 100644 --- a/src/vm/transitions/async_results.rs +++ b/src/vm/transitions/async_results.rs @@ -1,120 +1,132 @@ use crate::vm::context::Context; -use crate::vm::errors::{ - AwaitingTwoAsyncResultError, UnexpectedStateError, INPUT_CLOSED_WHILE_WAITING_ENTRIES, -}; +use crate::vm::errors::UnexpectedStateError; use crate::vm::transitions::{HitSuspensionPoint, Transition, TransitionAndReturn}; use crate::vm::State; -use crate::{Error, SuspendedError, Value}; -use tracing::warn; +use crate::{DoProgressResponse, Error, NotificationHandle, SuspendedError, Value}; +use tracing::trace; -pub(crate) struct NotifyInputClosed; +pub(crate) struct DoProgress(pub(crate) Vec); -impl Transition for State { - fn transition(self, context: &mut Context, _: NotifyInputClosed) -> Result { - match self { - State::Replaying { - current_await_point: Some(await_point), - ref async_results, - .. - } - | State::Processing { - current_await_point: Some(await_point), - ref async_results, - .. - } if !async_results.has_ready_result(await_point) => { - self.transition(context, HitSuspensionPoint(await_point)) - } - State::WaitingStart | State::WaitingReplayEntries { .. } => { - Err(INPUT_CLOSED_WHILE_WAITING_ENTRIES) - } - _ => Ok(self), - } - } -} +impl TransitionAndReturn for State { + type Output = Result; -pub(crate) struct NotifyAwaitPoint(pub(crate) u32); - -impl Transition for State { - fn transition( + fn transition_and_return( mut self, context: &mut Context, - NotifyAwaitPoint(await_point): NotifyAwaitPoint, - ) -> Result { + DoProgress(awaiting_on): DoProgress, + ) -> Result<(Self, Self::Output), Error> { match self { State::Replaying { - ref mut current_await_point, - ref async_results, + ref mut async_results, .. + } => { + // Check first if any was completed already + if awaiting_on + .iter() + .any(|h| async_results.is_handle_completed(*h)) + { + // We're good, let's give back control to user code + return Ok((self, Ok(DoProgressResponse::AnyCompleted))); + } + + let notification_ids = async_results.resolve_notification_handles(awaiting_on); + if notification_ids.is_empty() { + return Ok((self, Ok(DoProgressResponse::AnyCompleted))); + } + + // Let's try to find it in the notifications we already have + if async_results.process_next_until_any_found(¬ification_ids) { + // We're good, let's give back control to user code + return Ok((self, Ok(DoProgressResponse::AnyCompleted))); + } + + // Check suspension condition + if context.input_is_closed { + let state = self.transition(context, HitSuspensionPoint(notification_ids))?; + return Ok((state, Err(SuspendedError))); + }; + + // Nothing else can be done, we need more input + Ok((self, Ok(DoProgressResponse::ReadFromInput))) } - | State::Processing { - ref mut current_await_point, - ref async_results, + State::Processing { + ref mut async_results, + ref mut run_state, .. } => { - if let Some(previous) = current_await_point { - if *previous != await_point { - if context.options.fail_on_wait_concurrent_async_result { - return Err(AwaitingTwoAsyncResultError { - previous: *previous, - current: await_point, - } - .into()); - } else { - warn!( - "{}", - AwaitingTwoAsyncResultError { - previous: *previous, - current: await_point, - } - ) - } - } + // Check first if any was completed already + if awaiting_on + .iter() + .any(|h| async_results.is_handle_completed(*h)) + { + // We're good, let's give back control to user code + return Ok((self, Ok(DoProgressResponse::AnyCompleted))); + } + + let notification_ids = + async_results.resolve_notification_handles(awaiting_on.clone()); + if notification_ids.is_empty() { + trace!("Could not resolve any of the {awaiting_on:?} handles"); + return Ok((self, Ok(DoProgressResponse::AnyCompleted))); + } + + // Let's try to find it in the notifications we already have + if async_results.process_next_until_any_found(¬ification_ids) { + // We're good, let's give back control to user code + return Ok((self, Ok(DoProgressResponse::AnyCompleted))); } - if context.input_is_closed && !async_results.has_ready_result(await_point) { - return self.transition(context, HitSuspensionPoint(await_point)); + + // We couldn't find any notification for the given ids, let's check if there's some run to execute + if let Some(run_to_execute) = + run_state.try_execute_run(&awaiting_on.iter().cloned().collect()) + { + return Ok((self, Ok(DoProgressResponse::ExecuteRun(run_to_execute)))); + } + + // Check suspension condition + if context.input_is_closed { + // Maybe something is executing and we're awaiting it to complete, + // in this case we don't suspend yet! + if run_state.any_executing(&awaiting_on) { + return Ok((self, Ok(DoProgressResponse::WaitingPendingRun))); + } + + let state = self.transition(context, HitSuspensionPoint(notification_ids))?; + return Ok((state, Err(SuspendedError))); }; - *current_await_point = Some(await_point); + // Nothing else can be done, we need more input + Ok((self, Ok(DoProgressResponse::ReadFromInput))) } - s => return Err(UnexpectedStateError::new(s.into(), "NotifyAwaitPoint").into()), - }; - - Ok(self) + s => Err(UnexpectedStateError::new(s.into(), "DoProgress").into()), + } } } -pub(crate) struct TakeAsyncResult(pub(crate) u32); +pub(crate) struct TakeNotification(pub(crate) NotificationHandle); -impl TransitionAndReturn for State { +impl TransitionAndReturn for State { type Output = Result, SuspendedError>; fn transition_and_return( mut self, _: &mut Context, - TakeAsyncResult(async_result): TakeAsyncResult, + TakeNotification(handle): TakeNotification, ) -> Result<(Self, Self::Output), Error> { match self { State::Processing { - ref mut current_await_point, ref mut async_results, .. } | State::Replaying { - ref mut current_await_point, ref mut async_results, .. } => { - let opt = async_results.take_ready_result(async_result); - - // Reset current await point if matches - if opt.is_some() && current_await_point.is_some_and(|i| i == async_result) { - *current_await_point = None; - } - - Ok((self, Ok(opt))) + let opt = async_results.take_handle(handle); + Ok((self, Ok(opt.map(Into::into)))) } State::Suspended => Ok((self, Err(SuspendedError))), - s => Err(UnexpectedStateError::new(s.into(), "TakeAsyncResult").into()), + s => Err(UnexpectedStateError::new(s.into(), "TakeNotification").into()), } } } diff --git a/src/vm/transitions/combinators.rs b/src/vm/transitions/combinators.rs deleted file mode 100644 index ac6676a..0000000 --- a/src/vm/transitions/combinators.rs +++ /dev/null @@ -1,204 +0,0 @@ -use crate::service_protocol::messages::{CombinatorEntryMessage, SuspensionMessage}; -use crate::vm::context::Context; -use crate::vm::errors::{UnexpectedStateError, BAD_COMBINATOR_ENTRY}; -use crate::vm::transitions::{PopJournalEntry, TransitionAndReturn}; -use crate::vm::State; -use crate::{ - AsyncResultAccessTracker, AsyncResultCombinator, AsyncResultHandle, AsyncResultState, Error, - Value, -}; -use std::collections::HashMap; -use std::iter::Peekable; -use std::vec::IntoIter; - -pub(crate) enum AsyncResultAccessTrackerInner { - Processing { - known_results: HashMap, - tracked_access_to_completed_results: Vec, - tracked_access_to_uncompleted_results: Vec, - }, - Replaying { - replay_combinators: Peekable>, - }, -} - -impl AsyncResultAccessTrackerInner { - pub fn get_state(&mut self, handle: AsyncResultHandle) -> AsyncResultState { - match self { - AsyncResultAccessTrackerInner::Processing { - known_results, - tracked_access_to_completed_results, - tracked_access_to_uncompleted_results, - } => { - // Record if a known result is available - if let Some(res) = known_results.get(&handle) { - tracked_access_to_completed_results.push(handle); - *res - } else { - tracked_access_to_uncompleted_results.push(handle); - AsyncResultState::NotReady - } - } - AsyncResultAccessTrackerInner::Replaying { - replay_combinators: replay_status, - } => { - if let Some((_, result)) = - replay_status.next_if(|(peeked_handle, _)| *peeked_handle == handle) - { - result - } else { - // It's a not completed handle! - AsyncResultState::NotReady - } - } - } - } -} - -pub(crate) struct SysTryCompleteCombinator(pub(crate) C); - -impl TransitionAndReturn> for State -where - C: AsyncResultCombinator, -{ - type Output = Option; - - fn transition_and_return( - mut self, - context: &mut Context, - SysTryCompleteCombinator(combinator): SysTryCompleteCombinator, - ) -> Result<(Self, Self::Output), Error> { - self.check_side_effect_guard()?; - match self { - State::Processing { - ref mut async_results, - .. - } => { - // Try complete the combinator - let mut async_result_tracker = - AsyncResultAccessTracker(AsyncResultAccessTrackerInner::Processing { - known_results: async_results.get_ready_results_state(), - tracked_access_to_completed_results: vec![], - tracked_access_to_uncompleted_results: vec![], - }); - - if let Some(combinator_result) = combinator.try_complete(&mut async_result_tracker) - { - // --- Combinator is ready! - - // Prepare the message to write out - let completed_entries_order = match async_result_tracker.0 { - AsyncResultAccessTrackerInner::Processing { - tracked_access_to_completed_results, - .. - } => tracked_access_to_completed_results, - _ => unreachable!(), - }; - let message = CombinatorEntryMessage { - completed_entries_order: completed_entries_order - .into_iter() - .map(Into::into) - .collect(), - ..CombinatorEntryMessage::default() - }; - - // Let's execute the transition - context.journal.transition(&message); - let current_journal_index = context.journal.expect_index(); - - // Cache locally the Combinator result, the user will be able to access this once the ack is received. - async_results.insert_waiting_ack_result( - current_journal_index, - Value::CombinatorResult(combinator_result), - ); - - // Write out the combinator message - context.output.send(&message); - - Ok((self, Some(AsyncResultHandle(current_journal_index)))) - } else { - // --- The combinator is not ready yet! Let's wait for more completions to come. - - if context.input_is_closed { - let uncompleted_entries_order = match async_result_tracker.0 { - AsyncResultAccessTrackerInner::Processing { - tracked_access_to_uncompleted_results, - .. - } => tracked_access_to_uncompleted_results, - _ => unreachable!(), - }; - - // We can't do progress anymore, let's suspend - context.output.send(&SuspensionMessage { - entry_indexes: uncompleted_entries_order - .into_iter() - .map(Into::into) - .collect(), - }); - context.output.send_eof(); - - Ok((State::Suspended, None)) - } else { - Ok((self, None)) - } - } - } - s => { - let expected = CombinatorEntryMessage::default(); - - // We increment the index now only if we're not processing. - context.journal.transition(&expected); - let current_journal_index = context.journal.expect_index(); - - // We should get the combinator message now - let (mut s, msg) = s.transition_and_return( - context, - PopJournalEntry("SysTryCompleteCombinator", expected), - )?; - - match s { - State::Replaying { - ref mut async_results, - .. - } - | State::Processing { - ref mut async_results, - .. - } => { - let ar_states = async_results.get_ready_results_state(); - - // Compute the replay_combinators - let mut replay_combinators = - Vec::with_capacity(msg.completed_entries_order.capacity()); - for idx in msg.completed_entries_order { - let handle = AsyncResultHandle(idx); - let async_result_state = - ar_states.get(&handle).ok_or(BAD_COMBINATOR_ENTRY)?; - replay_combinators.push((handle, *async_result_state)); - } - - // Replay combinator - let mut async_result_tracker = - AsyncResultAccessTracker(AsyncResultAccessTrackerInner::Replaying { - replay_combinators: replay_combinators.into_iter().peekable(), - }); - let combinator_result = combinator - .try_complete(&mut async_result_tracker) - .ok_or(BAD_COMBINATOR_ENTRY)?; - - // Store the ready result - async_results.insert_ready_result( - current_journal_index, - Value::CombinatorResult(combinator_result), - ); - - Ok((s, Some(AsyncResultHandle(current_journal_index)))) - } - s => { - Err(UnexpectedStateError::new(s.into(), "SysTryCompleteCombinator").into()) - } - } - } - } - } -} diff --git a/src/vm/transitions/input.rs b/src/vm/transitions/input.rs index ed31a9e..0839094 100644 --- a/src/vm/transitions/input.rs +++ b/src/vm/transitions/input.rs @@ -1,7 +1,10 @@ -use crate::service_protocol::messages::{CompletionMessage, EntryAckMessage, StartMessage}; +use crate::service_protocol::messages::StartMessage; use crate::service_protocol::{MessageType, RawMessage}; use crate::vm::context::{Context, EagerState, StartInfo}; -use crate::vm::errors::{BadEagerStateKeyError, KNOWN_ENTRIES_IS_ZERO, UNEXPECTED_INPUT_MESSAGE}; +use crate::vm::errors::{ + BadEagerStateKeyError, INPUT_CLOSED_WHILE_WAITING_ENTRIES, KNOWN_ENTRIES_IS_ZERO, + UNEXPECTED_INPUT_MESSAGE, +}; use crate::vm::transitions::Transition; use crate::vm::{errors, State}; use crate::Error; @@ -16,15 +19,8 @@ impl Transition for State { MessageType::Start => { self.transition(context, NewStartMessage(msg.decode_to::()?)) } - MessageType::Completion => self.transition( - context, - NewCompletionMessage(msg.decode_to::()?), - ), - MessageType::EntryAck => self.transition( - context, - NewEntryAckMessage(msg.decode_to::()?), - ), - ty if ty.is_entry() => self.transition(context, NewEntryMessage(msg)), + ty if ty.is_command() => self.transition(context, NewCommandMessage(msg)), + ty if ty.is_notification() => self.transition(context, NewNotificationMessage(msg)), _ => Err(UNEXPECTED_INPUT_MESSAGE)?, } } @@ -66,67 +62,45 @@ impl Transition for State { } Ok(State::WaitingReplayEntries { + received_entries: 0, entries: Default::default(), async_results: Default::default(), }) } } -struct NewCompletionMessage(CompletionMessage); +struct NewNotificationMessage(RawMessage); -impl Transition for State { +impl Transition for State { fn transition( mut self, - _: &mut Context, - NewCompletionMessage(msg): NewCompletionMessage, + context: &mut Context, + NewNotificationMessage(msg): NewNotificationMessage, ) -> Result { - // Add completion to completions buffer - let CompletionMessage { - entry_index, - result, - } = msg; - match &mut self { + match self { State::WaitingReplayEntries { - ref mut async_results, - .. - } - | State::Replaying { - ref mut async_results, - .. - } - | State::Processing { - ref mut async_results, - .. + mut received_entries, + entries, + mut async_results, } => { - async_results.insert_unparsed_completion( - entry_index, - result.ok_or(errors::EXPECTED_COMPLETION_RESULT)?, - )?; - } - State::Ended | State::Suspended => { - // Can ignore - } - s => return Err(s.as_unexpected_state("NewCompletionMessage")), - } + async_results.enqueue(msg.decode_as_notification()?); - Ok(self) - } -} - -struct NewEntryAckMessage(EntryAckMessage); - -impl Transition for State { - fn transition( - mut self, - _: &mut Context, - NewEntryAckMessage(msg): NewEntryAckMessage, - ) -> Result { - match self { - State::WaitingReplayEntries { - ref mut async_results, - .. + received_entries += 1; + if context.expect_start_info().entries_to_replay == received_entries { + Ok(State::Replaying { + entries, + run_state: Default::default(), + async_results, + }) + } else { + Ok(State::WaitingReplayEntries { + received_entries, + entries, + async_results, + }) + } } - | State::Replaying { + State::Replaying { ref mut async_results, .. } @@ -134,40 +108,44 @@ impl Transition for State { ref mut async_results, .. } => { - async_results.notify_ack(msg.entry_index); + async_results.enqueue(msg.decode_as_notification()?); + Ok(self) } State::Ended | State::Suspended => { // Can ignore + Ok(self) } - s => return Err(s.as_unexpected_state("NewEntryAck")), + s => Err(s.as_unexpected_state("NewNotificationMessage")), } - Ok(self) } } -struct NewEntryMessage(RawMessage); +struct NewCommandMessage(RawMessage); -impl Transition for State { +impl Transition for State { fn transition( self, context: &mut Context, - NewEntryMessage(msg): NewEntryMessage, + NewCommandMessage(msg): NewCommandMessage, ) -> Result { match self { State::WaitingReplayEntries { + mut received_entries, mut entries, async_results, } => { + received_entries += 1; entries.push_back(msg); - if context.expect_start_info().entries_to_replay == entries.len() as u32 { + if context.expect_start_info().entries_to_replay == received_entries { Ok(State::Replaying { - current_await_point: None, entries, + run_state: Default::default(), async_results, }) } else { Ok(State::WaitingReplayEntries { + received_entries, entries, async_results, }) @@ -177,3 +155,16 @@ impl Transition for State { } } } + +pub(crate) struct NotifyInputClosed; + +impl Transition for State { + fn transition(self, _: &mut Context, _: NotifyInputClosed) -> Result { + match self { + State::WaitingStart | State::WaitingReplayEntries { .. } => { + Err(INPUT_CLOSED_WHILE_WAITING_ENTRIES) + } + _ => Ok(self), + } + } +} diff --git a/src/vm/transitions/journal.rs b/src/vm/transitions/journal.rs index 60864c9..ad7e768 100644 --- a/src/vm/transitions/journal.rs +++ b/src/vm/transitions/journal.rs @@ -1,65 +1,57 @@ use crate::retries::NextRetry; use crate::service_protocol::messages::{ - run_entry_message, CompletableEntryMessage, EntryMessage, EntryMessageHeaderEq, - InputEntryMessage, RestateMessage, RunEntryMessage, WriteableRestateMessage, + get_eager_state_command_message, propose_run_completion_message, CommandMessageHeaderEq, + GetEagerStateCommandMessage, GetEagerStateKeysCommandMessage, GetLazyStateCommandMessage, + GetLazyStateKeysCommandMessage, InputCommandMessage, NamedCommandMessage, + ProposeRunCompletionMessage, RestateMessage, RunCommandMessage, StateKeys, Void, }; -use crate::vm::context::{Context, RunState}; +use crate::service_protocol::{ + messages, CompletionId, MessageType, Notification, NotificationId, NotificationResult, +}; +use crate::vm::context::{Context, EagerGetState, EagerGetStateKeys, RunState}; use crate::vm::errors::{ - EntryMismatchError, UnavailableEntryError, UnexpectedStateError, INSIDE_RUN, - INVOKED_RUN_EXIT_WITHOUT_ENTER, UNEXPECTED_NONE_RUN_RESULT, + EmptyGetEagerState, EmptyGetEagerStateKeys, EntryMismatchError, UnavailableEntryError, + UnexpectedGetState, UnexpectedGetStateKeys, UnexpectedStateError, }; use crate::vm::transitions::{Transition, TransitionAndReturn}; use crate::vm::State; -use crate::{ - AsyncResultHandle, Error, Header, Input, NonEmptyValue, RetryPolicy, RunEnterResult, - RunExitResult, TerminalFailure, -}; -use std::{fmt, mem}; - -impl State { - pub(crate) fn check_side_effect_guard(&self) -> Result<(), Error> { - if let State::Processing { run_state, .. } = self { - if run_state.is_running() { - return Err(INSIDE_RUN); - } - } - Ok(()) - } -} +use crate::{EntryRetryInfo, Error, Header, Input, NotificationHandle, RetryPolicy, RunExitResult}; +use bytes::Bytes; +use std::fmt; +use tracing::trace; pub(crate) struct PopJournalEntry(pub(crate) &'static str, pub(crate) M); -impl +impl TransitionAndReturn> for State { type Output = M; fn transition_and_return( self, - context: &mut Context, + _: &mut Context, PopJournalEntry(sys_name, expected): PopJournalEntry, ) -> Result<(Self, Self::Output), Error> { match self { State::Replaying { mut entries, - current_await_point, - mut async_results, + run_state, + async_results, } => { let actual = entries .pop_front() .ok_or(UnavailableEntryError::new(M::ty()))? .decode_to::()?; let new_state = if entries.is_empty() { - async_results.notify_ack(context.journal.expect_index()); State::Processing { - run_state: RunState::NotRunning, - current_await_point, + processing_first_entry: true, + run_state: RunState::default(), async_results, } } else { State::Replaying { - current_await_point, entries, + run_state, async_results, } }; @@ -75,18 +67,22 @@ impl struct PopOrWriteJournalEntry(&'static str, M); -impl +impl TransitionAndReturn> for State { type Output = M; fn transition_and_return( - self, + mut self, context: &mut Context, PopOrWriteJournalEntry(sys_name, expected): PopOrWriteJournalEntry, ) -> Result<(Self, Self::Output), Error> { match self { - State::Processing { .. } => { + State::Processing { + ref mut processing_first_entry, + .. + } => { + *processing_first_entry = false; context.output.send(&expected); Ok((self, expected)) } @@ -105,12 +101,11 @@ impl TransitionAndReturn for State { context: &mut Context, _: SysInput, ) -> Result<(Self, Self::Output), Error> { - context.journal.transition(&InputEntryMessage::default()); - self.check_side_effect_guard()?; + context.journal.transition(&InputCommandMessage::default()); let (s, msg) = TransitionAndReturn::transition_and_return( self, context, - PopJournalEntry("SysInput", InputEntryMessage::default()), + PopJournalEntry("SysInput", InputCommandMessage::default()), )?; let start_info = context.expect_start_info(); @@ -121,7 +116,7 @@ impl TransitionAndReturn for State { random_seed: compute_random_seed(&start_info.id), key: start_info.key.clone(), headers: msg.headers.into_iter().map(Header::from).collect(), - input: msg.value, + input: msg.value.map(|v| v.content).unwrap_or_default(), }, )) } @@ -148,7 +143,7 @@ fn compute_random_seed(id: &[u8]) -> u64 { pub(crate) struct SysNonCompletableEntry(pub(crate) &'static str, pub(crate) M); -impl +impl Transition> for State { fn transition( @@ -157,40 +152,101 @@ impl, ) -> Result { context.journal.transition(&expected); - self.check_side_effect_guard()?; let (s, _) = self.transition_and_return(context, PopOrWriteJournalEntry(sys_name, expected))?; Ok(s) } } -pub(crate) struct SysCompletableEntry(pub(crate) &'static str, pub(crate) M); +pub(crate) struct SysNonCompletableEntryWithCompletion( + pub(crate) &'static str, + pub(crate) M, + pub(crate) Notification, +); -impl< - M: RestateMessage - + CompletableEntryMessage - + EntryMessageHeaderEq - + EntryMessage - + Clone - + WriteableRestateMessage, - > TransitionAndReturn> for State +impl + TransitionAndReturn> for State { - type Output = AsyncResultHandle; + type Output = NotificationHandle; fn transition_and_return( self, context: &mut Context, - SysCompletableEntry(sys_name, expected): SysCompletableEntry, + SysNonCompletableEntryWithCompletion(sys_name, expected, notification): SysNonCompletableEntryWithCompletion, ) -> Result<(Self, Self::Output), Error> { context.journal.transition(&expected); - self.check_side_effect_guard()?; - let (mut s, actual) = TransitionAndReturn::transition_and_return( + let (mut s, _) = + self.transition_and_return(context, PopOrWriteJournalEntry(sys_name, expected))?; + match s { + State::WaitingReplayEntries { + ref mut async_results, + .. + } + | State::Replaying { + ref mut async_results, + .. + } + | State::Processing { + ref mut async_results, + .. + } => { + let handle = async_results.create_handle_mapping(notification.id.clone()); + async_results.insert_ready(notification); + Ok((s, handle)) + } + s => Err(UnexpectedStateError::new(s.into(), sys_name).into()), + } + } +} + +pub(crate) struct SysSimpleCompletableEntry( + pub(crate) &'static str, + pub(crate) M, + pub(crate) CompletionId, +); + +impl + TransitionAndReturn> for State +{ + type Output = NotificationHandle; + + fn transition_and_return( + self, + context: &mut Context, + SysSimpleCompletableEntry(sys_name, expected, completion_id): SysSimpleCompletableEntry, + ) -> Result<(Self, Self::Output), Error> { + let (s, handles) = TransitionAndReturn::transition_and_return( + self, + context, + SysCompletableEntryWithMultipleCompletions(sys_name, expected, vec![completion_id]), + )?; + Ok((s, handles[0])) + } +} + +pub(crate) struct SysCompletableEntryWithMultipleCompletions( + pub(crate) &'static str, + pub(crate) M, + pub(crate) Vec, +); + +impl + TransitionAndReturn> for State +{ + type Output = Vec; + + fn transition_and_return( + self, + context: &mut Context, + SysCompletableEntryWithMultipleCompletions(sys_name, expected, completion_ids): SysCompletableEntryWithMultipleCompletions, + ) -> Result<(Self, Self::Output), Error> { + context.journal.transition(&expected); + let (mut s, _) = TransitionAndReturn::transition_and_return( self, context, PopOrWriteJournalEntry(sys_name, expected), )?; - let ar_handle = AsyncResultHandle(context.journal.expect_index()); match s { State::Replaying { ref mut async_results, @@ -200,92 +256,432 @@ impl< ref mut async_results, .. } => { - if let Some(c) = actual.into_completion()? { - async_results.insert_ready_result(ar_handle.0, c); - } else { - async_results.insert_completion_parsing_hint( - ar_handle.0, - M::completion_parsing_hint(), - )?; + // Create mapping for all the necessary notification ids + let mut notification_handles = Vec::with_capacity(completion_ids.len()); + for completion_id in completion_ids { + notification_handles.push( + async_results + .create_handle_mapping(NotificationId::CompletionId(completion_id)), + ) } + + Ok((s, notification_handles)) } - s => return Err(UnexpectedStateError::new(s.into(), sys_name).into()), + s => Err(UnexpectedStateError::new(s.into(), sys_name).into()), } - Ok((s, ar_handle)) } } -pub(crate) struct SysRunEnter(pub(crate) String); +pub(crate) struct CreateSignalHandle(pub(crate) &'static str, pub(crate) NotificationId); -impl TransitionAndReturn for State { - type Output = RunEnterResult; +impl TransitionAndReturn for State { + type Output = NotificationHandle; + + fn transition_and_return( + mut self, + _: &mut Context, + CreateSignalHandle(sys_name, notification_id): CreateSignalHandle, + ) -> Result<(Self, Self::Output), Error> { + match self { + State::Replaying { + ref mut async_results, + .. + } + | State::Processing { + ref mut async_results, + .. + } => { + // Create mapping for the notification id + let handle = async_results.create_handle_mapping(notification_id); + + Ok((self, handle)) + } + s => Err(UnexpectedStateError::new(s.into(), sys_name).into()), + } + } +} + +pub(crate) struct SysStateGet(pub(crate) String); + +impl TransitionAndReturn for State { + type Output = NotificationHandle; fn transition_and_return( mut self, context: &mut Context, - SysRunEnter(name): SysRunEnter, + SysStateGet(key): SysStateGet, ) -> Result<(Self, Self::Output), Error> { - let expected = RunEntryMessage { - name: name.clone(), - ..RunEntryMessage::default() - }; - context.journal.transition(&expected); - self.check_side_effect_guard()?; + let completion_id = context.journal.next_completion_notification_id(); + match self { State::Processing { - ref mut run_state, .. + ref mut processing_first_entry, + ref mut async_results, + .. } => { - *run_state = RunState::Running(name); + *processing_first_entry = false; + + // Let's look into the eager_state + let result = match context.eager_state.get(&key) { + EagerGetState::Unknown => None, + EagerGetState::Empty => Some(( + get_eager_state_command_message::Result::Void(Void::default()), + NotificationResult::Void(Void::default()), + )), + EagerGetState::Value(v) => Some(( + get_eager_state_command_message::Result::Value(v.clone().into()), + NotificationResult::Value(v.clone().into()), + )), + }; - Ok(( - self, - RunEnterResult::NotExecuted(context.infer_entry_retry_info()), - )) + if let Some((get_state_result, notification_result)) = result { + // Eager state case, we're good let's prepare the ready notification and send the get eager state entry + let new_entry = GetEagerStateCommandMessage { + key: Bytes::from(key), + result: Some(get_state_result), + ..Default::default() + }; + let new_notification = Notification { + id: NotificationId::CompletionId(completion_id), + result: notification_result, + }; + let handle = async_results.create_handle_mapping(new_notification.id.clone()); + + async_results.insert_ready(new_notification); + context.journal.transition(&new_entry); + context.output.send(&new_entry); + + Ok((self, handle)) + } else { + let new_entry = GetLazyStateCommandMessage { + key: Bytes::from(key), + result_completion_id: completion_id, + ..Default::default() + }; + let handle = async_results + .create_handle_mapping(NotificationId::CompletionId(completion_id)); + + context.journal.transition(&new_entry); + context.output.send(&new_entry); + + Ok((self, handle)) + } } - s => { - let (s, msg) = - s.transition_and_return(context, PopJournalEntry("SysRunEnter", expected))?; - Ok(( - s, - RunEnterResult::Executed(msg.result.ok_or(UNEXPECTED_NONE_RUN_RESULT)?.into()), - )) + State::Replaying { + mut entries, + mut async_results, + run_state, + } => { + context + .journal + .transition(&GetEagerStateCommandMessage::default()); + let handle = async_results + .create_handle_mapping(NotificationId::CompletionId(completion_id)); + + let actual = entries + .pop_front() + .ok_or(UnavailableEntryError::new(GetLazyStateCommandMessage::ty()))?; + + match actual.ty() { + MessageType::GetEagerStateCommand => { + let get_eager_state_command = + actual.decode_to::()?; + check_entry_header_match( + &get_eager_state_command, + &GetEagerStateCommandMessage { + key: key.into_bytes().into(), + result: get_eager_state_command.result.clone(), + name: "".to_string(), + }, + )?; + + let notification_result = + match get_eager_state_command.result.ok_or(EmptyGetEagerState)? { + get_eager_state_command_message::Result::Void(v) => { + NotificationResult::Void(v) + } + get_eager_state_command_message::Result::Value(v) => { + NotificationResult::Value(v) + } + }; + + async_results.insert_ready(Notification { + id: NotificationId::CompletionId(completion_id), + result: notification_result, + }); + } + MessageType::GetLazyStateCommand => { + let get_lazy_state_command = + actual.decode_to::()?; + check_entry_header_match( + &get_lazy_state_command, + &GetLazyStateCommandMessage { + key: key.into_bytes().into(), + result_completion_id: completion_id, + name: "".to_string(), + }, + )?; + } + message_type => { + return Err(UnexpectedGetState { + actual: message_type, + } + .into()) + } + } + + let new_state = if entries.is_empty() { + State::Processing { + processing_first_entry: true, + run_state: RunState::default(), + async_results, + } + } else { + State::Replaying { + entries, + run_state, + async_results, + } + }; + + Ok((new_state, handle)) } + s => Err(UnexpectedStateError::new(s.into(), "SysStateGet").into()), } } } -pub(crate) struct SysRunExit(pub(crate) RunExitResult, pub(crate) RetryPolicy); +pub(crate) struct SysStateGetKeys; -impl TransitionAndReturn for State { - type Output = AsyncResultHandle; +impl TransitionAndReturn for State { + type Output = NotificationHandle; fn transition_and_return( mut self, context: &mut Context, - SysRunExit(run_exit_result, retry_policy): SysRunExit, + _: SysStateGetKeys, ) -> Result<(Self, Self::Output), Error> { + let completion_id = context.journal.next_completion_notification_id(); + match self { State::Processing { + ref mut processing_first_entry, ref mut async_results, - ref mut run_state, .. } => { - let name = match mem::replace(run_state, RunState::NotRunning) { - RunState::Running(n) => n, - RunState::NotRunning => { - return Err(INVOKED_RUN_EXIT_WITHOUT_ENTER); + *processing_first_entry = false; + + // Let's look into the eager_state + let result = match context.eager_state.get_keys() { + EagerGetStateKeys::Unknown => None, + EagerGetStateKeys::Keys(keys) => { + let state_keys = StateKeys { + keys: keys.into_iter().map(Bytes::from).collect(), + }; + Some(( + state_keys.clone(), + NotificationResult::StateKeys(state_keys), + )) } }; - let current_journal_index = context.journal.expect_index(); + + if let Some((get_state_result, notification_result)) = result { + // Eager state case, we're good let's prepare the ready notification and send the get eager state entry + let new_entry = GetEagerStateKeysCommandMessage { + value: Some(get_state_result), + ..Default::default() + }; + let new_notification = Notification { + id: NotificationId::CompletionId(completion_id), + result: notification_result, + }; + let handle = async_results.create_handle_mapping(new_notification.id.clone()); + + async_results.insert_ready(new_notification); + context.journal.transition(&new_entry); + context.output.send(&new_entry); + + Ok((self, handle)) + } else { + let new_entry = GetLazyStateKeysCommandMessage { + result_completion_id: completion_id, + ..Default::default() + }; + let handle = async_results + .create_handle_mapping(NotificationId::CompletionId(completion_id)); + + context.journal.transition(&new_entry); + context.output.send(&new_entry); + + Ok((self, handle)) + } + } + State::Replaying { + mut entries, + mut async_results, + run_state, + } => { + context + .journal + .transition(&GetEagerStateKeysCommandMessage::default()); + let handle = async_results + .create_handle_mapping(NotificationId::CompletionId(completion_id)); + + let actual = entries.pop_front().ok_or(UnavailableEntryError::new( + GetLazyStateKeysCommandMessage::ty(), + ))?; + + match actual.ty() { + MessageType::GetEagerStateKeysCommand => { + let get_eager_state_command = + actual.decode_to::()?; + check_entry_header_match( + &get_eager_state_command, + &GetEagerStateKeysCommandMessage { + value: get_eager_state_command.value.clone(), + name: "".to_string(), + }, + )?; + + let notification_result = NotificationResult::StateKeys( + get_eager_state_command + .value + .ok_or(EmptyGetEagerStateKeys)?, + ); + + async_results.insert_ready(Notification { + id: NotificationId::CompletionId(completion_id), + result: notification_result, + }); + } + MessageType::GetLazyStateKeysCommand => { + let get_lazy_state_command = + actual.decode_to::()?; + check_entry_header_match( + &get_lazy_state_command, + &GetLazyStateKeysCommandMessage { + result_completion_id: completion_id, + name: "".to_string(), + }, + )?; + } + message_type => { + return Err(UnexpectedGetStateKeys { + actual: message_type, + } + .into()) + } + } + + let new_state = if entries.is_empty() { + State::Processing { + processing_first_entry: true, + run_state: RunState::default(), + async_results, + } + } else { + State::Replaying { + entries, + run_state, + async_results, + } + }; + + Ok((new_state, handle)) + } + s => Err(UnexpectedStateError::new(s.into(), "SysStateGet").into()), + } + } +} + +pub(crate) struct SysRun(pub(crate) String); + +impl TransitionAndReturn for State { + type Output = NotificationHandle; + + fn transition_and_return( + self, + context: &mut Context, + SysRun(name): SysRun, + ) -> Result<(Self, Self::Output), Error> { + let result_completion_id = context.journal.next_completion_notification_id(); + let expected = RunCommandMessage { + name: name.clone(), + result_completion_id, + }; + + let (mut s, handle) = TransitionAndReturn::transition_and_return( + self, + context, + SysSimpleCompletableEntry("SysRun", expected, result_completion_id), + )?; + + let notification_id = NotificationId::CompletionId(result_completion_id); + let mut needs_execution = true; + if let State::Replaying { async_results, .. } = &mut s { + // If we're replying, + // we need to check whether there is a completion already, + // otherwise enqueue it to execute it. + if async_results.non_deterministic_find_id(¬ification_id) { + trace!( + "Found notification for {handle:?} with id {notification_id:?} while replaying, the run closure won't be executed." + ); + needs_execution = false; + } + } + if needs_execution { + trace!( + "Run notification for {handle:?} with id {notification_id:?} not found while replaying, \ + so we enqueue the run to be executed later" + ); + match &mut s { + State::Replaying { run_state, .. } | State::Processing { run_state, .. } => { + run_state.insert_run_to_execute(handle) + } + _ => {} + }; + } + + Ok((s, handle)) + } +} + +pub(crate) struct ProposeRunCompletion( + pub(crate) NotificationHandle, + pub(crate) RunExitResult, + pub(crate) RetryPolicy, +); + +impl Transition for State { + fn transition( + mut self, + context: &mut Context, + ProposeRunCompletion(notification_handle, run_exit_result, retry_policy): ProposeRunCompletion, + ) -> Result { + match self { + State::Processing { + ref mut async_results, + ref mut run_state, + processing_first_entry, + } => { + let notification_id = + async_results.must_resolve_notification_handle(¬ification_handle); + run_state.notify_executed(notification_handle); let value = match run_exit_result { - RunExitResult::Success(s) => NonEmptyValue::Success(s), - RunExitResult::TerminalFailure(f) => NonEmptyValue::Failure(f), + RunExitResult::Success(s) => propose_run_completion_message::Result::Value(s), + RunExitResult::TerminalFailure(f) => { + propose_run_completion_message::Result::Failure(f.into()) + } RunExitResult::RetryableFailure { - error: failure, + error, attempt_duration, } => { - let mut retry_info = context.infer_entry_retry_info(); + let mut retry_info = if processing_first_entry { + context.infer_entry_retry_info() + } else { + EntryRetryInfo::default() + }; retry_info.retry_count += 1; retry_info.retry_loop_duration += attempt_duration; @@ -293,39 +689,42 @@ impl TransitionAndReturn for State { NextRetry::Retry(next_retry_interval) => { // We need to retry! context.next_retry_delay = next_retry_interval; - return Err(Error::new(failure.code, failure.message)); + return Err(Error::new(error.code, error.message)); } NextRetry::DoNotRetry => { // We don't retry, but convert the retryable error to actual error - NonEmptyValue::Failure(TerminalFailure { - code: failure.code, - message: failure.message.to_string(), + propose_run_completion_message::Result::Failure(messages::Failure { + code: error.code.into(), + message: error.message.to_string(), }) } } } }; - async_results - .insert_waiting_ack_result(current_journal_index, value.clone().into()); - - let expected = RunEntryMessage { - name, - result: Some(match value { - NonEmptyValue::Success(b) => run_entry_message::Result::Value(b), - NonEmptyValue::Failure(f) => run_entry_message::Result::Failure(f.into()), - }), + let result_completion_id = match notification_id { + NotificationId::CompletionId(cid) => cid, + nid => { + panic!("NotificationId for run should be a completion id, but was {nid:?}") + } + }; + let expected = ProposeRunCompletionMessage { + result_completion_id, + result: Some(value), }; context.output.send(&expected); - Ok((self, AsyncResultHandle(current_journal_index))) + Ok(self) + } + s => { + trace!("Going to ignore proposed completion for run with handle {notification_handle:?}, because state is {}", <&'static str>::from(&s)); + Ok(s) } - s => Err(UnexpectedStateError::new(s.into(), "SysRunExit").into()), } } } -fn check_entry_header_match( +fn check_entry_header_match( actual: &M, expected: &M, ) -> Result<(), Error> { diff --git a/src/vm/transitions/mod.rs b/src/vm/transitions/mod.rs index 2363745..582b649 100644 --- a/src/vm/transitions/mod.rs +++ b/src/vm/transitions/mod.rs @@ -1,5 +1,4 @@ mod async_results; -mod combinators; mod input; mod journal; mod terminal; @@ -9,7 +8,6 @@ use crate::vm::context::Context; use crate::vm::State; use crate::{CoreVM, Error}; pub(crate) use async_results::*; -pub(crate) use combinators::*; pub(crate) use input::*; pub(crate) use journal::*; use std::mem; @@ -81,11 +79,11 @@ impl CoreVM { code: e.code as u32, message: e.message.clone().into_owned(), description: e.description.clone().into_owned(), - related_entry_index: Some(self.context.journal.index() as u32), - related_entry_name: Some( + related_command_index: Some(self.context.journal.command_index() as u32), + related_command_name: Some( self.context.journal.current_entry_name.clone(), ), - related_entry_type: Some(u16::from( + related_command_type: Some(u16::from( self.context.journal.current_entry_ty, ) as u32), next_retry_delay: self diff --git a/src/vm/transitions/terminal.rs b/src/vm/transitions/terminal.rs index 1d7916e..9fd5f4f 100644 --- a/src/vm/transitions/terminal.rs +++ b/src/vm/transitions/terminal.rs @@ -1,9 +1,11 @@ use crate::service_protocol::messages::{EndMessage, SuspensionMessage}; +use crate::service_protocol::NotificationId; use crate::vm::context::Context; use crate::vm::errors::UnexpectedStateError; use crate::vm::transitions::Transition; use crate::vm::State; use crate::Error; +use std::collections::HashSet; use std::time::Duration; pub(crate) struct HitError { @@ -27,22 +29,38 @@ impl Transition for State { } } -pub(crate) struct HitSuspensionPoint(pub(crate) u32); +pub(crate) struct HitSuspensionPoint(pub(crate) HashSet); impl Transition for State { fn transition( self, context: &mut Context, - HitSuspensionPoint(await_point): HitSuspensionPoint, + HitSuspensionPoint(awaiting_on): HitSuspensionPoint, ) -> Result { if matches!(self, State::Suspended | State::Ended) { // Nothing to do return Ok(self); } tracing::debug!("Suspending"); - context.output.send(&SuspensionMessage { - entry_indexes: vec![await_point], - }); + + let mut suspension_message = SuspensionMessage { + waiting_completions: vec![], + waiting_signals: vec![], + waiting_named_signals: vec![], + }; + for notification_id in awaiting_on { + match notification_id { + NotificationId::CompletionId(cid) => { + suspension_message.waiting_completions.push(cid) + } + NotificationId::SignalId(sid) => suspension_message.waiting_signals.push(sid), + NotificationId::SignalName(name) => { + suspension_message.waiting_named_signals.push(name) + } + } + } + + context.output.send(&suspension_message); context.output.send_eof(); Ok(State::Suspended) diff --git a/tests/bootstrap.rs b/tests/bootstrap.rs index d1690ff..0cacfa0 100644 --- a/tests/bootstrap.rs +++ b/tests/bootstrap.rs @@ -12,16 +12,11 @@ fn bootstrap() { .bytes(["."]) .protoc_arg("--experimental_allow_proto3_optional") .enum_attribute(".", "#[allow(clippy::enum_variant_names)]") + .enum_attribute("protocol.NotificationTemplate.id", "#[derive(Eq, Hash)]") .out_dir(out_dir.clone()) .compile_protos( - &[ - root_dir.join("service-protocol/dev/restate/service/protocol.proto"), - root_dir.join("service-protocol-ext/combinators.proto"), - ], - &[ - root_dir.join("service-protocol"), - root_dir.join("service-protocol-ext"), - ], + &[root_dir.join("service-protocol/dev/restate/service/protocol.proto")], + &[root_dir.join("service-protocol")], ) { panic!("failed to compile `console-api` protobuf: {}", error);