Ayende @ Rahien

It's a girl

Code Challenge: Make the assertion fire

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
06/12/2014 12:13 PM by
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
06/12/2014 12:21 PM by
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
06/12/2014 12:24 PM by
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
06/12/2014 12:27 PM by
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
06/12/2014 12:32 PM by
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
06/12/2014 12:34 PM by
Ayende Rahien

Mike, Why do you need the exception there?

Mike
06/12/2014 12:36 PM by
Mike

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

Mike
06/12/2014 12:38 PM by
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
06/12/2014 12:43 PM by
Ayende Rahien

Andres, That wouldn't cause the assertion to fire

Andres
06/12/2014 12:39 PM by
Andres
class CancellationToken {
    IsCancellationRequested { get; set; }
}

var token = new CancellationToken();

foreach (var n in Fibonnaci(token)) {
    if (n > 100) {
        token.IsCancellationRequested = true;
    }
}
Andres
06/12/2014 12:58 PM by
Andres

Why not?

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

Ayende Rahien
06/12/2014 01:00 PM by
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
06/12/2014 01:30 PM by
Bjørn Moe
            var ct = new CancellationToken();
            foreach (var test in Fibonnaci(ct))
            {
                if (test > 1)
                    throw new InvalidOperationException("corvus");
            }
Mital Kakaiya
06/12/2014 01:34 PM by
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
06/12/2014 01:45 PM by
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
06/12/2014 01:53 PM by
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
06/12/2014 04:29 PM by
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
06/12/2014 06:53 PM by
Rafal
        foreach (var i in Fibonnaci(ct))

        {

            Console.WriteLine(i);

            if (i > 10000) break;

        }
Aleksander Polak
06/12/2014 07:56 PM by
Aleksander Polak

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

markrendle
06/12/2014 09:39 PM by
markrendle

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

markrendle
06/12/2014 10:34 PM by
markrendle

Golfing now:

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

markrendle
06/12/2014 10:41 PM by
markrendle

Think this must be par:

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

Pavol Kovalik
06/13/2014 12:27 AM by
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
06/13/2014 01:42 AM by
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
06/13/2014 06:29 AM by
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
06/13/2014 07:20 AM by
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
06/13/2014 02:00 PM by
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
06/14/2014 12:55 PM by
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
06/16/2014 09:02 AM by
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
06/16/2014 03:08 PM by
Neil Mosafi
        foreach (var num in Fibonnaci(new CancellationToken(true)))
            if (num > 1) break;
Neil Mosafi
06/16/2014 03:09 PM by
Neil Mosafi

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

João Bragança
06/16/2014 05:42 PM by
João Bragança

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

macpak
06/16/2014 05:48 PM by
macpak
 var cancellationToken = new CancellationTokenSource();
        foreach (var i in Fibonnaci(cancellationToken.Token))
        {
            cancellationToken.Cancel();
        }
Scott Seligman
06/17/2014 11:11 PM by
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
06/17/2014 11:12 PM by
Scott Seligman

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

Eamon Nerbonne
06/19/2014 07:08 PM by
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
06/19/2014 07:29 PM by
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
06/19/2014 07:33 PM by
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
09/08/2014 07:58 AM by
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();