Override WriteAsync in ShellStream#1711
Merged
Rob-Hague merged 2 commits intosshnet:developfrom Oct 23, 2025
Merged
Conversation
ShellStream does not currently override the Read/Write async variants. They fall back to the base class implementations which run the sync variants on a thread pool thread, only allowing one call of either at a time in order to protect implementations that would break if Read/Write were called simultaneously. In ShellStream, reads and writes are independent so mutually excluding their use is unnecessary and can lead to effective deadlocks. We therefore override WriteAsync to get around this restriction. We do not override ReadAsync because the sync implementation does not lend itself well to async given the use of Monitor.Wait/Pulse. Note that while reading and writing simultaneously is allowed, it is not intended that ShellStream is used with multiple simultaneous reads or multiple simultaneous writes, so it is fine to keep the base one-at-a-time implementation on ReadAsync. Another note is that the new WriteAsync will be simple (synchronous) buffer copying in most cases, with a call to FlushAsync in others. We also do not override FlushAsync, so that will go onto a thread pool thread and potentially acquire some locks. But given that the current base implementation of WriteAsync does that unconditionally, it makes the new WriteAsync slightly better and certainly no worse than the current version.
There was a problem hiding this comment.
Pull Request Overview
This PR overrides the WriteAsync methods in ShellStream to prevent mutual exclusion between read and write operations. The base class implementation unnecessarily restricts concurrent reads and writes, which can cause deadlock-like behavior when both operations are attempted simultaneously.
Key changes:
- Override
WriteAsync(byte[], int, int, CancellationToken)andWriteAsync(ReadOnlyMemory<byte>, CancellationToken)to allow concurrent read/write operations - Override
BeginWriteandEndWriteAPM methods to use the new async implementation - Add test coverage verifying that
ReadAsyncno longer blocksWriteAsync
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Renci.SshNet/ShellStream.cs | Implements custom WriteAsync overrides that copy data to internal buffer without acquiring locks that would block concurrent reads |
| test/Renci.SshNet.Tests/Classes/ShellStreamTest_ReadExpect.cs | Adds test verifying ReadAsync and WriteAsync can execute concurrently without blocking |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
WojciechNagorski
approved these changes
Oct 23, 2025
Collaborator
Author
|
Thanks! |
This was referenced Oct 27, 2025
This was referenced Nov 3, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ShellStream does not currently override the Read/Write async variants. They fall back to the base class implementations which run the sync variants on a thread pool thread, only allowing one call of either at a time in order to protect implementations that would break if Read/Write were called simultaneously. In ShellStream, reads and writes are independent so mutually excluding their use is unnecessary and can lead to effective deadlocks like in #1707.
We therefore override WriteAsync to get around this restriction. We do not override ReadAsync because the sync implementation does not lend itself well to async given the use of Monitor.Wait/Pulse. Note that while reading and writing simultaneously is allowed, it is not intended that ShellStream is used with multiple simultaneous reads or multiple simultaneous writes, so it is fine to keep the base one-at-a-time implementation on ReadAsync.
Another note is that the new WriteAsync will be simple (synchronous) buffer copying in most cases, with a call to FlushAsync in others. We also do not override FlushAsync, so that will go onto a thread pool thread and potentially acquire some locks. But given that the current base implementation of WriteAsync does that unconditionally, it makes the new WriteAsync slightly better and certainly no worse than the current version.
closes #1707