Merge pull request #17536 from gnattu/fix-concurrent-racing

Fix concurrent ffmpeg segment racing
This commit is contained in:
Cody Robibero
2026-08-07 21:38:45 -04:00
committed by GitHub
4 changed files with 119 additions and 19 deletions
@@ -1456,22 +1456,16 @@ public class DynamicHlsController : BaseJellyfinApiController
var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
TranscodingJob? job;
if (System.IO.File.Exists(segmentPath))
{
job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
_logger.LogDebug("returning {0} [it exists, try 1]", segmentPath);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
}
// Keep segment selection and transcoding replacement under the same playlist lock.
// An out-of-order request must not replace a job while another request is using its output.
using (await _transcodeManager.LockAsync(playlistPath, cancellationToken).ConfigureAwait(false))
{
TranscodingJob? job;
var startTranscoding = false;
if (System.IO.File.Exists(segmentPath))
{
job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
_logger.LogDebug("returning {0} [it exists, try 2]", segmentPath);
_logger.LogDebug("returning {0} [it exists]", segmentPath);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
}
@@ -1505,6 +1499,9 @@ public class DynamicHlsController : BaseJellyfinApiController
// If the playlist doesn't already exist, startup ffmpeg
try
{
var currentJob = _transcodeManager.GetTranscodingJob(playlistPath, TranscodingJobType);
await WaitForActiveTranscodingRequests(currentJob, cancellationToken).ConfigureAwait(false);
await _transcodeManager.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false)
.ConfigureAwait(false);
@@ -1540,11 +1537,19 @@ public class DynamicHlsController : BaseJellyfinApiController
await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false);
}
}
}
_logger.LogDebug("returning {0} [general case]", segmentPath);
job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("returning {0} [general case]", segmentPath);
job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
}
}
internal static async Task WaitForActiveTranscodingRequests(TranscodingJob? job, CancellationToken cancellationToken)
{
while (job?.ActiveRequestCount > 0)
{
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
}
private static double[] GetSegmentLengths(StreamState state)