-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Enable a way to Unregister Message Handler and Session Handler #14021
Changes from all commits
8c46a41
db9b322
da77a24
5e1576a
61f0998
2988d02
d3faba2
4b921a6
78aff54
25f6b8d
d4a5589
ecd820b
cd8f044
fff0016
fae7bf1
642dc36
ffc4249
fbd31d0
f4dac4b
9692122
7741ad9
2bb96a5
c0ec74a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,7 +54,11 @@ public class MessageReceiver : ClientEntity, IMessageReceiver | |
int prefetchCount; | ||
long lastPeekedSequenceNumber; | ||
MessageReceivePump receivePump; | ||
// Cancellation token to cancel the message pump. Once this is fired, all future message handling operations registered by application will be | ||
// cancelled. | ||
CancellationTokenSource receivePumpCancellationTokenSource; | ||
// Cancellation token to cancel the inflight message handling operations registered by application in the message pump. | ||
CancellationTokenSource runningTaskCancellationTokenSource; | ||
|
||
/// <summary> | ||
/// Creates a new MessageReceiver from a <see cref="ServiceBusConnectionStringBuilder"/>. | ||
|
@@ -899,6 +903,51 @@ public void RegisterMessageHandler(Func<Message, CancellationToken, Task> handle | |
this.OnMessageHandler(messageHandlerOptions, handler); | ||
} | ||
|
||
/// <summary> | ||
/// Unregister message handler from the receiver if there is an active message handler registered. This operation waits for the completion | ||
/// of inflight receive and message handling operations to finish and unregisters future receives on the message handler which previously | ||
/// registered. | ||
/// <param name="inflightMessageHandlerTasksWaitTimeout"> is the waitTimeout for inflight message handling tasks. | ||
/// </summary> | ||
public async Task UnregisterMessageHandlerAsync(TimeSpan inflightMessageHandlerTasksWaitTimeout) | ||
{ | ||
this.ThrowIfClosed(); | ||
|
||
if (inflightMessageHandlerTasksWaitTimeout <= TimeSpan.Zero) | ||
{ | ||
throw Fx.Exception.ArgumentOutOfRange(nameof(inflightMessageHandlerTasksWaitTimeout), inflightMessageHandlerTasksWaitTimeout, Resources.TimeoutMustBePositiveNonZero.FormatForUser(nameof(inflightMessageHandlerTasksWaitTimeout), inflightMessageHandlerTasksWaitTimeout)); | ||
} | ||
|
||
MessagingEventSource.Log.UnregisterMessageHandlerStart(this.ClientId); | ||
lock (this.messageReceivePumpSyncLock) | ||
{ | ||
if (this.receivePump == null || this.receivePumpCancellationTokenSource.IsCancellationRequested) | ||
{ | ||
// Silently return if handler has already been unregistered. | ||
return; | ||
} | ||
|
||
this.receivePumpCancellationTokenSource.Cancel(); | ||
this.receivePumpCancellationTokenSource.Dispose(); | ||
} | ||
|
||
Stopwatch stopWatch = Stopwatch.StartNew(); | ||
while (this.receivePump != null | ||
&& stopWatch.Elapsed < inflightMessageHandlerTasksWaitTimeout | ||
&& this.receivePump.maxConcurrentCallsSemaphoreSlim.CurrentCount < this.receivePump.registerHandlerOptions.MaxConcurrentCalls) | ||
{ | ||
await Task.Delay(10).ConfigureAwait(false); | ||
} | ||
|
||
lock (this.messageReceivePumpSyncLock) | ||
{ | ||
this.runningTaskCancellationTokenSource.Cancel(); | ||
this.runningTaskCancellationTokenSource.Dispose(); | ||
this.receivePump = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest cancelling and disposing the runningTaskCancellationTokenSource here. |
||
} | ||
MessagingEventSource.Log.UnregisterMessageHandlerStop(this.ClientId); | ||
} | ||
|
||
/// <summary> | ||
/// Registers a <see cref="ServiceBusPlugin"/> to be used with this receiver. | ||
/// </summary> | ||
|
@@ -1003,6 +1052,9 @@ protected override async Task OnClosingAsync() | |
{ | ||
this.receivePumpCancellationTokenSource.Cancel(); | ||
this.receivePumpCancellationTokenSource.Dispose(); | ||
// For back-compatibility | ||
this.runningTaskCancellationTokenSource.Cancel(); | ||
this.runningTaskCancellationTokenSource.Dispose(); | ||
this.receivePump = null; | ||
} | ||
} | ||
|
@@ -1279,7 +1331,13 @@ protected virtual void OnMessageHandler( | |
} | ||
|
||
this.receivePumpCancellationTokenSource = new CancellationTokenSource(); | ||
this.receivePump = new MessageReceivePump(this, registerHandlerOptions, callback, this.ServiceBusConnection.Endpoint, this.receivePumpCancellationTokenSource.Token); | ||
|
||
if (this.runningTaskCancellationTokenSource == null) | ||
{ | ||
this.runningTaskCancellationTokenSource = new CancellationTokenSource(); | ||
} | ||
|
||
this.receivePump = new MessageReceivePump(this, registerHandlerOptions, callback, this.ServiceBusConnection.Endpoint, this.receivePumpCancellationTokenSource.Token, this.runningTaskCancellationTokenSource.Token); | ||
} | ||
|
||
try | ||
|
@@ -1295,6 +1353,8 @@ protected virtual void OnMessageHandler( | |
{ | ||
this.receivePumpCancellationTokenSource.Cancel(); | ||
this.receivePumpCancellationTokenSource.Dispose(); | ||
this.runningTaskCancellationTokenSource.Cancel(); | ||
this.runningTaskCancellationTokenSource.Dispose(); | ||
this.receivePump = null; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,5 +77,13 @@ public interface IQueueClient : IReceiverClient, ISenderClient | |
/// <param name="sessionHandlerOptions">Options used to configure the settings of the session pump.</param> | ||
/// <remarks>Enable prefetch to speed up the receive rate. </remarks> | ||
void RegisterSessionHandler(Func<IMessageSession, Message, CancellationToken, Task> handler, SessionHandlerOptions sessionHandlerOptions); | ||
|
||
/// <summary> | ||
/// Unregister session handler from the receiver if there is an active session handler registered. This operation waits for the completion | ||
/// of inflight receive and session handling operations to finish and unregisters future receives on the session handler which previously | ||
/// registered. | ||
/// <param name="inflightSessionHandlerTasksWaitTimeout"> is the waitTimeout for inflight session handling tasks. | ||
/// </summary> | ||
Task UnregisterSessionHandlerAsync(TimeSpan inflightSessionHandlerTasksWaitTimeout); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add another function signature for UnregisterSession/MessageHandlerAsync that takes in empty parameter and set a default wait timeout? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could change the function signature like this and check for null in the code What should null represent though? It can't be zero or draining will not happen so infinite time seems like the best default. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I thought about this but our current function signature doesn't have optional parameters, eg. ReceiveAsync() and ReceiveAsync(TimeSpan operationTimeout). So I am not sure if we want to introduce this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. To be consistent then, that convention is appropriate. In the case of ReceiveAsync() its using the OperationTimeout value configured on the connection string or specified when the connection was created and technically is a timeout for a single operation v/s Unregister* which is allowing multiple handlers to complete execution as well as interact over the service bus connection (to complete/abandon messages). It may be ok to repurpose this.OperationTimeout but if we want a different default, I'd suggest just creating a new configuration element that has a default value. This allows consumers to configure it in a pass through way with managed services like Functions (as is the case for most of the service bus sdk configuration). With a configuration element v/s function argument, you can go back to having a single function "Unregister*()" |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not very comfortable with this potential infinite loop. Imagine OnMessage delegate never completes.then UnregisterMessageHandler never completes. This Unregister doesn't guarantee a clean close, but only a timed unregister. I mean it will wait for some time for all OnMessage delegates to finish, but there is no guarantee. We should ideally take some max timeout or some default timeout for the Unregister.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be important to make this timeout configurable (and a default used if not specified). In Azure Functions, we provide a large amount of time for function execution to complete and need to provide the same amount of time while shutting down (this API will be invoked during shutdown)