mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 01:05:42 +00:00
Use ArrayPool for upload buffer in SftpClient to reduce allocations
Co-authored-by: WojciechNagorski <17333903+WojciechNagorski@users.noreply.github.com>
This commit is contained in:
@@ -2419,103 +2419,111 @@ 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;
|
||||
|
||||
// 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;
|
||||
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
var bytesRead = isAsync
|
||||
#if NET
|
||||
? await input.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)
|
||||
#else
|
||||
? await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)
|
||||
#endif
|
||||
: input.Read(buffer, 0, buffer.Length);
|
||||
var expectedResponses = 0;
|
||||
|
||||
if (bytesRead == 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);
|
||||
|
||||
ExceptionDispatchInfo? exception = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
break;
|
||||
var bytesRead = isAsync
|
||||
#if NET
|
||||
? await input.ReadAsync(buffer.AsMemory(0, bufferLength), cancellationToken).ConfigureAwait(false)
|
||||
#else
|
||||
? await input.ReadAsync(buffer, 0, bufferLength, cancellationToken).ConfigureAwait(false)
|
||||
#endif
|
||||
: input.Read(buffer, 0, bufferLength);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (asyncResult is not null && asyncResult.IsUploadCanceled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
exception?.Throw();
|
||||
|
||||
var writtenBytes = offset + (ulong)bytesRead;
|
||||
|
||||
_ = Interlocked.Increment(ref expectedResponses);
|
||||
mres.Reset();
|
||||
|
||||
_sftpSession.RequestWrite(handle, offset, buffer, offset: 0, bytesRead, wait: null, s =>
|
||||
{
|
||||
var setHandle = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (Sftp.SftpSession.GetSftpException(s) is Exception ex)
|
||||
{
|
||||
exception = ExceptionDispatchInfo.Capture(ex);
|
||||
}
|
||||
|
||||
if (exception is not null)
|
||||
{
|
||||
setHandle = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(s.StatusCode == StatusCode.Ok);
|
||||
|
||||
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)
|
||||
{
|
||||
mres.Set();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
offset += (ulong)bytesRead;
|
||||
}
|
||||
|
||||
if (asyncResult is not null && asyncResult.IsUploadCanceled)
|
||||
// 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)
|
||||
{
|
||||
break;
|
||||
if (isAsync)
|
||||
{
|
||||
await _sftpSession.WaitOnHandleAsync(mres.WaitHandle, _operationTimeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_sftpSession.WaitOnHandle(mres.WaitHandle, _operationTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
exception?.Throw();
|
||||
|
||||
var writtenBytes = offset + (ulong)bytesRead;
|
||||
|
||||
_ = Interlocked.Increment(ref expectedResponses);
|
||||
mres.Reset();
|
||||
|
||||
_sftpSession.RequestWrite(handle, offset, buffer, offset: 0, bytesRead, wait: null, s =>
|
||||
{
|
||||
var setHandle = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (Sftp.SftpSession.GetSftpException(s) is Exception ex)
|
||||
{
|
||||
exception = ExceptionDispatchInfo.Capture(ex);
|
||||
}
|
||||
|
||||
if (exception is not null)
|
||||
{
|
||||
setHandle = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(s.StatusCode == StatusCode.Ok);
|
||||
|
||||
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)
|
||||
{
|
||||
mres.Set();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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.
|
||||
|
||||
if (Volatile.Read(ref expectedResponses) != 0)
|
||||
finally
|
||||
{
|
||||
if (isAsync)
|
||||
{
|
||||
await _sftpSession.WaitOnHandleAsync(mres.WaitHandle, _operationTimeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_sftpSession.WaitOnHandle(mres.WaitHandle, _operationTimeout);
|
||||
}
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
exception?.Throw();
|
||||
|
||||
if (isAsync)
|
||||
{
|
||||
await _sftpSession.RequestCloseAsync(handle, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user