fix: make transcode leases ownership-checked and cleanup-aware
Cleanup never matched a live session because the controller registered empty manifest and segment paths, renewal was a read-modify-write that could revert a takeover, and the takeover script compared an ISO date to a number, so it errored. - populate the session record's manifest and segment paths from the playlist path - renew the lease via a Lua compare-and-set on the owning pod - store the lease expiry as unix milliseconds so the scripts can compare it - retain the session record past its lease so an orphan can still be taken over - test the Redis store against a real Redis, including the renew-vs-takeover race - drop the live stream record nothing ever read back
This commit is contained in:
+11
-9
@@ -32,21 +32,24 @@ auth or plugin logic is rewritten.
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `MediaBrowser.Controller/MediaEncoding/ITranscodeSessionStore.cs` | DI contract for durable transcode and live stream session tracking |
|
||||
| `MediaBrowser.Controller/MediaEncoding/ITranscodeSessionStore.cs` | DI contract for durable transcode session tracking |
|
||||
| `MediaBrowser.Controller/MediaEncoding/TranscodeSession.cs` | Session record: owning pod, lease expiry, manifest and segment paths, last durable segment |
|
||||
| `MediaBrowser.Controller/MediaEncoding/LiveStreamSession.cs` | Record of an open live stream and the pod holding it |
|
||||
| `MediaBrowser.Controller/MediaEncoding/TranscodeStoreOptions.cs` | `RedisConnectionString` and `LeaseDurationSeconds` |
|
||||
| `MediaBrowser.Controller/MediaEncoding/TranscodeStoreOptions.cs` | `RedisConnectionString`, `LeaseDurationSeconds` and `SessionRetentionSeconds` |
|
||||
| `MediaBrowser.Controller/MediaEncoding/NullTranscodeSessionStore.cs` | No-op store used when no Redis connection is configured |
|
||||
| `Emby.Server.Implementations/MediaEncoding/RedisTranscodeSessionStore.cs` | Redis store; sessions under `jellyfin:transcode:{playSessionId}`, live streams under `jellyfin:livestream:{liveStreamId}:{sessionId}`, key TTL mirrors the lease |
|
||||
| `Emby.Server.Implementations/MediaEncoding/RedisTranscodeSessionStore.cs` | Redis store; sessions under `jellyfin:transcode:{playSessionId}`, key TTL is the retention window so an orphaned session outlives its lease |
|
||||
|
||||
Lease takeover runs as a single Lua script, so concurrent pods cannot both claim
|
||||
an expired lease:
|
||||
Lease takeover and renewal each run as a single Lua script, so concurrent pods cannot both
|
||||
claim an expired lease and a renewal cannot revert a takeover. The expiry is stored as unix
|
||||
milliseconds so the script can compare it:
|
||||
|
||||
```lua
|
||||
local raw = redis.call('GET', KEYS[1])
|
||||
if not raw then return 0 end
|
||||
local session = cjson.decode(raw)
|
||||
if session['LeaseExpiresUtc'] > tonumber(ARGV[1]) then return 0 end
|
||||
-- takeover: only an expired lease may be claimed
|
||||
if tonumber(session['LeaseExpiresUtc']) > tonumber(ARGV[1]) then return 0 end
|
||||
-- renewal: only the pod that still owns the lease may extend it
|
||||
-- if session['OwnerPod'] ~= ARGV[2] then return 0 end
|
||||
session['OwnerPod'] = ARGV[2]
|
||||
-- update expiry and SET with PX in the same script
|
||||
return 1
|
||||
@@ -84,9 +87,8 @@ optionally uploading a pre-migration copy of the SQLite file to S3.
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `Jellyfin.Server/CoreAppHost.cs` (+46) | Registers the Redis or null transcode store and scan-leader lease from startup config |
|
||||
| `Jellyfin.Api/Controllers/DynamicHlsController.cs` (+133/-9) | Registers the play session, runs lease renewal, and shortens segments when resuming a stored session |
|
||||
| `Jellyfin.Api/Controllers/DynamicHlsController.cs` | Registers the play session with its manifest and segment paths, renews the lease under this pod's identity, and shortens segments when resuming a stored session |
|
||||
| `Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs` (+52/-6) | Keeps files belonging to an active session in the store |
|
||||
| `Emby.Server.Implementations/Session/SessionManager.cs` (+37/-4) | Persists and deletes the durable live stream record |
|
||||
| `Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs` (+28/-1) | Skips timer-driven gated tasks without the leader lease |
|
||||
| `Emby.Server.Implementations/ScheduledTasks/TaskManager.cs` (+12/-2) | Passes the lease and options to each worker |
|
||||
| `Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs` (+53) | Registers the PostgreSQL provider and a pooled `NpgsqlDataSource` |
|
||||
|
||||
Reference in New Issue
Block a user