Transcode takeover is never invoked #21

Open
opened 2026-09-13 13:33:16 +10:00 by unkin-agent · 0 comments
Member

Context: multi-replica stateless deployment (two 2-replica apps, cheeztv and fafflix, each one PostgreSQL plus valkey, no sticky sessions). Not a 12.0 regression — live in the deployed build. Tracking: #12.

Problem

ITranscodeSessionStore.TryTakeoverAsync has no caller in any production path.

IsHaTakeoverAsync only calls TryGetAsync, and the resuming pod then overwrites the session record with SetAsync. That is not a claim: two pods can both conclude they own the same transcode session, and the record simply reflects whichever wrote last. The Redis store's takeover logic is implemented and unit-tested, but unreachable — so the tests pass while the behaviour they describe never runs.

Proposal

Wire TryTakeoverAsync into the takeover path in place of the TryGetAsync + SetAsync pair.

The substance of this issue is deciding what a pod does when it loses the claim. Options, each with a real cost:

  • 302 the client to the owning pod — correct and cheap, but needs pod-addressable URLs reachable by the client.
  • Wait and poll for the owner to finish or to stop heartbeating — no new networking, but adds latency and needs a bound.
  • Restart the transcode locally — simplest, accepts duplicated ffmpeg work and a second segment stream.

Pick one, document why, and make the losing path observable in logs.

Acceptance

  • A production code path calls TryTakeoverAsync; a test fails if the claim is performed with SetAsync instead.
  • Two independently constructed stores against the real backing store (not an in-memory fake): both attempt takeover of the same session, exactly one wins, and the loser follows the documented behaviour.
  • Where any part of this touches the database, the test runs against real PostgreSQL via PostgreSqlTestServer in the postgres-migration-chain Woodpecker step (JELLYFIN_TEST_POSTGRES). In-memory and fake stores have hidden different semantics — an unreachable-but-tested code path is exactly how this one was missed.
Context: multi-replica stateless deployment (two 2-replica apps, `cheeztv` and `fafflix`, each one PostgreSQL plus valkey, no sticky sessions). Not a 12.0 regression — live in the deployed build. Tracking: #12. ## Problem `ITranscodeSessionStore.TryTakeoverAsync` has no caller in any production path. `IsHaTakeoverAsync` only calls `TryGetAsync`, and the resuming pod then overwrites the session record with `SetAsync`. That is not a claim: two pods can both conclude they own the same transcode session, and the record simply reflects whichever wrote last. The Redis store's takeover logic is implemented and unit-tested, but unreachable — so the tests pass while the behaviour they describe never runs. ## Proposal Wire `TryTakeoverAsync` into the takeover path in place of the `TryGetAsync` + `SetAsync` pair. The substance of this issue is deciding what a pod does when it **loses** the claim. Options, each with a real cost: - 302 the client to the owning pod — correct and cheap, but needs pod-addressable URLs reachable by the client. - Wait and poll for the owner to finish or to stop heartbeating — no new networking, but adds latency and needs a bound. - Restart the transcode locally — simplest, accepts duplicated ffmpeg work and a second segment stream. Pick one, document why, and make the losing path observable in logs. ## Acceptance - A production code path calls `TryTakeoverAsync`; a test fails if the claim is performed with `SetAsync` instead. - Two independently constructed stores against the real backing store (not an in-memory fake): both attempt takeover of the same session, exactly one wins, and the loser follows the documented behaviour. - Where any part of this touches the database, the test runs against real PostgreSQL via `PostgreSqlTestServer` in the `postgres-migration-chain` Woodpecker step (`JELLYFIN_TEST_POSTGRES`). In-memory and fake stores have hidden different semantics — an unreachable-but-tested code path is exactly how this one was missed.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unkin/jellyfin-ha-src#21