-
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
Named pipes hang when written asynchronously w/o async flag #31390
Comments
That's the problem, though: if you don't pass the Asynchronous flag, then the operations can't be done with async I/O (at the Windows level). That also means we don't have an overlapped with which to call CancelIoEx. So when Asynchronous isn't used, these async methods just check the cancellation token upfront, and then queue a work item that does the synchronous operation. |
OK, so if this is a Windows-OS level limitation, that's fine. I just wanted to make sure. |
FileStream has the same issue. It's just harder to see with FileStream, as they're most commonly used on Windows with disk-based files, where reads and writes don't block indefinitely and are usually very fast. |
You can call CancelSynchronousIo to cancel synchronous io. Seems like it might need some api work to support using it though as the base BeginRead / BeginWrite functions on Stream currently have no way of knowing how to cancel the operation. |
Maybe. What's not clear is how much overhead the tracking would add. Locks would need to be used to ensure that the right I/O was canceled (and not some other I/O the thread moved on to running completely unrelated to the original work), and the thread handle would need to be retrieved before each operation (though potentially as a pseudo handle we could play tricks to avoid that). Still, yes, it could be prototyped and measured. |
Doesn't seem to need any locks in the common path, CancellationTokenRegistration.Dispose will wait until any outstanding callbacks are completed, but only uses a lock if there are any callbacks in flight. Proof of concept here (for AsyncRead only, and I'm currently just testing with a FileStream over a pipe handle): https://github.com/poizan42/coreclr/tree/cancelable-pipe-poc Some testing: https://github.com/poizan42/dotnet-TestSyncStreamCancel (only the TestPipeCancel project currently does anything) I will try to do some benchmarking. |
Thanks for sharing.
poizan42/coreclr@0544212#diff-de1f7994963a3dc0f1068ff2ff1c3c2aR348-R352 is a spin lock. It's also not clear to me from the docs what guarantees if any CancelSynchronousIo makes, especially since it states "other operations can continue toward completion before they are actually canceled"... as such it makes me very nervous to block the cancellation callback waiting for the actual operation to complete; while it'd be bad enough blocking two threads, the second thread is likely to be the one calling Cancel. Also seems like this could lead to deadlock; maybe contrived, but imagine if an override of Read was the one calling Cancel. |
Well, I meant no locks in the path when not cancelling, performance when cancelling is probably less important. But I agree that the cancellation callback needs more work, for now it was mostly for testing the potentiel impact on existing code not using cancellation. |
I have rebased the PoC on dotnet/runtime with some improvements (especially to OnCancelReadWrite), it is here now: https://github.com/poizan42/dotnet-runtime/tree/cancelable-pipe-poc Using the FileStreamPerformance project from https://github.com/poizan42/dotnet-TestSyncStreamCancel I find Old
Adding CancellationToken to semaphore wait and checking in RunReadWriteTaskWhenReady continuation + adding fields to ReadWriteTask + adding onCancelReadWriteDelegate.
New
Any performance penalty in the case of a None CancellationToken completely drowns out in the noice. Most of the cost in the case of using a real cancelable CancellationToken actually comes from passing it along to the wait for the _asyncActiveSemaphore. We of course pay a bit in memory usage for the two extra fields on ReadWriteTask, but a real implementation could use a different classs when given a cancelable CancellationToken. There is one extra collection happening after the changes when using a None CancellationToken, this a bit surprisingly appears the moment the cached static delegate field onCancelReadWriteDelegate is added whether it is used or not. Being a single static field I doubt this has any effect outside of the artificial benchmark. |
Triage: @stephentoub Do you have any further comments on @poizan42's updates? Is the deadlock risk addressed? |
When creating named pipes on .NET or .NET Core, unless I use
PipeOptions.Asynchronous
, async I/O operations done later end up hanging the I/O operations. Even if I pass in aCancellationToken
, these async I/O methods do not cancel when demanded.IMO the async flag should be simply an optimization -- not something that if set incorrectly causes a hang.
But even if a hang is acceptable, surely the
CancellationToken
should be honored, no?Minimal repro: XUnitTestProject5.zip
You'll see the test passes, but if you change
PipeOptions
used in the repro toNone
, the test hangs.Source code:
The text was updated successfully, but these errors were encountered: