Tigzy
Tigzy

Reputation: 181

C# Invalid Handle thrown in UI thread on Control.Invoke

I scratched my head for days on this issue... First, let's put some context:


I'm coding an application that communicates with a windows service through a NamedPipeServer/Client, and displays stuff in a Windows Form.

The problem is located in my System.Threading.Timer callback. This callback is rescheduled dynamically at the end of its own execution, based on a fixed interval minus execution time.

In this callback, I'm executing Pipe client request, and based on answer I update the UI with Invoke (classic cross thread message).

Code:

private void OnTimerSamplesEvent(object data)
{
    Stopwatch watch = new Stopwatch();
    watch.Start();

    try
    {      
        PipeClient pipe = new PipeClient("MyPipe");
        PipeMessage.Message msg_struct  = new PipeMessage.Message();
        msg_struct.magic                = PipeMessage.Message.Magic;
        msg_struct.command              = PipeMessage.Message.Command.GetSamples;
        PipeMessage msg                 = new PipeMessage(msg_struct);

        byte[] reply_struct = pipe.Send(msg.ToByteArray());
        if (reply_struct != null)
        {
            PipeMessage reply = new PipeMessage(reply_struct);
            if (reply.m_Message.IsValid()
             && reply.m_Message.command == PipeMessage.Message.Command.GetSamples
             && reply.m_Message.getsamples.samples != null)
            {
                //Crashes here
                UpdateListView(this.listViewMySamples, reply.m_Message.getsamples.samples, new List<string>(), new List<string>() { "Misc" });
            }
        }
    }
    catch(Exception ex)
    {
        Logger.Log("MainForm::OnTimerSamplesEvent: " + ex.Message);
    }
    finally
    {
        // reschedule worker
        m_TimerSamples.Change(Math.Max(0, SamplesCheckIntervalms - watch.ElapsedMilliseconds), Timeout.Infinite);
    }
}

private void UpdateListView(ListView lv, List<MySample> samples, List<string> type_included, List<string> type_excluded)
{
    // Update view with modified samples / Remove out of sync
    int items_count = DelegatesUI.LVGetItemsCount(this, lv);    //<-- Crashes here

    // some other code, but commented out for testing
    ... 
}

private delegate int _LVGetItemsCount(ListView lv);
static private int _lvGetItemsCount(ListView lv) 
{ 
    return lv.Items.Count; 
}
static public int LVGetItemsCount(Form f, ListView lv)
{
    if (f.InvokeRequired)
    {
        object ret = f.Invoke(new _LVGetItemsCount(_lvGetItemsCount), lv);
        return (ret == null) ? 0 : (int)ret;
    }
    else
        return _lvGetItemsCount(lv);
}

This works well, but for some reason, about 5 minutes after execution the main thread crashes on a System.IO.__Error.WinIOError exception (invalid handle). When I look at worker thread state, it's waiting on Invoke (WaitHandle.WaitOne), problably waiting for UI thread to signal its handle.

UI Call stack:

ERROR_INVALID_HANDLE   
à System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
à System.IO.__Error.WinIOError()
à System.Threading.EventWaitHandle.Set()
à System.Windows.Forms.Control.ThreadMethodEntry.Complete()
à System.Windows.Forms.Control.InvokeMarshaledCallbacks()
à System.Windows.Forms.Control.WndProc(Message& m)
à System.Windows.Forms.ScrollableControl.WndProc(Message& m)
à System.Windows.Forms.ContainerControl.WndProc(Message& m)
à System.Windows.Forms.Form.WndProc(Message& m)
à System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
à System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
à System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
à System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
à System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
à System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
à System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
à System.Windows.Forms.Application.Run(Form mainForm)
à agentui.Program.Main() dans c:\MBAM\cosmos\cosmos-agent\ui\Program.cs:ligne 17
à System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
à System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
à Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ThreadHelper.ThreadStart()

Worker Call stack:

