refactor(hls): critico-driven hardening of fase 3.2
Addresses items raised by the multi-agent code review of the 0.9.9 HW accel + first-start work: - EncoderProfile now carries DecodeHwAccel so the demuxer `-hwaccel` flag and the encoder argv derive from a single resolved profile. Adding a new backend can no longer leave the two switches out of sync. - VAAPI no longer passes `-hwaccel_output_format vaapi`. That option pinned decoded frames to GPU memory, but the filter chain (scale, format, setparams) runs on CPU and would fail with "impossible to convert between formats". Frames now decode HW + flow on CPU; the encoder uploads back to GPU. Pre-existing bug, never reported because no one had VAAPI auto-detected in practice. - readyMax field comment + name: documented that it's a COUNT (segments ready), not an index. The semantics were correct but the comment read "highest index" which made `idx < readyMax` look like an off-by-one to reviewers. - probe_cache background janitor: 5-minute sweeper that drops expired entries even when no lookup retouches the key. Lookup-only eviction was fine for small libraries but unbounded for users who browse and abandon thousands of files within a TTL window. Lazy + sync.Once. - probe_cache TTL eviction now re-checks under the write lock so a concurrent re-insert isn't accidentally evicted. - probe_cache size-change test now Chtimes the file back to its original mtime so only `size` differs between store and lookup keys — properly exercises the size-check path. - New TestProbeCache_SweepDropsExpired covers the janitor sweep. - CHANGELOG: backfilled missing compare links 0.6.4 → 0.9.9. - Stale "line ~1119" reference in VideoToolbox comment dropped; the bitrate block moved a few lines and the comment was already wrong.
This commit is contained in:
parent
0f4ad67827
commit
bf8ed0d928
5 changed files with 181 additions and 47 deletions
|
|
@ -73,15 +73,24 @@ func TestProbeCache_SizeChangeInvalidates(t *testing.T) {
|
|||
if err := os.WriteFile(path, []byte("aaaaa"), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
originalMtime := time.Now().Add(-1 * time.Hour) // stable, in the past
|
||||
if err := os.Chtimes(path, originalMtime, originalMtime); err != nil {
|
||||
t.Fatalf("chtimes original: %v", err)
|
||||
}
|
||||
|
||||
probe := &StreamProbe{VideoCodec: "h264", DurationSec: 100}
|
||||
storeProbeCache(path, probe)
|
||||
|
||||
// Truncate file to a different size + reset mtime to the original (to
|
||||
// isolate the size-check path). Stat picks up new size immediately.
|
||||
// Truncate to a different size, then reset mtime to the original so
|
||||
// only `size` differs between store and lookup keys — isolates the
|
||||
// size-check path. Without the Chtimes, WriteFile bumps mtime and the
|
||||
// test would pass via mtime invalidation regardless of size logic.
|
||||
if err := os.WriteFile(path, []byte("a"), 0o644); err != nil {
|
||||
t.Fatalf("rewrite: %v", err)
|
||||
}
|
||||
if err := os.Chtimes(path, originalMtime, originalMtime); err != nil {
|
||||
t.Fatalf("chtimes restore: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := lookupProbeCache(path); ok {
|
||||
t.Fatal("expected MISS after size change")
|
||||
|
|
@ -152,3 +161,42 @@ func TestProbeCache_StoreNonexistentNoOp(t *testing.T) {
|
|||
t.Fatalf("expected 0 entries; got %d", ProbeCacheSize())
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeCache_SweepDropsExpired(t *testing.T) {
|
||||
ResetProbeCache()
|
||||
t.Cleanup(ResetProbeCache)
|
||||
|
||||
dir := t.TempDir()
|
||||
// Two entries: one expired, one fresh.
|
||||
expiredPath := filepath.Join(dir, "old.mkv")
|
||||
freshPath := filepath.Join(dir, "new.mkv")
|
||||
if err := os.WriteFile(expiredPath, []byte("a"), 0o644); err != nil {
|
||||
t.Fatalf("write expired: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(freshPath, []byte("b"), 0o644); err != nil {
|
||||
t.Fatalf("write fresh: %v", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
fiExp, _ := os.Stat(expiredPath)
|
||||
fiFresh, _ := os.Stat(freshPath)
|
||||
|
||||
probeCacheMu.Lock()
|
||||
probeCache[probeCacheKey{path: expiredPath, mtime: fiExp.ModTime().UnixNano(), size: fiExp.Size()}] = probeCacheEntry{
|
||||
probe: &StreamProbe{VideoCodec: "h264"},
|
||||
expires: now.Add(-1 * time.Minute), // expired
|
||||
}
|
||||
probeCache[probeCacheKey{path: freshPath, mtime: fiFresh.ModTime().UnixNano(), size: fiFresh.Size()}] = probeCacheEntry{
|
||||
probe: &StreamProbe{VideoCodec: "h264"},
|
||||
expires: now.Add(10 * time.Minute), // fresh
|
||||
}
|
||||
probeCacheMu.Unlock()
|
||||
|
||||
removed := sweepProbeCache(now)
|
||||
if removed != 1 {
|
||||
t.Fatalf("expected 1 expired entry removed; got %d", removed)
|
||||
}
|
||||
if ProbeCacheSize() != 1 {
|
||||
t.Fatalf("expected 1 fresh entry kept; got %d", ProbeCacheSize())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue