Ayende @ Rahien

Hi!
My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 18 | Comments: 72

filter by tags archive

Code Challenge: Make the assertion fire

time to read 2 min | 256 words

Can you create a calling code that would make this code fire the assertion?

public static IEnumerable<int> Fibonnaci(CancellationToken token)
{
    yield return 0;
    yield return 1;

    var prev = 0;
    var cur = 1;

    try
    {
        while (token.IsCancellationRequested == false)
        {
            var tmp = prev + cur;
            prev = cur;
            cur = tmp;
            yield return tmp;
        }
    }
    finally
    {
        Debug.Assert(token.IsCancellationRequested);
    }
}

Note that cancellation token cannot be changed, once cancelled, it can never revert.


Comments

Oded

Call this from code within a checked context and iterate over the results.

Once the integer overflows, an exception will occur and the finally block will run.

adam

you need a token from a cancellation token source and trigger the cancel on the cts in a different thread that is consuming the enumerable.

Ayende Rahien

Adam, No, that would work just fine. The cancellation token property would be true, and that would result in no assertion being fired.

Damien

Well, one obvious way to run code between the try block and the finally block is to use an exception filter - available in VB.NET from day one and coming soon to C# (I believe).

So that's an easy place to put code that changes the cancellationtoken's IsCancellationRequested value. Only issue now is how to raise the exception in the first place - I don't think Oded's idea will work because the checked/unchecked nature of code is, I believe, determined at compile time - different IL is emitted for checked/unchecked integer math.

Mike

var source = new CancellationTokenSource(); var nums = Fibonnaci(source.Token).GetEnumerator(); nums.MoveNext(); nums.MoveNext(); nums.MoveNext(); try { throw new Exception(); } catch { nums.Dispose(); }

Ayende Rahien

Mike, Why do you need the exception there?

Mike

A simpler way... var token = new CancellationToken(); var nums = Fibonnaci(token).GetEnumerator(); nums.MoveNext(); nums.MoveNext(); nums.MoveNext(); nums.Dispose();

Mike

Equivalent way but how it probably arises in real code... var token = new CancellationToken(); using (var nums = Fibonnaci(token).GetEnumerator()) { nums.MoveNext(); nums.MoveNext(); nums.MoveNext(); }

Ayende Rahien

Andres, That wouldn't cause the assertion to fire

Andres
class CancellationToken {
    IsCancellationRequested { get; set; }
}

var token = new CancellationToken();

foreach (var n in Fibonnaci(token)) {
    if (n > 100) {
        token.IsCancellationRequested = true;
    }
}
Andres

Why not?

In the next iteration it will not enter in the while, so it will be catched by finally

Ayende Rahien

Andres, Yes, and that the IsCancellationRequested will return true, and will work for the assert. Note that you cannot just create a new cancellation token, though. I intend you use the current one.

Bjørn Moe
            var ct = new CancellationToken();
            foreach (var test in Fibonnaci(ct))
            {
                if (test > 1)
                    throw new InvalidOperationException("corvus");
            }
Mital Kakaiya

The following calling code makes it fire the assertion!

int index = 0; foreach (var value in Fibonnaci(new CancellationToken())) { index++; if (index > 2) { break; } }

Matthew Wills

http://blogs.msdn.com/b/ericlippert/archive/2009/07/16/iterator-blocks-part-three-why-no-yield-in-finally.aspx

"We generate a Dispose method that checks the current state and executes any pending finally blocks."

Matthew Wills

Based on that, I would assume that Take(3) would cause the issue:

    [Test]
    public void TestBob()
    {
        var token = new CancellationToken();

        var firstThree = Fibonnaci(token).Take(3).ToList();

        Assert.AreEqual(3, firstThree.Count());
    }
payam yazdkhasti

static void Main(string[] args) { var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; Task.Run(() => { foreach (var fibonnaciNumber in Fibonnaci(token)) { Console.WriteLine(fibonnaciNumber); Task.Delay(2000).Wait(); } }, token);

        Task.Delay(10000).Wait();
        tokenSource.Cancel();
        Console.ReadLine();

    }
Rafal
        foreach (var i in Fibonnaci(ct))

        {

            Console.WriteLine(i);

            if (i > 10000) break;

        }
Aleksander Polak

var cts = new CancellationTokenSource(); Fibonnaci(cts.Token).Skip(2).First();

markrendle

try{Fibonnaci(new CancellationToken(false)).Sum();}catch{}

markrendle

Golfing now:

try{Fibonnaci(CancellationToken.None).Sum();}catch{}

markrendle

Think this must be par:

Fibonnaci(CancellationToken.None).Any(x=>x>2);

Pavol Kovalik

Without modifying the method:

Oded was on the right track, but you have to enable check for arithmetic overflow for whole project. See Properties -> Build -> Advanced -> Check for arithmetic overflow/underflow.

Alex Spence

class Program { static void Main(string[] args) {

        var source = new CancellationTokenSource();
        source.CancelAfter(TimeSpan.FromSeconds(30));

        foreach (var value in Fibonnaci(source.Token)) {

            Console.Out.WriteLine("Fib: {0}", value);

        }
    }


    public static IEnumerable<int> Fibonnaci(CancellationToken token) {
        yield return 0;
        yield return 1;

        var prev = 0;
        var cur = 1;

        try {
            while (token.IsCancellationRequested == false) {
                var tmp = prev + cur;
                prev = cur;
                cur = tmp;
                yield return tmp;
            }
        } finally {
            Console.Out.WriteLine("Cancelled");
        }
    }
}
Pascal Van Vlaenderen

Easy:

CancellationToken token = new CancellationToken();

        foreach (int number in Fibonnaci(token))
            Console.Write(number);

Throws out of memory exception and then the Assertion is called

Frank

This does the trick: var source = new CancellationTokenSource(); Fibonnaci(source.Token).Take(3).ToList();

The assert fails, because of the Dispose that is called on the enumerator that is used underneath. Essentially this happens:

var e = Fibonnaci(source.Token).GetEnumerator(); e.MoveNext(); e.MoveNext(); e.MoveNext(); e.Dispose();

The 3 MoveNext calls are necessary to get into the try block. Calling dispose will result in the finally block being executed, while IsCancellationRequested is still false.

Itzhak Kasovitch

ar tokenSource = new CancellationTokenSource();

var enumerable = Fibonnaci(tokenSource.Token);

var enumerator = enumerable.GetEnumerator();

for (int i = 0; i < 4; i++) { enumerator.MoveNext(); }

enumerator.Dispose();

Johannes
var cts = new CancellationTokenSource();
var enumerator = Fibonnaci(cts.Token).GetEnumerator();
enumerator.MoveNext(); // 0
enumerator.MoveNext(); // 1
enumerator.MoveNext(); // 1, state machine is within try block
enumerator.Dispose();
Ron Sijm

Wouldn't the easiest way be to just call Fibonnaci(null);?

This would cause a nullreference exception at the while, jump to the finally block, and fire the assertion. (Would cause another nullreference exception there, but the challenge is to fire the assertion...)

Neil Mosafi
        foreach (var num in Fibonnaci(new CancellationToken(true)))
            if (num > 1) break;
Neil Mosafi

Ron Sijm - Sneaky suggestion, but CancellationToken is actually a struct

João Bragança

Nope. CancellationToken is a struct. I think Johannes has it, just don't use the foreach syntactic sugar :)

macpak
 var cancellationToken = new CancellationTokenSource();
        foreach (var i in Fibonnaci(cancellationToken.Token))
        {
            cancellationToken.Cancel();
        }
Scott Seligman
var source = new CancellationTokenSource();
try
{
    foreach (var cur in Fibonnaci(source.Token))
    {
        source.Cancel();
        if (cur > 100)
        {
            throw new Exception("Boom");
        }
    }
}
catch
{
    source.Cancel();
}
Scott Seligman

Well, get rid of the spurious source.Cancel();

Eamon Nerbonne

@markrendle: on the code-golfing topic your second-to-last entry can simply omit the try-catch:

This is enough to trigger the assertion:

Fibonnaci(CancellationToken.None).Sum();
Eamon Nerbonne

Trying to win prize for nastiest solution here... obviously aborting the iteration is easier :-)

var cts = new CancellationTokenSource();
var field = cts.GetType().GetField("m_state",BindingFlags.Instance|BindingFlags.NonPublic);
var parE = Expression.Parameter(cts.GetType());
var nasty = Expression.Lambda<Action>(Expression.Assign(Expression.Field(Expression.Constant(cts),field),Expression.Constant(1))).Compile();
Task.Factory.StartNew(()=>{
    while(true) {       
        cts.Cancel();
        nasty();
    }
}, TaskCreationOptions.LongRunning);
while(true) 
    Fibonnaci(cts.Token).Count();
Eamon Nerbonne

In the previous code, you can replace the inane generated action simply with "field.SetValue(cts, 1);" - I mistakenly assumed that it would be hard to trigger this with slow reflection, but SetValue is apparently fast enough. On my machine - :-).

Stephen Zeng

It just needs to get out the infinite loop in the try block, so take(3) will do the trick:

Fibonnaci(new CancellationToken()).Take(3).ToList();

Comment preview

Comments have been closed on this topic.

FUTURE POSTS

  1. RavenDB 3.0 New Stable Release - 7 hours from now
  2. Production postmortem: The industry at large - about one day from now
  3. The insidious cost of allocations - 2 days from now
  4. Buffer allocation strategies: A possible solution - 5 days from now
  5. Buffer allocation strategies: Explaining the solution - 6 days from now

And 3 more posts are pending...

There are posts all the way to Sep 11, 2015

RECENT SERIES

  1. Find the bug (5):
    20 Apr 2011 - Why do I get a Null Reference Exception?
  2. Production postmortem (10):
    01 Sep 2015 - The case of the lying configuration file
  3. What is new in RavenDB 3.5 (7):
    12 Aug 2015 - Monitoring support
  4. Career planning (6):
    24 Jul 2015 - The immortal choices aren't
View all series

RECENT COMMENTS

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats