Skip to main content

ExecutionStore

Struct ExecutionStore 

Source
pub struct ExecutionStore { /* private fields */ }
Expand description

The executions of one process.

Implementations§

Source§

impl ExecutionStore

Source

pub fn new(limits: Limits) -> Self

A store with these limits.

Source

pub fn limits(&self) -> Limits

The limits this store was built with.

Source

pub fn create( &self, id: ExecutionId, capability: &str, title: &str, input: Value, cancellable: bool, actor: Actor, repository: RepositoryRef, ) -> Arc<AtomicBool> ⓘ

Record a new execution and publish its first event. Returns its cancellation token.

The execution is created queued: what runs it decides when it starts.

Source

pub fn adopt( &self, id: ExecutionId, seed: Seed<'_>, pid: u32, ) -> Result<(), AdoptError>

Hold an execution another process runs and reports here, so that this process’s listing, snapshot, history and live channel show it as they show their own.

Nothing runs here: what the record says is what the reporter says, through Self::report. Adopting an id already held as a reported execution changes nothing, so a reporter may send its description with every report. An id this process runs itself is refused.

use majordomus_cli::execution::{Actor, EventPayload, ExecutionId, ExecutionState};
use majordomus_cli::execution::{ExecutionStore, Limits, RepositoryRef, Seed};
let store = ExecutionStore::new(Limits::default());
let seed = || Seed {
    capability: "executions.demonstrate",
    title: "Demonstrate an execution",
    input: serde_json::json!({ "steps": 1 }),
    cancellable: true,
    actor: Actor::reported(4242),
    repository: RepositoryRef { name: "r".into(), id: "id".into(), branch: None },
};
let id = ExecutionId::fresh();
store.adopt(id.clone(), seed(), 4242).unwrap();
store.adopt(id.clone(), seed(), 4242).unwrap(); // again: nothing changes
assert!(store.is_reported(&id));
assert_eq!(store.get(&id).unwrap().event_count, 1, "one `created` event");
Source

pub fn report( &self, id: &ExecutionId, events: impl IntoIterator<Item = (u64, EventPayload)>, ) -> Option<ReportOutcome>

Apply what the process running an execution reports, in its own sequence: each event at most once and in order, so a report sent twice or cut short is harmless.

An event after a gap is not applied; the answer’s through says where the reporter is to resume. An event this store’s state machine refuses is passed over rather than believed, as one from a handler of this process would be. None when the id is not a reported execution of this store.

use majordomus_cli::execution::{Actor, EventPayload, ExecutionId, ExecutionState};
use majordomus_cli::execution::{ExecutionStore, Limits, RepositoryRef, Seed};
let store = ExecutionStore::new(Limits::default());
let seed = || Seed {
    capability: "executions.demonstrate",
    title: "Demonstrate an execution",
    input: serde_json::json!({ "steps": 1 }),
    cancellable: true,
    actor: Actor::reported(4242),
    repository: RepositoryRef { name: "r".into(), id: "id".into(), branch: None },
};
let id = ExecutionId::fresh();
store.adopt(id.clone(), seed(), 4242).unwrap();
let started = store.report(&id, vec![(2, EventPayload::Started)]).unwrap();
assert_eq!((started.applied, started.through), (1, 2));
// sent twice: nothing more is applied
assert_eq!(store.report(&id, vec![(2, EventPayload::Started)]).unwrap().applied, 0);
// after a gap: nothing is applied, and the answer says where to resume
let done = EventPayload::Completed { output: serde_json::json!(1) };
assert_eq!(store.report(&id, vec![(4, done)]).unwrap().through, 2);
assert_eq!(store.get(&id).unwrap().state, ExecutionState::Running);
assert!(store.report(&ExecutionId::fresh(), Vec::new()).is_none(), "not a reported one");
Source

pub fn abandon_silent(&self, silence: Duration) -> Vec<ExecutionId>

Fail every reported execution that is not finished and has not been heard from for longer than silence, and answer which. The process that ran it was killed or lost its way to this one; what it did afterwards is unknown here, which the error says.

use majordomus_cli::execution::{Actor, EventPayload, ExecutionId, ExecutionState};
use majordomus_cli::execution::{ExecutionStore, Limits, RepositoryRef, Seed};
let store = ExecutionStore::new(Limits::default());
let seed = || Seed {
    capability: "executions.demonstrate",
    title: "Demonstrate an execution",
    input: serde_json::json!({ "steps": 1 }),
    cancellable: true,
    actor: Actor::reported(4242),
    repository: RepositoryRef { name: "r".into(), id: "id".into(), branch: None },
};
use std::time::Duration;
let id = ExecutionId::fresh();
store.adopt(id.clone(), seed(), 4242).unwrap();
store.report(&id, vec![(2, EventPayload::Started)]).unwrap();
assert!(store.abandon_silent(Duration::from_secs(30)).is_empty(), "heard just now");
std::thread::sleep(Duration::from_millis(20));
assert_eq!(store.abandon_silent(Duration::from_millis(5)), vec![id.clone()]);
let failed = store.get(&id).unwrap();
assert_eq!(failed.state, ExecutionState::Failed);
assert_eq!(failed.error.unwrap().code, "unavailable");
Source

pub fn is_reported(&self, id: &ExecutionId) -> bool

Is this an execution another process runs and reports here?

use majordomus_cli::execution::{Actor, EventPayload, ExecutionId, ExecutionState};
use majordomus_cli::execution::{ExecutionStore, Limits, RepositoryRef, Seed};
let store = ExecutionStore::new(Limits::default());
let seed = || Seed {
    capability: "executions.demonstrate",
    title: "Demonstrate an execution",
    input: serde_json::json!({ "steps": 1 }),
    cancellable: true,
    actor: Actor::reported(4242),
    repository: RepositoryRef { name: "r".into(), id: "id".into(), branch: None },
};
let reported = ExecutionId::fresh();
store.adopt(reported.clone(), seed(), 4242).unwrap();
assert!(store.is_reported(&reported));
assert!(!store.is_reported(&ExecutionId::fresh()), "nothing is held under that id");
Source

pub fn publish(&self, id: &ExecutionId, payload: EventPayload) -> Option<u64>

Apply an event to an execution: move its state, update its snapshot, retain it, and hand it to every subscriber that wants it.

Returns the sequence the event was given, or None when there is no such execution or the payload asks for a transition the state machine does not allow — which is how a second completed after a cancelled is refused rather than believed.

Source

pub fn get(&self, id: &ExecutionId) -> Option<Execution>

The snapshot of one execution.

Source

pub fn list( &self, state: Option<ExecutionState>, capability: Option<&str>, limit: usize, ) -> Vec<Execution>

Every execution this process remembers, newest first, optionally narrowed.

Source

pub fn len(&self) -> usize

How many executions this store remembers.

Source

pub fn is_empty(&self) -> bool

Does it remember none?

Source

pub fn count(&self, state: ExecutionState) -> usize

How many executions are in a state right now.

Source

pub fn active(&self) -> usize

How many are not finished: running, starting, cancelling or queued.

Source

pub fn events( &self, id: &ExecutionId, after: u64, limit: usize, ) -> Option<EventPage>

A page of one execution’s retained history, after a sequence.

Source

pub fn token(&self, id: &ExecutionId) -> Option<Arc<AtomicBool>>

The cancellation token of an execution, for the worker that runs it.

Source

pub fn request_cancel(&self, id: &ExecutionId, by: &str) -> CancelOutcome

Ask an execution to stop. Publishes execution.cancelling and sets the token; the handler decides when it stops, and a handler that never looks at its token will finish normally, which the final state will say.

Both happen under one hold of the lock. A worker reads the token without the lock, but a handler that stops publishes execution.cancelled, which needs the lock that cancelling is recorded under, so a handler that sees the token cannot publish until cancelling is recorded, whichever of the two is done first inside. Recording the state before the token is extra ordering on top of that: on its own it would hold even were the token set after the lock is let go. What neither may be is the token set in one hold and cancelling published in the next: a handler looking in between publishes cancelled from running — a move the state machine refuses — and the cancelling that follows is the last thing the execution ever says.

Source

pub fn subscribe(&self, filter: Filter) -> (u64, Receiver<Delivery>)

Listen. The receiver is dropped to unsubscribe; the store forgets a subscriber the first time a send finds it gone, so nothing has to be cleaned up by hand.

Source

pub fn resubscribe(&self, subscriber: u64, filter: Filter) -> bool

Change what a subscriber wants, without reconnecting.

Source

pub fn unsubscribe(&self, subscriber: u64)

Stop listening.

Source

pub fn subscribers(&self) -> usize

How many subscribers this store is feeding.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more