Reputation: 181
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
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