Throw OperationCanceledException when processing is cancelled

This commit is contained in:
Malte Rosenbjerg 2022-04-15 15:08:30 +02:00
parent 6c752e4edb
commit 4f38eed753
4 changed files with 96 additions and 40 deletions

View file

@ -113,13 +113,11 @@ public void Video_ToMP4_Args_Pipe_DifferentImageSizes()
}; };
var videoFramesSource = new RawVideoPipeSource(frames); var videoFramesSource = new RawVideoPipeSource(frames);
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments var ex = Assert.ThrowsException<FFMpegStreamFormatException>(() => FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264)) .WithVideoCodec(VideoCodec.LibX264))
.ProcessSynchronously()); .ProcessSynchronously());
Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException));
} }
@ -135,13 +133,11 @@ public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async()
}; };
var videoFramesSource = new RawVideoPipeSource(frames); var videoFramesSource = new RawVideoPipeSource(frames);
var ex = await Assert.ThrowsExceptionAsync<FFMpegException>(() => FFMpegArguments var ex = await Assert.ThrowsExceptionAsync<FFMpegStreamFormatException>(() => FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264)) .WithVideoCodec(VideoCodec.LibX264))
.ProcessAsynchronously()); .ProcessAsynchronously());
Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException));
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
@ -156,13 +152,11 @@ public void Video_ToMP4_Args_Pipe_DifferentPixelFormats()
}; };
var videoFramesSource = new RawVideoPipeSource(frames); var videoFramesSource = new RawVideoPipeSource(frames);
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments var ex = Assert.ThrowsException<FFMpegStreamFormatException>(() => FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264)) .WithVideoCodec(VideoCodec.LibX264))
.ProcessSynchronously()); .ProcessSynchronously());
Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException));
} }
@ -178,13 +172,11 @@ public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async()
}; };
var videoFramesSource = new RawVideoPipeSource(frames); var videoFramesSource = new RawVideoPipeSource(frames);
var ex = await Assert.ThrowsExceptionAsync<FFMpegException>(() => FFMpegArguments var ex = await Assert.ThrowsExceptionAsync<FFMpegStreamFormatException>(() => FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264)) .WithVideoCodec(VideoCodec.LibX264))
.ProcessAsynchronously()); .ProcessAsynchronously());
Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException));
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
@ -596,6 +588,27 @@ public async Task Video_Cancel_Async()
Assert.IsFalse(result); Assert.IsFalse(result);
} }
[TestMethod, Timeout(10000)]
public void Video_Cancel()
{
var outputFile = new TemporaryFile("out.mp4");
var task = FFMpegArguments
.FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
.WithCustomArgument("-re")
.ForceFormat("lavfi"))
.OutputToFile(outputFile, false, opt => opt
.WithAudioCodec(AudioCodec.Aac)
.WithVideoCodec(VideoCodec.LibX264)
.WithSpeedPreset(Speed.VeryFast))
.CancellableThrough(out var cancel);
Task.Delay(300).ContinueWith((_) => cancel());
var result = task.ProcessSynchronously(false);
Assert.IsFalse(result);
}
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public async Task Video_Cancel_Async_With_Timeout() public async Task Video_Cancel_Async_With_Timeout()
{ {
@ -615,11 +628,10 @@ public async Task Video_Cancel_Async_With_Timeout()
await Task.Delay(300); await Task.Delay(300);
cancel(); cancel();
var result = await task; await task;
var outputInfo = await FFProbe.AnalyseAsync(outputFile); var outputInfo = await FFProbe.AnalyseAsync(outputFile);
Assert.IsTrue(result);
Assert.IsNotNull(outputInfo); Assert.IsNotNull(outputInfo);
Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width); Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width);
Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height); Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height);
@ -645,14 +657,58 @@ public async Task Video_Cancel_CancellationToken_Async()
.CancellableThrough(cts.Token) .CancellableThrough(cts.Token)
.ProcessAsynchronously(false); .ProcessAsynchronously(false);
await Task.Delay(300); cts.CancelAfter(300);
cts.Cancel();
var result = await task; var result = await task;
Assert.IsFalse(result); Assert.IsFalse(result);
} }
[TestMethod, Timeout(10000)]
public async Task Video_Cancel_CancellationToken_Async_Throws()
{
var outputFile = new TemporaryFile("out.mp4");
var cts = new CancellationTokenSource();
var task = FFMpegArguments
.FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
.WithCustomArgument("-re")
.ForceFormat("lavfi"))
.OutputToFile(outputFile, false, opt => opt
.WithAudioCodec(AudioCodec.Aac)
.WithVideoCodec(VideoCodec.LibX264)
.WithSpeedPreset(Speed.VeryFast))
.CancellableThrough(cts.Token)
.ProcessAsynchronously();
cts.CancelAfter(300);
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => task);
}
[TestMethod, Timeout(10000)]
public void Video_Cancel_CancellationToken_Throws()
{
var outputFile = new TemporaryFile("out.mp4");
var cts = new CancellationTokenSource();
var task = FFMpegArguments
.FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
.WithCustomArgument("-re")
.ForceFormat("lavfi"))
.OutputToFile(outputFile, false, opt => opt
.WithAudioCodec(AudioCodec.Aac)
.WithVideoCodec(VideoCodec.LibX264)
.WithSpeedPreset(Speed.VeryFast))
.CancellableThrough(cts.Token);
cts.CancelAfter(300);
Assert.ThrowsException<OperationCanceledException>(() => task.ProcessSynchronously());
}
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public async Task Video_Cancel_CancellationToken_Async_With_Timeout() public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
{ {
@ -671,14 +727,12 @@ public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
.CancellableThrough(cts.Token, 8000) .CancellableThrough(cts.Token, 8000)
.ProcessAsynchronously(false); .ProcessAsynchronously(false);
await Task.Delay(300); cts.CancelAfter(300);
cts.Cancel();
var result = await task; await task;
var outputInfo = await FFProbe.AnalyseAsync(outputFile); var outputInfo = await FFProbe.AnalyseAsync(outputFile);
Assert.IsTrue(result);
Assert.IsNotNull(outputInfo); Assert.IsNotNull(outputInfo);
Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width); Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width);
Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height); Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height);

