-
Notifications
You must be signed in to change notification settings - Fork 13
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
Make INetworkConnection Disposable and AsyncDisposable #1400
Comments
It's actually inconvenient to make a class or interface disposable and async disposable with 2 different semantics (silent close vs graceful close) as suggested above. The issue is in an "async context", the compiler will make you use DisposeAsync, when you may want to call Dispose to get the silent non-graceful close semantics. As a result, we have to chose between: Say we pick (a). Should ShutdownAsync call Dispose or not? The disposable contract means the client must call Dispose no matter what, and it's not helpful to have a separate API that calls Dispose for you:
This does not help. It makes the API confusing. So arguably ShutdownAsync should not call Dispose, even if the only thing you can do with a shut down connection is closing it. |
That's how most transport APIs are designed (
Afaict, in .NET the only API that considers
It's a reasonable choice for an API which is often going to be used with this pattern (which will be the case for For other APIs which are typically used as a child of another class (the simple network connection of the ice protocol connection), I find separating the responsibility of the "graceful close" from the "resource disposal" clearer. See also this endless discussion about this for
As mentioned above, I find it's better to have a separate For the
Our implementation of the ice protocol and Slic are using both |
We are in agreement on this point.
To be clear, you want to keep DisposeAsync as a graceful close that logically calls ShutdownAsync for our public APIs - Server, ClientConnection, ResumableClientConnection, ConnectionPool ?? (I think we should).
I think it's ok to separate graceful close and resource-disposal for all network connections. Once For IProtocolConnection, IceProtocolConnection and IceRpcProtocolConnection start tasks when dispatching incoming requests and I think it would be incorrect to consider a protocol connection "disposed" while these tasks as still running. Therefore I propose we make IProtocolConnection async disposable, where DisposeAsync waits for these tasks to complete. Now, we can discuss the semantics of this IProtocolConnection.DisposeAsync:
|
Yes, that's still fine with me. I don't really have a preference for the implementation of One question however: what are the semantics of |
The IProtocolConnection in question is:
If we keep it, I think its semantics should be:
I think the question is whether or not we should provide a public Abort API on ClientConnection and Server. For Server and connections created by Server, we currently don't have an Abort. The only option is a graceful shutdown. Maybe we should indeed do the same for ClientConnection and remove ClientConnection.Abort. The implementation of ShutdownAsync already (logically) aborts the connection after the close timeout. |
I'm leaning toward keeping them on I see the connect timeout as being the equivalent of:
Right now, the shutdown timeout is different however since it aborts the connection. Is that correct though? It would be more logical if it was the same as:
As for keeping |
Also... Presumably, an |
I think it's much simpler to make Abort cancel the dispatches. This way, we can keep a single OnClose that does not need to worry about this cancellation token. |
I assume that's not the case since For me, it should be more the equivalent of:
And I think the same makes sense for shutdown and the shutdown timeout:
|
As we've established, Abort would not free all resources. If a middleware aborts a connection, the server would still need to DisposeAsync this connection and wait for all dispatches on this connection to complete. Abort probably triggers a faster cleanup, but not an immediate one. |
We do indeed use a cancellation token with ConnectAsync, and count of the implementation to throw OperationCanceledException when the timeout expires. It also seems the connection is not aborted when ConnectAsync times out. Does this make sense? I thought a connect attempt failure (including timeout) would be / should be fatal for the connection. |
Update: see first comment below and #1404.
INetworkConnection is currently disposable. I think we should make it async disposable as well.
The semantics of Dispose is Close/Abort immediately and silently with (if needed) a non-specific exception such as ConnectionAbortedException. There is no need for an error code since the closure is silent.
It's already implemented:
https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc/Transports/Internal/TcpNetworkConnection.cs#L28
https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc/Transports/Internal/SlicNetworkConnection.cs#L289 (uses ConnectionClosedException though)
The semantics of DisposeAsync is ShutdownAsync = graceful closure.
That's currently not implemented. We have ShutdownAsync but it does not close the connection, it only shuts it down, just like the Sockets and SslStream API. I don't see why we would want ShutdownAsync to only shut down the connection and not close it. This is also inconsistent with our Server and ClientConnection APIs.
Other issues:
see https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc/Transports/IMultiplexedNetworkConnection.cs#L25
Do we need these error codes? As far I can tell, we only use error code 0:
see
https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc/Transports/IMultiplexedNetworkConnection.cs#L25
https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc/Transports/ISimpleNetworkConnection.cs#L20
For ISimpleNetworkConnection, I don't think we need to cancellation token at all, and we can simply replace ShutdownAsync by DisposeAsync.
see https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc/Transports/Internal/TcpNetworkConnection.cs#L78 (does not use cancellation token)
https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc.Coloc/Transports/Internal/ColocNetworkConnection.cs#L131 (does not use it either).
For IMultiplexedNetworkConnection, it's not clear. We mostly pass CancellationToken.None, but Slic also passes its own token:
https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc/Transports/Internal/SlicNetworkConnection.cs#L266
The semantics of canceling the cancellation token given to this ShutdownAsync are not clear to me. We give up on waiting for the response from the remote peer?
https://github.com/zeroc-ice/icerpc-csharp/blob/main/src/IceRpc/Transports/Internal/SlicNetworkConnection.cs#L291
It's also odd to give
_readCancelSource.Token
to ShutdownAsync, which is really a write operation.If we don't need an error code and cancellation token, we could eliminate ShutdownAsync and have only DisposeAsync.
The text was updated successfully, but these errors were encountered: