mule/runtime/executor
Types
Mirrors the executor.ex struct’s error fields: kind defaults to
KindError (like kind: :error), error is the structured exception
analog (not the persisted record — job.format_attempt flattens it at ack
time), stacktrace the formatted frames of a rescued crash.
pub type Executor {
Executor(
job: job.Job,
config: config.Config,
worker: option.Option(worker.ErasedWorker),
state: State,
kind: exceptions.Kind,
error: option.Option(exceptions.ExecutionError),
stacktrace: List(String),
result: option.Option(worker.WorkerResult),
snooze: option.Option(duration.Duration),
timer: option.Option(Timer),
start_mono: Int,
safe: Bool,
ack: Bool,
raw_crash: option.Option(rescue.RawCrash),
)
}
Constructors
-
Executor( job: job.Job, config: config.Config, worker: option.Option(worker.ErasedWorker), state: State, kind: exceptions.Kind, error: option.Option(exceptions.ExecutionError), stacktrace: List(String), result: option.Option(worker.WorkerResult), snooze: option.Option(duration.Duration), timer: option.Option(Timer), start_mono: Int, safe: Bool, ack: Bool, raw_crash: option.Option(rescue.RawCrash), )
pub type State {
Unset
Success
Failure
Cancelled
Discard
Snoozed
Exhausted
}
Constructors
-
Unset -
Success -
Failure -
Cancelled -
Discard -
Snoozed -
Exhausted
The reason start_timeout arms timer:exit_after with. Its compiled tuple
shape ({timeout_reason, Worker, Timeout}) is pattern-matched by
mule_rescue_ffi:classify_exit_reason/1, so the producer’s DOWN handler
can tell a timeout from any other crash.
pub type TimeoutReason {
TimeoutReason(worker: String, timeout: Int)
}
Constructors
-
TimeoutReason(worker: String, timeout: Int)
Values
pub fn ack_event(exec: Executor) -> Executor
Ack the terminal state to the engine. Every call rides
backoff.with_retry (infinite exponential+jitter on EngineFailures and
crashes — Elixir’s DB-resilience wrapper). A returned Error(JobNotFound)
is dropped silently: the typed equivalent of Elixir’s 0-row update_all
returning :ok (the row was deleted mid-run). Run record_unsaved first —
the Failure/Discard/Cancelled branches persist job.format_attempt.
ack: False (the testing no-persistence path) skips everything.
pub fn new(config: config.Config, job: job.Job) -> Executor
pub fn record_unsaved(exec: Executor) -> Executor
executor.ex record_unsaved/1: stamp %{kind, reason, stacktrace} onto the
job before acking, so the worker’s backoff and the engine see it.
pub fn report(exec: Executor) -> Executor
Emit the terminal job event from the executor’s own process, after the ack.
Success/Cancelled/Snoozed/Discard are a :stop — a manual
Discarded return is the worker’s own choice, not an error (executor.ex
emit_event puts :discard in the stop clause); Failure/Exhausted
are an :exception, with Exhausted reported under the Discard outcome
(Elixir remaps :exhausted to :discard in the metadata). The producer’s
recovery / cancellation paths end in this too, so every execution — golden
path, crash, pkill — reports exactly once.
pub fn reraise_unsafe(exec: Executor) -> Executor
executor.ex reraise_unsafe/1: when unsafe and perform rescued a crash,
re-raise it with its original kind/reason/stacktrace — AFTER the ack and
report, matching Elixir’s record_unsaved → ack_event → emit_event order
(so an unsafe drain acks the failure first, then re-raises). The
raw_crash-only trigger is executor.ex’s stacktrace: [_ | _] guard:
only caught crashes re-raise; a worker’s returned Failed(...) completes
normally.
pub fn synthesize_cancellation(
exec: Executor,
reason reason: String,
) -> Executor
Producer backstop: a pkill/shutdown-terminated job becomes a cancellation
(producer.ex:96-100’s {:cancel, :shutdown} wrapped in PerformError).
pub fn synthesize_crash(
exec: Executor,
reason reason: String,
stacktrace stacktrace: List(String),
) -> Executor
Producer backstop: the job process died from an exit signal
(producer.ex:102-106, kind {:EXIT, pid} normalized to :exit).