mscorlib.dll!System.Threading.WaitHandle.WaitOne(long timeout, bool exitContext)    Inconnu
mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext) Inconnu
System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle waitHandle) Inconnu
System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous) Inconnu
System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args) Inconnu
>   agentui.exe!SharpUtils.DelegatesUI.InvokeAndClose(System.Windows.Forms.Control c, System.Delegate invok, object[] args) Ligne 38    C#
agentui.exe!SharpUtils.DelegatesUI.LVGetItemsCount(System.Windows.Forms.Form f, System.Windows.Forms.ListView lv) Ligne 541 C#
agentui.exe!agentui.MainForm.UpdateListView(System.Windows.Forms.ListView lv, System.Collections.Generic.List<agentsvc.MySample> samples, System.Collections.Generic.List<string> type_included, System.Collections.Generic.List<string> type_excluded) Ligne 142   C#
agentui.exe!agentui.MainForm.OnTimerSamplesEvent(object data) Ligne 283 C#
mscorlib.dll!System.Threading._TimerCallback.TimerCallback_Context(object state)    Inconnu
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Inconnu
mscorlib.dll!System.Threading._TimerCallback.PerformTimerCallback(object state) Inconnu

The question is: What could cause that WaitHandle to be closed? Also, as this happens in the UI thread I can't catch anything since I don't have user code here (frame is "Application.Run(new MainForm());")

Any idea is welcomed. Thanks :)


EDIT: I just noticed GC thread is throwing a bunch of ReleaseHandleFailed on Microsoft.Win32.SafeHandles.SafeWaitHandle because of code in that Pipe worker thread. Don't know if it's something usual (doesn't seem very legit)...

GC thread:

>   mscorlib.dll!System.Runtime.InteropServices.SafeHandle.Dispose(bool disposing)  Inconnu
mscorlib.dll!System.Runtime.InteropServices.SafeHandle.Finalize()   Inconnu

Worker thread:

[Transition Managé à Natif] 
>   System.Core.dll!System.IO.Pipes.PipeStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafePipeHandle handle, byte[] buffer, int offset, int count, System.Threading.NativeOverlapped* overlapped, out int hr)   Inconnu
System.Core.dll!System.IO.Pipes.PipeStream.ReadCore(byte[] buffer, int offset, int count)   Inconnu
System.Core.dll!System.IO.Pipes.PipeStream.Read(byte[] buffer, int offset, int count)   Inconnu
agentui.exe!agentui.PipeClient.Send(byte[] message, int TimeOut) Ligne 172  C#
agentui.exe!agentui.MainForm.OnTimerSamplesEvent(object data) Ligne 275 C#
mscorlib.dll!System.Threading._TimerCallback.TimerCallback_Context(object state)    Inconnu
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Inconnu
mscorlib.dll!System.Threading._TimerCallback.PerformTimerCallback(object state) Inconnu

EDIT 2: I've noticed if I comment the code to read from Pipe (the one throwing ReleaseHandleFailed) I don't have that problem anymore.

So I'm gonna post that part too:

Client:

    public byte[] Send(byte[] message, int TimeOut = 1000)
    {
        byte[] answer = null;

        // Write query
        NamedPipeClientStream pipeStream = new NamedPipeClientStream(".", m_PipeName, PipeDirection.InOut);  
        try
        {
            // The connect function will indefinitely wait for the pipe to become available
            // If that is not acceptable specify a maximum waiting time (in ms)
            pipeStream.Connect(TimeOut);
            pipeStream.ReadMode = PipeTransmissionMode.Message;
            pipeStream.Write(message, 0, message.Length);
        }
        catch (Exception ex)
        {
            Logger.Log("PipeClient: " + ex.Message);
            try { pipeStream.Close(); } catch(Exception) {}
            return answer;
        }

        // Read reply
        try
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    // We read chunks while the message is incomplete
                    do
                    {
                        // Read the incoming chunk
                        byte[] buffer = new byte[256];
                        int bytesRead = pipeStream.Read(buffer, 0, buffer.Length);

                        // Append to the binary stream
                        bw.Write(buffer, 0, bytesRead);
                    }
                    while (!pipeStream.IsMessageComplete);

                    pipeStream.Close();                        
                    answer = ms.ToArray().Length > 0 ? ms.ToArray() : null;
                }
            }
        }
        catch (Exception ex)
        {
            Logger.Log("PipeClient: " + ex.Message);
            try { pipeStream.Close(); } catch (Exception) { }
        }

        return answer;
    }

