mandar1045
I’ve been fixing race condition / crash bugs in an open source org, for a few months now and almost all of them had the same problem. They only happen with a very specific order of events (process crashes mid-message, a reply arrives late, a timer fires at the wrong moment) so the normal ExUnit tests never catch them. We find them in prod, then I spend a day trying to reproduce it and 20 minutes fixing it.
So I started reading about how FoundationDB and TigerBeetle deal with this. They do “deterministic simulation testing”: run the whole system inside a simulator that controls time, message order, randomness and crashes, run it thousands of times with different seeds, and when something breaks you get a seed that replays the exact same failure every time.
I couldn’t find anything like this for Elixir and I’m thinking of building it. Very rough idea of what a test could look like:
simtest "cache stays consistent when workers crash", runs: 1_000 do
start_supervised!(MyApp.CacheSupervisor)
Sim.spawn_clients(10, fn -> MyApp.Cache.put(random_key(), random_value()) end)
Sim.inject(:crash, MyApp.Cache.Worker, probability: 0.05)
Sim.inject(:delay_messages, max: :timer.seconds(2))
Sim.advance(:timer.minutes(10))
assert MyApp.Cache.consistent?()
end
and when it fails you’d get the seed plus the smallest set of events that still breaks it, something like:
failed on run 412 (seed 8812734019)
shrunk to:
1. worker 2 receives {:put, "a", 1}
2. worker 2 crashes before replying
3. supervisor restarts it, client retries the put
My plan for how it works: in test env, rewrite modules at compile time so send/receive, timers, monotonic_time and :rand go through a fake scheduler instead of the real one. Concuerror does something kind of similar for Erlang but it’s aimed at exhaustively exploring interleavings, not seeded fault injection with virtual time, and using it from Elixir is painful (at least it was for me).
Other stuff I looked at: gen_server_virtual_time and Klotho (both nice but only handle time), and StreamData (seeds, but for data not scheduling). If something already does all of this please tell me before I sink weeks into it lol.
Things I’m not sure about:
- is compile time rewriting going to be too fragile? the alternative is making people use a special GenServer-ish behaviour, which feels worse to me but maybe it’s more realistic
- I’m planning single node first and network partitions / multi node later. does that order make sense?
- what would you need to see before trusting something like this? my idea is to run it against some well known libs and see if it finds real bugs
Also curious if you’ve had bugs like this in prod and how you ended up reproducing them.
If there’s a reason this can’t work on the BEAM I’d rather hear it now .
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #code-sync
- #discussion
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #ai
- #umbrella
- #elixirconf-us
- #full-time-contract
- #podcasts-by-brainlid
- #blog-post
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #elixirconf-eu
- #processes
- #security
- #api
- #forms
- #performance










Showing Posts 1 to 3- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
jstimps
Check out eta:
Erlang support was released in 0.1.0. Elixir support is on main but hasn’t been released yet.
Eta is still early, but I have been working to prove it’s efficacy in real projects, and have found several real bugs both in my own code and in open projects.
The basic idea of eta is that it suspends all the processes under test and schedules them one at a time using a deterministic scheduler.
It’s most effective when combined with a formal model like TLA+ and a checker like TLC. The model checks your design and DST can help confirm your code matches your design.
That modeling effort, combined with the writing of the DST harness means that formal verification requires a significant time investment in the development of your project, but in my experience it produces high quality bug reports for elusive defects, especially when paired with a capable LLM.
I’m currently working on an implementation of Flexible Paxos, with a large suite of formal verification backing it.
ausimian
A process that I (the clankers) experimented with recently was:
{next_event, internal, …}The final thing - the bit I thought was reasonably novel - was to get the clankers to emit TLC-compatible traces from the running code, and get TLC to validate them against the spec (see below).
The cool thing is you can just use erlang tracing to capture the
handle_evententry and exit, which gets you a long way to producing the trace - no further instrumentation required.jstimps
Agreed, avoiding deep instrumentation is preferable. And especially if your system under test is already deterministic, you’re unlikely to need a framework to help.
However nondeterminism coming from outside a pure function (scheduling, network, disk, timers, etc) is where DST projects like eta are trying to help. Quick note on scheduling: I don’t know if the BEAM scheduler is actually deterministic or not, but from the perspective of the developer, I think we must treat it as nondet since we have little control over its low level decisions.
Trace validation looks like another great way to link code to a formal model.