diff --git a/src/Renci.SshNet/BaseClient.cs b/src/Renci.SshNet/BaseClient.cs index 4b0850b15..df42b31aa 100644 --- a/src/Renci.SshNet/BaseClient.cs +++ b/src/Renci.SshNet/BaseClient.cs @@ -550,7 +550,7 @@ private void StartKeepAliveTimer() /// 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() diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs index 77014e9a3..c9d633cfb 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -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; @@ -2120,7 +2119,7 @@ private List 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(); @@ -2170,7 +2169,7 @@ private List 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)) @@ -2237,7 +2236,7 @@ private List InternalListDirectory(string path, SftpListDirectoryAsyn if (!basePath.EndsWith("/", StringComparison.Ordinal)) #endif { - basePath = string.Format("{0}/", fullPath); + basePath = $"{fullPath}/"; } var result = new List(); @@ -2249,7 +2248,7 @@ private List 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)); } @@ -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.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.Shared.Return(buffer); + } if (isAsync) { diff --git a/src/Renci.SshNet/SshClient.cs b/src/Renci.SshNet/SshClient.cs index 3051074e1..e80b560f9 100644 --- a/src/Renci.SshNet/SshClient.cs +++ b/src/Renci.SshNet/SshClient.cs @@ -18,6 +18,11 @@ public class SshClient : BaseClient, ISshClient /// private readonly List _forwardedPorts; + /// + /// Cached readonly collection of forwarded ports. + /// + private readonly IEnumerable _forwardedPortsReadOnly; + /// /// Holds a value indicating whether the current instance is disposed. /// @@ -33,7 +38,7 @@ public IEnumerable ForwardedPorts { get { - return _forwardedPorts.AsReadOnly(); + return _forwardedPortsReadOnly; } } @@ -137,6 +142,7 @@ internal SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServ : base(connectionInfo, ownsConnectionInfo, serviceFactory) { _forwardedPorts = new List(); + _forwardedPortsReadOnly = _forwardedPorts.AsReadOnly(); } ///