Server:

    private static void WorkerThread(object data)
    {
        PipeServer pipeServer = (PipeServer)data;
        while ( true )
        {
            try
            {
                PipeSecurity pipesecurity = new PipeSecurity();

                // Who has access to the pipe?
                pipesecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), PipeAccessRights.ReadWrite, AccessControlType.Allow));

                // Who can control the pipe? TODO: Find a SID representing the service
                pipesecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), PipeAccessRights.FullControl, AccessControlType.Allow)); 

                // Create the new async pipe 
                NamedPipeServerStream namedPipe = new NamedPipeServerStream(pipeServer.m_PipeName, 
                    PipeDirection.InOut, 
                    NamedPipeServerStream.MaxAllowedServerInstances, 
                    PipeTransmissionMode.Message,
                    PipeOptions.Asynchronous,
                    4096, 
                    4096, 
                    pipesecurity);

                // Wait for a connection with event
                var asyncResult = namedPipe.BeginWaitForConnection(_ => pipeServer.m_TerminateEvent.Set(), null);
                pipeServer.m_TerminateEvent.WaitOne();  // We block on that event until we have a client or a shutdown request
                pipeServer.m_TerminateEvent.Reset();    // We reset the event to reuse it

                if (asyncResult.IsCompleted)
                {
                    // We have a client, stop listening while we process it
                    namedPipe.EndWaitForConnection(asyncResult);

                    // Start a new thread for that client
                    Thread t = new Thread(() => ClientThread(namedPipe, pipeServer));
                    t.Start();
                }
                else
                {
                    // We requested a shutdown
                    namedPipe.Close();
                    namedPipe.Dispose();
                    break;
                }                    
            }
            catch (Exception ex)
            {
                Logger.Log("PipeServer::WorkerThread: " + ex.Message);
            }
        }

        // Notify the main thread we end the worker thread
        pipeServer.m_TerminatedEvent.Set();
    }

    private static void ClientThread(object pipe, object server)
    {
        NamedPipeServerStream namedPipe = (NamedPipeServerStream)pipe;
        PipeServer pipeServer           = (PipeServer)server;        

        try
        {     
            MemoryStream ms = new MemoryStream();
            BinaryWriter bw = new BinaryWriter(ms);

            // We read chunks while the message is incomplete
            do
            {
                // Read the incoming chunk
                byte[] buffer = new byte[256];
                int bytesRead = namedPipe.Read(buffer, 0, buffer.Length);

                // Append to the binary stream
                bw.Write(buffer, 0, bytesRead);
            }
            while (!namedPipe.IsMessageComplete);

            // Consume message.
            if (pipeServer.PipeEvent != null)
            {
                PipeEventArgs args = new PipeEventArgs( ms.ToArray() );
                pipeServer.PipeEvent(pipeServer, args);

                // Handle a possible reply
                if ( args.Reply != null && args.Reply.Length != 0 )
                    namedPipe.Write(args.Reply, 0, args.Reply.Length);
            } 
        }
        catch (Exception ex)
        {
            Logger.Log("PipeServer::ClientThread: " + ex.Message);
        }
        finally
        {
            namedPipe.Close();
            namedPipe.Dispose();
        }
    }

Upvotes: 1

Views: 1309

Answers (1)

Tigzy
Tigzy

Reputation: 181

I finally fixed it. And it was outside of the code I posted, you were right.

I'm using Json serialization / deserialization to pass objects through the Pipe. One of the objects passed through was containing a public member of type "Event".

After I looked at the serialized string, I realized it was passing a Handle from server side to UI side.

I'm pretty sure that handle was being closed on UI side by some obscur mechanism in the main loop (when resource is being freed (?), that's strange I thought GC was responsible for that kind of things....)

Anyway, I just added a [DataContract] to that object with [DataMember] on properties needed to be serialized, ignoring the others.

No more "ReleaseHandleFailed", et no more crash. If that can help someone.

Upvotes: 2

Related Questions