I am getting intermittent deadlocks when using HttpClient
to send http requests and sometimes they are never returning back to await SendAsync
in my code. I was able to figure out the thread handling the request internally in HttpClient
/HttpClientHandler
for some reason has a SynchronizationContext
during the times it is deadlocking. I would like to figure out how the thread getting used ends up with a SynchronizationContext
, when normally they don't have one. I would assume that whatever object is causing this SynchronizationContext
to be set is also blocking on the Thread
, which is causing the deadlock.
Would I be able to see anything relevant in the TPL ETW events?
How can I troubleshoot this?
Edit 2:
The place that I have been noticing these deadlocks is in a wcf ServiceContract
(see code below) inside of a windows service. The SynchronizationContext
that is causing an issue is actually a WindowsFormsSynchronizationContext
, which I assume is caused by some control getting created and not cleaned up properly (or something similar). I realize there almost certainly shouldn't be any windows forms stuff going on inside of a windows service, and I'm not saying I agree with how it's being used. However, I didn't write any of the code using it, and I can't just trivially go change all of the references.
Edit: here is an example of the general idea of the wcf service I was having a problem with. It's a simplified version, not the exact code:
[ServiceContract]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
internal class SampleWcfService
{
private readonly HttpMessageInvoker _invoker;
public SampleWcfService(HttpMessageInvoker invoker)
{
_invoker = invoker;
}
[WebGet(UriTemplate = "*")]
[OperationContract(AsyncPattern = true)]
public async Task<Message> GetAsync()
{
var context = WebOperationContext.Current;
using (var request = CreateNewRequestFromContext(context))
{
var response = await _invoker.SendAsync(request, CancellationToken.None).ConfigureAwait(false);
var stream = response.Content != null ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null;
return StreamMessageHelper.CreateMessage(MessageVersion.None, "GETRESPONSE", stream ?? new MemoryStream());
}
}
}
Adding ConfigureAwait(false)
to the 2 places above didn't completely fix my problem because a threadpool thread used to service a wcf request coming into here may already have a SynchronizationContext
. In that case the request makes it all the way through this whole GetAsync
method and returns. However, it still ends up deadlocked in System.ServiceModel.Dispatcher.TaskMethodInvoker
, because in that microsoft code, it doesn't use ConfigureAwait(false)
and I want to assume there is a good reason for that (for reference):
var returnValueTask = returnValue as Task;
if (returnValueTask != null)
{
// Only return once the task has completed
await returnValueTask;
}
It feels really wrong, but would converting this to using APM (Begin/End) instead of using Tasks fix this? Or, is the only fix to just correct the code that is not cleaning up its SynchronizationContext
properly?
SynchronizationContext
was set in the problem thread based on a process dump, but the thread doesn't have a managed stacktrace, only unmanaged stuff. – HumfreySynchronizationContext
is getting set in the first place, and then i'll decide based on that whether it makes sense to use theConfigureAwait(false)
. Also I wanna make sure adding theConfigureAwait(false)
won't add any unintended side effects, even though it appears to help this case. – HumfreyHttpClient
code you are not awaiting a task, which until you addedConfigureAwait(false)
was causing the deadlock when the async resumption attempted to grab the same context that was already held by the upstream thread. Here's some more reading: medium.com/bynder-tech/… – BrisbaneSynchronizationContext
is being set at runtime. So I was hoping there was some way I could capture more info from the process to see the callstack before it's getting created, or something of that nature. – HumfreySystem.ServiceModel.Dispatcher.TaskMethodInvoker
. The code I posted isn't where the method gets called, but it's right above that. And then in the code I posted itawait
s theTask
that gets returned from invokingGetAsync
. – Humfrey