Skip to content
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

Perf improvement on queue/table bindings in high throughput scenarios (V1) #1657

Merged
merged 4 commits into from
Apr 19, 2018

Conversation

mhoeger
Copy link
Contributor

@mhoeger mhoeger commented Apr 9, 2018

Dupe of PR #1631, which had strange merge behavior
In the binding flow, there is a point where we transition from async code => sync code => async code. In the sync => async transition, we force the async code to run synchronously by using Task.Run(() => AsyncMethod().GetAwaiter().GetResult() on the async method.

This pattern leads to thread pool contention.

Task.Run() creates a child thread, AsyncMethod() does the work that the parent thread needs, and GetAwaiter().GetResult() blocks the parent thread until the work from AsyncMethod() is completed. In low throughput scenarios, this pattern doesn't cause visible issues because there are enough threads in the thread pool to complete the work in AsyncMethod(). However, when there is a high volume of requests through this code path that blocks on GetAwaiter().GetResult(), the child threads that are spun up to do work that unblock the parents end up queued, waiting for blocked parent threads (this happens because there are a limited number of threads in the thread pool, read [2] for more details). Although work eventually completes, significant delays have led to timeouts (this fix therefore resolves #1467).

Background
There are a number of places where we are not writing our methods so that they are sync OR async all the way down (should not be a combination of both). This blog post [1] from Stephen Toub details some of the dangers of wrapping a synchronous method in an async wrapper. This blog post [2] details some of the dangers with wrapping an async method in an sync wrapper

[1] Async over sync
[2] Sync over async

Copy link
Member

@fabiocav fabiocav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small little change.


private readonly CloudStorageAccount _storageAccount;
private readonly RandomNameResolver _resolver;
private readonly JobHostConfiguration _hostConfiguration;

public AsyncCancellationEndToEndTests()
{
// Run tests in multithreaded environment
_oldContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you wrap this in a try/finally block to ensure the synchronization context is restored?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I misread the diff here. But after chatting, I do think it would be a better approach and (safer in the long run) if we were to scope this per method, ensuring the context is restored at the end of the method call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants