Files
jellyfin-ha-src/MediaBrowser.Controller/MediaEncoding/ITranscodeSessionStore.cs
T
unkin-agent baa16b6586 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
2026-09-12 10:19:29 +10:00

80 lines
3.9 KiB
C#

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.MediaEncoding;
/// <summary>
/// Provides a durable store for HLS transcoding session state, enabling
/// HA recovery and lease-based ownership between pods.
/// </summary>
public interface ITranscodeSessionStore
{
/// <summary>
/// Attempts to retrieve a transcoding session by its play session identifier.
/// </summary>
/// <param name="playSessionId">The play session identifier.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>
/// The <see cref="TranscodeSession"/> if it exists and its lease has not expired;
/// otherwise <c>null</c>.
/// </returns>
Task<TranscodeSession?> TryGetAsync(string playSessionId, CancellationToken cancellationToken = default);
/// <summary>
/// Attempts to take over ownership of an existing session by claiming the lease for
/// <paramref name="claimingPod"/>. Takeover succeeds only when the session exists and
/// its current lease has already expired.
/// </summary>
/// <param name="playSessionId">The play session identifier.</param>
/// <param name="claimingPod">The name of the pod attempting to claim ownership.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>
/// <c>true</c> if the takeover succeeded (the claiming pod now holds the lease);
/// <c>false</c> if the session does not exist, its lease is still valid, or another
/// concurrent caller already claimed it.
/// </returns>
Task<bool> TryTakeoverAsync(string playSessionId, string claimingPod, CancellationToken cancellationToken = default);
/// <summary>
/// Persists a new or updated transcoding session.
/// </summary>
/// <param name="session">The session to store.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task SetAsync(TranscodeSession session, CancellationToken cancellationToken = default);
/// <summary>
/// Renews the lease for an existing session, extending its
/// <see cref="TranscodeSession.LeaseExpiresUtc"/> by the store's configured lease duration.
/// The renewal is rejected when <paramref name="ownerPod"/> no longer owns the lease, so a
/// renewal in flight while another pod wins <see cref="TryTakeoverAsync"/> cannot revert the takeover.
/// </summary>
/// <param name="playSessionId">The play session identifier.</param>
/// <param name="ownerPod">The name of the pod that believes it owns the lease.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>
/// <c>true</c> if the lease was renewed; <c>false</c> if the session no longer exists or is
/// owned by another pod.
/// </returns>
Task<bool> RenewLeaseAsync(string playSessionId, string ownerPod, CancellationToken cancellationToken = default);
/// <summary>
/// Removes a transcoding session from the store.
/// </summary>
/// <param name="playSessionId">The play session identifier.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task DeleteAsync(string playSessionId, CancellationToken cancellationToken = default);
/// <summary>
/// Returns all currently active transcoding sessions from the store.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>
/// An enumerable of <see cref="TranscodeSession"/> objects representing all active sessions.
/// Returns an empty enumerable if no sessions are active or if the store cannot be reached.
/// </returns>
Task<IEnumerable<TranscodeSession>> GetActiveSessionsAsync(CancellationToken cancellationToken = default);
}