question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

HttpClientFactory managed HttpClient deadlocks in single-threaded SynchronizationContext

See original GitHub issue

Describe the bug

my legacy app is still running on ASP.NET, after internal library implementation adopting the HttpClientFactory managed HttpClient, async call with await in HttpClient never returns. this issue is very similar to #563

To Reproduce

dotnet new console -o Console1
cd Console1
dotnet add package Microsoft.Extensions.DependencyInjection --version 2.2.0
dotnet add package Microsoft.Extensions.Http --version 2.2.0

Program.cs

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;

namespace Console1
{
    public class Program
    {
        static void Main(string[] args)
        {
            var services = new ServiceCollection();
            services.AddHttpClient();
            var provider = services.BuildServiceProvider();
            var factory = provider.GetRequiredService<IHttpClientFactory>();
            var client1 = new HttpClient();
            SingleThreadedSynchronizationContext.Run(() =>
            {
                Console.Write("default HttpClient in single-threaded SynchronizationContext ... ");
                client1.GetAsync("http://example.com").GetAwaiter().GetResult();
                Console.WriteLine($"Done");
            });
            var client2 = factory.CreateClient();
            SingleThreadedSynchronizationContext.Run(() =>
            {
                Console.Write("managed HttpClient in single-threaded SynchronizationContext ... ");
                client2.GetAsync("http://example.com").GetAwaiter().GetResult();
                Console.WriteLine($"Done");
            });
        }
    }

    public class SingleThreadedSynchronizationContext : SynchronizationContext
    {
        private readonly Queue<(SendOrPostCallback Callback, object State)> _queue = new Queue<(SendOrPostCallback Callback, object State)>();

        public override void Post(SendOrPostCallback d, object state)
        {
            _queue.Enqueue((d, state));
        }

        public static void Run(Action action)
        {
            var previous = Current;
            var context = new SingleThreadedSynchronizationContext();
            SetSynchronizationContext(context);
            try
            {
                action();
                while (context._queue.TryDequeue(out var item))
                {
                    item.Callback(item.State);
                }
            }
            finally
            {
                SetSynchronizationContext(previous);
            }
        }
    }
}

Run this code

$ dotnet run
default HttpClient in single-threaded SynchronizationContext ... Done
managed HttpClient in single-threaded SynchronizationContext ...

you will see the second async call with await in HttpClient hangs

Expected behavior

HttpClientFactory managed HttpClient should also working in single-threaded SynchronizationContext

Screenshots

httpclient_deadlocks

Additional context

$ dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   2.2.103
 Commit:    8edbc2570a

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.14
 OS Platform: Darwin
 RID:         osx.10.14-x64
 Base Path:   /usr/local/share/dotnet/sdk/2.2.103/

Host (useful for support):
  Version: 2.2.1
  Commit:  878dd11e62

.NET Core SDKs installed:
  2.2.103 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.2.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.2.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.2.1 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
davidfowlcommented, Jan 28, 2019

We wouldn’t want that as a dependency in our tests. A simple single threaded sync context could look like this:

using System;
using System.Collections.Concurrent;
using System.Threading;

namespace ConsoleApp16
{
    class Program
    {
        static void Main(string[] args)
        {
            var previous = SynchronizationContext.Current;
            SynchronizationContext.SetSynchronizationContext(new SingleThreadedSynchronizationContext());
            try
            {
                // Do work here
            }
            finally
            {
                SynchronizationContext.SetSynchronizationContext(previous);
            }
        }
    }

    public class SingleThreadedSynchronizationContext : SynchronizationContext
    {
        private readonly BlockingCollection<(SendOrPostCallback Callback, object State)> _queue = new BlockingCollection<(SendOrPostCallback Callback, object State)>();
        private readonly Thread _thread;

        public SingleThreadedSynchronizationContext()
        {
            _thread = new Thread(ProcessQueue);
            _thread.Start();
        }

        private void ProcessQueue(object obj)
        {
            foreach (var item in _queue.GetConsumingEnumerable())
            {
                item.Callback(item.State);
            }
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            _queue.Add((d, state));
        }
    }
}
0reactions
rynowakcommented, Feb 17, 2019

Merged the fix. Thanks @akunzai

Read more comments on GitHub >

github_iconTop Results From Across the Web

HttpClientFactory managed HttpClient deadlocks in single- ...
my legacy app is still running on ASP.NET, after internal library implementation adopting the HttpClientFactory managed HttpClient, async call ...
Read more >
How to trigger (NOT avoid!) an HttpClient deadlock
First, SynchronizationContext is not special context we discussed above ... queue and executes them one by one on separate, single thread.
Read more >
ConfigureAwait FAQ - .NET Blog
I've heard ConfigureAwait(false) is no longer necessary in .NET Core. True? · SynchronizationContext or · TaskScheduler present. · Task scheduled ...
Read more >
Professional C# and .NET [2021 ed.] 1119797209 ...
Chapter 13, “Managed and Unmanaged Memory,” is the last chapter of Part I, which not only shows using the IDisposable interface with the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found