fix(playback): make session liveness server-observed
Client progress reports could keep a zero-byte phantom session alive and, worse, make it outrank a genuinely-serving stream when the enforcer picked over-cap victims. `streammonitor.LiveLocalSessions` substituted `Session.LastActivityAt` for a zero `LastServedAt`, and `LastActivityAt` is advanced by `UpdateProgress` and by the realtime WebSocket hello/ack/result handlers. Since `streamenforcer.selectVictims` keeps the `limit` most-recently-served streams, a progress-only phantom sorted ahead of a real stream and the real one was trimmed instead. Reaping had the same root cause: `sessionIsInactiveLocked` keyed idleness on `LastActivityAt`, so a client that kept pinging held a session open forever. Per decision A5 (Option C), client progress is now UI metadata only and never feeds enforcement or reaping: - `LiveLocalSessions` projects `LastServedAt` verbatim, emitting an empty timestamp when the session has never served, so it sorts as the stalest over-cap victim. - `sessionIsInactiveLocked` measures idleness from `LastServedAt`, falling back only to `StartedAt`. - A configurable never-served window (`DefaultUnservedSessionGrace`, 2m, via `SetUnservedSessionGrace`) keeps a legitimately slow start from being reaped before its first byte, without granting a phantom unbounded life. It is a separate knob rather than a hardcoded floor so it cannot silently override `SetLivenessGracePeriods`. In-flight transports remain exempt, so direct-play and remux long pours and per-segment HLS serves are unaffected. Paused sessions with an open realtime/WebSocket connection are exempt from reaping. That preserves the issue #243 fix (reaping a paused transcode froze clients) while staying within Option C: an open, ping-checked connection is server-observed, unlike a client's reported progress, and the session still consumes one of the user's cap slots. Part 1 of 3 for the Batch 4 liveness/replica work. Part of #305
This commit is contained in:
@@ -142,6 +142,47 @@ func TestEvaluateOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateOnceProgressOnlySessionIsVictimBeforeServingStreams(t *testing.T) {
|
||||
sm := playback.NewSessionManager(0, 0)
|
||||
phantom, err := sm.StartSession(7, "profile", 1, playback.PlayDirect, false)
|
||||
if err != nil {
|
||||
t.Fatalf("StartSession(phantom): %v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
session, startErr := sm.StartSession(7, "profile", 10+i, playback.PlayDirect, false)
|
||||
if startErr != nil {
|
||||
t.Fatalf("StartSession(serving %d): %v", i, startErr)
|
||||
}
|
||||
if beginErr := sm.BeginTransport(session.ID); beginErr != nil {
|
||||
t.Fatalf("BeginTransport(serving %d): %v", i, beginErr)
|
||||
}
|
||||
if bytesErr := sm.AddServedBytes(session.ID, 1024); bytesErr != nil {
|
||||
t.Fatalf("AddServedBytes(serving %d): %v", i, bytesErr)
|
||||
}
|
||||
if endErr := sm.EndTransport(session.ID); endErr != nil {
|
||||
t.Fatalf("EndTransport(serving %d): %v", i, endErr)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Until(time.Now().Truncate(time.Second).Add(time.Second)) + 10*time.Millisecond)
|
||||
if err := sm.UpdateProgress(phantom.ID, 30, false); err != nil {
|
||||
t.Fatalf("UpdateProgress(phantom): %v", err)
|
||||
}
|
||||
|
||||
source := streammonitor.NewFuncSource(func(context.Context) ([]nodesessions.SessionInfo, error) {
|
||||
return streammonitor.LiveLocalSessions(sm, "local"), nil
|
||||
})
|
||||
rev := &fakeRevoker{}
|
||||
e := New(source, func(context.Context, int) (int, error) { return 2, nil }, rev, 0)
|
||||
if err := e.EvaluateOnce(context.Background()); err != nil {
|
||||
t.Fatalf("EvaluateOnce: %v", err)
|
||||
}
|
||||
if len(rev.revoked) != 1 || rev.revoked[0] != phantom.ID {
|
||||
t.Fatalf("revoked = %v, want progress-only phantom %q", rev.revoked, phantom.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateOnceSourceError(t *testing.T) {
|
||||
rev := &fakeRevoker{}
|
||||
e := New(fakeSource{err: errors.New("scan failed")}, func(context.Context, int) (int, error) { return 1, nil }, rev, 0)
|
||||
|
||||
Reference in New Issue
Block a user