1

I am working with a large and complex event driven body of code and there are piles of opportunity to accidentally create a recursive condition.

Sometimes the recursive condition is temporary and the application catches up with itself but even that usually creates unnecessary lag. Other times it creates a stackoverflow which is often very difficult to debug when it happens at a client site.

I would like to have a way to either blacklist or whitelist sections of code that are permitted to recurse. If the recursive condition happens during DEV then I want it to assert so that I can correct the code.

What I am considering is having the application examine its own stack to ensure that the method it just entered is not already on the stack.

Any pointers would be appreciated.

Note: This is for a Web Application but I have run into this challenge in multiple environments.

AnthonyVO
  • 3,821
  • 1
  • 36
  • 41
  • 1
    Define "accidental recursion". There are algorithms that *depend* on fast tail recursion and won't even raise a stackoverflow. – Panagiotis Kanavos Nov 17 '17 at 13:59
  • 1
    yeah.,....don't use `Application.DoEvents()` –  Nov 17 '17 at 14:01
  • Perhaps you should rethink the design? ReactiveX and observables were created to deal with event streams and allow you to use query logic on event streams, combine event streams, etc. Agent architectures allow you to send messages and possibly attach TTL fields to prevent infinite loops – Panagiotis Kanavos Nov 17 '17 at 14:02
  • @PanagiotisKanavos, Recursion that I did not expect / design. A calling B calling C calling A (in a loop) when I expected A to resolve without ever having called back to itself. – AnthonyVO Nov 17 '17 at 14:02
  • Perfomance is not a problem? Because exploring stack is quite expensive thing. – Evk Nov 17 '17 at 14:02
  • To put it another way, complex systems *will* become unstable without a feedback/dampening mechanism. That's why messaging systems, queues, etc have TTL fields, maximum retries etc. – Panagiotis Kanavos Nov 17 '17 at 14:03
  • @Evk, I would only explore the Stack in Dev. – AnthonyVO Nov 17 '17 at 14:03
  • @AnthonyVO you can't define something with "what I expect". How is the compiler or runtime going to know what *you* expect? Just don't do it this way, and don't allow unconstrained events. It's not a language limitation. It's math and circuit theory. Think about a microphone getting too close to a speaker. Or network theory - imagine a router retrying to send a packet – Panagiotis Kanavos Nov 17 '17 at 14:05
  • @PanagiotisKanavos, What are type declarations? There are many ways we code our assumptions and expectations. – AnthonyVO Nov 17 '17 at 14:08
  • Try looking at this [SO Question](https://stackoverflow.com/questions/18076673/detect-recursive-calls-in-c-sharp-code) – Icemanind Nov 17 '17 at 14:12
  • @Icemanind, That question looks interesting but it won't cover what I need. – AnthonyVO Nov 17 '17 at 14:32
  • @AnthonyVO you can look for what isn't there, you can use an extremely expensive stack check that's essentially the same as a TTL, or you can use the simple technique that ensures the largest event-driven system on the planet works without collapsing. This isn't a new problem – Panagiotis Kanavos Nov 17 '17 at 14:34

2 Answers2

3

You can inspect stack like this:

[MethodImpl(MethodImplOptions.NoInlining)]
// optionally decorate with Conditional to only be used in Debug configuration
[Conditional("DEBUG")]
public static void FailIfCallerIsRecursive() {
    var trace = new StackTrace();
    // previous frame is the caller
    var caller = trace.GetFrame(1).GetMethod();
    // inspect the rest
    for (int i = 2; i < trace.FrameCount; i++) {
        // if found caller somewhere up the stack - throw
        if (trace.GetFrame(i).GetMethod() == caller)
            throw new Exception("Recursion detected");
    }            
}

Then just call it a the beginning:

void MyPotentiallyRecursiveMethod() {
    FailIfCallerIsRecursive()
}

But note that it's quite expensive. However since you are going to use that only in dev (debug) configuration - why not. You can also modify it a bit to throw only when certain level of recursion is detected (so caller appears X time up the stack).

Evk
  • 98,527
  • 8
  • 141
  • 191
  • Works perfectly, Thanks. I changed my version to throw the following message: throw new Exception("Unexpected Recursion detected in " + caller.Name) – AnthonyVO Nov 17 '17 at 16:14
  • @AnthonyVO I also updated answer and marked with [Conditional] attribute. That way all calls to this method will not be compiled when not in Debug configuration, so in Release version that will have 0 effect without any #IF debug checks. – Evk Nov 17 '17 at 17:39
  • 1
    4 years later and this piece of code just saved me a wack of debugging again. Note: I updated my version to also allow me to optionally enable the detection in production code as well. – AnthonyVO Jan 03 '22 at 17:29
1

You could call the RuntimeHelpers.EnsureSufficientExecutionStack method and then catch the InsufficientExecutionStackException that is thrown if the next method call would cause a (not catchable) StackOverflowException.

You could create an extension method for it:

public static T EnsureSafeRecursiveCall<T>(this Func<T> method)
{
    try
    {
        RuntimeHelpers.EnsureSufficientExecutionStack();
        return method();
    }
    catch (InsufficientExecutionStackException ex)
    {
        string msg = $"{method.Method.Name} would cause a {nameof(StackOverflowException)} on the next call";
        Debug.Fail(msg);
        // logging here is essential here because Debug.Fail works only with debug
        throw new StackOverflowException(msg, ex); // wrap in new exception to avoid that we get into this catch again and again(note we are in a recursive call)
    }
}

Now your original method remains almost unchanged:

public static IEnumerable<T> YourRecursiveMethod<T>(IEnumerable<T> seq)
{
    var method = new Func<IEnumerable<T>>(() => YourRecursiveMethod(seq));
    return method.EnsureSafeRecursiveCall();
}
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • This seems to work only with `Debug.Fail` (which is not always useful). If you try to do anything else (for example Console.WriteLine) - it seems you still end up with stack overflow. – Evk Nov 17 '17 at 15:29
  • @Evk: it was OP's requirement as far as i've understood. But i couldnt reproduce what you have said – Tim Schmelter Nov 17 '17 at 15:31
  • 1
    No it's fine - you just don't need to rethrow exception with `throw`, because each `YourRecursiveMethod` has catch clause for this specific type, and so each `catch` block is triggered up the stack, many times. It's better to throw some another exception instead of rethrowing (of course with Debug.Fail it does not matter). – Evk Nov 17 '17 at 15:34
  • @Evk: was just to prevent compiler error(method must return something). You could also return `default(T)`(in the extension version `EnsureSafeRecursiveCall`). – Tim Schmelter Nov 17 '17 at 15:36
  • Yes I understand, as I said with `Debug.Fail` it doesn't matter, but if you want to do something different (write to log or something) - better not rethrow it but throw different exception instead. – Evk Nov 17 '17 at 15:38
  • @Evk: You're right, edited the answer to throw a `StackOverflowException` – Tim Schmelter Nov 17 '17 at 16:02
  • Thanks this is definitely something to keep in my hat for those cases where I want recursion. But in this case I specifically wanted to detect recursion where there should not have been any. – AnthonyVO Nov 17 '17 at 16:52