Commit 927ea14
cas: fix false GC-lease steal — heartbeat seq compared across different hb owners
The steal gate's liveness check compared the observed `gc/hb` sequence
against the previous observation while matching the hb OWNER against the
CURRENT LEASE owner:
hb_alive = has_observation && hb.owner == lease.owner && hb.hb_seq > last_seen_hb_seq
`hb_seq` values are only comparable under the same hb owner — which is
exactly why `last_seen_hb_owner` was stored — but the stored owner was
never read (the half-finished check this fixes). The hazard is reachable:
a deposed leader's heartbeat thread keeps pulsing (`i_am_leader` resets
only on its next round) with `owner = itself`, and `pulseHeartbeat`'s
losing CAS writes vanish silently, so a zombie old leader can keep
`gc/hb.owner` pointing at itself while the live new leader is pulsing
too. On both of a follower's ticks `hb.owner != lease.owner` then made
`hb_alive` false, and with the new leader's lease tuple frozen mid-round
the follower stole the lease from a live, pulsing leader.
The gate is now observation-symmetric with the frozen-lease check: ANY
movement of the observed `(owner, hb_seq)` pair between the contender's
two ticks is proof of life, and `hb_seq` is compared only under the same
remembered hb owner (an owner change re-arms the window). Liveness is
preserved — only leaders pulse and a deposed leader stops within one round
interval, so a genuinely dead leader still gets stolen from one window
later at most.
Failing-first regression test
`CasGcLease.StaleOwnerHeartbeatDoesNotEnableFalseSteal` reproduces the
zombie-masking interleaving deterministically (steal confirmed on the old
predicate) and pins both the no-false-steal and the still-steals-when-
frozen halves.
Validated: full corrected CAS gtest battery 906/907 — the one failure is
the pre-existing tracked `RefWriterRecoverySeal` F3-1a fragility
(`docs/superpowers/cas/BACKLOG.md`), unrelated to the lease protocol.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>1 parent 7eb246d commit 927ea14
3 files changed
Lines changed: 55 additions & 3 deletions
File tree
- src/Disks
- DiskObjectStorage/MetadataStorages/ContentAddressed/Gc
- tests
Lines changed: 11 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2322 | 2322 | | |
2323 | 2323 | | |
2324 | 2324 | | |
| 2325 | + | |
| 2326 | + | |
| 2327 | + | |
| 2328 | + | |
| 2329 | + | |
| 2330 | + | |
| 2331 | + | |
| 2332 | + | |
| 2333 | + | |
| 2334 | + | |
2325 | 2335 | | |
2326 | | - | |
2327 | | - | |
| 2336 | + | |
2328 | 2337 | | |
2329 | 2338 | | |
2330 | 2339 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
374 | 374 | | |
375 | 375 | | |
376 | 376 | | |
377 | | - | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
378 | 380 | | |
379 | 381 | | |
380 | 382 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| |||
329 | 330 | | |
330 | 331 | | |
331 | 332 | | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
332 | 373 | | |
333 | 374 | | |
334 | 375 | | |
| |||
0 commit comments