You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
StreamReader on ShellStream runs into a lock #1485
I tried to update ssh.net to the latest version in my project and encountered a problem while testing my application.
The problematic change in ShellStream came with the commit “Fix a few issues with ShellStream (https://github.com/sshnet/SSH.NET/pull/1322)”. The method where the lock occurs is the read method in line 747 in the ShellStream.cs file. There is the following while loop:
But since _head == _tail returns true and the server sends no further response, the Monitor.Wait method does not return.
Before the change, it only checked if _incoming.Count > 0.
Is there a reason for this change that I can't see, or is it an error?
Edit: Maby a little bit more context could be nice
try{_shell=_shellStreamClient.CreateShellStream("xterm",80,24,800,600,1024);_reader=newStreamReader(_shell);_writer=newStreamWriter(_shell){AutoFlush=true,NewLine="\n"};_writer.Write("/tmp/power.sh\n");while(true){if(DateTime.Now.Subtract(startTime)>timeout){thrownewTimeoutException("Timeout message [...]");}varoutput=_reader.ReadLine();// The application locks hereif(output==null||output==""){Thread.Sleep(100);continue;}if(output.StartsWith("Enter on, off")){_writer.Write("on\n");}if(output.StartsWith("relay on")){break;}}}catch{DisposeConnection();throw;}
I can't put the reader, writer and stream in using blocks because I continue to use them outside the method scope.
Yeah it was an intentional decision to make ShellStream conform more to the usual Stream contract - the previous implementation of ShellStream.Read would return 0 when it shouldn't and at other times block when it shouldn't. The new implementation only returns when data is available and returns as soon as data is available, but I can see how it could make this kind of interaction difficult. Apologies that it was not called out in the release notes.
For your example, are you able to use the Write and ReadLine methods defined on ShellStream? ReadLine has a timeout argument. I imagine it would look something like:
try
{
_shell = _shellStreamClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024);
- _reader = new StreamReader(_shell);- _writer = new StreamWriter(_shell)- {- AutoFlush = true,- NewLine = "\n"- };- _writer.Write("/tmp/power.sh\n");+ _shell.Write("/tmp/power.sh\n");
while (true)
{
- if (DateTime.Now.Subtract(startTime) > timeout)+ var output = _shell.ReadLine(timeout);++ if (output == null)
{
throw new TimeoutException("Timeout message [...]");
}
- var output = _reader.ReadLine(); // The application locks here-- if (output == null || output == "")- {- Thread.Sleep(100);- continue;- }-
if (output.StartsWith("Enter on, off"))
{
- _writer.Write("on\n");+ _shell.Write("on\n");
}
if (output.StartsWith("relay on"))
{
break;
}
}
}
catch
{
DisposeConnection();
throw;
}
I am interacting with a shell script that contains read functions where no new line character is sent before waiting for input.
In order to handle this output, I needed a method that returns on a new line character or if nothing is sent for x seconds.
I came up with this solution.
publicstaticstringReadUntilEolOrTimeout(ShellStreamstream,inttimeoutMilliseconds){varsb=newStringBuilder();varbuffer=newbyte[1];// Read one char at a timevarstartTime=DateTime.Now;booldataRead=false;while(true){if(stream.DataAvailable){// Attempt to read one characterstream.Read(buffer,0,1);// Reset the start time after a successful readstartTime=DateTime.Now;dataRead=true;if(buffer[0]=='\r'){continue;}// If we encounter EOL, exit the loopif(buffer[0]=='\n'){break;}// If previous character was '\r' and current is not '\n', we have EOLif(sb.Length>1&&sb[sb.Length-2]=='\r'&&buffer[0]!='\n'){stream.Position--;// not possible becaue this always throws *NotSupportedException*break;}sb.Append(value:(char)buffer[0]);}else{// If no data was read, check if the timeout has been reachedif(!dataRead||(DateTime.Now-startTime).TotalMilliseconds>=timeoutMilliseconds){break;}Thread.Sleep(10);}}returnsb.ToString();}
But there is a problem when the stream returns a \r without a following \n, which is the normal behavior on Mac systems.
I can't move the _readHead back one step and I can't peek the next charakter in the stream.
So I have no way to check which end of line is being sent.
Do you need to know whether you hit \r or \n? If not, does this suit? ReadLine deals with \r and \n
publicstaticstringReadUntilEolOrTimeout(ShellStreamstream,inttimeoutMilliseconds){string?line=stream.ReadLine(TimeSpan.FromMilliseconds(timeoutMilliseconds));if(line!=null){returnline;}else// Timeout{returnstream.Read();// This reads all available data in the buffer as a string (does not block)}}
Activity
Rob-Hague commentedon Sep 6, 2024
Yeah it was an intentional decision to make ShellStream conform more to the usual Stream contract - the previous implementation of
ShellStream.Readwould return 0 when it shouldn't and at other times block when it shouldn't. The new implementation only returns when data is available and returns as soon as data is available, but I can see how it could make this kind of interaction difficult. Apologies that it was not called out in the release notes.For your example, are you able to use the
WriteandReadLinemethods defined on ShellStream?ReadLinehas a timeout argument. I imagine it would look something like:try { _shell = _shellStreamClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024); - _reader = new StreamReader(_shell); - _writer = new StreamWriter(_shell) - { - AutoFlush = true, - NewLine = "\n" - }; - _writer.Write("/tmp/power.sh\n"); + _shell.Write("/tmp/power.sh\n"); while (true) { - if (DateTime.Now.Subtract(startTime) > timeout) + var output = _shell.ReadLine(timeout); + + if (output == null) { throw new TimeoutException("Timeout message [...]"); } - var output = _reader.ReadLine(); // The application locks here - - if (output == null || output == "") - { - Thread.Sleep(100); - continue; - } - if (output.StartsWith("Enter on, off")) { - _writer.Write("on\n"); + _shell.Write("on\n"); } if (output.StartsWith("relay on")) { break; } } } catch { DisposeConnection(); throw; }cjung95 commentedon Sep 9, 2024
Hey Rob,
I am interacting with a shell script that contains read functions where no new line character is sent before waiting for input.
In order to handle this output, I needed a method that returns on a new line character or if nothing is sent for x seconds.
I came up with this solution.
But there is a problem when the stream returns a \r without a following \n, which is the normal behavior on Mac systems.
I can't move the _readHead back one step and I can't peek the next charakter in the stream.
So I have no way to check which end of line is being sent.
Do you have any idea how to fix this?
Rob-Hague commentedon Sep 9, 2024
Do you need to know whether you hit \r or \n? If not, does this suit? ReadLine deals with \r and \n
cjung95 commentedon Sep 19, 2024
Yes, you're right, that's all I need - and it's that simple!
Thank you so much for your help! :)