1

There's an ASP.NET MVC3 application where some of controller actions use an external mechanism for processing the input data. The input data is first saved into a temporary file and then an external program is being run and the path to the temporary file is passed at the command line. This is done using System.Diagnostics.Process class.

The external program usually exits promptly - earlier than in one second after it was started, so no problem, just wait for its completion with Process.WaitForProcessExit() and the dispose the Process object.

It gets harder if the external program hangs and doesn't exit in a timely manner. It happens very very rarely, but when it happens then Process.WaitForProcessExit() hangs too and the controller action runs for some time but then the client timeout happens and the controller action is cancelled but there's no way I could find that would cause forcible termination of the external program. I could of cause use Process.Kill() but I have to wire this call into somewhere such that it's invoked when the controller action is cancelled because of client timeout.

I tried to override Controller.OnException() but it looks like it's not invoked in this scenario.

How can ASP.NET MVC3 code be changed such that it calls some custom code (such as external program termination) when a client timeout occurs?

sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • Not-so-clear solution, but how about this: [here](https://stackoverflow.com/a/50461641/2716623) you can find a way to transform your `Process.WaitForProcessExit()` into common `Task`. Then you can use `var timeoutTask = Task.Delay(30 * 1000); if (await Task.WhenAny(task, timeoutTask) == timeoutTask) { ... }` – vasily.sib Jan 27 '20 at 11:50
  • @vasily.sib It looks like that code doesn't have "forcible termination" of the external process. – sharptooth Jan 27 '20 at 12:04

1 Answers1

1

I've checked the solution from the comments based on Task - it is useful when you have blocking operation. You can avoid the blocking call WaitForProcessExit() and create more simple solution.

The solution is in the form of wrapper.

public enum SafeProcessState { Exit = 1, Killed = 2 }

public class SafeProcess
{
    private readonly Process process;
    private readonly Func<bool> killPredicate;

    public SafeProcess(Func<bool> killPredicate, Process process)
    {
        this.killPredicate = killPredicate;
        this.process = process;
    }

    public async Task<SafeProcessState> WaitAsync()
    {
        var state = SafeProcessState.Exit;

        while (true) 
        {
            await Task.Delay(100);
            if (this.killPredicate())
            {
                state = SafeProcessState.Killed;
                if (this.process.HasExited == false)
                {
                    this.process.Kill();
                }
                break;
            }
            else if (this.process.HasExited) 
            {
                break;
            }
        }

        return state;
    }
}

This is how you can use it in ASP NET MVC action (Not .NET CORE);

public async ...... ActionName()
{
  var process = Process.Start("....");
  var wrapper = new SafeProcess(() => HttpContext.Current.Response.IsClientConnected == false, process);

 var state = await wrapper.WaitAsync();
}

It basically waits (by checking every 100ms) for the process to exit or the client to disconnect and returns information about the exit reason - Exit or Killed.
Kills the process if the user has disconnected.


Using async / await Task.Delay ensures we will free the execution thread and doing so for 100 ms is purely done for performance reasons.

NOTE: You can get into scenario of killing already exited process, which will throw exception (you have concurrency between the process and your asp net action). You you will need some synchronization between the process execution and .Kill() ... or you can wrap the call in try / catch.
If you choose the try / catch forget scenario, definitely log the situation.

vasil oreshenski
  • 2,788
  • 1
  • 14
  • 21