1

We have a large and pretty complicated C# application that sometimes (rarely) sends back crash reports from users containing the following:

The handle is invalid
   <stacktrace>
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.Threading.EventWaitHandle.Set()
   at System.Windows.Forms.Control.ThreadMethodEntry.Complete()
   at System.Windows.Forms.Control.InvokeMarshaledCallbacks()
   at System.Windows.Forms.Control.WndProc(Message&amp; m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   </stacktrace>

Using IlSpy i checked the framework code, and it seems the exception must come from exactly here (in Control.InvokeMarshaledCallback):

    try
    {
        if (NativeWindow.WndProcShouldBeDebuggable && !threadMethodEntry.synchronous)
        {
            this.InvokeMarshaledCallback(threadMethodEntry);
        }
        else
        {
            try
            {
                this.InvokeMarshaledCallback(threadMethodEntry);
            }
            catch (Exception ex)
            {
                threadMethodEntry.exception = ex.GetBaseException();
            }
        }
    }
    finally
    {
        threadMethodEntry.Complete(); // <-- here! This calls Set() on the wait handle
        if (!NativeWindow.WndProcShouldBeDebuggable && threadMethodEntry.exception != null && !threadMethodEntry.synchronous)
        {
            Application.OnThreadException(threadMethodEntry.exception);
        }
    }
}

I then created a test program to see if I could replicate the situation, and sure enough, this test does it:

    using System;
    using System.Windows.Forms;

    namespace Test
    {
        static class Program
        {
            [STAThread]
            static void Main()
            {
                AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
                Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);

                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }

            static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
            {
                var ex = (Exception)e.ExceptionObject;
                MessageBox.Show(ex.Message);
                MessageBox.Show(ex.StackTrace);
            }
        }
    }


using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Test
{
    public partial class Form1 : Form
    {
        [DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)]
        static extern bool CloseHandle(
            [In] IntPtr handle);

        public Form1()
        {
            InitializeComponent();
        }

        void Post()
        {
            Text = "Some async method";
        }

        private void button1_Click(object sender, EventArgs e)
        {
            IAsyncResult wait = this.BeginInvoke((Action)Post);

            // Close the wait handle. When Post() returns the framework
            // tries to call .Set() on the wait event, but we closed it!
            IntPtr nativehandle = wait.AsyncWaitHandle.SafeWaitHandle.DangerousGetHandle();
            CloseHandle(nativehandle);
        }
    }
}

Bonus info: I can only get 'the handle is invalid' by closing the native handle, using DangerousGetHandle() and p/invoke. Calling AsyncWaitHandle.Close() generates 'the safe handle is closed', instead.

Now, the problem is that searching through our code base, I find not a single instance of AsyncWaitHandle. There are tons of BeginInvoke, but no waiting that could accidentally close the native handle. Unless there are other ways to do it, that I missed.

So the question:

What could cause this native handle to be closed?

Is there some framework race condition I should be aware of? It seems to only happen when lots of things are going on at the same time, from multiple threads.

  • 1
    My crystal ball says that you are catching ObjectDisposedException in your real program. That was a band-aid for a threading race bug, it didn't stop the bleeding however. [More here](http://stackoverflow.com/a/1732361/17034). – Hans Passant Sep 05 '14 at 11:03
  • Thank you Hans, that looks interesting. There are indeed a few ObjectDisposedException's that are caught and ignored. I will see if it leads anywhere. – user3812532 Sep 05 '14 at 13:06
  • Score one for the crystal ball. Remove that code so you get a good diagnostic for the bugs. Exceptions are your friend. – Hans Passant Sep 05 '14 at 13:36
  • Unfortunately we cannot reproduce it easily (never on any development machine) so it will take a while to test. – user3812532 Sep 05 '14 at 13:41

0 Answers0