|
2 | 2 | using System.Globalization; |
3 | 3 | using System.Runtime.ExceptionServices; |
4 | 4 | using System.Threading; |
| 5 | +using System.Threading.Tasks; |
5 | 6 |
|
6 | 7 | using Renci.SshNet.Abstractions; |
7 | 8 | using Renci.SshNet.Channels; |
@@ -29,13 +30,8 @@ internal abstract class SubsystemSession : ISubsystemSession |
29 | 30 | private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(initialState: false); |
30 | 31 | private bool _isDisposed; |
31 | 32 |
|
32 | | - /// <summary> |
33 | | - /// Gets or set the number of seconds to wait for an operation to complete. |
34 | | - /// </summary> |
35 | | - /// <value> |
36 | | - /// The number of seconds to wait for an operation to complete, or -1 to wait indefinitely. |
37 | | - /// </value> |
38 | | - public int OperationTimeout { get; private set; } |
| 33 | + /// <inheritdoc/> |
| 34 | + public int OperationTimeout { get; set; } |
39 | 35 |
|
40 | 36 | /// <summary> |
41 | 37 | /// Occurs when an error occurred. |
@@ -250,6 +246,59 @@ public void WaitOnHandle(WaitHandle waitHandle, int millisecondsTimeout) |
250 | 246 | } |
251 | 247 | } |
252 | 248 |
|
| 249 | + protected async Task<T> WaitOnHandleAsync<T>(TaskCompletionSource<T> tcs, int millisecondsTimeout, CancellationToken cancellationToken) |
| 250 | + { |
| 251 | + cancellationToken.ThrowIfCancellationRequested(); |
| 252 | + |
| 253 | + var errorOccuredReg = ThreadPool.RegisterWaitForSingleObject( |
| 254 | + _errorOccuredWaitHandle, |
| 255 | + (tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(_exception), |
| 256 | + state: tcs, |
| 257 | + millisecondsTimeOutInterval: -1, |
| 258 | + executeOnlyOnce: true); |
| 259 | + |
| 260 | + var sessionDisconnectedReg = ThreadPool.RegisterWaitForSingleObject( |
| 261 | + _sessionDisconnectedWaitHandle, |
| 262 | + static (tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(new SshException("Connection was closed by the server.")), |
| 263 | + state: tcs, |
| 264 | + millisecondsTimeOutInterval: -1, |
| 265 | + executeOnlyOnce: true); |
| 266 | + |
| 267 | + var channelClosedReg = ThreadPool.RegisterWaitForSingleObject( |
| 268 | + _channelClosedWaitHandle, |
| 269 | + static (tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(new SshException("Channel was closed.")), |
| 270 | + state: tcs, |
| 271 | + millisecondsTimeOutInterval: -1, |
| 272 | + executeOnlyOnce: true); |
| 273 | + |
| 274 | + using var timeoutCts = new CancellationTokenSource(millisecondsTimeout); |
| 275 | + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); |
| 276 | + |
| 277 | + using var tokenReg = linkedCts.Token.Register( |
| 278 | + static s => |
| 279 | + { |
| 280 | + (var tcs, var cancellationToken) = ((TaskCompletionSource<T>, CancellationToken))s; |
| 281 | + _ = tcs.TrySetCanceled(cancellationToken); |
| 282 | + }, |
| 283 | + state: (tcs, cancellationToken), |
| 284 | + useSynchronizationContext: false); |
| 285 | + |
| 286 | + try |
| 287 | + { |
| 288 | + return await tcs.Task.ConfigureAwait(false); |
| 289 | + } |
| 290 | + catch (OperationCanceledException oce) when (timeoutCts.IsCancellationRequested) |
| 291 | + { |
| 292 | + throw new SshOperationTimeoutException("Operation has timed out.", oce); |
| 293 | + } |
| 294 | + finally |
| 295 | + { |
| 296 | + _ = errorOccuredReg.Unregister(waitObject: null); |
| 297 | + _ = sessionDisconnectedReg.Unregister(waitObject: null); |
| 298 | + _ = channelClosedReg.Unregister(waitObject: null); |
| 299 | + } |
| 300 | + } |
| 301 | + |
253 | 302 | /// <summary> |
254 | 303 | /// Blocks the current thread until the specified <see cref="WaitHandle"/> gets signaled, using a |
255 | 304 | /// 32-bit signed integer to specify the time interval in milliseconds. |
|
0 commit comments