Public Access
SSH: open a shell off the caller's thread, and stop waiting when the caller gives up
This commit is contained in:
1 parent
365954c541
commit
f997e87c5f
2 files changed
+99
-9
No files matched your search
@@ -468,7 +468,21 @@ internal sealed class SshNetConnection(
|
||||
public string Cipher { get; } = client.ConnectionInfo.CurrentServerEncryption ?? string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ISshShellSession> OpenShellAsync(TerminalSize size, CancellationToken cancellationToken)
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// On the thread pool, because <c>CreateShellStream</c> is synchronous and is three round trips to the
|
||||
/// server — open a channel, ask for a terminal, ask for a shell — and this used to make them on whichever
|
||||
/// thread called it, which is the UI thread. On a distant or overloaded server the window stopped
|
||||
/// painting for as long as they took, and the cancellation token it was given was read once, before any
|
||||
/// of them.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// SSH.NET's call cannot itself be cancelled, so a cancellation stops the wait rather than the call. A
|
||||
/// shell that arrives after nobody is waiting for it is closed as it lands rather than left holding a
|
||||
/// channel on the server.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public async Task<ISshShellSession> OpenShellAsync(TerminalSize size, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -476,15 +490,53 @@ internal sealed class SshNetConnection(
|
||||
|
||||
// 4 KiB read buffer inside SSH.NET. Output is coalesced a layer up, so a larger buffer here
|
||||
// only delays the first byte reaching the screen.
|
||||
var shell = client.CreateShellStream(
|
||||
"xterm-256color",
|
||||
effective.Columns,
|
||||
effective.Rows,
|
||||
effective.PixelWidth,
|
||||
effective.PixelHeight,
|
||||
4096);
|
||||
var opening = Task.Run(
|
||||
() => client.CreateShellStream(
|
||||
"xterm-256color",
|
||||
effective.Columns,
|
||||
effective.Rows,
|
||||
effective.PixelWidth,
|
||||
effective.PixelHeight,
|
||||
4096),
|
||||
CancellationToken.None);
|
||||
|
||||
return Task.FromResult<ISshShellSession>(new SshNetShellSession(shell, client));
|
||||
try
|
||||
{
|
||||
var shell = await opening.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new SshNetShellSession(shell, client);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_ = CloseWhenItLandsAsync(opening);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Closes a shell nobody is waiting for any more, or lets its failure go unremarked.</summary>
|
||||
/// <remarks>
|
||||
/// A shell that failed to open after its caller gave up has nobody to tell, and the connection it was
|
||||
/// on reports its own death through the session that owns it. The failures are the ones SSH.NET raises
|
||||
/// for a channel or a connection that went away, named so that anything else still surfaces.
|
||||
/// </remarks>
|
||||
private static async Task CloseWhenItLandsAsync(Task<ShellStream> opening)
|
||||
{
|
||||
ShellStream shell;
|
||||
|
||||
try
|
||||
{
|
||||
shell = await opening.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception) when (exception is SshException
|
||||
or System.Net.Sockets.SocketException
|
||||
or ObjectDisposedException
|
||||
or InvalidOperationException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await shell.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in new issue
Block a user