v3.1.1
StateChart 3.1.1
May 15, 2026
Bug fixes in 3.1.1
Thread-safety hardening of the configuration cache
Two races in Configuration (introduced indirectly by the cache + no-copy
design in 3.1.0) have been fixed. Both surfaced under concurrent reads of
machine.configuration while another thread is sending events to the same
state machine instance, a scenario explicitly supported by the sync engine.
-
Cache read race.
Configuration.stateschecked
self._cached is not Noneand then returnedself._cached. Another
thread invalidating between the check and the return could cause the
property to returnNone, leading to aTypeErrorin callers that
iterate the result (e.g.,list(machine.configuration)). The getter now
snapshots the cache fields locally before the freshness check.
#620. -
In-place mutation race.
Configuration.add()and
Configuration.discard()mutated theOrderedSetstored on the model
in place and rewrote the same reference. A concurrent reader iterating
.configurationcould observe a partially mutated set (raising
RuntimeError: Set changed size during iteration) or read back a stale
cached resolution missing the new state. Both methods now use
copy-on-write, producing a freshOrderedSetper call. This affects
onlyStateChart(whereatomic_configuration_update=Falseis the
default to support parallel regions). The atomic update path used by
StateMachinewas never affected.
#620.
Both fixes are covered by new stress tests in
tests/test_threading.py::TestThreadSafety:
test_concurrent_send_and_read_configuration and
test_concurrent_parallel_region_send_and_read, plus a deterministic
copy-on-write contract test test_add_discard_produce_fresh_orderedset.
Performance impact
Copy-on-write in add() / discard() reintroduces an O(n) shallow copy of
the active configuration on every state entry and exit. For the typical
configuration sizes used in practice (1–7 states), this is sub-microsecond.
Measured on macOS / Python 3.14, pytest-benchmark median, vs 3.1.0:
| Benchmark | 3.1.0 | 3.1.1 | Δ |
|---|---|---|---|
test_parallel_region_events |
175.2 μs | 184.5 μs | +5.3% |
test_many_transitions_reset |
125.9 μs | 139.5 μs | +10.9% |
test_guarded_transitions |
70.0 μs | 75.7 μs | +8.2% |
test_history_pause_resume |
88.4 μs | 91.4 μs | +3.4% |
test_many_transitions_full_cycle |
156.9 μs | 162.1 μs | +3.3% |
test_flat_self_transition |
38.7 μs | 39.1 μs | +1.0% |
Overall 4.7x–7.7x event throughput improvement vs 3.0.0 (declared in
3.1.0 release notes)
is unchanged.