From 1466583b24c682fd7ee68aee67285361e3e2f7db Mon Sep 17 00:00:00 2001 From: Amir Fathi Date: Fri, 4 Sep 2026 10:33:17 +0000 Subject: [PATCH] fix: don't advance eventlog LastSeq() until the write is confirmed Log.Append incremented l.last before attempting the write, so a failed write left LastSeq() pointing past a gap that Since() answers as empty instead of unanswerable. Session.Attach trusts since<=LastSeq() as proof the log can serve a resume, so the documented snapshot fallback never fired and a reconnecting viewer was left silently stuck. Fixes #46 --- internal/eventlog/eventlog.go | 4 ++-- internal/eventlog/eventlog_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/internal/eventlog/eventlog.go b/internal/eventlog/eventlog.go index 0844c84..1cadfcc 100644 --- a/internal/eventlog/eventlog.go +++ b/internal/eventlog/eventlog.go @@ -74,8 +74,7 @@ func Open(path string) (*Log, error) { func (l *Log) Append(typ string, data []byte) (uint64, error) { l.mu.Lock() defer l.mu.Unlock() - l.last++ - e := Entry{Seq: l.last, Type: typ, Data: append([]byte(nil), data...), TS: time.Now().UnixMilli()} + e := Entry{Seq: l.last + 1, Type: typ, Data: append([]byte(nil), data...), TS: time.Now().UnixMilli()} b, err := json.Marshal(e) if err != nil { return 0, err @@ -86,6 +85,7 @@ func (l *Log) Append(typ string, data []byte) (uint64, error) { if err := l.w.Flush(); err != nil { return 0, err } + l.last = e.Seq l.entries = append(l.entries, e) return e.Seq, nil } diff --git a/internal/eventlog/eventlog_test.go b/internal/eventlog/eventlog_test.go index c8693ca..b96e0e7 100644 --- a/internal/eventlog/eventlog_test.go +++ b/internal/eventlog/eventlog_test.go @@ -85,3 +85,33 @@ func TestOpenTruncatesCorruptTail(t *testing.T) { } } } + +// A failed Append must not advance LastSeq() past a write Since() cannot answer. +func TestFailedAppendDoesNotAdvanceLastSeq(t *testing.T) { + p := filepath.Join(t.TempDir(), "s.log") + l, err := Open(p) + if err != nil { + t.Fatal(err) + } + if s, err := l.Append("output", []byte("a")); err != nil || s != 1 { + t.Fatalf("first Append = %d, %v, want 1, nil", s, err) + } + + // Close the underlying file to force the next write to fail deterministically. + l.f.Close() + + if _, err := l.Append("output", []byte("b")); err == nil { + t.Fatal("Append over a closed file returned nil error, want a write failure") + } + + if got := l.LastSeq(); got != 1 { + t.Fatalf("LastSeq() after failed Append = %d, want 1 (unchanged)", got) + } + got, err := l.Since(0) + if err != nil { + t.Fatal(err) + } + if len(got) != 1 || got[0].Seq != 1 { + t.Fatalf("Since(0) after failed Append = %+v, want exactly seq 1", got) + } +}