View file

@ -1,4 +1,5 @@
using System.IO.Pipes; using System;
using System.IO.Pipes;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
@ -23,7 +24,7 @@ protected override async Task ProcessDataAsync(CancellationToken token)
{ {
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false); await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
if (!Pipe.IsConnected) if (!Pipe.IsConnected)
throw new TaskCanceledException(); throw new OperationCanceledException();
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false); await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
} }
} }

View file

@ -42,14 +42,15 @@ public async Task During(CancellationToken cancellationToken = default)
{ {
await ProcessDataAsync(cancellationToken).ConfigureAwait(false); await ProcessDataAsync(cancellationToken).ConfigureAwait(false);
} }
catch (TaskCanceledException) catch (OperationCanceledException)
{ {
Debug.WriteLine($"ProcessDataAsync on {GetType().Name} cancelled"); Debug.WriteLine($"ProcessDataAsync on {GetType().Name} cancelled");
} }
finally finally
{ {
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}"); Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
Pipe?.Disconnect(); if (Pipe is { IsConnected: true })
Pipe.Disconnect();
} }
} }

View file

@ -87,16 +87,17 @@ public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOpti
{ {
var options = GetConfiguredOptions(ffMpegOptions); var options = GetConfiguredOptions(ffMpegOptions);
var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource); var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
processArguments.Exited += delegate { cancellationTokenSource.Cancel(); };
IProcessResult? processResult = null; IProcessResult? processResult = null;
try try
{ {
processResult = Process(processArguments, cancellationTokenSource).ConfigureAwait(false).GetAwaiter().GetResult(); processResult = Process(processArguments, cancellationTokenSource).ConfigureAwait(false).GetAwaiter().GetResult();
} }
catch (Exception e) catch (OperationCanceledException)
{ {
if (!HandleException(throwOnError, e, processResult?.ErrorData ?? Array.Empty<string>())) return false; if (throwOnError)
throw;
} }
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>()); return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
@ -112,9 +113,10 @@ public async Task<bool> ProcessAsynchronously(bool throwOnError = true, FFOption
{ {
processResult = await Process(processArguments, cancellationTokenSource).ConfigureAwait(false); processResult = await Process(processArguments, cancellationTokenSource).ConfigureAwait(false);
} }
catch (Exception e) catch (OperationCanceledException)
{ {
if (!HandleException(throwOnError, e, processResult?.ErrorData ?? Array.Empty<string>())) return false; if (throwOnError)
throw;
} }
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>()); return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
@ -127,8 +129,10 @@ private async Task<IProcessResult> Process(ProcessArguments processArguments, Ca
_ffMpegArguments.Pre(); _ffMpegArguments.Pre();
using var instance = processArguments.Start(); using var instance = processArguments.Start();
var cancelled = false;
void OnCancelEvent(object sender, int timeout) void OnCancelEvent(object sender, int timeout)
{ {
cancelled = true;
instance.SendInput("q"); instance.SendInput("q");
if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true)) if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
@ -148,6 +152,11 @@ await Task.WhenAll(instance.WaitForExitAsync().ContinueWith(t =>
_ffMpegArguments.Post(); _ffMpegArguments.Post();
}), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false); }), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false);
if (cancelled)
{
throw new OperationCanceledException("ffmpeg processing was cancelled");
}
return processResult; return processResult;
} }
finally finally
@ -209,15 +218,6 @@ private void ErrorData(object sender, string msg)
_onError?.Invoke(msg); _onError?.Invoke(msg);
} }
private static bool HandleException(bool throwOnError, Exception e, IEnumerable<string> errorData)
{
if (!throwOnError)
return false;
throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e, string.Join("\n", errorData));
}
private void OutputData(object sender, string msg) private void OutputData(object sender, string msg)
{ {
Debug.WriteLine(msg); Debug.WriteLine(msg);