Created
May 10, 2012 21:17
-
-
Save chgeuer/2655951 to your computer and use it in GitHub Desktop.
Hosting a process, and ensuring that it's restarted even if it terminates unexpectedly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace chgeuer.WebRoleUtils | |
{ | |
using System; | |
using System.Diagnostics; | |
using System.Threading; | |
using System.Threading.Tasks; | |
public class RestartingProcessHost | |
{ | |
internal class ValueTypeWrapper<T> | |
{ | |
// Maybe I'm stoopid, but how can I change a value type which is wrapped in a closure, unless I wrap it in an object?!? | |
public T Value { get; set; } | |
} | |
private readonly Func<Process> CreateProcess; | |
private readonly CancellationTokenSource cancellationTokenSource; | |
private bool _onStopRequested = false; | |
public RestartingProcessHost(Func<Process> createProcess, CancellationTokenSource cancellationTokenSource) | |
{ | |
this.CreateProcess = createProcess; | |
this.cancellationTokenSource = cancellationTokenSource; | |
this.warn = null; | |
this.info = null; | |
this.error = null; | |
} | |
#region Logging | |
private Action<string> _error; | |
public Action<string> error | |
{ | |
get { return _error; } | |
set { _error = value ?? ((s) => { }); } | |
} | |
private Action<string> _warn; | |
public Action<string> warn | |
{ | |
get { return _warn; } | |
set { _warn = value ?? ((s) => { }); } | |
} | |
private Action<string> _info; | |
public Action<string> info | |
{ | |
get { return _info; } | |
set { _info = value ?? ((s) => { }); } | |
} | |
#endregion | |
public Task StartRunTask() | |
{ | |
var cancellationToken = cancellationTokenSource.Token; | |
var processLaunched = new ValueTypeWrapper<bool> { Value = false }; | |
var processExitedEventHappened = new ValueTypeWrapper<bool> { Value = false }; | |
var pid = new ValueTypeWrapper<int>(); | |
var processName = new ValueTypeWrapper<string>(); | |
Action hostProcess = () => | |
{ | |
#region hostProcess | |
var process = this.CreateProcess(); | |
process.Exited += (s, a) => processExitedEventHappened.Value = true; | |
info(string.Format("Try to launch {0}", process.StartInfo.FileName)); | |
process.Start(); | |
if (process.StartInfo.RedirectStandardOutput) process.BeginOutputReadLine(); | |
if (process.StartInfo.RedirectStandardError) process.BeginErrorReadLine(); | |
pid.Value = process.Id; | |
processLaunched.Value = true; | |
processName.Value = process.StartInfo.FileName; | |
info(string.Format("Launched {0} (pid {1})", processName.Value, pid.Value)); | |
if (cancellationToken.WaitHandle.WaitOne()) // wait infinite | |
{ | |
warn(string.Format("Killing {0} now", processName.Value)); | |
if (!process.HasExited) | |
{ | |
process.Kill(); | |
} | |
} | |
#endregion | |
}; | |
var processTask = Task.Factory.StartNew(hostProcess, cancellationToken); | |
while (!processLaunched.Value) | |
{ | |
warn("Waiting a until the task is launched..."); | |
Thread.Sleep(TimeSpan.FromSeconds(1)); | |
} | |
Action hostMonitor = () => | |
{ | |
int i = 0; | |
while (true) | |
{ | |
#region Ensure task didn't crash accidentally, and if so, re-launch | |
bool processFoundInMemory = false; | |
try | |
{ | |
var processFromSystem = Process.GetProcessById(pid.Value); | |
processFoundInMemory = true; | |
} | |
catch (ArgumentException) { } | |
bool unexpectedTermination = processExitedEventHappened.Value || !processFoundInMemory; | |
if (processTask.IsCompleted || unexpectedTermination) | |
{ | |
if (_onStopRequested) | |
{ | |
error(string.Format("Process {0} successfully shut down because of an OnStop() call", processName.Value)); | |
return; // Leave the Run() method | |
} | |
else if (unexpectedTermination) | |
{ | |
error(string.Format("Process {0} stopped working for unknown reasons... Restarting it", processName.Value)); | |
processExitedEventHappened.Value = false; | |
processLaunched.Value = false; | |
processTask = Task.Factory.StartNew(hostProcess, cancellationToken); | |
while (!processLaunched.Value) | |
{ | |
Thread.Sleep(TimeSpan.FromSeconds(1)); | |
} | |
} | |
} | |
#endregion | |
Thread.Sleep(TimeSpan.FromMilliseconds(500)); | |
if ((i++) % 600 == 0) | |
{ | |
info("Running"); | |
} | |
} | |
}; | |
return Task.Factory.StartNew(hostMonitor, cancellationToken); | |
} | |
public void Stop() | |
{ | |
this._onStopRequested = true; | |
cancellationTokenSource.Cancel(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment