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

Fix synchronization of semaphore in retry delegating handler #3161

Closed
wants to merge 16 commits into from

Conversation

abhipsaMisra
Copy link
Member

@abhipsaMisra abhipsaMisra commented Mar 14, 2023

Changes from #3135, #3149

The sepmaphores associated with enabling and disabling subscriptions for callbacks should not be dependent on the Semaphore used for opening or closing the client.
The only purpose of the enable-disable events semaphore is to ensure that requests to enable and disable the subscription invoked by parallel threads are executed in a thread-safe manner.

  • Decouple client open-close semaphore from callback subscription semaphore
  • Cancel pending operations when CloseAsync() is invoked

@@ -60,14 +73,17 @@ public override async Task SendTelemetryAsync(TelemetryMessage message, Cancella

try
{
await VerifyIsOpenAsync(cancellationToken).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

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

I've added a pre-check operation to each API call - VerifyIsOpenAsync() once outside the retry block.

If the verification that the client is open is always linked with the retry block, then closing a client and then calling SendTelemetryAsync() will result in the SDK complaining that the linked cancellation token source (the one that signals in-progress operations for closure) has been canceled already, instead of complaining that the client is closed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can be modified further in the "separate-delegating-handlers" PR.

{
case ClientTransportStatus.Open:
case ClientTransportStatus.Opening:
return;
Copy link
Member Author

Choose a reason for hiding this comment

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

If the client is currently open or opening, simply exit.

{
_isOpen = true;
case ClientTransportStatus.Closing:
throw new InvalidOperationException($"The client is currently closing. Wait until {nameof(CloseAsync)} completes and then invoke {nameof(OpenAsync)} again.");
Copy link
Member Author

Choose a reason for hiding this comment

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

If Openasync() is called while the client is closing, throw an InvalidOperationException.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this documented on the client's OpenAsync call?

{
_cancelPendingOperationsCts?.Cancel();
_handleDisconnectCts?.Cancel();
await base.CloseAsync(cancellationToken).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

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

@drwill-ms We had discussed encapsulating the CloseAync() within a try-catch block so that close failures are suppressed. However, as I was testing this flow I had another thought. Since we do allow open-close-open flow, I wanted to avoid suppressing any exceptions in here as failure to cleanly close the transport layer might result in issues down the line if subsequently OpenAsync() is called. For this reason, I wanted to leave this as-is and evaluate this in the future if we get feedback from customers on this flow.

{
throw new InvalidOperationException($"The client connection must be opened before operations can begin. Call '{nameof(OpenAsync)}' and try again.");
if (GetClientTransportStatus() != ClientTransportStatus.Open)
Copy link
Member Author

Choose a reason for hiding this comment

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

This will result in operations invoked when the client is "opening" to also get rejected. It seemed like a reasonable expectation since we do want the client behavior to be deterministic.

Copy link
Contributor

@patilsnr patilsnr left a comment

Choose a reason for hiding this comment

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

optimistic sign off!

}

// Send the request for transport close notification.
_transportClosedTask = HandleDisconnectAsync();
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm worried about the lifetime of this handle disconnect code. It is called by someone else the first time and then by itself the second time.

Is any given instance ever pointing to the wrong token that it captures off of _handleDisconnectCts? Are we sure none of these calls duplicate over time?

I'd suggest diagraming it to be sure, and honestly this pattern is probably too fragile that anyone who touches this code and doesn't thoroughly understand this lifetime could break it.

I know you didn't introduce it in this PR, but perhaps we should look for a simpler impl like a callback.

Copy link
Contributor

Choose a reason for hiding this comment

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

It occurs to me that this file which is supposed to be about retrying, and it looks like it does a lot more than that, especially with the addition of reconnection and overall connection (sort of) management.

Should checking the connection, reconnection attempts, etc. be a separate delegating handler, so this class doesn't get quite so full of mixed concerns?

David R. Williamson and others added 2 commits March 24, 2023 11:23
@abhipsaMisra
Copy link
Member Author

Closing in favor of #3251

@abhipsaMisra abhipsaMisra deleted the abmisr/semaphore branch April 11, 2023 21:30
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.

4 participants