Skip to content
Draft
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
2 changes: 1 addition & 1 deletion src/Renci.SshNet/BaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ private void StartKeepAliveTimer()
/// </returns>
private Timer CreateKeepAliveTimer(TimeSpan dueTime, TimeSpan period)
{
return new Timer(state => SendKeepAliveMessage(), Session, dueTime, period);
return new Timer(static state => ((BaseClient)state!).SendKeepAliveMessage(), this, dueTime, period);
}

private ISession CreateAndConnectSession()
Expand Down
153 changes: 80 additions & 73 deletions src/Renci.SshNet/SftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
Expand Down Expand Up @@ -2120,7 +2119,7 @@ private List<FileInfo> InternalSynchronizeDirectories(string sourcePath, string
{
if (!Directory.Exists(sourcePath))
{
throw new FileNotFoundException(string.Format("Source directory not found: {0}", sourcePath));
throw new FileNotFoundException($"Source directory not found: {sourcePath}");
}

var uploadedFiles = new List<FileInfo>();
Expand Down Expand Up @@ -2170,7 +2169,7 @@ private List<FileInfo> InternalSynchronizeDirectories(string sourcePath, string

if (isDifferent)
{
var remoteFileName = string.Format(CultureInfo.InvariantCulture, @"{0}/{1}", destinationPath, localFile.Name);
var remoteFileName = $"{destinationPath}/{localFile.Name}";
try
{
using (var file = File.OpenRead(localFile.FullName))
Expand Down Expand Up @@ -2237,7 +2236,7 @@ private List<ISftpFile> InternalListDirectory(string path, SftpListDirectoryAsyn
if (!basePath.EndsWith("/", StringComparison.Ordinal))
#endif
{
basePath = string.Format("{0}/", fullPath);
basePath = $"{fullPath}/";
}

var result = new List<ISftpFile>();
Expand All @@ -2249,7 +2248,7 @@ private List<ISftpFile> InternalListDirectory(string path, SftpListDirectoryAsyn
foreach (var f in files)
{
result.Add(new SftpFile(_sftpSession,
string.Format(CultureInfo.InvariantCulture, "{0}{1}", basePath, f.Key),
$"{basePath}{f.Key}",
f.Value));
}

Expand Down Expand Up @@ -2420,102 +2419,110 @@ private async Task InternalUploadFile(

ulong offset = 0;

// create buffer of optimal length
var buffer = new byte[_sftpSession.CalculateOptimalWriteLength(_bufferSize, handle)];
// create buffer of optimal length using ArrayPool
var bufferLength = (int)_sftpSession.CalculateOptimalWriteLength(_bufferSize, handle);
var buffer = ArrayPool<byte>.Shared.Rent(bufferLength);

var expectedResponses = 0;
try
{
var expectedResponses = 0;

// We will send out all the write requests without waiting for each response.
// Afterwards, we may wait on this handle until all responses are received
// or an error has occurred.
using var mres = new ManualResetEventSlim(initialState: false);
// We will send out all the write requests without waiting for each response.
// Afterwards, we may wait on this handle until all responses are received
// or an error has occurred.
using var mres = new ManualResetEventSlim(initialState: false);

ExceptionDispatchInfo? exception = null;
ExceptionDispatchInfo? exception = null;

while (true)
{
var bytesRead = isAsync
while (true)
{
var bytesRead = isAsync
#if NET
? await input.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)
? await input.ReadAsync(buffer.AsMemory(0, bufferLength), cancellationToken).ConfigureAwait(false)
#else
? await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)
? await input.ReadAsync(buffer, 0, bufferLength, cancellationToken).ConfigureAwait(false)
#endif
: input.Read(buffer, 0, buffer.Length);
: input.Read(buffer, 0, bufferLength);

if (bytesRead == 0)
{
break;
}

if (asyncResult is not null && asyncResult.IsUploadCanceled)
{
break;
}
if (bytesRead == 0)
{
break;
}

exception?.Throw();
if (asyncResult is not null && asyncResult.IsUploadCanceled)
{
break;
}

var writtenBytes = offset + (ulong)bytesRead;
exception?.Throw();

_ = Interlocked.Increment(ref expectedResponses);
mres.Reset();
var writtenBytes = offset + (ulong)bytesRead;

_sftpSession.RequestWrite(handle, offset, buffer, offset: 0, bytesRead, wait: null, s =>
{
var setHandle = false;
_ = Interlocked.Increment(ref expectedResponses);
mres.Reset();

try
_sftpSession.RequestWrite(handle, offset, buffer, offset: 0, bytesRead, wait: null, s =>
{
if (Sftp.SftpSession.GetSftpException(s) is Exception ex)
{
exception = ExceptionDispatchInfo.Capture(ex);
}
var setHandle = false;

if (exception is not null)
try
{
setHandle = true;
return;
}
if (Sftp.SftpSession.GetSftpException(s) is Exception ex)
{
exception = ExceptionDispatchInfo.Capture(ex);
}

Debug.Assert(s.StatusCode == StatusCode.Ok);
if (exception is not null)
{
setHandle = true;
return;
}

asyncResult?.Update(writtenBytes);
Debug.Assert(s.StatusCode == StatusCode.Ok);

// Call callback to report number of bytes written
if (uploadCallback is not null)
{
// Execute callback on different thread
ThreadAbstraction.ExecuteThread(() => uploadCallback(writtenBytes));
asyncResult?.Update(writtenBytes);

// Call callback to report number of bytes written
if (uploadCallback is not null)
{
// Execute callback on different thread
ThreadAbstraction.ExecuteThread(() => uploadCallback(writtenBytes));
}
}
}
finally
{
if (Interlocked.Decrement(ref expectedResponses) == 0 || setHandle)
finally
{
mres.Set();
if (Interlocked.Decrement(ref expectedResponses) == 0 || setHandle)
{
mres.Set();
}
}
}
});
});

offset += (ulong)bytesRead;
}
offset += (ulong)bytesRead;
}

// Make sure the read of exception cannot be executed ahead of
// the read of expectedResponses so that we do not miss an
// exception.
// Make sure the read of exception cannot be executed ahead of
// the read of expectedResponses so that we do not miss an
// exception.

if (Volatile.Read(ref expectedResponses) != 0)
{
if (isAsync)
if (Volatile.Read(ref expectedResponses) != 0)
{
await _sftpSession.WaitOnHandleAsync(mres.WaitHandle, _operationTimeout, cancellationToken).ConfigureAwait(false);
}
else
{
_sftpSession.WaitOnHandle(mres.WaitHandle, _operationTimeout);
if (isAsync)
{
await _sftpSession.WaitOnHandleAsync(mres.WaitHandle, _operationTimeout, cancellationToken).ConfigureAwait(false);
}
else
{
_sftpSession.WaitOnHandle(mres.WaitHandle, _operationTimeout);
}
}
}

exception?.Throw();
exception?.Throw();
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}

if (isAsync)
{
Expand Down
8 changes: 7 additions & 1 deletion src/Renci.SshNet/SshClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public class SshClient : BaseClient, ISshClient
/// </summary>
private readonly List<ForwardedPort> _forwardedPorts;

/// <summary>
/// Cached readonly collection of forwarded ports.
/// </summary>
private readonly IEnumerable<ForwardedPort> _forwardedPortsReadOnly;

/// <summary>
/// Holds a value indicating whether the current instance is disposed.
/// </summary>
Expand All @@ -33,7 +38,7 @@ public IEnumerable<ForwardedPort> ForwardedPorts
{
get
{
return _forwardedPorts.AsReadOnly();
return _forwardedPortsReadOnly;
}
}

Expand Down Expand Up @@ -137,6 +142,7 @@ internal SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServ
: base(connectionInfo, ownsConnectionInfo, serviceFactory)
{
_forwardedPorts = new List<ForwardedPort>();
_forwardedPortsReadOnly = _forwardedPorts.AsReadOnly();
}

/// <summary>
Expand Down