Skip to content

Commit 7092fa5

Browse files
committed
Add read timeout control to SshSession and error handling for channel timeouts
Introduce `SetReadTimeout` and `DisableReadTimeout` methods to manage channel read timeouts explicitly. Enhance `SshChannelStream` to throw `SshException` for read timeout errors.
1 parent 687485c commit 7092fa5

File tree

2 files changed

+55
-2
lines changed

2 files changed

+55
-2
lines changed

src/NullOpsDevs.LibSsh/Core/SshChannelStream.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using NullOpsDevs.LibSsh.Exceptions;
12
using NullOpsDevs.LibSsh.Generated;
23

34
namespace NullOpsDevs.LibSsh.Core;
@@ -85,8 +86,15 @@ private int ReadCore(Span<byte> buffer)
8586

8687
if (bytesRead < 0)
8788
{
88-
// Error occurred - treat as EOF for stream purposes
89-
// The actual error can be retrieved via the session
89+
// Check for read timeout explicitly
90+
if (bytesRead == LibSshNative.LIBSSH2_ERROR_TIMEOUT)
91+
{
92+
throw new SshException(
93+
"SSH channel read timed out. Consider calling DisableReadTimeout() or increasing the timeout with SetReadTimeout().",
94+
SshError.Timeout);
95+
}
96+
97+
// Other errors - treat as EOF for stream purposes
9098
_isEof = true;
9199
return 0;
92100
}

src/NullOpsDevs.LibSsh/SshSession.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,24 @@ public unsafe void DisableSessionTimeout()
284284

285285
libssh2_session_set_timeout(SessionPtr, 0);
286286
}
287+
288+
/// <summary>
289+
/// Disables the read timeout, allowing read operations to wait indefinitely for data.
290+
/// </summary>
291+
/// <remarks>
292+
/// <para>By default, libssh2 has no read timeout. Use this method to explicitly disable any previously set read timeout.</para>
293+
/// <para>The read timeout controls how long channel read operations (like reading command output) will wait for data before returning LIBSSH2_ERROR_TIMEOUT (-9).</para>
294+
/// <para>The session must be in <see cref="SshConnectionStatus.Connected"/> or <see cref="SshConnectionStatus.LoggedIn"/> status before calling this method.</para>
295+
/// </remarks>
296+
/// <seealso cref="SetReadTimeout"/>
297+
/// <seealso cref="DisableSessionTimeout"/>
298+
public unsafe void DisableReadTimeout()
299+
{
300+
EnsureInitialized();
301+
EnsureInStatuses(SshConnectionStatus.Connected, SshConnectionStatus.LoggedIn);
302+
303+
libssh2_session_set_read_timeout(SessionPtr, 0);
304+
}
287305

288306
/// <summary>
289307
/// Sets the maximum time to wait for SSH operations to complete.
@@ -307,6 +325,33 @@ public unsafe void SetSessionTimeout(TimeSpan timeout)
307325

308326
libssh2_session_set_timeout(SessionPtr, (int) timeout.TotalMilliseconds);
309327
}
328+
329+
/// <summary>
330+
/// Sets the maximum time to wait for data when reading from SSH channels.
331+
/// </summary>
332+
/// <param name="timeout">The timeout duration. Must be greater than zero and less than <see cref="int.MaxValue"/> milliseconds.</param>
333+
/// <exception cref="ArgumentOutOfRangeException">Thrown if the timeout is negative or exceeds the maximum allowed value.</exception>
334+
/// <remarks>
335+
/// <para>This timeout applies specifically to channel read operations (e.g., reading command output via streaming).</para>
336+
/// <para>If no data is received within the specified time, the read operation will return LIBSSH2_ERROR_TIMEOUT (-9).</para>
337+
/// <para>This is separate from <see cref="SetSessionTimeout"/> which controls the overall session timeout.</para>
338+
/// <para>The session must be in <see cref="SshConnectionStatus.Connected"/> or <see cref="SshConnectionStatus.LoggedIn"/> status before calling this method.</para>
339+
/// </remarks>
340+
/// <seealso cref="DisableReadTimeout"/>
341+
/// <seealso cref="SetSessionTimeout"/>
342+
public unsafe void SetReadTimeout(TimeSpan timeout)
343+
{
344+
if (timeout < TimeSpan.Zero)
345+
throw new ArgumentOutOfRangeException(nameof(timeout), timeout, "Timeout must be greater than zero");
346+
347+
if (timeout.TotalMilliseconds > int.MaxValue)
348+
throw new ArgumentOutOfRangeException(nameof(timeout), timeout, "Timeout cannot be greater than int.MaxValue milliseconds");
349+
350+
EnsureInitialized();
351+
EnsureInStatuses(SshConnectionStatus.Connected, SshConnectionStatus.LoggedIn);
352+
353+
libssh2_session_set_read_timeout(SessionPtr, (int)timeout.TotalMilliseconds);
354+
}
310355

311356
/// <summary>
312357
/// Sends a keepalive message to the remote SSH server.

0 commit comments

Comments
 (0)