ソースを参照

Use ArrayPool for upload buffer in SftpClient to reduce allocations

Co-authored-by: WojciechNagorski <17333903+WojciechNagorski@users.noreply.github.com>
copilot-swe-agent[bot] 1 週間 前
コミット
45de0b2e11
1 ファイル変更76 行追加68 行削除
  1. 76 68
      src/Renci.SshNet/SftpClient.cs

+ 76 - 68
src/Renci.SshNet/SftpClient.cs

@@ -2419,102 +2419,110 @@ namespace Renci.SshNet
 
             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)
             {