7

I have a series of code blocks that are taking too long. I don't need any finesse when it fails. In fact, I want to throw an exception when these blocks take too long, and just fall out through our standard error handling. I would prefer to NOT create methods out of each block (which are the only suggestions I've seen so far), as it would require a major rewrite of the code base.

Here's what I would LIKE to create, if possible.

public void MyMethod( ... )
{

 ...

    using (MyTimeoutObject mto = new MyTimeoutObject(new TimeSpan(0,0,30)))
    {
        // Everything in here must complete within the timespan
        // or mto will throw an exception. When the using block
        // disposes of mto, then the timer is disabled and 
        // disaster is averted.
    }

 ...
}

I've created a simple object to do this using the Timer class. (NOTE for those that like to copy/paste: THIS CODE DOES NOT WORK!!)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;

    public class MyTimeoutObject : IDisposable
    {
        private Timer timer = null;

        public MyTimeoutObject (TimeSpan ts)
        {
            timer = new Timer();
            timer.Elapsed += timer_Elapsed;
            timer.Interval = ts.TotalMilliseconds;

            timer.Start();
        }

        void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            throw new TimeoutException("A code block has timed out.");
        }

        public void Dispose()
        {
            if (timer != null)
            {
                timer.Stop();
            }
        }
    }

It does not work because the System.Timers.Timer class captures, absorbs and ignores any exceptions thrown within, which -- as I've discovered -- defeats my design. Any other way of creating this class/functionality without a total redesign?

This seemed so simple two hours ago, but is causing me much headache.

Jerry
  • 4,507
  • 9
  • 50
  • 79
  • Are you handling any exceptions at the upper level, from where you call this method? Moreover, is it a multi-threaded environment? – Shaharyar Jan 04 '16 at 18:15
  • You could use a static store of `CancellationToken` and use them everywhere you have a Task, or - less elegant but maybe more effective - have another process that can kill your main process and is invoked over local HTTP or named pipes – Sten Petrov Jan 04 '16 at 18:15
  • Shaharyar: Yes. The catch is far up the chain. And yes, this is a multi-threaded environment. The library I'm writing is to be used within WinForms applications, not web. – Jerry Jan 04 '16 at 18:16
  • One more question, will this code always run on a background thread? – Shaharyar Jan 04 '16 at 18:24
  • 3
    You said you don't want to turn those blocks into methods. I assume you only want to avoid having to use named methods? Anonymous methods could be quite useful here, allowing for something like `RunWithTimeout(TimeSpan.FromSeconds(30), () => { /* 'block' goes here */ });` – Pieter Witvoet Jan 04 '16 at 18:25
  • I am not sure. For now, the answer is yes... but I can see where -- if it would work -- we would use this to toss timeout exceptions in other areas in the future. – Jerry Jan 04 '16 at 18:26
  • Pieter: With several places to put that, I'm concerned that it would cause some code readability issues. If this is the ONLY way to do this, then I suppose it would work, but I'm trying to stick to the USING(...) form if possible. – Jerry Jan 04 '16 at 18:28
  • The using statement is meant to be used for disposing of resources when you are done with an object. So, while it looks neat it's really not what the using statement is for. – juharr Jan 04 '16 at 18:31
  • I think you're dealing with the issue of having timer work in a separate thread. Here is a question http://stackoverflow.com/questions/17244469/timer-is-not-working-in-cross-threads **Hans Passant** answered in very detail about this topic. I hope it will help you better understand why your timer is not raising exception. Probably there's no listener to it's handler. – Shaharyar Jan 04 '16 at 18:32
  • Shaharyar: No. I've found that per Microsoft that the System.Timers.Timer class will not raise an exception from within the event handler. I've walked through the code. There is a listener. – Jerry Jan 04 '16 at 18:38
  • 1
    Its not possible to bubble up the exception from the using statement in the background that way. Any exception handling would need to be via some hook or callback mechanism. You can use tasks instead of your timerobject for the timeout mechanism: var task = Task.Run(() => { }); if (!task.Wait(TimeSpan.FromSeconds(1))) throw new TimeoutException(); – DeveloperGuo Jan 04 '16 at 18:42
  • (1) what do you expect to happen to the app if a deadlock existed? (2) how does the (possibility of) deadlock arise, what do you lock, how do you lock it etc? You can use async/await, Task and SlimSemaphore to make your app fully async, pass CancellationToken(s) everywhere and do Task.Wait(x) whenever the user launches something – Sten Petrov Jan 04 '16 at 18:46
  • You should look all the answers of this thread. The OP is dealing with the exact same problem as you. http://stackoverflow.com/questions/3749785/how-do-i-get-the-exception-that-happens-in-timer-elapsed-event Every answer has a different approach. May be it could help you out there. – Shaharyar Jan 04 '16 at 19:17

