-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sync API for HttpClient #32125
Comments
Do we have any existing synchronous API methods in .NET accepting |
It would be better to clearly separate the public |
We do not. CancellationToken's are something that was introduced into .NET as part of adding async/await/Task APIs. In general, we have avoided adding new sync APIs for I/O. But as described above, there is reason now to support sync API for broadly used APIs like HttpClient. I am curious, though, how the CancellationToken parameter will be used in these APIs. Sync APIs by their nature are difficult to cancel. |
How is it any more difficult than implementing timeout support? |
We will need to make the sync HTTP/1 implementation dispose the Socket, as the cancelable async methods won't be used. I think that's probably fine. |
That approach has a long history of AVs. Have those been dealt with? |
Does it? I hope so! |
We should consider using default parameters here: public Task<HttpResponseMessage> GetAsync(string requestUri);
public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption);
public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken);
public Task<HttpResponseMessage> GetAsync(string requestUri, CancellationToken cancellationToken); into public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default); |
Add Also the Finally, in the prototype, the @alnikola: I've seen your PR, the timeout is still propagated by |
Team discussion:
|
Is the motivation strong enough for adding sync methods? C# has great support for async programming. |
One reason would be legacy code. |
I think adding sync methods is a mistake and if upstream apis are exposing things as sync when there is underlying async that is their mistake to make, if you put these methods in I can see people falling into the trap of using them in a ecosystem that has embraced async. What is the scenario (outside of legacy) that you need a sync API these days. Servers are mostly async (Asp.net core being the most used in this space). UIs should definitely be using async |
They aren't sync; they are blocking apis; could they be prefixed or postfixed as such? public HttpResponseMessage BlockingGet(string requestUri); or public HttpResponseMessage GetBlocking(string requestUri); |
Two:
I'll let @Petermarcu and @KrzysztofCwalina speak to Azure SDK, though it's just one example. |
How so? |
I'm fine with that (though it hampers the "getting started" case). |
How is HTTP/2 going to work? |
There will be some blocking involved; that's unavoidable. HTTP/1.x should be sync all the way down. |
I guess my main issue is now the first thing people will see is the "Send" method and until now async only methods on HttpClient has been a pit of success story, I fear that will change. |
That's exactly the problem... it hasn't been. Lots of call sites that require sync are forced to figure out how to block and then when they do they fall over at even small loads. |
I don't think it is. Forget the alphabetical ordering, we're talking about 1 method on HttpClient among more than 30. GetAync/PostAsync are much more likely to be the "first thing people will see". We're simply making it possible for sync clients to not suck as much as they currently do. And sometimes you have no choice but to be sync. |
This API is a performance optimization for a specific use. Do we have any estimates to quantify the improvement we expect to get out this API for these specific use cases, e.g. microbenchmark that demonstrates the improvement? |
Yes. The key is it ends up requiring many more threads in order to sustain throughput. And it can take a long time for the thread pool to ramp up to the necessary level, while in the interim the system can essentially be deadlocked. That means either the app-level dev needs to explicitly set a high min thread count to force the ramp up, or we need to make the pool way more aggressive at thread injection, which harms other cases. |
Maybe there could be an external library with the extention methods to add sync to httpclient. I wouldn't put them in .Net for many reason:
There is also the others solutions :
Even after reading the thread i still thinks this is for :
-- edit |
@GeraudFabien : Sometimes you cannot use an async call because you're implementing an interface that isn't async. Note that |
I've followed three different recommendations on this, they were all wrong. The real answer is it's impossible to run an async method synchronously. |
I'll add my point of view to the motivation. |
@jhudsoncedaron My main point was there are other solution thant implemented it in the runtime. Documentation can point to other implementation that cover the need and are syncronous. I didn't use it for years but https://github.com/restsharp/RestSharp/wiki/Getting-Started isn't async (and seems to be the only real sync client library). There is also https://github.com/jeffijoe/httpclientgoodies.net https://flurl.dev/ that are async but allow to read simply the value in a syncronus way (but i believe this two have the same drawback than .result and end up with deadlock). @valeriob yes. My point was did your client give you money to migrate you're application. Wether it was yes or no in both case it's not just because of async in Httpclient that you make you're choice. But on the ROI for your projet and the global risk taken. In a nutshell can you promise me that if we add for sync in the native HttpClient will you migrate all you're legacy to dotnet core. Sync api are just a very very small problem to the migration. The fear of breaking change will block most of the application to migrate until they have a really good reason to. And when they will have a reason to they may consider thinks like nuget change, project.json, tooling upgrade, learning curve, ... Before even thinking of the lack of sync api on httpclient. |
The thread pool will still starve, because you're still blocking a thread pool thread. It'll just happen slightly slower than if you didn't have the sync APIs. |
Since sync over async will consume 2 threads instead of one, that's the core of the problem, am i right ? I'm not sure how it will translate in the performance characteristics of the application. |
You are absolutly right @GeraudFabien, i've migrated many small applications already, and a big one. It all depends on how heavy is the usage of http apis. If your database has an http interface, you will starve pretty quickly when switching to aspnet core, if you use EntityFramework for example, it still have sync api, so the application will behave the same and no change needed. Anyway my point is, if you do have a sync codebase with heavy usage of httpclient, the client today has to do sync over async, so you need to raise the threadpool limits pretty quickly (we had to do it as soon as we went from MVC to aspnetcore) to survive.
For example, this is the code that a library has to implement to allow sync apis on top of httpclient async ones, but maybe it's just too late, the cat is out of the bag :D It would be interesting to hear the opinion of libraries autors that are close to customers, for example is it just me suffering from having a sync solution using databases over http ? 😄 |
I thought starvation is different and better than the "soft deadlock" we are talking about here. A sync app, at some point, will run out of thread-pool threads and so work will be advancing slowly (up to and including timeouts), but it will be advancing. In sync-over-async case, there are fairness issues between the I/O completion threads and threads picking up new work, and so the app might not be able to make progress. At least this is what I understood after talking to @stephentoub. |
Yes. Here's a (maybe-overly-simplified-but-gets-the-point-across) example. Uncomment just one of Sync/SyncOverAsync/Async and run it: using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
var sw = Stopwatch.StartNew();
Sync();
//SyncOverAsync();
//Async();
Console.WriteLine(sw.Elapsed);
}
static void Sync() => Task.WaitAll(Enumerable.Range(0, Environment.ProcessorCount * 10).Select(_ => Task.Run(() => Thread.Sleep(100))).ToArray());
static void SyncOverAsync() => Task.WaitAll(Enumerable.Range(0, Environment.ProcessorCount * 10).Select(_ => Task.Run(() => Task.Delay(100).Wait())).ToArray());
static void Async() => Task.WaitAll(Enumerable.Range(0, Environment.ProcessorCount * 10).Select(_ => Task.Run(async () => await Task.Delay(100))).ToArray());
} I get results like this:
|
@KrzysztofCwalina The following is actually a hard deadlock. I've hit it.
Everybody's saying it's a soft deadlock. But sometimes the thread pool can't make new threads. Everything grinds to a halt. (Nobody is disputing the correct fix is to use |
On .NET Core? Please share a repro. I'm skeptical it's hitting a thread pool limit, unless you've got other code elsewhere beyond the shown sample (which presumably is missing an await after the return) that's blocking thread pool threads, e.g. if the caller of Method blocked waiting for it synchronously. |
@stephentoub : We ran out of nonpaged memory due to overload. It took awhile to get any diagnostics because Remote Desktop was dead meat and nothing was being logged. (No nonpaged memory -> no new threads.) |
Sure, if you run out of memory, that's an entirely different ball of wax. |
Introduces sync version of HttpClient.Send and all necessary changes to make it work synchronously (e.g.: HttpContent, HttpMessageHandler and their child classes). The change works properly only for HTTP1.1, for unsupported scenarios like HTTP2 throws. Testing the change uses existing tests calling HttpClient.SendAsync and introduces test argument calling the same test twice, once with HttpClient.SendAsync and then with HttpClient.Send. Resolves #32125
Introduces sync version of HttpClient.Send and all necessary changes to make it work synchronously (e.g.: HttpContent, HttpMessageHandler and their child classes). The change works properly only for HTTP1.1, for unsupported scenarios like HTTP2 throws. Testing the change uses existing tests calling HttpClient.SendAsync and introduces test argument calling the same test twice, once with HttpClient.SendAsync and then with HttpClient.Send. Resolves dotnet#32125
While I dislike the idea of introducing a sync mechanism, I understand the need to address real pain points that people are expressing. However, everything I've seen in this thread suggests adding methods to I like this over an interface on
In summary: Give the people sync, but don't muddy up |
Considering on analysis that we shouldn't use the same instance of HttpClient for both sync and async calls (need to disable HTTP/2 for sync calls to really be sync), casperOne is most likely correct. Too bad this idea appeared so late. |
Exactly agreed too, casperOne. I feel people will confuse and can't understand how to implement better asynchronous and scalability for safer. (In my experience, they can't understand what is topic for 'logical Task' at now and the interface will mislead) |
I've been looking at the httpclient sync api, i'm surprised about the implementation, it's just a GetAwaiter().GetResult() behind a method ? how does this help anybody suffering from thread pool starvation ? |
See the Debug.Assert on the previous line. That GetAwaiter().GetResult() isn't blocking because the task will have completed synchronously. The GetResult() is just there to propagate any exceptions. |
Thanks @stephentoub |
I know this is closed and I don't want to beat a dead horse... but for the benefit of anybody still wondering why we would want a sync Send method, here's one reason. In ASP.NET Core (or any app that uses IHostBuilder I assume) you can use A better solution to that particular problem would be to support asynchronous configuration providers. But I can't find that support today. |
Provide sync API on
HttpClient
, which currently has only async methods.Motivation
Azure SDK currently exposes synchronous API which does sync-over-async on
HttpClient
(see HttpClientTransport.cs). It would be much better if we provided them with sync API with proper sync implementation, where feasible.Also there are lots of existing code bases that have very deep synchronous call stacks. Developers are simply not willing to rewrite these code bases to be asynchronous. If they need to call async only API in these synchronous methods, they use sync-over-async, which then in turn causes "soft" deadlock. We want to provide synchronous APIs for these developers because synchronous APIs, although inefficient, can can help in avoiding these soft deadlocks.
Another advantage of sync API is that it is much easier to grasp. Especially for people with no prior knowledge of asynchronous processing. If someone is starting with
HttpClient
, they also have to be knowledgeable of C#async/await
. Also many examples with ``HttpClient` might be simplified and thus making the entry into .NET easier.Proposed API
Minimal Necessary Change
This change is based on @stephentoub prototype in stephentoub/corefx@0e4d640. The prototype introduces synchronous API for
SocketsHttpHandler
and also properly implements it for HTTP 1.1 scenarios (for HTTP 2 does sync-over-async). This proposal extends the existing prototype with sync API onHttpClient
. Following changes are a minimal set to achieve synchronousHttpClient.Send
behaving trully synchronously at least for HTTP 1.1.Note that
CancellationToken
is used in synchronous methods in order to propagateHttpClient
timeout and cancellations. ForHttpContent
it's up for discussion whether to add other overload toCopyTo
andSerializeToStream
to match the async counterparts.The prototype/early implementation for the minimal change can be found in my branch https://github.com/ManickaP/runtime/tree/mapichov/sync_http_api.
Full Change
This includes sync counterparts to all async
HttpClient
methods. Since their implementation delegates toSend
underneath, there's no need to add any other supporting sync methods.Note that all the sync methods on
HttpClient
(except forHttpMessageInvoker.Send
override) do not need oveloads withCancellationToken
. Whether to include them or not is up for discussion.@stephentoub @KrzysztofCwalina @dotnet/ncl
The text was updated successfully, but these errors were encountered: