We've also seen the loss of one client at a time and tracked it down
to a deadlock on the server. The deadlock looked like this:
Thread 1 call stack:
[blocked while invoking callback]
CometAsyncResult.CompleteRequestWithMessages()
Client.FlushQueue() <----- acquires lock on syncRoot
FowardingHandler.SendMessageToRecipients()
FowardingHandler.HandleMessage()
MessagesProcessor.Process(Message)
MessagesProcessor.Process(IEnumerable<Message>)
MessageBus.CreateProcessorAndProcess()
MessageBus.HandleMessages()
CometHttpHandler.BeginProcessRequest()
CometHttpHandler.BeginProcessRequest()
Thread 2 call stack:
[waiting for lock on syncRoot held by Thread 1]
Client.Enqueue()
ClientExtensions.Enqueue()
ForwardingHandler.SendMessageToRecipients()
ForwardingHandler.HandleMessage()
MessagesProcessor.Process(Message)
MessagesProcessor.Process(IEnumerable<Message>)
MessageBus.CreateProcessorAndProcess()
MessageBus.HandleMessages()
CometHttpHandler.BeginProcessRequest()
CometHttpHandler.BeginProcessRequest()
The changes that appear to fix this are in Client.cs:
1) Change GetMessages() to iterate over list immediately:
private IEnumerable<Message> GetMessages()
{
IList<Message> result = new List<Message>();
while (this.messageQueue.Count > 0)
result.Add( this.messageQueue.Dequeue() );
return result;
}
2) Release lock on syncRoot earlier in FlushQueue(), before the
callback:
public void FlushQueue()
{
if (this.messageQueue.Count > 0 && this.CurrentAsyncResult != null)
{
IEnumerable<Message> response = null;
ICometAsyncResult asyncResult = null;
lock (syncRoot) // double checked lock
{
if (this.messageQueue.Count > 0 && this.CurrentAsyncResult !=
null)
{
response = this.GetMessages();
asyncResult = CurrentAsyncResult;
this.CurrentAsyncResult = null;
}
}
if (response != null)
{
asyncResult.CompleteRequestWithMessages(response);