-
Notifications
You must be signed in to change notification settings - Fork 148
Mix of Async and Sync methods may result in deadlocks #237
Description
The library is exposing methods in both versions: async and sync. For example: we have SendAsync and Send, CloseAsync and Close.
The mix of Async and Sync method calls might not be recommended. When one is using Async methods then he should probably prefer Async versions for all of the operations. There are however cases in which the mix seems to be reasonable and can be done as both APIs are given by the library. The additional reasoning can also be that not everything is async those days in .NET. Consider for example the IDisposable interface and the using statements.
Skipping the considerations when async and sync version should be used, how async and sync items should be disposed and similar aspects (which are for a separate post) it is fairly easy to write something like this:
Connection connection = new Connection(new Address("amqp://localhost:5672"), null, new Open
{
// Some open params here ...
}, (_, __) => { });
Session session = new Session(connection);
Message message = new Message("Hello AMQP") { Properties = new Properties { To = "queue" } };
SenderLink sender = new SenderLink(session, "sender123", new Attach
{
Role = false,
SndSettleMode = SenderSettleMode.Unsettled,
Target = new Target { Address = "queue" },
// Other Attach params here ...
}, null);
await sender.SendAsync(message);
sender.Close(); // It will deadlock here
session.Close();
connection.Close();The above will open a connection synchronously. It will send a message using the async version and then it will try to close the objects using synchronous versions again.
The code will open the connection, send a message but it will deadlock when trying to close the objects. It is worth adding that similar mixes work with no issues on standard .NET APIs (you may use any version of the methods – sync or async and it just works).
In the above sample if we do SendAsync then the callback (task completion) is run by the message pump. It is run when a Disposition is received. In a short, Disposition calls the SetResult on the TaskCompletionSource. SetResult blocks running the continuation so the main thread (message pump) cannot move and continue. The continuation itself tries to close the objects. Unfortunately close waits for an event (Detach) which can only be set by a message pump. The message pump is blocked waiting. We have a deadlock as the TaskCompletionSource.SetResult(…) invokes the code awaiting the task and it does it on the same thread as the message pump.