b1d5fbefcba02fdc6c19fef85cec1c4e82fc8dea
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1ce5743bc1 |
fix(539): WorkAheadSlots.Release clamps before decrementing, reports unbalance in-band
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
PR Gates / Docs update reminder (pull_request) Successful in 15s
PR Gates / decisions lifecycle (pull_request) Successful in 16s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 5m31s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 20m12s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 20m41s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Three Low findings from the #536 clamp re-review, unreachable today (one guarded release site) but filed against the day a second release site is added. - §1: Release() now reads the count and CAS-decrements only when current > 0, so it never publishes a negative count even transiently. The prior decrement-first-then-clamp shape dipped to -1, which a concurrent TryAcquire could read as phantom room and over-admit at the limit (re-opening the #529 QSV pool exhaustion). It records the unbalanced release synchronously on the offending thread rather than blaming a later innocent release. - §3: Release() returns bool; HlsSessionWorker logs a warning on the false (unbalanced) return — the one in-band signal a future second release site would need. WorkAheadSlots stays logger-free by design. - §2: UnbalancedReleases doc-comment corrected — it can under-count (an over-release while count > 0 cancels a coexisting leak and goes unrecorded); no false positives, but zero does not prove correctness. Test: Release_Unbalanced_NeverPublishesNegativeCount (2M unbalanced releases vs 4 count-samplers) with a documented, verified negative control (reverting to the decrement-first body makes readers observe the transient -1). Adds a decisions.md entry (ffmpeg.work-ahead-slot-release-never-negative). fixes #539 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5db0836d41 |
fix(536): clamp an unbalanced work-ahead release instead of going negative
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 13s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 14s
Build ErsatzTV Image / decisions lifecycle (pull_request) Successful in 14s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 18s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 15m10s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 19m37s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Pre-push adversarial review, Low finding. The pool is process-wide and lives for the life of the app, so a `Release()` not matched by a successful `TryAcquire` would drive the count to -1 permanently: with a limit of 1 that silently admits two unthrottled transcodes forever, which is exactly the #529 QSV pool exhaustion with nothing in the logs to find it by. Clamp at zero and record the breakage in `UnbalancedReleases` rather than throwing — the sole caller releases from a `finally`, where a throw would swallow the real exception. The hammer tests now also assert `UnbalancedReleases == 0`, so the clamp cannot mask drift it was added to survive. Also moves the #536 index line to the end of the in-file decisions index (it was inserted in the 2026-07-11 block while its body appends at the end) — review nit, anchors were already correct. refs #536 |
||
|
|
c0d3dab190 |
fix(536): enforce workAheadSegmenterLimit with an atomic slot claim [decisions-edit]
The slot check and its increment straddled an await: `Run` compared `Volatile.Read(ref _workAheadCount)` against a DB-backed limit, and the increment happened later inside `Transcode`. Every simultaneous tune-in therefore observed `0 < limit` and started unthrottled — three concurrent tunes on prod with a limit of 1 all ran with no `-readrate`. `Interlocked` on the write side alone buys nothing when the read side is a separate, earlier load (same class as #231/#250). Extract the counter into a `WorkAheadSlots` pool whose `TryAcquire(limit)` claims via compare-exchange, so the count never even transiently exceeds the limit that the QSV hardware-frame pool sizing (#529) is derived from. `Run` claims the slot and passes ownership in; `Transcode(bool ownsWorkAheadSlot, ...)` derives `realtime` from it and releases it in its existing `finally`, keeping acquire/release one-for-one. Acquisition stays in the caller because `Run` sets `_state` from the outcome and `Transcode` reads that state on entry to pick the item start time. Tests hammer 8 threads x 20k rounds (a single Barrier round does not collide on this hardware); the documented negative control reinstates the check-then-act body and produces 15912 over-claiming rounds of 20000. Also annotates the #350 decision record, whose "every concurrent tune-in falls back to the throttled path" bullet described the intent rather than the behaviour. fixes #536 |