| index 68f14c928..7520c15e0 100644 |
| --- a/internal/sim/hostlife/protocol_test.go |
| +++ b/internal/sim/hostlife/protocol_test.go |
| @@ -1328,7 +1328,7 @@ func (w *world) admitCommit(b *box, c *claim, a admitInput) bool { |
| return true |
| } |
| h := c.Holder |
| - var lost *writesLost |
| + var lost, lostSrc *writesLost |
| ok := w.cas(c, func(n *claim) bool { |
| if n.Holder == nil || n.Holder.Session != a.ObservedSession || n.Holder.Gen != a.ObservedGeneration { |
| return false |
| @@ -1377,6 +1377,20 @@ func (w *world) admitCommit(b *box, c *claim, a admitInput) bool { |
| lost = &writesLost{Box: h.WriterBox, Gen: h.Gen, Session: h.Session, RemoteMaxTXID: a.RemoteMaxTXID} |
| e.Lost = lost |
| } |
| + 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 |
| + // source's writes beyond its own replica, and no lane brings |
| + // them: the source's cut session is declared too (§7.10) |
| + srcSession := 0 |
| + for _, p := range n.Op.Writers { |
| + if p.Box == n.Op.Src && p.Gen == n.Op.SrcGen { |
| + srcSession = p.Session |
| + } |
| + } |
| + lostSrc = &writesLost{Box: n.Op.Src, Gen: n.Op.SrcGen, Session: srcSession, RemoteMaxTXID: a.RemoteMaxTXID} |
| + e.LostSrc = lostSrc |
| + } |
| // no retire chore here: the predecessor is the baseline of an |
| // unfinished admission until generation.json names the new |
| // generation; the chore is queued then (claim rev 6.10, R10-2). |
| @@ -1437,6 +1451,21 @@ 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) |
| } |
| + if lostSrc != nil { |
| + n := 0 |
| + for i := range w.acks { |
| + x := &w.acks[i] |
| + if x.Box == lostSrc.Box && x.Inc == h.Inc && x.Kind == "write" && !x.Exempt && !w.durableInLineage(h.Location, a.NewGeneration, x.W) { |
| + x.Exempt = true |
| + n++ |
| + } |
| + } |
| + w.stats["writes_lost declared"]++ |
| + w.stats["writes declared lost"] += n |
| + w.stats["writes_lost: source session behind a lost target"]++ |
| + w.lostAlert++ |
| + w.alert("box %d: writes_lost {box %d, generation %q, session %d, remote_max_txid %d}: %d acknowledged writes (the seal in the lost target's region held them)", b.ID, lostSrc.Box, lostSrc.Gen, lostSrc.Session, lostSrc.RemoteMaxTXID, n) |
| + } |
| return true |
| } |
| |