nobroker/playground
Docs →

Break it on purpose.

nobroker's real record format, reimplemented in JavaScript — same frame, real CRC-32, real byte offsets. Crash a write mid-record and watch recovery find the tear. What's real and what's simulated →

New here? Start with this. What the two panels are, and a five-minute tour.

What you are looking at

A job queue is a to-do list that survives its program being killed. Work is written down first, then done later by something else — so a crash loses nothing that was already promised.

Everything on this page is that list, shown twice:

Jobs is what the queue believes — one card per job, with its state. The log file is what is actually on disk, record by record, with real byte offsets. The second one is the truth: the Jobs panel is rebuilt from it every time you press Recover. Keep an eye on both, because the whole design is that they can never disagree after a crash.

Nothing here is sent anywhere. It runs entirely in this tab, and reloading starts over.

A five-minute tour

  1. Put work on the list Press Enqueue. A card appears under Jobs, and one ENQUEUE record appears in the log. Look at the byte offsets — that record is now on the disk, and the real thing would not have returned until it was.
  2. Take the work, but do not remove it Press Lease. The job goes leased: hidden from everyone else for 30 seconds, but still on the list. This is the whole trick. If it were removed and the worker died, the job would be gone forever.
  3. Kill the worker without saying so Instead of Ack, press +35s to run the clock past the 30-second deadline. The job comes back by itself. Nothing had to notice the worker died — the deadline is just a timestamp that passed.
  4. Break the disk mid-write Press Crash mid-write, then read the last line of the log: half a record, cut off exactly where the write stopped. Now press Recover. The torn piece is detected and thrown away, and every complete record before it survives. That is the entire crash-safety story in two clicks.
  5. Corrupt a byte that is already written Press Flip a bit, then Recover. The record is still the right length, so only the checksum can catch it — and it does. Without that, the queue would happily act on damaged data.

The five buttons at the top of the page do these sequences for you if you would rather watch than click. Hover any button for a one-line explanation of what it does.

The words on the buttons

Enqueue
Add a job to the list.
Lease
Claim a job for 30 seconds without removing it.
Ack
"Done" — now it can leave the list.
Nack
"That failed" — put it back, after a wait.
Extend
"Still working on it" — push the deadline out.
Backoff
Waiting longer before each retry: 1s, 2s, 4s. Stops a failing job hammering something already broken.
DLQ
Dead-letter queue: where a job goes after failing too many times, so a person can look at it instead of it retrying forever.
Compact
Rewrite the log with only the jobs that still matter. Finished ones are dropped; failed ones are kept.
CRC-32
A number computed from a record's bytes. If any bit changes, it stops matching — which is how corruption is caught.
fsync
Forcing bytes out of memory and onto the physical disk. The one thing this page cannot do; see below.
Consume
Clock
t+0s
Failure
Maintain

Jobs

0 jobs

jobs/default.000000.log

16 B
What's real here, and what isn't A reimplementation, not Python in your browser.

Real — byte-for-byte the same as Python

  • The record frame: [u32 length][u8 type][u32 crc32][json], little-endian, plus the 16-byte file header.
  • The CRC-32, same polynomial as zlib.crc32, over the type byte plus the payload.
  • The JSON encoder: sorted keys, no whitespace, non-ASCII escaped as \uXXXX the way ensure_ascii does it.
  • The byte offsets in the log are real positions in a real byte array.
  • The recovery algorithm: scan forward, stop at the first record that fails its length or checksum, truncate there.
  • The state machine and all nine record types, including the rule that replay never reads a clock.

Verified, not asserted. The encoder was diffed against nobroker.codec across file headers and every record type — identical hex output, including a payload with non-ASCII characters.

Simulated — different from the real thing

  • The "disk" is a JavaScript array. There is no file, and nothing survives a reload.
  • No fsync — durability is the one guarantee a browser cannot demonstrate.
  • The clock is a variable you advance by hand, so a 30-second lease expires on a button press.
  • The heaps are sorted arrays. The real thing uses two heapq heaps with lazy deletion; here n is tiny and the trick would only obscure the point.
  • No file lock — one tab, one thread. Multi-process safety is the part you take on trust, or read the tests for.
  • Python writes integral floats as 30.0 where JavaScript writes 30. The virtual clock is whole seconds so it never comes up here, but a log written by Python would differ in those bytes.

The playground exists to make the failure modes tangible — a torn tail, a stale fencing token, a lease that expired while you weren't looking. For the durability guarantee itself, the evidence is the test suite: design notes.

Nothing here talks to a server. Reload to start over.