mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 22:37:03 +00:00
3e8bf36f3d
pad_collab_append_failures_total counts each batch of sync frames whose op-log append failed. The frames are still broadcast, so if the originating tab closes before its catch-up, a later SyncStep2 can be the only stored copy of the edit, and the item reads clean. The lead ruled this counter the trigger for the SyncStep2 coverage check: any nonzero week where that case could have occurred means build it. Wired as an optional AppendFailureObserver on the TASK-3501 observer, like OverflowObserver; the Room calls onAppendFail, set by the manager. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
58 lines
1.6 KiB
Go
58 lines
1.6 KiB
Go
package collab
|
|
|
|
import (
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
type appendFailureCounter struct {
|
|
resumeCounter
|
|
fails atomic.Int32
|
|
}
|
|
|
|
func (o *appendFailureCounter) AppendFailed() { o.fails.Add(1) }
|
|
|
|
// TASK-3574: a failed op-log append reaches the manager's observer, once per
|
|
// failed batch, and a clean append does not. Driven through a real Join so the
|
|
// count vouches for the Room's binding to the manager, not only the callback
|
|
// (CONVE-19). The barrier ack orders each read after its frame's append.
|
|
func TestTASK3574_AFailedAppendIsCounted(t *testing.T) {
|
|
fake := &fakeOpLog{}
|
|
bus := NewMemoryOpBus()
|
|
defer bus.Close()
|
|
mgr := NewRoomManager(fake, bus)
|
|
obs := &appendFailureCounter{}
|
|
mgr.SetObserver(obs)
|
|
srv := newCollabTestServer(t, mgr)
|
|
defer srv.Close()
|
|
|
|
a := dialWS(t, srv, "item-append-fail")
|
|
defer a.Close()
|
|
initialCursor(t, a)
|
|
|
|
if err := a.WriteMessage(websocket.BinaryMessage, []byte{yMessageSync, 0x02, 0x02, 0x01, 0x7f}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sendBarrier(t, a, 1)
|
|
readBarrierAck(t, a)
|
|
if got := obs.fails.Load(); got != 0 {
|
|
t.Fatalf("a clean append counted %d failures, want 0", got)
|
|
}
|
|
|
|
fake.mu.Lock()
|
|
fake.failOn = "db down"
|
|
fake.mu.Unlock()
|
|
if err := a.WriteMessage(websocket.BinaryMessage, []byte{yMessageSync, 0x02, 0x02, 0x02, 0x7f}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sendBarrier(t, a, 2)
|
|
if ack := readBarrierAck(t, a); ack.OK == nil || *ack.OK {
|
|
t.Fatalf("premise: the append did not fail (ack ok %s)", okString(ack.OK))
|
|
}
|
|
if got := obs.fails.Load(); got != 1 {
|
|
t.Fatalf("a failed append counted %d failures, want 1", got)
|
|
}
|
|
}
|