fix: remove context reaper to prevent background tabs from going stale (#4)
Some checks failed
CI / Build and Test (push) Failing after 35s
Some checks failed
CI / Build and Test (push) Failing after 35s
This commit was merged in pull request #4.
This commit is contained in:
157
via_test.go
157
via_test.go
@@ -400,95 +400,6 @@ func TestPage_PanicsOnNoView(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestReaperCleansOrphanedContexts(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("orphan-1", "/", v)
|
||||
c.createdAt = time.Now().Add(-time.Minute) // created 1 min ago
|
||||
v.registerCtx(c)
|
||||
|
||||
_, err := v.getCtx("orphan-1")
|
||||
assert.NoError(t, err)
|
||||
|
||||
v.reapOrphanedContexts(5*time.Second, 10*time.Second)
|
||||
|
||||
_, err = v.getCtx("orphan-1")
|
||||
assert.Error(t, err, "orphaned context should have been reaped")
|
||||
}
|
||||
|
||||
func TestReaperSuspendsContext(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("suspend-1", "/", v)
|
||||
c.createdAt = time.Now().Add(-30 * time.Minute)
|
||||
dc := time.Now().Add(-20 * time.Minute)
|
||||
c.sseDisconnectedAt.Store(&dc)
|
||||
v.registerCtx(c)
|
||||
|
||||
v.reapOrphanedContexts(15*time.Minute, time.Hour)
|
||||
|
||||
got, err := v.getCtx("suspend-1")
|
||||
assert.NoError(t, err, "suspended context should still be in registry")
|
||||
assert.True(t, got.suspended.Load(), "context should be marked suspended")
|
||||
}
|
||||
|
||||
func TestReaperReapsAfterTTL(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("reap-1", "/", v)
|
||||
c.createdAt = time.Now().Add(-3 * time.Hour)
|
||||
dc := time.Now().Add(-2 * time.Hour)
|
||||
c.sseDisconnectedAt.Store(&dc)
|
||||
c.suspended.Store(true)
|
||||
v.registerCtx(c)
|
||||
|
||||
v.reapOrphanedContexts(15*time.Minute, time.Hour)
|
||||
|
||||
_, err := v.getCtx("reap-1")
|
||||
assert.Error(t, err, "context past TTL should have been reaped")
|
||||
}
|
||||
|
||||
func TestReaperIgnoresAlreadySuspended(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("already-sus-1", "/", v)
|
||||
c.createdAt = time.Now().Add(-30 * time.Minute)
|
||||
dc := time.Now().Add(-20 * time.Minute)
|
||||
c.sseDisconnectedAt.Store(&dc)
|
||||
c.suspended.Store(true)
|
||||
// give it a fresh pageStopChan so we can verify it's not re-closed
|
||||
c.pageStopChan = make(chan struct{})
|
||||
v.registerCtx(c)
|
||||
|
||||
v.reapOrphanedContexts(15*time.Minute, time.Hour)
|
||||
|
||||
got, err := v.getCtx("already-sus-1")
|
||||
assert.NoError(t, err, "already-suspended context within TTL should survive")
|
||||
assert.True(t, got.suspended.Load())
|
||||
// pageStopChan should still be open (not re-suspended)
|
||||
select {
|
||||
case <-got.pageStopChan:
|
||||
t.Fatal("pageStopChan was closed — context was re-suspended")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaperIgnoresConnectedContexts(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("connected-1", "/", v)
|
||||
c.createdAt = time.Now().Add(-time.Minute)
|
||||
c.sseConnected.Store(true)
|
||||
v.registerCtx(c)
|
||||
|
||||
v.reapOrphanedContexts(5*time.Second, 10*time.Second)
|
||||
|
||||
_, err := v.getCtx("connected-1")
|
||||
assert.NoError(t, err, "connected context should survive reaping")
|
||||
}
|
||||
|
||||
func TestReaperDisabledWithNegativeTTL(t *testing.T) {
|
||||
v := New()
|
||||
v.cfg.ContextTTL = -1
|
||||
v.startReaper()
|
||||
assert.Nil(t, v.reaperStop, "reaper should not start with negative TTL")
|
||||
}
|
||||
|
||||
func TestCleanupCtxIdempotent(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("idempotent-1", "/", v)
|
||||
@@ -503,74 +414,6 @@ func TestCleanupCtxIdempotent(t *testing.T) {
|
||||
assert.Error(t, err, "context should be removed after cleanup")
|
||||
}
|
||||
|
||||
func TestReaperRespectsLastSeenAt(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("seen-1", "/", v)
|
||||
c.createdAt = time.Now().Add(-30 * time.Minute)
|
||||
// Disconnected 20 min ago, but client retried (lastSeenAt) 2 min ago
|
||||
dc := time.Now().Add(-20 * time.Minute)
|
||||
c.sseDisconnectedAt.Store(&dc)
|
||||
seen := time.Now().Add(-2 * time.Minute)
|
||||
c.lastSeenAt.Store(&seen)
|
||||
v.registerCtx(c)
|
||||
|
||||
v.reapOrphanedContexts(15*time.Minute, time.Hour)
|
||||
|
||||
_, err := v.getCtx("seen-1")
|
||||
assert.NoError(t, err, "context with recent lastSeenAt should survive suspend threshold")
|
||||
assert.False(t, c.suspended.Load(), "context should not be suspended")
|
||||
}
|
||||
|
||||
func TestReaperFallsBackWithoutLastSeenAt(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("noseen-1", "/", v)
|
||||
c.createdAt = time.Now().Add(-30 * time.Minute)
|
||||
dc := time.Now().Add(-20 * time.Minute)
|
||||
c.sseDisconnectedAt.Store(&dc)
|
||||
// no lastSeenAt set — should fall back to sseDisconnectedAt
|
||||
v.registerCtx(c)
|
||||
|
||||
v.reapOrphanedContexts(15*time.Minute, time.Hour)
|
||||
|
||||
got, err := v.getCtx("noseen-1")
|
||||
assert.NoError(t, err, "context should still be in registry (suspended, not reaped)")
|
||||
assert.True(t, got.suspended.Load(), "context should be suspended using sseDisconnectedAt fallback")
|
||||
}
|
||||
|
||||
func TestReaperReapsWithStaleLastSeenAt(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("stale-seen-1", "/", v)
|
||||
c.createdAt = time.Now().Add(-3 * time.Hour)
|
||||
dc := time.Now().Add(-2 * time.Hour)
|
||||
c.sseDisconnectedAt.Store(&dc)
|
||||
// lastSeenAt is also old — beyond TTL
|
||||
seen := time.Now().Add(-90 * time.Minute)
|
||||
c.lastSeenAt.Store(&seen)
|
||||
c.suspended.Store(true)
|
||||
v.registerCtx(c)
|
||||
|
||||
v.reapOrphanedContexts(15*time.Minute, time.Hour)
|
||||
|
||||
_, err := v.getCtx("stale-seen-1")
|
||||
assert.Error(t, err, "context with stale lastSeenAt beyond TTL should be reaped")
|
||||
}
|
||||
|
||||
func TestLastSeenAtUpdatedOnSSEConnect(t *testing.T) {
|
||||
v := New()
|
||||
c := newContext("seen-sse-1", "/", v)
|
||||
v.registerCtx(c)
|
||||
|
||||
assert.Nil(t, c.lastSeenAt.Load(), "lastSeenAt should be nil before SSE connect")
|
||||
|
||||
// Simulate what the SSE handler does after getCtx
|
||||
now := time.Now()
|
||||
c.lastSeenAt.Store(&now)
|
||||
|
||||
got := c.lastSeenAt.Load()
|
||||
assert.NotNil(t, got, "lastSeenAt should be set after SSE connect")
|
||||
assert.WithinDuration(t, now, *got, time.Second)
|
||||
}
|
||||
|
||||
func TestDevModeRemovePersistedFix(t *testing.T) {
|
||||
v := New()
|
||||
v.cfg.DevMode = true
|
||||
|
||||
Reference in New Issue
Block a user