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.statescheckedself._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()andConfiguration.discard()mutated theOrderedSetstored on the model in place and rewrote the same reference. A concurrent reader iterating.configurationcould observe a partially mutated set (raisingRuntimeError: 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 byStateMachinewas 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 |
Δ |
|---|---|---|---|
|
175.2 μs |
184.5 μs |
+5.3% |
|
125.9 μs |
139.5 μs |
+10.9% |
|
70.0 μs |
75.7 μs |
+8.2% |
|
88.4 μs |
91.4 μs |
+3.4% |
|
156.9 μs |
162.1 μs |
+3.3% |
|
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.