diff --git a/docs/changes/unreleased/2180-watch-receipt.md b/docs/changes/unreleased/2180-watch-receipt.md new file mode 100644 index 0000000000..46443683dc --- /dev/null +++ b/docs/changes/unreleased/2180-watch-receipt.md @@ -0,0 +1,6 @@ +--- +kind: fixed +title: Another computer taking a chat retires this window's send receipt before any reader is told +pr: 2180 +surface: [chat, app] +--- diff --git a/internal/syncsetup/drive.go b/internal/syncsetup/drive.go index 749ebd9717..be0588dcd6 100644 --- a/internal/syncsetup/drive.go +++ b/internal/syncsetup/drive.go @@ -613,17 +613,7 @@ func (d *Drive) CheckLease(ctx context.Context) error { } if err == nil && (v.Cell.Lease.Expires <= v.Now || v.Cell.Lease.Device != d.sync.Device.ID() || v.Cell.Lease.Fence != d.batcher.Fence()) { if v.Cell.Lease.Device != d.sync.Device.ID() { - // Another computer holds the chat. Retire the hold at once so no - // new seal starts, wait for any seal already past Gate, and let the - // batcher keep unsent turns as a branch before the window names the - // new holder (#2091). The hold is retired quietly: becomeViewer - // announces the change once, with the viewer line already set. - d.mu.Lock() - d.lost = true - d.mu.Unlock() - d.sealMu.Lock() - d.sealMu.Unlock() - d.batcher.Displaced(ctx) + d.retireForAnotherHolder(ctx, held) } d.becomeViewer(cellsync.Superseded{By: v.Cell.Lease.Device}) } else if err == nil { @@ -632,6 +622,26 @@ func (d *Drive) CheckLease(ctx context.Context) error { return nil } +// retireForAnotherHolder retires this writer's hold when CheckLease finds +// another computer holding the chat. It retires the hold at once so no new +// seal starts, waits for any seal already past Gate, and lets the batcher keep +// unsent turns as a branch before the window names the new holder (#2091). +// The send receipt goes before the +// batcher marks itself stale, because that mark already wakes renewal readers +// and none of them may still find a receipt for a hold another computer has. +// The hold is retired quietly: becomeViewer announces the change once. +func (d *Drive) retireForAnotherHolder(ctx context.Context, held sendLease) { + d.mu.Lock() + d.lost = true + d.mu.Unlock() + d.sealMu.Lock() + d.sealMu.Unlock() + if err := d.sync.forgetSendLease(d.cell.ID, held); err != nil { + d.notice(err.Error()) + } + d.batcher.Displaced(ctx) +} + // sendLeaseSnapshot binds retirement to the receipt held before a relay reading, // so an older reading cannot retire a receipt recorded by a later reacquisition. func (d *Drive) sendLeaseSnapshot() sendLease {