-
Notifications
You must be signed in to change notification settings - Fork 14
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
ConnectAsync and ShutdownAsync refactoring #1434
Conversation
@@ -26,6 +26,11 @@ public interface IConnection | |||
/// <param name="request">The outgoing request being sent.</param> | |||
/// <param name="cancel">The cancellation token.</param> | |||
/// <returns>The corresponding <see cref="IncomingResponse"/>.</returns> | |||
/// <exception cref="ConnectionCanceledException">Thrown if the connection was canceled.</exception> |
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 find the terminology for "a connection being cancelled" weird. I'm not sure users will understand what this means.
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.
The GenericHost
sample needs fixing
catch (OperationCanceledException exception) when (exception.CancellationToken != cancel) | ||
{ | ||
// exception comes from another call to ConnectAsync | ||
throw new ConnectionCanceledException(); |
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 don't quite understand why we need this exception, the core never handles this concrete type
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.
The retry interceptor probably need to be fixed to handle it.
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.
We need an exception to report that the first/main ConnectAsync failed with an OperationCanceledException and left the connection in an unusable state.
For ConnectAsync, we could abort the connection (in the first failed ConnectAsync) and throw ConnectionClosedException here. For ShutdownAsync, it's not as clear, in particular, I think it would be weird for ShutdownAsync to throw ConnectionClosedException in this situation.
I'd rather leave the fixing of the retry interceptor (if any) for later until we figure out what we want to do here.
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.
About the ClientConnection.ConnectAsync
semantics... It can be called from multiple threads. Shouldn't the cancellation of any of the pending ConnectAsync
trigger the connect cancellation? I'm not mistaken, on the fix ConnectAsync
call will trigger the connect cancellation. It's the same for ShutdownAsync
.
IMO, any canceled connect or shutdown should leave the connection is a state where the only operation that can be called is DisposeAsync
. Calling any other operations should raise ConnectionCanceledException
.
There's also the issue that we now raise TimeoutException
from ConnectAsync
the retry interceptor can no longer distinguish the possibly non-retryable invocation timeout or the always retryable connect. Or is the idea to not support retrying invocation timeouts with the retry interceptor?
catch (OperationCanceledException exception) when (exception.CancellationToken != cancel) | ||
{ | ||
// exception comes from another call to ConnectAsync | ||
throw new ConnectionCanceledException(); |
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.
The retry interceptor probably need to be fixed to handle it.
I think the rule should be to not retry after we exceed the request deadline, if a connect operation timeouts we should still be able to retry, so it is important that we can distinguish between the request deadline expired and other timeouts. |
Co-authored-by: Joe George <joe@externl.com>
Co-authored-by: Joe George <joe@externl.com>
Co-authored-by: Joe George <joe@externl.com>
No, It's only the first or "main" ConnectAsync call that matters. If it's canceled, then the connection is canceled and it becomes unusable forever.
Yes.
That's not the way I see it. |
The solution is obvious here: you install your deadline interceptor before your retry interceptor. Then your retry interceptor won't see Timeout exceptions thrown by your deadline interceptor. |
As far as retrying on connect Timeout exceptions, the retry interceptor always handles this properly. It's going to retry on ANY exception if the request is idempotent or its payload was not read yet:
ClientConnection.InvokeAsync "connects" (and throws TimeoutException if ConnectAsync times out) before reading the payload. |
/// <exception cref="ConnectionClosedException">Thrown if the connection is already closed.</exception> | ||
/// <exception cref="OperationCanceledException">Thrown if cancellation was requested through the cancellation | ||
/// token.</exception> | ||
public async Task ConnectAsync(CancellationToken cancel = default) |
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.
Shouldn't the implementation create a cancelation token source with _connectTimeout
as we do in InvokeAsync
, I don't see why this is only done when ConnectAsync
is called from InvokeAsync
.
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.
No, the idea is if you call ConnectAsync or ShutdownAsync directly, you pass a cancellation token and decide if/when this call times out through this cancellation token.
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.
This doesn't feel right, if you create a connection and passed a ConnectTimeout
it should apply when you call ConnectAsync
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.
No, see #1417 point 1.
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.
Ok, this means the connection pool is not using connection timeouts anymore.
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.
We need to revisit ConnectionPool in a follow-up PR.
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.
Looks good to me!
This PR refactors ConnectAsync, ShutdownAsync and DisposeAsync (when applicable) in ClientConnection, ServerConnection, ResumableClientConnection, ConnectionPool and Server.
It's only a partial fix - the full fix requires protocol connection refactoring, in one or more follow-up PRs.
ClientConnection/ServerConnection DisposeAsync is no longer an alias for ShutdownAsync, in particular ShutdownAsync can now throw exceptions and its cancellation token behaves like a normal cancellation token.
See #1413 and #1417.