Packages

Optimistic parallel discrete-event simulation (Time Warp) on the BEAM: logical processes execute events speculatively and roll back automatically on causality violations, so models implement only pure event handling and never write rollback logic.

Current section

Files

Jump to
timewarp CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to this project are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.0] — 2026-08-20
### Added
- `c:TimeWarp.Model.commit/2` may return `{:ok, new_model_state}` in addition to
`:ok`. Fossil collection threads the returned state back into the logical
process, which gives a model the only point at which it can release state that a
commit has made unreachable. `handle_event/3` never observes GVT and so cannot
determine what is safe to drop; a model that accumulates per-event state and
always returns `:ok` grows for the length of the run. Events are committed in
ascending virtual-time order and the state is threaded through that fold, so
commit order is now observable in the resulting state.
Pruning is bounded by GVT lag rather than immediate: a rollback restores a state
snapshot from history, and snapshots at or above GVT were taken before the prune,
so they still reference the released terms until the same fossil-collection pass
drops them.
Returning `:ok` remains valid, so existing models are unaffected.
- `:lp_call_timeout` — timeout in milliseconds for the coordinator's own calls
into logical processes, default `5_000`. These execute inside the coordinator
process and a logical process may be remote, so exceeding the timeout fails the
run rather than an individual call.
- `:quiescence_timeout_ms` — ceiling on the freeze-confirm quiescence poll,
default `1_000`. The workable value scales with link latency, so a distributed
run may require more than a local one.
- `TimeWarp.stop_run/1` cancels a run, tearing down its logical processes and
returning the engine to idle. The function existed on the coordinator and the
public documentation directed callers into that internal module; it is now part
of the public surface.
It waits without a timeout. Teardown reaches into every placement node's
supervisor, which can exceed the default five-second call timeout on a
multi-node run; a caller that timed out would exit while the teardown continued
and completed, reporting a crash from a call that had in fact worked.
### Changed
- `TimeWarp.start_run/1` returns `{:error, :run_active}` when a run is already in
progress, leaving the active run untouched.
- `TimeWarp.Examples.KeyedWindow` and `TimeWarp.Examples.BufferedWindow` release
each window's accumulator and buffer on commit. Both previously retained every
window for the length of the run.
### Fixed
- `start_run/1` against an active run matched no clause and raised
`FunctionClauseError` inside the coordinator. This terminated the coordinator and
every caller awaiting the *active* run, so the run already in progress was lost
and the failure surfaced as a crash in the second.
- Three coordinator calls into logical processes — the per-process dump behind
`report/1`, the GVT query, and the freeze-confirm poll — used the default
five-second call timeout while executing inside the coordinator process. A remote
or slow logical process therefore terminated the coordinator instead of failing a
single call. These now honour `:lp_call_timeout`.
- The test suite aborted before ExUnit started when distribution was unavailable,
reporting zero tests run. Tests requiring distribution are tagged `:distributed`
and excluded when `Node.start/2` fails.
### Documentation
- `:placement`, `:gvt_mode` and `:net_delay` are documented in the `TimeWarp`
moduledoc. All three were read by the coordinator and absent from the option
list. `:net_delay` is marked test-only.
- The one-run-per-BEAM constraint is stated explicitly. The coordinator is a
global singleton, and `start_run/1` tears down every logical process on the
placement nodes without scoping the teardown to a single run, so concurrent runs
destroy one another.
- `:time_window` documents that it is the only bound on retained state. Fossil
collection reclaims only below GVT, so a workload holding GVT near the floor
grows retained state until the VM exhausts memory, and `:none` places no limit
on how far a logical process runs ahead.
- The `TimeWarp` moduledoc example referenced a model that does not exist and
would not compile, and set `time_window: :none` — the value documented
immediately below it as unbounded. It now uses `TimeWarp.Examples.PHOLD` with a
binding window.
- `TimeWarp.Coordinator`'s moduledoc described the superseded stop-the-world
computation as current and Mattern's algorithm as future work, though
`:gvt_mode` defaults to `:mattern`. It now leads with Mattern's algorithm as the
computation in use, and presents the stop-the-world variant as the test oracle
and termination confirmation it actually is.
- References to an unpublished specification (`§N`, `Mitigation N`, `Phase-N`)
are resolved to their meaning throughout the published modules.
## [0.1.0] — 2026-08-04
Initial release. Jefferson's Time Warp with Mattern's distributed Global Virtual
Time, mapped onto BEAM primitives: speculative execution with automatic rollback,
anti-message annihilation, fossil collection on GVT advance, an optional deadlock-free
time window, lazy cancellation, a purity divergence detector, and static multi-node
LP placement.
[0.2.0]: https://github.com/thatsme/timewarp/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/thatsme/timewarp/releases/tag/v0.1.0