Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cancellation Tokens to Hudl.FFmpeg #91

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ bin/
obj/
Bin/
Obj/
.vs/
hudl-jobs/Quartz.NET-1.0.2/build/
WebApp/WebApp.Publish.xml
ProTools/Pro Quagic/obj/*
Expand Down
11 changes: 9 additions & 2 deletions Hudl.FFmpeg.Core/Command/BaseTypes/ICommand.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
using System;
using System.Threading;

namespace Hudl.FFmpeg.Command.BaseTypes
{
public interface ICommand
{
ICommandFactory Owner { get; }

Action<ICommandFactory, ICommand, bool> PreExecutionAction { get; set; }

Action<ICommandFactory, ICommand, bool> PostExecutionAction { get; set; }

Action<ICommandFactory, ICommand, ICommandProcessor> OnSuccessAction { get; set; }

Action<ICommandFactory, ICommand, ICommandProcessor> OnErrorAction { get; set; }

ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>()
where TProcessorType : class, ICommandProcessor, new()
where TBuilderType : class, ICommandBuilder, new();

ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(int? timeoutMilliseconds)
ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(CancellationToken token = default(CancellationToken))
where TProcessorType : class, ICommandProcessor, new()
where TBuilderType : class, ICommandBuilder, new();

ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(TProcessorType commandProcessor, int? timeoutMilliseconds)
ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(TProcessorType commandProcessor, CancellationToken token = default(CancellationToken))
where TProcessorType : class, ICommandProcessor
where TBuilderType : class, ICommandBuilder, new();
}
Expand Down
4 changes: 3 additions & 1 deletion Hudl.FFmpeg.Core/Command/BaseTypes/ICommandProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Hudl.FFmpeg.Command.BaseTypes
{
Expand Down Expand Up @@ -45,6 +47,6 @@ public interface ICommandProcessor
/// <summary>
/// processes the given command string against the processor engine
/// </summary>
bool Send(string command, int? timeoutMilliseconds);
bool Send(string command, CancellationToken token = default(CancellationToken));
}
}
24 changes: 19 additions & 5 deletions Hudl.FFmpeg.Core/Command/Models/FFcommandBase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using Hudl.FFmpeg.Command.BaseTypes;
using Hudl.FFmpeg.Command.BaseTypes;
using Hudl.FFmpeg.Exceptions;
using System;
using System.Threading;

namespace Hudl.FFmpeg.Command.Models
{
Expand Down Expand Up @@ -34,6 +35,19 @@ public ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>()
public ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(int? timeoutMilliseconds)
where TProcessorType : class, ICommandProcessor, new()
where TBuilderType : class, ICommandBuilder, new()
{
var cancellationTokenSource = new CancellationTokenSource();
if (timeoutMilliseconds.HasValue)
{
cancellationTokenSource.CancelAfter(timeoutMilliseconds.Value);
}

return ExecuteWith<TProcessorType, TBuilderType>(cancellationTokenSource.Token);
}

public ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(CancellationToken token = default(CancellationToken))
where TProcessorType : class, ICommandProcessor, new()
where TBuilderType : class, ICommandBuilder, new()
{
var commandProcessor = new TProcessorType();

Expand All @@ -42,7 +56,7 @@ public ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(int? timeoutM
throw new FFmpegRenderingException(commandProcessor.Error);
}

var returnType = ExecuteWith<TProcessorType, TBuilderType>(commandProcessor, timeoutMilliseconds);
var returnType = ExecuteWith<TProcessorType, TBuilderType>(commandProcessor, token);

if (!commandProcessor.Close())
{
Expand All @@ -52,7 +66,7 @@ public ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(int? timeoutM
return returnType;
}

public ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(TProcessorType commandProcessor, int? timeoutMilliseconds)
public ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(TProcessorType commandProcessor, CancellationToken token = default(CancellationToken))
where TProcessorType : class, ICommandProcessor
where TBuilderType : class, ICommandBuilder, new()
{
Expand All @@ -66,7 +80,7 @@ public ICommandProcessor ExecuteWith<TProcessorType, TBuilderType>(TProcessorTyp

PreExecutionAction(Owner, this, true);

if (!commandProcessor.Send(commandBuilder.ToString(), timeoutMilliseconds))
if (!commandProcessor.Send(commandBuilder.ToString(), token))
{
PostExecutionAction(Owner, this, false);

Expand Down
18 changes: 3 additions & 15 deletions Hudl.FFmpeg.Core/Extensions/ProcessExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,17 @@ public static bool WaitForProcessStart(this Process process, TimeSpan processTim

public static bool WaitForProcessStop(this Process process)
{
return process.WaitForProcessStop(null);
return process.WaitForProcessStop(default(CancellationToken));
}
public static bool WaitForProcessStop(this Process process, int? timeoutMilliseconds)
{
var processTimeout = TimeSpan.FromMilliseconds(timeoutMilliseconds ?? 0);
return process.WaitForProcessStop(processTimeout);
}
public static bool WaitForProcessStop(this Process process, TimeSpan processTimeout)
public static bool WaitForProcessStop(this Process process, CancellationToken token = default(CancellationToken))
{
var processStopwatch = Stopwatch.StartNew();

while (!process.HasExited)
{
Thread.Sleep(1.Seconds());

if (processTimeout.TotalMilliseconds > 0 && processStopwatch.ElapsedMilliseconds > processTimeout.TotalMilliseconds)
{
process.Kill();

process.WaitForExit((int)5.Seconds().TotalMilliseconds);

return false;
}
token.ThrowIfCancellationRequested();
}

return true;
Expand Down
4 changes: 2 additions & 2 deletions Hudl.FFmpeg.Core/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyInformationalVersion("6.3.0")]
[assembly: AssemblyFileVersion("6.3.0")]
[assembly: AssemblyInformationalVersion("7.0.0")]
[assembly: AssemblyFileVersion("7.0.0")]
[assembly: AssemblyVersion("6.0.0.0")]
41 changes: 34 additions & 7 deletions Hudl.FFmpeg/Command/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using Hudl.FFmpeg.Command.BaseTypes;
using Hudl.FFmpeg.Exceptions;
using Hudl.FFmpeg.Resources.Interfaces;
using log4net;
using log4net;
using System.Threading.Tasks;
using System.Threading;

namespace Hudl.FFmpeg.Command
{
Expand Down Expand Up @@ -125,6 +127,14 @@ public List<IContainer> Render(TimeSpan timeout)
return RenderWith<FFmpegCommandProcessor, FFmpegCommandBuilder>((int)timeout.TotalMilliseconds);
}

/// <summary>
/// Renders the command stream with the defualt command processor
/// </summary>
public List<IContainer> Render(CancellationToken token = default(CancellationToken))
{
return RenderWith<FFmpegCommandProcessor, FFmpegCommandBuilder>(token);
}

private CommandFactory Add(FFmpegCommand command, bool export)
{
if (command == null)
Expand All @@ -148,19 +158,33 @@ private CommandFactory Add(FFmpegCommand command, bool export)
}

#region Internals

internal List<IContainer> RenderWith<TProcessor, TBuilder>(int? timeoutMilliseconds)
where TProcessor : class, ICommandProcessor, new()
where TBuilder : class, ICommandBuilder, new()
{
var cancellationTokenSource = new CancellationTokenSource();
if (timeoutMilliseconds.HasValue)
{
cancellationTokenSource.CancelAfter(timeoutMilliseconds.Value);
}

return RenderWith<TProcessor, TBuilder>(cancellationTokenSource.Token);
}

internal List<IContainer> RenderWith<TProcessor, TBuilder>(CancellationToken token = default(CancellationToken))
where TProcessor : class, ICommandProcessor, new()
where TBuilder : class, ICommandBuilder, new()
{
token.ThrowIfCancellationRequested();

var commandProcessor = new TProcessor();

if (!commandProcessor.Open())
{
throw new FFmpegRenderingException(commandProcessor.Error);
}

var returnType = RenderWith<TProcessor, TBuilder>(commandProcessor, timeoutMilliseconds);
var returnType = RenderWith<TProcessor, TBuilder>(commandProcessor, token);

if (!commandProcessor.Close())
{
Expand All @@ -170,7 +194,7 @@ internal List<IContainer> RenderWith<TProcessor, TBuilder>(int? timeoutMilliseco
return returnType;
}

internal List<IContainer> RenderWith<TProcessor, TBuilder>(TProcessor processor, int? timeoutMilliseconds)
internal List<IContainer> RenderWith<TProcessor, TBuilder>(TProcessor processor, CancellationToken token = default(CancellationToken))
where TProcessor : class, ICommandProcessor, new()
where TBuilder : class, ICommandBuilder, new()
{
Expand All @@ -185,9 +209,12 @@ internal List<IContainer> RenderWith<TProcessor, TBuilder>(TProcessor processor,
outputList.Count,
CommandList.Count);

CommandList.OfType<FFmpegCommand>()
.ToList()
.ForEach(command => command.ExecuteWith<TProcessor, TBuilder>(processor, timeoutMilliseconds));
foreach (var command in CommandList.OfType<FFmpegCommand>().ToList())
{
token.ThrowIfCancellationRequested();

command.ExecuteWith<TProcessor, TBuilder>(processor, token);
}

return outputList;
}
Expand Down
51 changes: 31 additions & 20 deletions Hudl.FFmpeg/Command/FFmpegCommandProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Hudl.FFmpeg.Command.StreamReaders;
using System.Threading;
using Hudl.FFmpeg.Extensions;
using System.Threading.Tasks;

namespace Hudl.FFmpeg.Command
{
Expand Down Expand Up @@ -81,10 +82,10 @@ public bool Close()

public bool Send(string command)
{
return Send(command, null);
return Send(command, default(CancellationToken));
}

public bool Send(string command, int? timeoutMilliseconds)
public bool Send(string command, CancellationToken token = default(CancellationToken))
{
if (Status != CommandProcessorStatus.Ready)
{
Expand All @@ -105,12 +106,16 @@ public bool Send(string command, int? timeoutMilliseconds)
{
Status = CommandProcessorStatus.Processing;

ProcessIt(command, timeoutMilliseconds);
ProcessIt(command, token);

Status = CommandProcessorStatus.Ready;

isSuccessful = true;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception err)
{
if (!CheckForKnownExceptions(err))
Expand Down Expand Up @@ -149,7 +154,7 @@ private void Delete()
}
}

private void ProcessIt(string command, int? timeoutMilliseconds)
private void ProcessIt(string command, CancellationToken token = default(CancellationToken))
{
using (var ffmpegProcess = new Process())
{
Expand All @@ -165,27 +170,32 @@ private void ProcessIt(string command, int? timeoutMilliseconds)

Log.Debug($"ffmpeg.exe MonoRuntime={ResourceManagement.IsMonoRuntime()} Args={ffmpegProcess.StartInfo.Arguments}");

var stdErrorReader = StandardErrorAsyncStreamReader.AttachReader(ffmpegProcess);
var stdErrorReader = StandardErrorAsyncStreamReader.AttachReader(ffmpegProcess);

ffmpegProcess.Start();
using (var registration = token.Register(() => ffmpegProcess.Kill()))
{
ffmpegProcess.Start();

//workaround for a bug in the mono process when attempting to read async from console output events
// - link http://mono.1490590.n4.nabble.com/System-Diagnostic-Process-and-event-handlers-td3246096.html
// we will wait a total of 10 seconds for the process to start, if nothing has happened in that time then we will
// return a failure for the event.
ffmpegProcess.WaitForProcessStart();
//workaround for a bug in the mono process when attempting to read async from console output events
// - link http://mono.1490590.n4.nabble.com/System-Diagnostic-Process-and-event-handlers-td3246096.html
// we will wait a total of 10 seconds for the process to start, if nothing has happened in that time then we will
// return a failure for the event.
ffmpegProcess.WaitForProcessStart();

stdErrorReader.Listen();
stdErrorReader.Listen();

var processStopped = ffmpegProcess.WaitForProcessStop(timeoutMilliseconds);
if (!processStopped)
{
throw new FFmpegTimeoutException(ffmpegProcess.StartInfo.Arguments);
}
var processStopped = ffmpegProcess.WaitForProcessStop();
if (!processStopped)
{
throw new FFmpegTimeoutException(ffmpegProcess.StartInfo.Arguments);
}

stdErrorReader.Stop();
stdErrorReader.Stop();

StdOut = stdErrorReader.ToString();
token.ThrowIfCancellationRequested();

StdOut = stdErrorReader.ToString();
}

Log.Debug($"ffmpeg.exe MonoRuntime={ResourceManagement.IsMonoRuntime()} Output={StdOut}.");

Expand Down Expand Up @@ -231,7 +241,8 @@ private static bool IsSignal15Error(string errorOutput)
return errorIndex > -1;
}

#endregion

#endregion

}
}
4 changes: 2 additions & 2 deletions Hudl.FFmpeg/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]

[assembly: AssemblyInformationalVersion("6.3.0")]
[assembly: AssemblyFileVersion("6.3.0")]
[assembly: AssemblyInformationalVersion("7.0.0")]
[assembly: AssemblyFileVersion("7.0.0")]
[assembly: AssemblyVersion("6.0.0.0")]
Loading