Skip to content

Commit 48a8fe6

Browse files
jacobslusserRob-HagueWojciechNagorski
authored
Plumbing for more async/await work (#1281)
* Changes _socketDisposeLock to a SemaphoreSlim so it can play nice with async/await * Adds a SendAsync method for .NET6+ * Fix false positive analyzer error * Formatting --------- Co-authored-by: Robert Hague <rh@johnstreetcapital.com> Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com> Co-authored-by: Rob Hague <rob.hague00@gmail.com>
1 parent 13a6b5d commit 48a8fe6

File tree

4 files changed

+81
-14
lines changed

4 files changed

+81
-14
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#if NET6_0_OR_GREATER
2+
3+
using System;
4+
using System.Diagnostics;
5+
using System.Net.Sockets;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace Renci.SshNet.Abstractions
10+
{
11+
internal static partial class SocketAbstraction
12+
{
13+
public static ValueTask<int> ReadAsync(Socket socket, byte[] buffer, CancellationToken cancellationToken)
14+
{
15+
return socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken);
16+
}
17+
18+
public static ValueTask SendAsync(Socket socket, ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
19+
{
20+
Debug.Assert(socket != null);
21+
Debug.Assert(data.Length > 0);
22+
23+
if (cancellationToken.IsCancellationRequested)
24+
{
25+
return ValueTask.FromCanceled(cancellationToken);
26+
}
27+
28+
return SendAsyncCore(socket, data, cancellationToken);
29+
30+
static async ValueTask SendAsyncCore(Socket socket, ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
31+
{
32+
do
33+
{
34+
try
35+
{
36+
var bytesSent = await socket.SendAsync(data, SocketFlags.None, cancellationToken).ConfigureAwait(false);
37+
data = data.Slice(bytesSent);
38+
}
39+
catch (SocketException ex) when (IsErrorResumable(ex.SocketErrorCode))
40+
{
41+
// Buffer may be full; attempt a short delay and retry
42+
await Task.Delay(30, cancellationToken).ConfigureAwait(false);
43+
}
44+
}
45+
while (data.Length > 0);
46+
}
47+
}
48+
}
49+
}
50+
#endif // NET6_0_OR_GREATER

src/Renci.SshNet/Abstractions/SocketAbstraction.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace Renci.SshNet.Abstractions
1212
{
13-
internal static class SocketAbstraction
13+
internal static partial class SocketAbstraction
1414
{
1515
public static bool CanRead(Socket socket)
1616
{
@@ -311,12 +311,7 @@ public static int Read(Socket socket, byte[] buffer, int offset, int size, TimeS
311311
return totalBytesRead;
312312
}
313313

314-
#if NET6_0_OR_GREATER
315-
public static async Task<int> ReadAsync(Socket socket, byte[] buffer, CancellationToken cancellationToken)
316-
{
317-
return await socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
318-
}
319-
#else
314+
#if NET6_0_OR_GREATER == false
320315
public static Task<int> ReadAsync(Socket socket, byte[] buffer, CancellationToken cancellationToken)
321316
{
322317
return socket.ReceiveAsync(buffer, 0, buffer.Length, cancellationToken);

src/Renci.SshNet/Connection/ProtocolVersionExchange.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ public async Task<SshIdentification> StartAsync(string clientVersion, Socket soc
8181
{
8282
// Immediately send the identification string since the spec states both sides MUST send an identification string
8383
// when the connection has been established
84+
#if NET6_0_OR_GREATER
85+
await SocketAbstraction.SendAsync(socket, Encoding.UTF8.GetBytes(clientVersion + "\x0D\x0A"), cancellationToken).ConfigureAwait(false);
86+
#else
8487
SocketAbstraction.Send(socket, Encoding.UTF8.GetBytes(clientVersion + "\x0D\x0A"));
88+
#endif // NET6_0_OR_GREATER
8589

8690
var bytesReceived = new List<byte>();
8791

src/Renci.SshNet/Session.cs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public class Session : ISession
119119
/// This is also used to ensure that <see cref="_socket"/> will not be disposed
120120
/// while performing a given operation or set of operations on <see cref="_socket"/>.
121121
/// </remarks>
122-
private readonly object _socketDisposeLock = new object();
122+
private readonly SemaphoreSlim _socketDisposeLock = new SemaphoreSlim(1, 1);
123123

124124
/// <summary>
125125
/// Holds an object that is used to ensure only a single thread can connect
@@ -1127,12 +1127,14 @@ internal void SendMessage(Message message)
11271127
/// </para>
11281128
/// <para>
11291129
/// This method is only to be used when the connection is established, as the locking
1130-
/// overhead is not required while establising the connection.
1130+
/// overhead is not required while establishing the connection.
11311131
/// </para>
11321132
/// </remarks>
11331133
private void SendPacket(byte[] packet, int offset, int length)
11341134
{
1135-
lock (_socketDisposeLock)
1135+
_socketDisposeLock.Wait();
1136+
1137+
try
11361138
{
11371139
if (!_socket.IsConnected())
11381140
{
@@ -1141,6 +1143,10 @@ private void SendPacket(byte[] packet, int offset, int length)
11411143

11421144
SocketAbstraction.Send(_socket, packet, offset, length);
11431145
}
1146+
finally
1147+
{
1148+
_ = _socketDisposeLock.Release();
1149+
}
11441150
}
11451151

11461152
/// <summary>
@@ -1798,8 +1804,9 @@ internal static string ToHex(byte[] bytes)
17981804
/// </remarks>
17991805
private bool IsSocketConnected()
18001806
{
1801-
#pragma warning disable S2222 // Locks should be released on all paths
1802-
lock (_socketDisposeLock)
1807+
_socketDisposeLock.Wait();
1808+
1809+
try
18031810
{
18041811
if (!_socket.IsConnected())
18051812
{
@@ -1821,7 +1828,10 @@ private bool IsSocketConnected()
18211828
Monitor.Exit(_socketReadLock);
18221829
}
18231830
}
1824-
#pragma warning restore S2222 // Locks should be released on all paths
1831+
finally
1832+
{
1833+
_ = _socketDisposeLock.Release();
1834+
}
18251835
}
18261836

18271837
/// <summary>
@@ -1848,9 +1858,13 @@ private void SocketDisconnectAndDispose()
18481858
{
18491859
if (_socket != null)
18501860
{
1851-
lock (_socketDisposeLock)
1861+
_socketDisposeLock.Wait();
1862+
1863+
try
18521864
{
1865+
#pragma warning disable CA1508 // Avoid dead conditional code; Value could have been changed by another thread.
18531866
if (_socket != null)
1867+
#pragma warning restore CA1508 // Avoid dead conditional code
18541868
{
18551869
if (_socket.Connected)
18561870
{
@@ -1879,6 +1893,10 @@ private void SocketDisconnectAndDispose()
18791893
_socket = null;
18801894
}
18811895
}
1896+
finally
1897+
{
1898+
_ = _socketDisposeLock.Release();
1899+
}
18821900
}
18831901
}
18841902

0 commit comments

Comments
 (0)