20

Ignore for a moment the absurdity of awaiting an Enumerable.Range call. It's just there to elicit the crash-y behavior. It just as easily could be a method that's doing some network IO to build a collection of value objects. (Indeed, this was where I saw the crash occur.)

If I comment out the Interlocked.Exchange line, the compiler doesn't crash.

public class Launcher
{
    private static IEnumerable<int> _foo;
    static void Main(string[] args)
    {
        DoWorkAsync().Wait();
    }

    private static async Task DoWorkAsync()
    {
        RefreshCache();
        foreach (var element in _foo)
        {
            Console.WriteLine(element);
        }

        Console.ReadLine();
    }

    private static async void RefreshCache()
    {
        Interlocked.Exchange(ref _foo, await Cache());
    }

    private static async Task<IEnumerable<int>> Cache()
    {
        return Enumerable.Range(0, 10);
    }
}

Changing RefreshCache() to this keeps the compiler from crashing:

private static async void RefreshCache()
{
    var foo = await Cache();
    Interlocked.Exchange(ref _foo, foo);
}

Edit:

An even simpler, self contained reproduce provided by @Servy

public class Worker
{
    private static int foo;
    private static async void DoWork()
    {
        DoNothing(ref foo, await Task.FromResult(0));
    }

    private static void DoNothing(ref int item, int other)
    {
    }
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
rianjs
  • 7,767
  • 5
  • 24
  • 40
  • Reproduces on my machine as well. Pinged @JaredPar about it, he'll try reproducing it on roslyn. I'll try it as well. – Yuval Itzchakov May 20 '15 at 19:07
  • 2
    This doesn't re-produce on [Roslyn](http://goo.gl/Vn3unK). Interesting. – Yuval Itzchakov May 20 '15 at 19:11
  • @YuvalItzchakov Those were the exact same results I had, C#5.0 (VS2013) has the error, C#6.0 (VS2015) does not. What's even more interesting, is telling VS2015 to compile as C#5.0 does not throw the error. – Der Kommissar May 20 '15 at 19:12
  • 3
    I was just thinking on my way into work yesterday "I will never find a bug in any compiler, ever. It will always be PEBCAK." – rianjs May 20 '15 at 19:30
  • Interlocked.Exchange introduces a memory barrier and thus is treated differently by the compiler. So I can understand why the first version wouldn't work. But no the second one. – Kryptos May 20 '15 at 19:34
  • 3
    @Kryptos I don't understand why the introduction of a memory barrier would cause the *compiler* to crash. I don't even see why it would fail to work properly at runtime, but crashing the compiler (and not even successfully providing a compile time error) is unquestionably a bug. – Servy May 20 '15 at 19:36
  • 2
    @Kryptos Even though a memory barrier is introduced, I still don't see why the first should fail, specifically causing a compiler crash. – Yuval Itzchakov May 20 '15 at 19:36
  • 1
    Could this be a bug in the compiler related to the use of `ref` and not necessarily `Interlocked.Exchange`? – David May 20 '15 at 19:38
  • Using `private static void DoNothing(ref T item, T other) { }` instead of `Interlocked.Exchange` also crashed the compiler. `private static void DoNothing(ref IEnumerable item, IEnumerable other) { }` also crashes the compiler, so it's not related to generics at all. – Servy May 20 '15 at 19:41
  • What happens if you create a method `DoInterlock(T foo) { Interlocked.Exchange(ref _foo, foo); }`and just call it from the `async` method? Does it still crash? – Kryptos May 20 '15 at 19:45
  • 2
    So I've been working on minimizing the example. There needs to be a call to a method with a `ref` parameter, a *static field* needs to be supplied as the reference to that field, the method needs to have a second (non-ref) argument that uses an `await` as the expression for the parameter. – Servy May 20 '15 at 19:49
  • 2
    Issue opened on [GitHub](https://github.com/dotnet/roslyn/issues/2953) – Yuval Itzchakov May 20 '15 at 20:09
  • 1
    The C# language does not know what Interlocked even is. It would be invalid for the compiler to special-case on it. This is not specific to Interlocked. I think it's funny that the C# team forgot to test await in combination with ref :) – usr May 20 '15 at 20:17
  • @usr Indeed, I've already said that this can be reproduced without using `Interlocked`. It's not special. You can see my smallest repo in the github link (and now in the question, it seems). – Servy May 20 '15 at 20:18
  • @Servy I noticed that and this was my first thought as well. I'm just pointing out for others how to correctly think about this bug. – usr May 20 '15 at 20:20
  • Edited the simpler repro to the question. – Yuval Itzchakov May 20 '15 at 20:20
  • @usr Interestingly enough, it's not *just* the use of `ref` and `await`. If the referenced variable isn't a static field (i.e. being an instance field or a local) then it works fine, which seems particularly odd. So *certain* uses of `await` with a `ref` method work fine. – Servy May 20 '15 at 20:22
  • 1
    There is no question here at all, so this should be closed. You found a compiler bug that reproduces in one version of the compiler; report it to Microsoft, not to StackOverflow. – Eric Lippert May 21 '15 at 13:19
  • Well, it was an implied question: is this PEBCAK? Is it a configuration problem? Is it a problem because I just installed 2015CTP alongside 2013 just before doing this? etc. I completely expected it to be PEBCAK or a configuration conflict. But you're right, I'll close it up, because the other possibilities have been ruled out. I do wonder if it'll be fixed, though, for older compiler versions. – rianjs May 21 '15 at 17:05
  • I'm voting to close this question as off-topic because it's not a question that has an answer: it's a compiler bug. – rianjs May 21 '15 at 17:34

0 Answers0