Skip to content
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

SshClient: replace DisconnectedAsync API with Disconnected API. #270

Merged
merged 2 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ class SshClient : IDisposable

// Not usable with AutoReconnect.
// A connection must be established before calling this method.
// Throws if the connection was closed for another reason than explicit close by the user.
Task DisconnectedAsync(CancellationToken cancellationToken = default);
CancellationToken Disconnected { get; }

Task<RemoteProcess> ExecuteAsync(string command, CancellationToken cancellationToken);
Task<RemoteProcess> ExecuteAsync(string command, ExecuteOptions? options = null, CancellationToken cancellationToken = default);
Expand Down
11 changes: 7 additions & 4 deletions src/Tmds.Ssh/SshClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,16 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
await GetSessionAsync(cancellationToken, explicitConnect: true).ConfigureAwait(false);
}

public Task DisconnectedAsync(CancellationToken cancellationToken = default)
public CancellationToken Disconnected
{
SshSession session = GetConnectedSesion(cancellationToken);
return session.DisconnectedAsync(cancellationToken);
get
{
SshSession session = GetConnectedSesion();
return session.ConnectionClosed;
}
}

private SshSession GetConnectedSesion(CancellationToken cancellationToken, [CallerMemberNameAttribute]string? callerMethod = null)
private SshSession GetConnectedSesion([CallerMemberNameAttribute]string? callerMethod = null)
{
if (_autoReconnect)
{
Expand Down
27 changes: 0 additions & 27 deletions src/Tmds.Ssh/SshSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,33 +98,6 @@ public async Task ConnectAsync(TimeSpan connectTimeout, CancellationToken ct = d
await task;
}

public async Task DisconnectedAsync(CancellationToken cancellationToken)
{
Task? runningConnectionTask = null;
lock (_gate)
{
// Throw if a user has Disposed before calling this method.
ThrowIfDisposed();

runningConnectionTask = _runningConnectionTask;
}

if (runningConnectionTask is null)
{
Debug.Assert(false); // this method shouldn't ever get called if we never connected.
ThrowNeverConnected();
}
await runningConnectionTask.WaitAsync(cancellationToken);

// Don't throw if the connection was closed due to an explicit close by the user.
if (_abortReason == DisposedException)
{
return;
}

ThrowNewConnectionClosedException();
}

private async Task<SshConnection> EstablishConnectionAsync(CancellationToken ct)
{
Debug.Assert(_settings is not null);
Expand Down
35 changes: 13 additions & 22 deletions test/Tmds.Ssh.Tests/ConnectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -461,62 +461,53 @@ public async Task SshConfig_Options()
}

[Fact]
public async Task DisconnectedAsync()
public async Task Disconnected()
{
using var client = await _sshServer.CreateClientAsync();

Task disconnectedTask = client.DisconnectedAsync();
CancellationToken disconnected = client.Disconnected;

client.Dispose();

await disconnectedTask;
Assert.True(disconnected.IsCancellationRequested);
}

[Fact]
public async Task DisconnectedAsync_WithError()
public async Task Disconnected_WithError()
{
using var client = await _sshServer.CreateClientAsync();

Task disconnectedTask = client.DisconnectedAsync();
CancellationToken disconnected = client.Disconnected;

client.ForceConnectionClose();

await Assert.ThrowsAsync<SshConnectionClosedException>(() => disconnectedTask);
Assert.True(disconnected.IsCancellationRequested);
Assert.True(client.Disconnected.IsCancellationRequested);
}

[Fact]
public async Task DisconnectedAsync_RequiresConnect()
public async Task Disconnected_RequiresConnect()
{
using var client = await _sshServer.CreateClientAsync(connect: false);

await Assert.ThrowsAsync<InvalidOperationException>(() => client.DisconnectedAsync());
Assert.Throws<InvalidOperationException>(() => client.Disconnected);
}

[Fact]
public async Task DisconnectedAsync_NotWhenAutoReconnect()
public async Task Disconnected_NotWhenAutoReconnect()
{
using var client = await _sshServer.CreateClientAsync(configure: settings => settings.AutoReconnect = true);

await Assert.ThrowsAsync<InvalidOperationException>(() => client.DisconnectedAsync());
Assert.Throws<InvalidOperationException>(() => client.Disconnected);
}

[Fact]
public async Task DisconnectedAsync_NotAfterDispose()
public async Task Disconnected_NotAfterDispose()
{
using var client = await _sshServer.CreateClientAsync();

client.Dispose();

await Assert.ThrowsAsync<ObjectDisposedException>(() => client.DisconnectedAsync());
}

[Fact]
public async Task DisconnectedAsync_CanCancel()
{
using var client = await _sshServer.CreateClientAsync();

var cts = new CancellationTokenSource(50);

await Assert.ThrowsAnyAsync<OperationCanceledException>(() => client.DisconnectedAsync(cts.Token));
Assert.Throws<ObjectDisposedException>(() => client.Disconnected);
}
}