3 Answers3

2

Here's an async implementation of timeouts:

   ...
      private readonly semaphore = new SemaphoreSlim(1,1);

   ...
      // total time allowed here is 100ms
      var tokenSource = new CancellationTokenSource(100); 
      try{
        await WorkMethod(parameters, tokenSource.Token); // work 
      } catch (OperationCancelledException ocx){
        // gracefully handle cancellations:
        label.Text = "Operation timed out";
      }
   ...  

    public async Task WorkMethod(object prm, CancellationToken ct){
      try{
        await sem.WaitAsync(ct); // equivalent to lock(object){...}
        // synchronized work, 
        // call  tokenSource.Token.ThrowIfCancellationRequested() or
        // check tokenSource.IsCancellationRequested in long-running blocks
        // and pass ct to other tasks, such as async HTTP or stream operations
      } finally {
        sem.Release();
      }
    }

NOT that I advise it, but you could pass the tokenSource instead of its Token into WorkMethod and periodically do tokenSource.CancelAfter(200) to add more time if you're certain you're not at a spot that can be dead-locked (waiting on an HTTP call) but I think that would be an esoteric approach to multithreading.

Instead your threads should be as fast as possible (minimum IO) and one thread can serialize the resources (producer) while others process a queue (consumers) if you need to deal with IO multithreading (say file compression, downloads etc) and avoid deadlock possibility altogether.

Sten Petrov
  • 10,943
  • 1
  • 41
  • 61
2

OK, I've spent some time on this one and I think I have a solution that will work for you without having to change your code all that much.

The following is how you would use the Timebox class that I created.

public void MyMethod( ... ) {

    // some stuff

    // instead of this
    // using(...){ /* your code here */ }

    // you can use this
    var timebox = new Timebox(TimeSpan.FromSeconds(1));
    timebox.Execute(() =>
    {
        /* your code here */
    });

    // some more stuff

}

Here's how Timebox works.

  • A Timebox object is created with a given Timespan
  • When Execute is called, the Timebox creates a child AppDomain to hold a TimeboxRuntime object reference, and returns a proxy to it
  • The TimeboxRuntime object in the child AppDomain takes an Action as input to execute within the child domain
  • Timebox then creates a task to call the TimeboxRuntime proxy
  • The task is started (and the action execution starts), and the "main" thread waits for for as long as the given TimeSpan
  • After the given TimeSpan (or when the task completes), the child AppDomain is unloaded whether the Action was completed or not.
  • A TimeoutException is thrown if action times out, otherwise if action throws an exception, it is caught by the child AppDomain and returned for the calling AppDomain to throw

A downside is that your program will need elevated enough permissions to create an AppDomain.

Here is a sample program which demonstrates how it works (I believe you can copy-paste this, if you include the correct usings). I also created this gist if you are interested.

public class Program
{
    public static void Main()
    {
        try
        {
            var timebox = new Timebox(TimeSpan.FromSeconds(1));
            timebox.Execute(() =>
            {
                // do your thing
                for (var i = 0; i < 1000; i++)
                {
                    Console.WriteLine(i);
                }
            });

            Console.WriteLine("Didn't Time Out");
        }
        catch (TimeoutException e)
        {
            Console.WriteLine("Timed Out");
            // handle it
        }
        catch(Exception e)
        {
            Console.WriteLine("Another exception was thrown in your timeboxed function");
            // handle it
        }
        Console.WriteLine("Program Finished");
        Console.ReadLine();
    }
}

public class Timebox
{
    private readonly TimeSpan _ts;

    public Timebox(TimeSpan ts)
    {
        _ts = ts;
    }

    public void Execute(Action func)
    {
        AppDomain childDomain = null;
        try
        {
            // Construct and initialize settings for a second AppDomain.  Perhaps some of
            // this is unnecessary but perhaps not.
            var domainSetup = new AppDomainSetup()
            {
                ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
                ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
                ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
                LoaderOptimization = LoaderOptimization.MultiDomainHost
            };

            // Create the child AppDomain
            childDomain = AppDomain.CreateDomain("Timebox Domain", null, domainSetup);

            // Create an instance of the timebox runtime child AppDomain
            var timeboxRuntime = (ITimeboxRuntime)childDomain.CreateInstanceAndUnwrap(
                typeof(TimeboxRuntime).Assembly.FullName, typeof(TimeboxRuntime).FullName);

            // Start the runtime, by passing it the function we're timboxing
            Exception ex = null;
            var timeoutOccurred = true;
            var task = new Task(() =>
            {
                ex = timeboxRuntime.Run(func);
                timeoutOccurred = false;
            });

            // start task, and wait for the alloted timespan.  If the method doesn't finish
            // by then, then we kill the childDomain and throw a TimeoutException
            task.Start();
            task.Wait(_ts);

            // if the timeout occurred then we throw the exception for the caller to handle.
            if(timeoutOccurred)
            {
                throw new TimeoutException("The child domain timed out");
            }

            // If no timeout occurred, then throw whatever exception was thrown
            // by our child AppDomain, so that calling code "sees" the exception
            // thrown by the code that it passes in.
            if(ex != null)
            {
                throw ex;
            }
        }
        finally
        {
            // kill the child domain whether or not the function has completed
            if(childDomain != null) AppDomain.Unload(childDomain);
        }
    }

