.Net ClientAPI occured heartbeat timeout when many concurrent threadpool thread

Hi,

I found that when use many thread pool thread synchronously append event to a single connection will cause event store connection disconnect frequently.

The following is the example code:

for( int i=0; i<100; i++ )
{
Task.Run
(
var beginTime = DateTime.Now();
connection.AppendToStreamAsync(Guid.NewGuid.ToString(), ExpectedVersion.Any, new EventData(Guid.NewGuid(), “String”, true, data, null)).Wait();
Console.WriteLine(DateTime.Now().Substract(beginTime).TotalMilliseconds.ToString());
);
}

``

I use console to output the duration of this method call, i found the duration became more and more longer, and at that time, the connection will disconnect randomly.

To keep connection stable, i increased server side heartbeat timeout value, it will be work fine, but the speed of event appending is very slow.

At first, I thought this problem maybe caused by thread pool exhausted, but it’s no use to increase MaxThreads of ThreadPool.

Is there something wrong with threadpool? so i shift code from threadpool thread to normal thread.

The following is the example code:

for( int i=0; i<100; i++ )
{
Task.Run
(
var thread = new Thread
(
()=>
{
var beginTime = DateTime.Now();
connection.AppendToStreamAsync(Guid.NewGuid.ToString(), ExpectedVersion.Any, new EventData(Guid.NewGuid(), “String”, true, data, null)).Wait();
Console.WriteLine(DateTime.Now().Substract(beginTime).TotalMilliseconds.ToString());
}
);

);
}

``

Fortunately,the above code execute very fast and the event store connection never disconnect again.

I don’t think using thread pool thread is the root cause of this problem, maybe something wrong with event store client API.

I download the source code of event store client API and debug it, at last, i found the following code is the root cause of this problem. (The following code is under class EnqueueMessage in project EventStore.ClientAPI).

public void EnqueueMessage(Message message)

{

Ensure.NotNull(message, “message”);

_messageQueue.Enqueue(message);

if (Interlocked.CompareExchange(ref _isProcessing, 1, 0) == 0)

ThreadPool.QueueUserWorkItem(ProcessQueue);

}

private void ProcessQueue(object state)

{

do

{

Message message;

while (_messageQueue.TryDequeue(out message))

{

Action handler;

if (!_handlers.TryGetValue(message.GetType(), out handler))

throw new Exception(string.Format(“No handler registered for message {0}”, message.GetType().Name));

handler(message);

}

Interlocked.Exchange(ref _isProcessing, 0);

} while (_messageQueue.Count > 0 && Interlocked.CompareExchange(ref _isProcessing, 1, 0) == 0);

}

``

The above code will delay event message processing when use many threadpool thread synchronously append event, the following graph figure out it.

I changed the code as following, the problem is solved.

public void EnqueueMessage(Message message)
{
Ensure.NotNull(message, “message”);

_messageQueue.Enqueue(message);

if (Interlocked.CompareExchange(ref _isProcessing, 1, 0) == 0)

{

var thread = new Thread(ProcessQueue);

thread.IsBackground = true;

thread.Start();

}

}

``

What you are seeing is thread pool exhaustion. Your code is competing with the code in the client for ability to run.

The fix you mention here probably is not a good one (don’t want to create/destroy a thread so often, part of why the code works like this is that its using the thread pool for scheduling). Better would be to have a single thread that idled as opposed to constantly creating a new one (or waited up to a timeout etc)

btw boosting the number of threads in your thread pool settings would also fix this.

Why are you running a Task and then waiting inside?

Task.Run(Task.WhenAll(Enumerable.Range(0, 100).Select(onnection.AppendToStreamAsync(Guid.NewGuid.ToString(), ExpectedVersion.Any, new EventData(Guid.NewGuid(), “String”, true, data, null))))

It’s no use to boost the number of threads in thread pool. I think the reason of this problem is using thread pool will cause duration of heartbeat becomes longer.

在 2015年8月3日星期一 UTC+8下午4:32:52,Greg Young写道:

This is just a simulation of asp.net synchronized writing event to eventstore streams. As you know asp.net is using thread pool to process request.

在 2015年8月4日星期二 UTC+8上午4:41:20,João Bragança写道:

How are you ensuring messages are pumped? Task.Run is not a way of simulating what ASP may or may not do. Try it in an actual ASP site and use wrk or boom to simulate some load - this should not be an issue there. Remember you should have one EventStoreConnection per process under the vast majority of circumstances, not one per thread.

Run the demo code in ASP.NET mvc, the result as same as Task.Run.

在 2015年8月5日星期三 UTC+8下午10:27:31,James Nugent写道:

I think you misunderstood. He was not saying to run your demo code as
is in asp he was saying to write normal code not using tasks and load
test with something like wrk

I’m sorry to not describe clearly. I create a MVC aciton, just put only one line code “connection.AppendToStreamAsync(…).Wait” in that action.When increase load, the action will be slowly and slowly. If replace ThreadPool.QueueUserWorkItem as normal thread, the action will be more faster.

在 2015年8月10日星期一 UTC+8下午6:37:07,Greg Young写道:

I’m not familiar enough with the threading model of ASP.NET MVC to be able to answer this definitively but I suspect that calling Wait in a handler will block whatever thread it is running on. As far as I’m aware the default model for ASP is still thread-per-client, so it’s quite possible you’re seeing threadpool exhaustion here?

Perhaps someone using ES with ASP.NET can comment further?

Yes, that’s exactly what it does. Also, the default scheduler for the taskpool is backed by the threadpool.

What I tried to explain earlier is that there is no point in using TPL and then calling Wait or Result. They are only there if you need to call async code from a sync method e.g. from the console (although in core clr you will return Task from your entry point).

https://msdn.microsoft.com/en-us/library/0ka9477y(v=vs.110).aspx