Skip to content

Commit 5a5770d

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into logging
2 parents 673e0b2 + fbedaab commit 5a5770d

File tree

7 files changed

+570
-343
lines changed

7 files changed

+570
-343
lines changed

src/Renci.SshNet/ISftpClient.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ public interface ISftpClient : IBaseClient
5656
/// The timeout to wait until an operation completes. The default value is negative
5757
/// one (-1) milliseconds, which indicates an infinite timeout period.
5858
/// </value>
59-
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
6059
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> represents a value that is less than -1 or greater than <see cref="int.MaxValue"/> milliseconds.</exception>
6160
TimeSpan OperationTimeout { get; set; }
6261

src/Renci.SshNet/ISubsystemSession.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ namespace Renci.SshNet
1111
internal interface ISubsystemSession : IDisposable
1212
{
1313
/// <summary>
14-
/// Gets or set the number of seconds to wait for an operation to complete.
14+
/// Gets or sets the number of milliseconds to wait for an operation to complete.
1515
/// </summary>
1616
/// <value>
17-
/// The number of seconds to wait for an operation to complete, or <c>-1</c> to wait indefinitely.
17+
/// The number of milliseconds to wait for an operation to complete, or <c>-1</c> to wait indefinitely.
1818
/// </value>
19-
int OperationTimeout { get; }
19+
int OperationTimeout { get; set; }
2020

2121
/// <summary>
2222
/// Gets a value indicating whether this session is open.

src/Renci.SshNet/Sftp/SftpSession.cs

Lines changed: 238 additions & 295 deletions
Large diffs are not rendered by default.

src/Renci.SshNet/SftpClient.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,21 @@ public class SftpClient : BaseClient, ISftpClient
4646
/// The timeout to wait until an operation completes. The default value is negative
4747
/// one (-1) milliseconds, which indicates an infinite timeout period.
4848
/// </value>
49-
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
5049
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> represents a value that is less than -1 or greater than <see cref="int.MaxValue"/> milliseconds.</exception>
5150
public TimeSpan OperationTimeout
5251
{
5352
get
5453
{
55-
CheckDisposed();
56-
5754
return TimeSpan.FromMilliseconds(_operationTimeout);
5855
}
5956
set
6057
{
61-
CheckDisposed();
62-
6358
_operationTimeout = value.AsTimeout(nameof(OperationTimeout));
59+
60+
if (_sftpSession is { } sftpSession)
61+
{
62+
sftpSession.OperationTimeout = _operationTimeout;
63+
}
6464
}
6565
}
6666

src/Renci.SshNet/SubsystemSession.cs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Globalization;
33
using System.Runtime.ExceptionServices;
44
using System.Threading;
5+
using System.Threading.Tasks;
56

67
using Microsoft.Extensions.Logging;
78

@@ -31,13 +32,8 @@ internal abstract class SubsystemSession : ISubsystemSession
3132
private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(initialState: false);
3233
private bool _isDisposed;
3334

34-
/// <summary>
35-
/// Gets or set the number of seconds to wait for an operation to complete.
36-
/// </summary>
37-
/// <value>
38-
/// The number of seconds to wait for an operation to complete, or -1 to wait indefinitely.
39-
/// </value>
40-
public int OperationTimeout { get; private set; }
35+
/// <inheritdoc/>
36+
public int OperationTimeout { get; set; }
4137

4238
/// <summary>
4339
/// Occurs when an error occurred.
@@ -253,6 +249,59 @@ public void WaitOnHandle(WaitHandle waitHandle, int millisecondsTimeout)
253249
}
254250
}
255251

252+
protected async Task<T> WaitOnHandleAsync<T>(TaskCompletionSource<T> tcs, int millisecondsTimeout, CancellationToken cancellationToken)
253+
{
254+
cancellationToken.ThrowIfCancellationRequested();
255+
256+
var errorOccuredReg = ThreadPool.RegisterWaitForSingleObject(
257+
_errorOccuredWaitHandle,
258+
(tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(_exception),
259+
state: tcs,
260+
millisecondsTimeOutInterval: -1,
261+
executeOnlyOnce: true);
262+
263+
var sessionDisconnectedReg = ThreadPool.RegisterWaitForSingleObject(
264+
_sessionDisconnectedWaitHandle,
265+
static (tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(new SshException("Connection was closed by the server.")),
266+
state: tcs,
267+
millisecondsTimeOutInterval: -1,
268+
executeOnlyOnce: true);
269+
270+
var channelClosedReg = ThreadPool.RegisterWaitForSingleObject(
271+
_channelClosedWaitHandle,
272+
static (tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(new SshException("Channel was closed.")),
273+
state: tcs,
274+
millisecondsTimeOutInterval: -1,
275+
executeOnlyOnce: true);
276+
277+
using var timeoutCts = new CancellationTokenSource(millisecondsTimeout);
278+
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
279+
280+
using var tokenReg = linkedCts.Token.Register(
281+
static s =>
282+
{
283+
(var tcs, var cancellationToken) = ((TaskCompletionSource<T>, CancellationToken))s;
284+
_ = tcs.TrySetCanceled(cancellationToken);
285+
},
286+
state: (tcs, cancellationToken),
287+
useSynchronizationContext: false);
288+
289+
try
290+
{
291+
return await tcs.Task.ConfigureAwait(false);
292+
}
293+
catch (OperationCanceledException oce) when (timeoutCts.IsCancellationRequested)
294+
{
295+
throw new SshOperationTimeoutException("Operation has timed out.", oce);
296+
}
297+
finally
298+
{
299+
_ = errorOccuredReg.Unregister(waitObject: null);
300+
_ = sessionDisconnectedReg.Unregister(waitObject: null);
301+
_ = channelClosedReg.Unregister(waitObject: null);
302+
}
303+
}
304+
256305
/// <summary>
257306
/// Blocks the current thread until the specified <see cref="WaitHandle"/> gets signaled, using a
258307
/// 32-bit signed integer to specify the time interval in milliseconds.

test/Renci.SshNet.Tests/Classes/SftpClientTest.cs

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -115,37 +115,5 @@ public void OperationTimeout_GreaterThanLowerLimit()
115115
Assert.AreEqual("OperationTimeout", ex.ParamName);
116116
}
117117
}
118-
119-
[TestMethod]
120-
public void OperationTimeout_Disposed()
121-
{
122-
var connectionInfo = new PasswordConnectionInfo("host", 22, "admin", "pwd");
123-
var target = new SftpClient(connectionInfo);
124-
target.Dispose();
125-
126-
// getter
127-
try
128-
{
129-
var actual = target.OperationTimeout;
130-
Assert.Fail("Should have failed, but returned: " + actual);
131-
}
132-
catch (ObjectDisposedException ex)
133-
{
134-
Assert.IsNull(ex.InnerException);
135-
Assert.AreEqual(typeof(SftpClient).FullName, ex.ObjectName);
136-
}
137-
138-
// setter
139-
try
140-
{
141-
target.OperationTimeout = TimeSpan.FromMilliseconds(5);
142-
Assert.Fail();
143-
}
144-
catch (ObjectDisposedException ex)
145-
{
146-
Assert.IsNull(ex.InnerException);
147-
Assert.AreEqual(typeof(SftpClient).FullName, ex.ObjectName);
148-
}
149-
}
150118
}
151119
}

0 commit comments

Comments
 (0)