Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

oversell

A correctness fuzzer for checkout and inventory races. You point it at a backend, it fires N requests at the same instant into a critical section, and then it checks a business invariant against the server's observable state — for example, you can never sell more units than ever existed. Every run prints a seed so a violation can be replayed.

Why this isn't a load tester

k6, Locust, and friends answer "how many requests per second can this take, and how slow does it get?" oversell asks a different question: "when two requests hit the last item at the same moment, does the server sell it twice?" It isn't trying to push volume. It releases a small batch of requests together to land them inside the same check-then-act window, then judges the result against an invariant. Pass/fail, not a latency histogram.

Install

go install github.com/imharjot/oversell@latest

Try it in two terminals

There's a deliberately-buggy backend bundled in, so you don't need your own server to see a violation.

Terminal one — start the buggy demo backend (one unit in stock):

oversell serve --kind buggy --stock 1
# buggy backend listening on http://127.0.0.1:8080 (stock=1)

Terminal two — fire 50 simultaneous checkouts at it:

oversell run --target http://127.0.0.1:8080 --scenario checkout-last-item --concurrency 50 --seed 42

You'll get something like:

seed 42 | scenario checkout-last-item | concurrency 50 | target http://127.0.0.1:8080
VIOLATION  no-oversell
      units_sold=50 exceeds initial_qty=1 (server-reported oversold=49)
      send order (slot->task): [16 36 27 4 45 44 19 33 7 ...] ... (50 total)
      replay: oversell run --target http://127.0.0.1:8080 --scenario checkout-last-item --concurrency 50 --seed 42

The process exits non-zero on a violation, so it drops straight into CI.

Now swap the backend for the locked version and run the exact same command:

oversell serve --kind correct --stock 1
seed 42 | scenario checkout-last-item | concurrency 50 | target http://127.0.0.1:8080
PASS  no invariant violated
      final state: units_sold=1 stock=0 initial=1

The target contract

A target needs two endpoints for v1:

  • POST /checkout — attempt to buy one unit. The status code doesn't matter to the tool; a sold-out 409 is a fine outcome. The verdict comes from state.

  • GET /state — return JSON the oracle can check. For checkout-last-item:

    { "stock": 0, "units_sold": 1, "oversold": 0, "initial_qty": 1 }

    Only the fields an invariant references need to be real.

Scenarios

checkout-last-item is the one wired up in v1. The Scenario interface (internal/scenario) has two halves — the per-worker request and the invariants — so adding one is small. Planned next, sketched in internal/scenario/next.go:

  • double-spend — one balance, two concurrent debits.
  • coupon-double-redeem — a single-use coupon redeemed by N carts at once.
  • reservation-leak — hold/confirm/release under churn, asserting nothing gets stuck in a phantom hold.

Limitations

Worth being upfront about:

  • It controls request release, not the server's scheduler. oversell can get N requests onto the wire as close to simultaneously as the OS and runtime allow, but it can't dictate how the server interleaves them once they land. Whether a race actually fires still depends on the target's internal timing.
  • A clean run is not a proof. Not seeing a violation means this seed and this concurrency didn't trigger one — not that the code is race-free. Vary the seed and concurrency.
  • v1 observes state over HTTP. The oracle reads a /state endpoint. If your invariant can't be observed that way (it lives only in a DB, say), v1 can't check it yet.
  • It's a single-process client. Truly simultaneous arrival is bounded by one machine's networking. For most check-then-act bugs that's plenty; for very tight windows it may not be.

See DESIGN.md for how the barrier, oracle, and replay work.

Development

go build ./...
go test ./... -race

About

A correctness fuzzer for checkout and inventory races — fires simultaneous requests, asserts no oversell, and replays any violation from a seed.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages