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

ConfigureAwait(false) for close stream/consumer/producer, query offset and create producer #231

Conversation

iuribrindeiro
Copy link

@iuribrindeiro iuribrindeiro commented Feb 4, 2023

The changes in #230 were good but not enough to fix deadlocks when trying to close consumers connections.
Adding a ConfigureAwait(false) only to MaybeClose was not enough because when it was being called by RawConsumer.Close like this:

await _client.MaybeClose($"_client-close-subscriber: {_subscriberId}");

A deadlock was still happening.
The ConfigureAwait in .NET is kind of... eeh.. stupid. You basically have to call it for every await you do inside library code because, other wise, you would be getting the application's SynchronizationContext. Since this code is supposed to be used anywhere, we don't actually know how the client SynchronizationContext works, we don't know it's limitations. If it is a single thread SynchronizationContext, for example, we would be causing a deadlock awaiting on the current thread to finish a job that it self started.

See the following example:

SynchronizationContext.SetSynchronizationContext(new MainThreadSynchronizationContext());
await LibraryCode();

async Task LibraryCode()
{
    await DoWork();
}

async Task DoWork()
{
    await Task.Delay(1000).ConfigureAwait(false);
}

On the example above, calling ConfigureAwait(false) on the Task.Delay(1000) method won't be enough. It will, for unblocking code after Task.Delay(1000), but once we are back to the last await(in LibraryCode method), the application SynchronizationContext will once again be used. That way we are now waiting on the current thread to finish work that it self started, making it busy to listen for the completed stated of DoWork. Deadlock.

If we simply add:

  await DoWork().ConfigureAwait(false);

It will now be the app responsability to deal with the task returned by the Task LibraryCode method.
It could either call an await like it is doing in our example above (but if so, would need to call .ConfigureAwait(false) on the LibraryCode as well. Same thing as the last paragraph), or it could do a while(!task.IsCompleted) {} to run code only after the task is completed.

TLDR:
ConfigureAwait(false) everywhere for library code.

Reference: https://devblogs.microsoft.com/dotnet/configureawait-faq/#:~:text=ConfigureAwait(false)%20involves%20a%20task,context%20that%20was%20there%20previously.

@lukebakken lukebakken self-assigned this Feb 4, 2023
@lukebakken lukebakken added this to the 1.2 milestone Feb 4, 2023
@lukebakken
Copy link
Contributor

@iuribrindeiro thanks for doing this. I have added a code analysis warning for CA2007 for the library (not the tests). Some of the warnings I have fixed with the "Roslynator" tool, and the remaining ones need to be fixed manually.

I can get that this weekend, but if you do that work, be sure to keep your branch updated so we don't duplicate it. Thanks!

@iuribrindeiro
Copy link
Author

@iuribrindeiro thanks for doing this. I have added a code analysis warning for CA2007 for the library (not the tests). Some of the warnings I have fixed with the "Roslynator" tool, and the remaining ones need to be fixed manually.

I can get that this weekend, but if you do that work, be sure to keep your branch updated so we don't duplicate it. Thanks!

Awesome! I'll do a few and keep updaing this PR

@iuribrindeiro
Copy link
Author

@iuribrindeiro thanks for doing this. I have added a code analysis warning for CA2007 for the library (not the tests). Some of the warnings I have fixed with the "Roslynator" tool, and the remaining ones need to be fixed manually.

I can get that this weekend, but if you do that work, be sure to keep your branch updated so we don't duplicate it. Thanks!

@lukebakken I've set the dotnet_diagnostic.CA2007.severity to error and fixed all the missing ConfigureAwait(false) that was causing errors on VS(not sure if that was a Resharper thing or VS by default). Then I moved dotnet_diagnostic.CA2007.severity back to warning. I think that there are no ConfigureAwait(false) missing in RabbitMQ.Stream.Client. Not sure if we need it for the tests though.

@Gsantomaggio Gsantomaggio merged commit ff3f637 into rabbitmq:improvement_reconnect Feb 6, 2023
@Gsantomaggio
Copy link
Member

Thank you @iuribrindeiro

Gsantomaggio added a commit that referenced this pull request Feb 6, 2023
* Improve the reconnect

* Handle some edge cases where the producer or consumer could go into a deadlock waiting for the semaphore.
* Add the Cancellation token producer side and call the token cancellation when the producer and the consumer
   are forced to disconnect.
* ConfigureAwait(false) for close stream/consumer/producer, query offset and create producer (#231)
* ConfigureAwait(false) for close stream, consumer, producer connections, query offset and create producer
* Apply roslynator.exe fixes
* Do not warn about ConfigureAwait in tests, fix more instances
* Fixing ConfigureAwait(false) for the rest of the lib
* Add test for close the producer/consumer in different threads 
* Improve super stream example
---------
Co-authored-by: Luke Bakken <luke@bakken.io>
Signed-off-by: Gabriele Santomaggio <G.santomaggio@gmail.com>
Co-authored-by: Iuri Brindeiro <iuri@fullstacklabs.co>
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.

3 participants