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
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.
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.
Adam, No, that would work just fine. The cancellation token property would be true, and that would result in no assertion being fired.
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.
var source = new CancellationTokenSource(); var nums = Fibonnaci(source.Token).GetEnumerator(); nums.MoveNext(); nums.MoveNext(); nums.MoveNext(); try { throw new Exception(); } catch { nums.Dispose(); }
Mike, Why do you need the exception there?
A simpler way... var token = new CancellationToken(); var nums = Fibonnaci(token).GetEnumerator(); nums.MoveNext(); nums.MoveNext(); nums.MoveNext(); nums.Dispose();
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(); }
Andres, That wouldn't cause the assertion to fire
Why not?
In the next iteration it will not enter in the while, so it will be catched by finally
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.
The following calling code makes it fire the assertion!
int index = 0; foreach (var value in Fibonnaci(new CancellationToken())) { index++; if (index > 2) { break; } }
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."
Based on that, I would assume that Take(3) would cause the issue:
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);
var cts = new CancellationTokenSource(); Fibonnaci(cts.Token).Skip(2).First();
try{Fibonnaci(new CancellationToken(false)).Sum();}catch{}
Golfing now:
try{Fibonnaci(CancellationToken.None).Sum();}catch{}
Think this must be par:
Fibonnaci(CancellationToken.None).Any(x=>x>2);
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.
class Program { static void Main(string[] args) {
Easy:
CancellationToken token = new CancellationToken();
Throws out of memory exception and then the Assertion is called
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.
ar tokenSource = new CancellationTokenSource();
var enumerable = Fibonnaci(tokenSource.Token);
var enumerator = enumerable.GetEnumerator();
for (int i = 0; i < 4; i++) { enumerator.MoveNext(); }
enumerator.Dispose();
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...)
Ron Sijm - Sneaky suggestion, but CancellationToken is actually a struct
Nope. CancellationToken is a struct. I think Johannes has it, just don't use the foreach syntactic sugar :)
Well, get rid of the spurious source.Cancel();
@markrendle: on the code-golfing topic your second-to-last entry can simply omit the try-catch:
This is enough to trigger the assertion:
Trying to win prize for nastiest solution here... obviously aborting the iteration is easier :-)
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 - :-).
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