10

Normally, when the debugger is attached, Visual Studio 2010 stops at an unhandled exception even if the Exceptions dialog doesn’t have the tickmark for the exception type in the “Thrown” column. The keyword here is unhandled; said dialog refers only to handled exceptions.

However, in the following minimal example, Visual Studio 2010 does not stop at the exception for me, even though it appears in the Immediate Window as a first-chance exception:

EDIT: The first minimal example I posted was fixed by the first answer I received, but unfortunately the following example still exhibits the problem:

using System;
using System.Net.Sockets;

namespace SocketTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var listener = new TcpListener(8080);
            listener.Start();
            AsyncCallback accepter = null;
            accepter = ar =>
            {
                var socket = listener.EndAcceptSocket(ar);
                var buffer = new byte[65536];
                AsyncCallback receiver = null;
                receiver = ar2 =>
                {
                    var bytesRead = socket.EndReceive(ar2);
                    throw new InvalidOperationException();
                    socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, receiver, null);
                };
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, receiver, null);
                listener.BeginAcceptSocket(accepter, null);
            };
            listener.BeginAcceptSocket(accepter, null);

            Console.WriteLine("Feel free to connect to port 8080 now.");
            Console.ReadLine();
        }
    }
}

If you run this, connect to it by running telnet localhost 8080 and then type any character into telnet, hopefully you will see what I see: the program just aborts silently.

Why does Visual Studio apparently swallow this exception? Can I get it to break at the exception as it usually does?

(Interestingly, throwing inside the BeginAcceptSocket callback does get caught, as does an exception in a normal thread started with Thread.Start. I can only reproduce the issue by throwing inside the BeginReceive callback.)

Timwi
  • 65,159
  • 33
  • 165
  • 230

3 Answers3

13

This is a known bug in CLR version 4. The feedback article is here. A possible workaround is to change the framework target to version 3.5. I'll just quote the relevant portion of the feedback response:

We have investigated the issue and determined there is a bug in CLR v4.0 which causes this. The process does throw the exception (you can catch it with a catch handler for example) but the debugger was not properly notified of the unhandled exception. This causes the process to appear to exit without any indication from the debugger about what happened. We have investigated potential fixes, however all of the fixes had moderate risk of breaking other functionality. Because it was very late in product cycle we decided it was safer not to fix this issue and risk creating new bugs. We will continue to track this issue as part of our next release cycle.

The issue is restricted to exceptions that escape managed code where the thread was created using a few specific API calls:
new System.Threading.Timer()
ThreadPool.UnsafeQueueNativeOverloapped
ThreadPool.BindHandle
ThreadPool.RegisterWaitForSingleObject.

It is RegisterWaitForSingleObject() in this specific case.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    Interesting. Even before posting this I came up with the workaround of wrapping the callback in a `new Thread(() => { ... }).Start();`, but for me this only fixed the example code in the question, but not my “real” code in which I’m actually having the problem. It turns out I was applying the fix wrongly; should have looked at the callstack :) Thanks! Would you like to add this workaround to your answer? That would make it even better. – Timwi May 30 '12 at 22:40
  • It is a shame that you wouldn’t update your answer. I guess I’ll have to do that myself. – Timwi Jun 01 '12 at 23:07
13

Since you see only a 'Thrown' checkbox in the exception settings, my suspicion is that you have '.Net Framework Source Stepping' enabled in your debug settings. If you enable 'Just My Code' (which disables '.Net Framework Source Stepping'), you should get 'User-Unhandled' and 'Thrown' checkboxes in your exception settings dialog and you will also catch the exception in the debugger.

Screen Shot O' Victory:

Screenshot showing the debugger halted at the exception as expected

Timwi
  • 65,159
  • 33
  • 165
  • 230
Ritch Melton
  • 11,498
  • 4
  • 41
  • 54
  • Fantastic, that was the solution. Thank you. Although it does make me wonder: what does Framework source-stepping have to do with this? And why can’t it break on a user exception when that is enabled? – Timwi Nov 19 '11 at 21:10
  • @Timwi - I do not have any supporting information, but I conjecture that it has something to do with the debugger's inability to understand the difference between user code and non-user code. With whatever problems that caused the debugger team, the 'safe' choice is to disable catching user-unhandled exceptions entirely. – Ritch Melton Nov 19 '11 at 21:14
  • I’m afraid I have to retract my tickmark again because it still does not work. I will edit the question to include a bigger testcase in which I am still getting a silent termination and no debugger break. – Timwi Nov 20 '11 at 02:00
  • @Timwi - I could make your example pass and fail based on that. – Ritch Melton Nov 20 '11 at 02:25
  • Then there must be another relevant option that we have set differently. Or it is a race condition and depends on the hardware. Or something else. – Timwi Nov 21 '11 at 03:52
  • By the way, I notice that the “Unhandled exception” console output (with stacktrace) occurs in the console window just before it disappears... – Timwi Nov 21 '11 at 03:52
  • @Timwi - I truncated my last post somehow, but I can catch the thrown exception with your new code, but it is not considered user-unhandled. – Ritch Melton Nov 21 '11 at 11:42
  • @Timwi - Am I allowed to comment that this is pretty wonky code? – Ritch Melton Nov 21 '11 at 11:42
  • ① I can *catch* the exception too. I don’t want to catch it, I want Visual Studio to break at it. — ② Of course this is not production code. This is a minimal example to demonstrate the problem. The real program is of course a complex system involving TCP in which the connection handler might throw and I want to know about it (instead of having the program abruptly disappear). – Timwi Nov 21 '11 at 23:52
  • @Timwi - By catch I mean visual studio catches the 1st chance exception and breaks execution. – Ritch Melton Nov 22 '11 at 00:29
1

This is what I have discovered so far:

  1. If no exceptions are explicitly checked in the Debug->Exceptions dialog and Options->Debugging->"Enable Just My Code" (EJMC) is not checked, then an exception thrown in any of the callbacks will not break with First Chance Exception (FCE)

  2. If no exceptions are explicitly checked in the Debug->Exceptions dialog and EJMC is checked, then an exception thrown in the TCPListener's callback will break with FCE, but will not break with FCE in the Socket's callback

    a. An exception thrown will not break with FCE in the Socket's callback, even if the TCPListener's blocking AcceptSocket() is called (so no callback for listener).

  3. If System.InvalidOperationException (or System) is checked in Debug->Exceptions, then an appropriate exception thrown in any of the callbacks will break with FCE, regardless of whether EJMC is checked or not.

  4. The above is true whether the callbacks are specified as lambda or in an excplicit user function

I do not know why VS does not break with FCE in the socket callback if the exception is not excplicitly checked in Debug->Exceptions (what makes the socket callback different from the listener callback).

Attila
  • 28,265
  • 3
  • 46
  • 55
  • A (somewhat clumsy) workaround I can think of is to wrap the body of the callback in a try/catch block that will just re-throw the exception and put a break-point on the re-throw. Breakpoints seem to work regardless of the exception settings – Attila May 26 '12 at 16:19