-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Add a clean way to cancel a long-running Socket operation (e.g. accept or connect) #16236
Comments
cc: @CIPop @SidharthNabar |
@himadrisarkar PTAL @GSPP thanks for the suggestion! I agree that a way to cancel operations while preserving the socket object should exist outside of calling Regarding the race conditions you've pointed out: you are correct. In some circumstances, calling |
@CIPop Are you sure that async operations can be aborted safely? The close might happen right before the OS receives the |
@stephentoub @ericeil @pgavlin can answer the *NIX question regarding sync/async cancellation. The Windows OS doesn't have a similar |
Next step: We need formal API proposal. |
The main point is to way to cancel these operations at all. This is not possible in a truly safe and clean way at the moment (see my lengthy discussion of this in the opening post). I think it's OK if the socket is destroyed when cancelling. Cancelling a read without taking data is very complicated (unclear if any OS level API supports this and there are race conditions leading to lost data). Cancelling a write would need to make sure that the application knows whether data was written or not (and how much). All of this is uncertainty and I don't see a need. I don't see a need to cancel a read or write without failing the entire connection. The best practice with socket connections is to write on demand and to have a single read loop busy at all times. User code can use this pattern to emulate cancellable reads and writes. I also do not propose to add a timeout to individual read/write operations (directly on the socket or on a Note, that other issues have referenced this one. Higher level APIs need lower level APIs to be cancellable to be cancellable themselves. Making networking cancellable is a fundamental building block. We have a great opportunity here to create a clean cancellation story for networking. My API proposal: Support
I chose this subset because this is what normal socket servers and clients need. They don't need more than that. Note, that We don't touch There is a I added Any cancellation must close the connection and dispose the socket. Cancellation must be thread safe. Cancellations can concurrently arise from parallel reads, writes and close operations on separate tokens and threads. Cancellations might arrive through |
The Windows documentation says this about cancelling operations:
This forces a design where any cancellation ends the connection. This should be fine. Cancellation should be instantaneous in all cases. If it is not then applications cannot rely on cancellation being effective to move the UI state forward. It's also a problem for lingering resource consumption if the application moves on before all resources are freed. |
Triage:
|
And regarding (1), |
On Windows and Linux. On macOS I believe we simply don't have any reliable way from the OS to interrupt a synchronous connect. @tmds can correct me if I'm wrong. |
👍 That is a great improvement. 👍 |
The normal pattern to listen for incoming connections is:
The only way to reliably cancel that loop is to close the socket. This works, but it causes an
ObjectDisposedException
which is usually one of the reserved exception types that always indicate a bug such asNRE
andArgumentNullException
. Catching that exception might hide real bugs. You might confuse the intendedObjectDisposedException
with one that is not supposed to happen but did (bug).Also, this seems to be racy. What if the handle is closed right before
Accept
passes it to the nativeaccept
function? Is that even thread-safe?! Could be a use of an invalid/unintended handle. Note sure if the BCL has a way to prevent that scenario.Some people suggest to break the loop by connecting to that socket. That might not be possible depending on what interface you are listening on. There are no guarantees to be able to connect anywhere at all. Also, this feels like such a dirty hack.
There are ideas of using
IAsyncResult.WaitHandle
with a timeout or in combination with another wait handle but that's quite nasty code and much slower than not using a wait handle. It's much faster to let the kernel wait as part of a synchronous call than to execute an asynchronous call, then create an event object, wait for it and then dispose of all of this. Also, in case you do aWaitHandle.WaitAny(ioHandle, cancelHandle)
and you find yourself in the cancellation case you still need to abort the IO usingSocket.Close
which is the same dirtiness.It does not matter for this issue whether you're using sync or async IO.
Please add a way to cancel any long-running socket operation such as accept, connect, read, write, shutdown in a clean way. This could take the form of a
CancellationToken
or a methodSocket.AbortPendingOperations()
. A cancelled operation should throw an exception that allows to identify the situation so that it can be caught reliably.Cancelling is not just important for breaking the accept loop but it's useful for all other long-running operations as well. Sometimes you need to be able to shut down processing.
The text was updated successfully, but these errors were encountered: