Skip to content

fix(dvr): bound the timeshift window so one request can't OOM the host#16

Merged
ntt0601zcoder merged 1 commit into
mainfrom
fix/dvr-timeshift-bound
Jun 13, 2026
Merged

fix(dvr): bound the timeshift window so one request can't OOM the host#16
ntt0601zcoder merged 1 commit into
mainfrom
fix/dvr-timeshift-bound

Conversation

@ntt0601zcoder

Copy link
Copy Markdown
Owner

Problem (audit A-2, HIGH)

Reader.Query resolved the timeshift window end to +inf (1<<62) whenever dur was absent (the common case) or non-positive. The hour filter then admitted every hour and the loop built a FragmentRef per fragment for the whole archive — and ServeMPD repeats this for every profile. So a single unauthenticated GET /<code>/index.mpd?from=0 (or ?ago=<huge>) allocated + read hundreds of MB on a multi-day, multi-profile archive; a few concurrent requests OOM-kill the shared process and take down every stream on the host. The 120 s request timeout couldn't abort it — Query held no context.

Fix

  • resolveWindowBounds (pure, table-tested): clamp depth to MaxTimeshiftWindow (24h default; a package var so ops can tune / tests can shrink) when dur is absent / ≤0 / oversized, and pull from up to the archive's earliest hour (earliestHourMs) so from=0 / ago=<huge> can't anchor at the epoch and admit every hour.
  • The open-ended 1<<62 sentinel is gone — endMs/endTicks are always finite (pairAudio/snapVideo unchanged, just always take the bounded path).
  • Query now takes context.Context and checks ctx.Err() at the top of the hour loop, so the server timeout + client disconnect abort a long scan. Both handler call sites (ServeTimeshift, ServeMPD's per-profile loop) pass r.Context().

Tests

TestResolveWindowBounds (clamp table: open-ended/negative/oversized dur → capped to max; from-below-earliest → pulled up; in-range dur preserved) and TestBlobReader_QueryClampsAndCancels (a from=0 query still returns the archive via the earliest-hour clamp; a cancelled context aborts the scan with context.Canceled).

go test -race ./internal/dvr/blob/ green, golangci-lint 0 issues, full go build ./... green.

Note

Also marks B-3 ✅ fixed-by-A-3 in the audit (doc-only): the codec-aware AV-path keyframe gate + hevc/hevc_cuvid decoder rebuild from fix/decoder-codec-aware already make HEVC AV transcode decode instead of silently dropping every packet.

Reader.Query resolved the timeshift window end to +inf whenever `dur` was
absent (the common case) or non-positive: the hour filter then admitted every
hour and the loop built a FragmentRef per fragment for the WHOLE archive — and
ServeMPD does this for EVERY profile. A single unauthenticated request like
GET /<code>/index.mpd?from=0 (or ?ago=<huge>) therefore allocated and read
hundreds of MB on a multi-day, multi-profile archive, and a few concurrent
requests OOM-kill the shared process, taking down every stream on the host.
The 120 s request timeout couldn't help — Query held no context.

Resolve the window through resolveWindowBounds: clamp the depth to
MaxTimeshiftWindow (24h default, a package var so ops can tune and tests can
shrink it) when dur is absent/non-positive/oversized, and pull the start UP to
the archive's earliest hour so from=0 / ago=<huge> can't anchor at the epoch
and admit every hour. The open-ended 1<<62 sentinel is gone — the end bound is
always finite. Query now takes a context.Context and checks ctx.Err() at the
top of the hour loop, so the server timeout and client disconnects abort a
long scan; both handler call sites pass r.Context().

Tests: TestResolveWindowBounds (clamp table — open-ended/oversized dur capped,
from-below-earliest pulled up) and TestBlobReader_QueryClampsAndCancels
(from=0 still returns the archive via the earliest-hour clamp; a cancelled
context aborts the scan).

Also marks B-3 fixed-by-A-3 in the audit: the codec-aware AV-path keyframe
gate + hevc/hevc_cuvid decoder rebuild already make HEVC AV transcode work, so
the "silent zero output" path is resolved (doc-only update here).
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 72.72727% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/dvr/blob/reader.go 80.00% 2 Missing and 2 partials ⚠️
internal/api/handler/blob_timeshift.go 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@ntt0601zcoder ntt0601zcoder merged commit 3889e6c into main Jun 13, 2026
4 checks passed
@ntt0601zcoder ntt0601zcoder deleted the fix/dvr-timeshift-bound branch June 13, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants