pub struct InMemoryExecutor<W: PureWorkflow> { /* private fields */ }Expand description
An in-memory WorkflowExecutor for any PureWorkflow.
It does the things an executor must do and nothing else:
- it refuses a batch whose expected revision is not the current one
(
ExecutionError::RevisionConflict, I13); - it replays known idempotency keys instead of repeating the effect, and
refuses a key reused with a different command
(
ExecutionError::IdempotencyMismatch, I14); - it resumes a batch that only half-executed, replaying the prefix and executing the rest;
- it emits one
CommittedEventper event the pureapplyproduced.
Event identifiers and timestamps are derived, not random: two runs of the same sequence of batches produce byte-identical commits, which is what makes snapshot and replay assertions possible.
§A batch owns one revision
A batch moves the case from expected_revision to expected_revision + 1
however many envelopes it carries, and a resumed batch lands on the same
revision the interrupted one reached. That is what makes
execute_prefix followed by
execute indistinguishable, from the case’s
point of view, from one uninterrupted execute — which is the property
crash recovery relies on.
Implementations§
Source§impl<W: PureWorkflow> InMemoryExecutor<W>
impl<W: PureWorkflow> InMemoryExecutor<W>
Sourcepub const fn definition(&self) -> &W
pub const fn definition(&self) -> &W
The definition transitions are validated against.
Sourcepub fn seed(
&self,
account: &AccountId,
case_id: &CaseId,
state: W::State,
revision: CaseRevision,
)
pub fn seed( &self, account: &AccountId, case_id: &CaseId, state: W::State, revision: CaseRevision, )
Installs a case at a chosen revision, bypassing commands. Use it to start a test from a state that would take many turns to reach.
Sourcepub fn case_ids(&self, account: &AccountId) -> Vec<CaseId>
pub fn case_ids(&self, account: &AccountId) -> Vec<CaseId>
The account’s cases, in the order each first appeared: what a directory over this executor lists.
Sourcepub fn revision_of(&self, account: &AccountId, case_id: &CaseId) -> CaseRevision
pub fn revision_of(&self, account: &AccountId, case_id: &CaseId) -> CaseRevision
The current revision of a case, or CaseRevision::ZERO when it does
not exist for this account.
Sourcepub fn state_of(
&self,
account: &AccountId,
case_id: &CaseId,
) -> Option<W::State>
pub fn state_of( &self, account: &AccountId, case_id: &CaseId, ) -> Option<W::State>
The state of a case, for a test that asserts on what a turn wrote.
Sourcepub fn case_count(&self) -> usize
pub fn case_count(&self) -> usize
Number of distinct cases stored.
Sourcepub fn has_executed(&self, key: &IdempotencyKey) -> bool
pub fn has_executed(&self, key: &IdempotencyKey) -> bool
Returns true when this idempotency key has already executed.
Sourcepub fn replayed_prefix_of(&self, batch: &CommandBatch<W::Command>) -> usize
pub fn replayed_prefix_of(&self, batch: &CommandBatch<W::Command>) -> usize
How many envelopes at the front of batch already executed.
0 means the batch is untouched, batch.envelopes.len() that the whole
batch is a replay, anything between that it was interrupted.
Sourcepub fn execute_prefix(
&self,
batch: &CommandBatch<W::Command>,
applied: usize,
) -> Result<Commit<W::State, W::Event>, ExecutionError>
pub fn execute_prefix( &self, batch: &CommandBatch<W::Command>, applied: usize, ) -> Result<Commit<W::State, W::Event>, ExecutionError>
Executes only the first applied envelopes of batch, as a process
that died mid-batch would have left it.
The case moves to expected_revision + 1 and the applied envelopes are
remembered, so executing the whole batch afterwards replays them and
runs only the rest. This is how a test creates a partially replayed
batch; nothing else in the kit produces one.
§Errors
ExecutionError::ScopeViolation when applied is zero or larger than
the batch, plus everything execute can
return.
Trait Implementations§
Source§impl<W: PureWorkflow> Debug for InMemoryExecutor<W>
impl<W: PureWorkflow> Debug for InMemoryExecutor<W>
Source§impl<W: PureWorkflow + Default> Default for InMemoryExecutor<W>
impl<W: PureWorkflow + Default> Default for InMemoryExecutor<W>
Source§impl<W: PureWorkflow> WorkflowExecutor<W> for InMemoryExecutor<W>
impl<W: PureWorkflow> WorkflowExecutor<W> for InMemoryExecutor<W>
Source§fn load<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
account: &'life1 AccountId,
case_id: &'life2 CaseId,
) -> Pin<Box<dyn Future<Output = Result<Versioned<Option<W::State>>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn load<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
account: &'life1 AccountId,
case_id: &'life2 CaseId,
) -> Pin<Box<dyn Future<Output = Result<Versioned<Option<W::State>>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
None value means the case does not exist;
its revision is then crate::ids::CaseRevision::ZERO. An executor
must never delete a case to express completion, because a completed case
keeps its row and moves to a terminal status, so an absent state always
means the case has not been created yet.Source§fn execute<'life0, 'async_trait>(
&'life0 self,
batch: CommandBatch<W::Command>,
) -> Pin<Box<dyn Future<Output = Result<Commit<W::State, W::Event>, ExecutionError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn execute<'life0, 'async_trait>(
&'life0 self,
batch: CommandBatch<W::Command>,
) -> Pin<Box<dyn Future<Output = Result<Commit<W::State, W::Event>, ExecutionError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Auto Trait Implementations§
impl<W> !Freeze for InMemoryExecutor<W>
impl<W> RefUnwindSafe for InMemoryExecutor<W>where
W: RefUnwindSafe,
Mutex<Store<<W as WorkflowDefinition>::State, <W as WorkflowDefinition>::Event>>: RefUnwindSafe,
impl<W> Send for InMemoryExecutor<W>
impl<W> Sync for InMemoryExecutor<W>
impl<W> Unpin for InMemoryExecutor<W>where
W: Unpin,
Mutex<Store<<W as WorkflowDefinition>::State, <W as WorkflowDefinition>::Event>>: Unpin,
impl<W> UnsafeUnpin for InMemoryExecutor<W>where
W: UnsafeUnpin,
Mutex<Store<<W as WorkflowDefinition>::State, <W as WorkflowDefinition>::Event>>: UnsafeUnpin,
impl<W> UnwindSafe for InMemoryExecutor<W>where
W: UnwindSafe,
Mutex<Store<<W as WorkflowDefinition>::State, <W as WorkflowDefinition>::Event>>: UnwindSafe,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<W, E> CaseLoader<W> for E
impl<W, E> CaseLoader<W> for E
Source§fn load_case<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
account: &'life1 AccountId,
case_id: &'life2 CaseId,
) -> Pin<Box<dyn Future<Output = Result<Versioned<Option<<W as WorkflowDefinition>::State>>, StoreError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
E: 'async_trait,
fn load_case<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
account: &'life1 AccountId,
case_id: &'life2 CaseId,
) -> Pin<Box<dyn Future<Output = Result<Versioned<Option<<W as WorkflowDefinition>::State>>, StoreError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
E: 'async_trait,
None value means the case does not exist;
its revision is then crate::ids::CaseRevision::ZERO. See
WorkflowExecutor::load for what an absent state does and does not
mean. Read more