Skip to content

Conversation

@Rob-Hague
Copy link
Collaborator

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

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.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) and WriteAsync(ReadOnlyMemory<byte>, CancellationToken) to allow concurrent read/write operations
  • Override BeginWrite and EndWrite APM methods to use the new async implementation
  • Add test coverage verifying that ReadAsync no longer blocks WriteAsync

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.

@Rob-Hague
Copy link
Collaborator Author

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WriteAsync doesn't complete with ReadAsync running in background

2 participants