Rollback and Optimistic Simulation

Suppose you are simulating something huge — a continent's air traffic, a war game, the internet itself — and you want to throw a thousand processors at it. The natural split: give each processor a piece of the world (an airport, a battalion, a router) with its own event queue and its own local virtual time. The nightmare: your airport happily simulates 3 pm… and then a message arrives from a slower neighbour, an aircraft that lands at 2:15. You have already simulated a past that turns out to be wrong.

The timid fix is to never let this happen: block every processor until it is provably safe to advance. The audacious fix is David Jefferson's Time Warp (1985): let every processor race ahead on its best guess — and when the past changes under you, undo it. Speculate forward; repair backward. That repair step is called rollback, and it turns this lesson into the payoff of the whole module: rollback is reverse execution deployed not to debug programs, but to unlock parallelism. Reversibility as a concurrency superpower.

Time Warp in one scenario

Each logical process (LP) executes the events in its queue in timestamp order, optimistically assuming no earlier event will ever show up. When one does — a straggler, a message stamped in the LP's past — three things must happen:

  1. Roll back the state to just before the straggler's timestamp: every event processed with a later timestamp is undone.
  2. Cancel the consequences. Some of those undone events sent messages to other LPs — messages that now were never sent. For each one, the LP emits an anti-message: a perfect negative copy that chases its positive twin. If the twin is still sitting in the receiver's queue, the pair annihilates; if the receiver has already processed it, the anti-message triggers a rollback there — cancellation cascades exactly as far as the wrong speculation spread, and no further.
  3. Re-execute forward from the straggler, now in correct timestamp order.

Watch it happen

And here is the same story as a running program. The event handler is deliberately invertible (count += delta), so rollback can use the module's signature move — run the inverse — rather than restoring a saved copy:

type Ev = { t: number; delta: number; sendsAt?: number }; class LP { name: string; count = 0; lvt = 0; // local virtual time done: Ev[] = []; constructor(name: string) { this.name = name; } exec(e: Ev): void { this.count += e.delta; this.lvt = e.t; this.done.push(e); let line = " [" + this.name + "] t=" + e.t + " count += " + e.delta + " -> " + this.count; if (e.sendsAt !== undefined) line += " (sends a message stamped t=" + e.sendsAt + ")"; console.log(line); } // REVERSE COMPUTATION: undo by running inverse handlers, newest first. rollbackTo(t: number): Ev[] { const undone: Ev[] = []; while (this.done.length > 0 && this.done[this.done.length - 1].t > t) { const e = this.done.pop()!; this.count -= e.delta; // the inverse of count += delta console.log(" [" + this.name + "] UNDO t=" + e.t + " count -= " + e.delta + " -> " + this.count); if (e.sendsAt !== undefined) console.log(" [" + this.name + "] ANTI-MESSAGE sent to cancel the message stamped t=" + e.sendsAt); undone.push(e); } return undone.reverse(); } } const a = new LP("A"); const queue: Ev[] = [ { t: 10, delta: 3 }, { t: 20, delta: 5, sendsAt: 22 }, { t: 30, delta: 2 }, { t: 40, delta: 4 }, ]; console.log("phase 1 - optimistic execution:"); for (const e of queue) a.exec(e); console.log("phase 2 - straggler arrives: { t: 15, delta: 7 } (LP A is at lvt " + a.lvt + ")"); const straggler: Ev = { t: 15, delta: 7 }; const redo = a.rollbackTo(straggler.t); console.log("phase 3 - re-execute in correct order:"); a.exec(straggler); for (const e of redo) a.exec(e); console.log("final count = " + a.count + " - exactly what a sequential, in-order run gives.");

Note what the anti-messages are in this trace: the events at 30 and 40 sent nothing, so undoing them is private; but the event at 20 had a side effect on the outside world, and cancelling it needs an explicit negative message. Reversibility inside the LP, anti-messages between LPs — together they make speculation safe: any wrong guess can be perfectly unwound.

Global Virtual Time: how far back can the past reach?

If any message might drag you back to any earlier time, no result could ever be trusted and no memory ever freed. Time Warp's safety rail is Global Virtual Time (GVT): the minimum over all LPs' local clocks and all messages still in flight. No straggler can ever be stamped earlier than GVT — nothing in the system exists that could produce one. Everything before GVT is therefore committed: irrevocable outputs (a printed statistic, an actual missile launch in a military sim) may be released, and the checkpoints and event histories kept for possible rollback may be reclaimed — a step charmingly known as fossil collection. GVT sweeps forward behind the speculating frontier like a tide line: ahead of it, everything is provisional and reversible; behind it, history has hardened into fact.

Two ways to roll back — and when reversibility wins

Rollback needs the LP's state as it was at the straggler's timestamp. Two ways to get it:

The tradeoff is the familiar one — copies cost space, inverses cost thought — but here it has a sharp engineering answer: reverse computation wins when events make small perturbations to big states. An event that nudges three counters in a megabyte-sized LP state needs a three-operation inverse, versus a megabyte of checkpoint. On supercomputer-scale runs (ROSS has simulated billions of events per second on millions of cores), state saving's memory traffic is the bottleneck, and reverse computation is the difference between scaling and stalling. This is reversible computing's most direct industrial payoff: writing the inverse of your code, by hand or by the inversion rules of this module, because it is faster than remembering.

Parallel simulation split into two camps in the 1980s and the argument still simmers. The conservative school (Chandy–Misra–Bryant) never lets an LP process an event until it is provably safe — no rollbacks ever, but every LP crawls at the speed of its slowest possible neighbour, and performance leans on lookahead: how far ahead each LP can promise not to send messages. The optimistic school (Jefferson's Time Warp) lets everyone sprint and pays for mistakes with rollback. Optimism wins when stragglers are rare and lookahead is poor; conservatism wins when rollbacks would cascade catastrophically. Real systems hedge — throttled optimism ("Moving Time Windows"), mixed protocols, adaptive switching — but the philosophical point stands: optimism is only possible at all because rollback exists. Without a way to unwind the past, guessing about it would be reckless; with reversibility, speculation becomes a strategy instead of a sin. (You have seen this pattern before: modern CPUs speculate down predicted branches and roll back mispredictions — Time Warp is the same gamble, at datacentre scale.)

Newcomers read "the LP rolled back 4,000 events" as a bug report. It is a billing statement: rollback is the price paid for the parallelism won by speculating. The only questions are whether the price is fair — did speculation buy more progress than repair cost? — and whether it is correct, which Time Warp guarantees: after all rollbacks, cancellations and re-executions, every LP's committed history is exactly what a sequential in-order simulation would have produced. A healthy Time Warp run rolls back constantly, the way a healthy CPU mispredicts branches constantly. The pathology to watch for is different: cascades of rollbacks re-triggering each other faster than real progress is made ("rollback thrashing"), which throttled optimism exists to prevent.