LockerGnomeFor serious tech professionals with a silly side.
Subscribe

Search

Softwarepythontestingconcurrencyopen-sourcepytest

Ploom Runs Every Thread Order of a Python Test Until One Fails, Then Saves That Order So the Bug Replays on Demand

A two-day-old Python testing tool from Adil Zouitine turns the thread race your test passes almost every time into a JSON trace you can replay, minimize and fix, borrowing its idea from Rust's Loom.

Instruction-manual cutaway of Chris locking a cash drawer as two strangers reach for the same stack of bills from opposite sides
Illustration: LockerGnome

Two people share a bank account holding 100. Both withdraw 100 at the same moment. Only one of them should get the money.

That’s the example in Adil Zouitine’s README, and it’s the race every concurrency course warns about. Each thread checks the balance, sees 100, and withdraws. Written with plain threading.Thread, the README says, “this code passes almost every time,” which means the test goes green on your laptop and red on the build server once a month.

Swap in ploom’s own Thread and SharedCell, call ploom.check, and the tool “finds the bad one in a few seconds.” The failure comes back as the account is overdrawn, with two files beside it: the original trace and a minimized one. The minimized trace reads like a crime scene report. Lucas reads 100. Mathieu reads 100. Mathieu withdraws, balance 0. Lucas withdraws, balance -100.

The bank example ships in the repo without the lock, so you can watch it fail. Put a Lock around the check and the withdrawal, and “now all 127 orders pass.”

The trick is small. Every ploom operation, a lock grab, a queue put, a shared read, is a scheduling point, and only one task runs at a time, “so the result does not depend on the GIL.” A depth-first search picks each runnable task at each point. Each schedule runs in a fresh Python process with the same hash seed, so state from one try never leaks into the next and set order doesn’t shuffle between runs.

Zouitine, whose GitHub profile lists him as a founding research scientist at UMA and formerly of Hugging Face, credits the idea to Loom, the Rust concurrency tester: “Loom showed how to explore schedules and reproduce failures.” Ploom defines its own Python operations and doesn’t copy Loom’s C11 memory model. The repo went up on October 25. What it hasn’t met yet is a real codebase’s thread pool.

The limits are written down, which is rarer than it should be. Ploom sees only its own operations, not native threading, queue or I/O, and it “does not roll back files, network calls, or other external effects between schedules.” So the next step is the one the documentation is built for: somebody pointing it at a real worker pool, with real queues, and finding out how many of their “flaky” tests were never flaky at all.

The open question is size. A two-thread bank account has 127 orders. A web server’s job queue has more than anyone will wait for, and whether two preemptions are enough to catch the bugs people actually ship is something only a lot of trace files will answer.

More Software

Every Software story →