| index d94625e30..73bffb8b0 100644 |
| --- a/internal/sim/hostlife/protocol_test.go |
| +++ b/internal/sim/hostlife/protocol_test.go |
| @@ -974,8 +974,17 @@ func (w *world) publishGeneration(b *box, c *claim, observedOpID, observedPhase |
| } |
| switch { |
| case e.Predecessor.Gen == published: |
| - e.Covered = true // the predecessor is the live generation again |
| - w.stats["publish: predecessor live"]++ |
| + // the predecessor is the live generation again (a clean |
| + // return's source generation, a republished lagging marker): |
| + // left uncovered, no chore. Covered here, as claim rev 6.14 |
| + // item 5's first bullet reads, nothing would retire it once a |
| + // later admission supersedes it, since that admission's |
| + // predecessor is the last record, not the generation it |
| + // restored from (seed 41914 / 100,000: the source's |
| + // republished marker covered the target's record while the |
| + // source's generation was live again, and the generation was |
| + // residue at the end; a reading for the claim) |
| + w.stats["publish: predecessor live, left uncovered"]++ |
| continue |
| case e.PredRegion < 0: |
| e.Covered = true // it holds nothing anywhere and no writer can add to it |
| @@ -1604,6 +1613,7 @@ func (w *world) admitCommit(b *box, c *claim, a admitInput) bool { |
| } |
| h := c.Holder |
| var lost, lostSrc *writesLost |
| + var lostTgt []*writesLost |
| var last *admissionRecord // the last admitted writer before this CAS, the predecessor's identity (claim rev 6.12, R11-4) |
| var entry admissionRecord |
| ok := w.cas(c, func(n *claim) bool { |
| @@ -1685,6 +1695,22 @@ func (w *world) admitCommit(b *box, c *claim, a admitInput) bool { |
| lost = &writesLost{Box: p.Box, Gen: p.Gen, Session: p.Session, RemoteMaxTXID: a.RemoteMaxTXID} |
| e.Lost = lost |
| } |
| + if n.Op.Kind == "restoring" && n.Op.LostAt != 0 && b.ID == n.Op.Src && on("writes-lost") { |
| + // every session the lost target was admitted into since the |
| + // cut is declared (rev 7.13; seed 33373 / 100,000): a probe |
| + // session that certified and a warm continuation that did |
| + // not are two sessions, the first's writes are durable only in |
| + // the lost target's region, and no return lane carries either |
| + // (§7.10). Rev 7.12's box-wide exemption hid the first behind |
| + // the record for the last |
| + lostTgt = nil |
| + for _, q := range n.Holder.Admissions { |
| + if q.Box == n.Op.Tgt && q.Session > n.Op.CutSession && (lost == nil || q.Session != lost.Session) { |
| + lostTgt = append(lostTgt, &writesLost{Box: q.Box, Gen: q.Gen, Session: q.Session, RemoteMaxTXID: a.RemoteMaxTXID}) |
| + } |
| + } |
| + e.LostMore = lostTgt |
| + } |
| if n.Op.Kind == "restoring" && n.Op.LostAt != 0 && !n.Op.SrcCovered && !n.Op.Dirty && b.ID == n.Op.Src && h.Gen == n.Op.SrcGen && on("writes-lost") { |
| // the return is clean only because the target was declared |
| // lost; the seal in its region was the one holder of the |
| @@ -1749,6 +1775,14 @@ func (w *world) admitCommit(b *box, c *claim, a admitInput) bool { |
| w.lostAlert++ |
| w.alert("box %d: writes_lost {box %d, generation %q, session %d, remote_max_txid %d}: %d acknowledged writes", b.ID, lost.Box, lost.Gen, lost.Session, lost.RemoteMaxTXID, n) |
| } |
| + for _, x := range lostTgt { |
| + n := w.exemptDeclared(b, x, h, a.NewGeneration, true) |
| + w.stats["writes_lost declared"]++ |
| + w.stats["writes declared lost"] += n |
| + w.stats["writes_lost: another session of a lost target"]++ |
| + w.lostAlert++ |
| + w.alert("box %d: writes_lost {box %d, generation %q, session %d, remote_max_txid %d}: %d acknowledged writes (a session of the lost target since the cut)", b.ID, x.Box, x.Gen, x.Session, x.RemoteMaxTXID, n) |
| + } |
| if lostSrc != nil { |
| n := w.exemptDeclared(b, lostSrc, h, a.NewGeneration, false) |
| w.stats["writes_lost declared"]++ |