Jepsen-style operation history recorder.
A history is a sequence of :invoke events (operations starting)
paired with :ok / :fail / :info completion events. It's the
raw input for consistency checkers like Lockstep.Checker.Linearizable.
Recording
Each operation has three lifecycle events:
invoke— a process announces it's starting an operationok— the operation completed and the caller knows the resultfail— the operation explicitly didn't apply (returned an error the caller can interpret as "no change")info— the operation's outcome is unknown (caller crashed, timed out, lost the connection mid-call). Linearizability checkers must consider that the op MIGHT or MIGHT NOT have applied.
Quick example
history = Lockstep.History.start_link!()
# Concurrent workers issuing reads/writes
for i <- 1..3 do
Lockstep.spawn(fn ->
Lockstep.History.op(history, :write, i, fn ->
Register.put(reg, i)
end)
end)
end
# ... wait for them ...
events = Lockstep.History.events(history)
{:ok, _info} =
Lockstep.Checker.Linearizable.check(events, Lockstep.Model.Register)Order
Events are indexed in the order they reach the recorder. Under
Lockstep's deterministic controller this is also the real-time
order — every invoke/ok call is a sync point routed through
the controller, so the strategy controls when each event is
recorded relative to others.
Summary
Functions
Return events in invocation order (oldest first).
Record a :fail (explicit non-apply) completion for f.
Record an :info (unknown outcome) completion for f. Use for
crashes, timeouts, or anything where the caller can't be sure
whether the op applied.
Record an :invoke event for f with input value from self().
Record an :ok (success) completion for f with output value.
Convenience wrapper: record :invoke before running fun, then
record :ok with the result on normal return, :info on raise/
exit/throw. Returns the function's result (or re-raises).
Start a history recorder under the current Lockstep iteration. Returns the pid; pass it into worker closures so they can record.
Functions
@spec events(pid()) :: [Lockstep.History.Event.t()]
Return events in invocation order (oldest first).
Record a :fail (explicit non-apply) completion for f.
Record an :info (unknown outcome) completion for f. Use for
crashes, timeouts, or anything where the caller can't be sure
whether the op applied.
Record an :invoke event for f with input value from self().
Record an :ok (success) completion for f with output value.
Convenience wrapper: record :invoke before running fun, then
record :ok with the result on normal return, :info on raise/
exit/throw. Returns the function's result (or re-raises).
Use this when the operation has clear "succeeded with this value" or
"crashed, who knows" outcomes. For explicit "didn't apply" failures,
call invoke/3 + fail/3 manually.
Lockstep.History.op(history, :write, 42, fn ->
Register.put(reg, 42)
:ok
end)
@spec start_link!() :: pid()
Start a history recorder under the current Lockstep iteration. Returns the pid; pass it into worker closures so they can record.