    // don't strictly need this, but I prefer having an interface point to the proxy
    private interface ITimeboxRuntime
    {
        Exception Run(Action action);
    }

    // Need to derive from MarshalByRefObject... proxy is returned across AppDomain boundary.
    private class TimeboxRuntime : MarshalByRefObject, ITimeboxRuntime
    {
        public Exception Run(Action action)
        {
            try
            {
                // Nike: just do it!
                action();
            }
            catch(Exception e)
            {
                // return the exception to be thrown in the calling AppDomain
                return e;
            }
            return null;
        }
    }
}

EDIT:

The reason I went with an AppDomain instead of Threads or Tasks only, is because there is no bullet proof way for terminating Threads or Tasks for arbitrary code [1][2][3]. An AppDomain, for your requirements, seemed like the best approach to me.

Community
  • 1
  • 1
Frank Bryce
  • 8,076
  • 4
  • 38
  • 56
  • While I can't always use elevated permissions, this is a very clean solution. I'll probably use this in bits and pieces. – Jerry Jan 06 '16 at 18:25
  • If `Thread.Abort()` is something that you are comfortable using, then this works the exact same way as w/ an `AppDomain`. Instead of `ex = timeboxRuntime.Run(func)` you can capture the thread (like you did in your posted solution) and call `func()` in the task. Later, instead of unloading the domain, you can abort the thread (if it's still running). This would not need to have elevated permissions. **I'd still recommend using the `AppDomain`**, because I always follow [Eric Lippert's advice](http://stackoverflow.com/questions/1559255/whats-wrong-with-using-thread-abort/1560567#1560567) :). – Frank Bryce Jan 06 '16 at 18:38
0

I really liked the visual idea of a using statement. However, that is not a viable solution. Why? Well, a sub-thread (the object/thread/timer within the using statement) cannot disrupt the main thread and inject an exception, thus causing it to stop what it was doing and jump to the nearest try/catch. That's what it all boils down to. The more I sat and worked with this, the more that came to light.

In short, it can't be done the way I wanted to do it.

However, I've taken Pieter's approach and mangled my code a bit. It does introduce some readability issues, but I've tried to mitigate them with comments and such.

public void MyMethod( ... )
{

 ...

    // Placeholder for thread to kill if the action times out.
    Thread threadToKill = null;
    Action wrappedAction = () => 
    {
        // Take note of the action's thread. We may need to kill it later.
        threadToKill = Thread.CurrentThread;

        ...
        /* DO STUFF HERE */
        ...

    };

    // Now, execute the action. We'll deal with the action timeouts below.
    IAsyncResult result = wrappedAction.BeginInvoke(null, null);

    // Set the timeout to 10 minutes.
    if (result.AsyncWaitHandle.WaitOne(10 * 60 * 1000))
    {
        // Everything was successful. Just clean up the invoke and get out.
        wrappedAction.EndInvoke(result);
    }
    else 
    {
        // We have timed out. We need to abort the thread!! 
        // Don't let it continue to try to do work. Something may be stuck.
        threadToKill.Abort();
        throw new TimeoutException("This code block timed out");
    }

 ...
}

Since I'm doing this in three or four places per major section, this does get harder to read over. However, it works quite well.

Jerry
  • 4,507
  • 9
  • 50
  • 79
  • There's no need to duplicate all this code - you can put it in a helper method that takes an `Action` as argument, and call that `Action` from within `wrappedAction`. Note that your code is blocking the calling thread, and that it's recommended to write code that cooperates with cancellation rather than 'violently' aborting a thread. The modern approach would be to use a `Task` together with a `CancellationToken(Source)` (which, conveniently, can take a timeout as argument). – Pieter Witvoet Jan 05 '16 at 10:50
  • 1
    @Jerry One note: `Thread.Abort()` is controversial. [According to Eric Lippert](http://stackoverflow.com/a/1560567/3960399), who is quite the authority on C#, this should be avoided at all costs. – Frank Bryce Jan 05 '16 at 18:49