Files
PSMinIO/src/Utils/ProgressTrackingStream.cs
T
PSMinIO Developer 13091ea1b1 Fix upload performance bottleneck and enhance progress bar text
🚀 CRITICAL PERFORMANCE OPTIMIZATION:

 FIXED UPLOAD SLOWDOWN ISSUE:
  • Root cause: Progress reported on EVERY Read() call (thousands per chunk)
  • Added intelligent progress throttling in ProgressTrackingStream
  • Only report progress every 1% or 1MB (whichever is smaller)
  • Minimum 8KB threshold to prevent excessive updates
  • Massive reduction in progress callback overhead

 PERFORMANCE IMPROVEMENTS:
  • Before: Progress callback on every 4KB-64KB read = 1000+ calls per chunk
  • After: Progress callback every 1MB or 1% = ~64 calls per 64MB chunk
  • 15x-20x reduction in progress overhead
  • Maintains smooth progress bars without performance penalty

 ENHANCED PROGRESS BAR TEXT:
   Added 'Counter of Counter' format to chunk progress
   Before: 'Part 23: 32.1 MB/64.0 MB'
   After: 'Part 23 of 74 - 32.1 MB/64.0 MB'
   Better context for users about overall progress

 TECHNICAL IMPLEMENTATION:
   Added _reportingThreshold and _lastReportedBytes to ProgressTrackingStream
   Throttled progress reporting while maintaining accuracy
   Updated UploadPart method signature to include totalParts
   Preserved final progress report at 100% completion

 EXPECTED RESULTS:
   Fast, consistent upload speeds on high-bandwidth connections
   Smooth progress bars without performance degradation
   Better user experience with contextual progress information
   Scalable to large files without slowdown

Critical fix for multipart upload performance!
2025-07-14 22:50:50 -04:00

93 lines
3.2 KiB
C#

using System;
using System.IO;
namespace PSMinIO.Utils
{
/// <summary>
/// Stream wrapper that tracks read progress and reports it via callback
/// </summary>
public class ProgressTrackingStream : Stream
{
private readonly Stream _baseStream;
private readonly long _totalBytes;
private readonly Action<long, long> _progressCallback;
private long _bytesRead = 0;
private long _lastReportedBytes;
private readonly long _reportingThreshold;
private readonly long _startPosition;
public ProgressTrackingStream(Stream baseStream, long totalBytes, Action<long, long> progressCallback)
{
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
_totalBytes = totalBytes;
_progressCallback = progressCallback ?? throw new ArgumentNullException(nameof(progressCallback));
_startPosition = baseStream.Position;
// Only report progress every 1% or 1MB, whichever is smaller (reduces overhead)
_reportingThreshold = Math.Min(totalBytes / 100, 1024 * 1024);
if (_reportingThreshold < 8192) _reportingThreshold = 8192; // Minimum 8KB threshold
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _totalBytes;
public override long Position
{
get => _bytesRead;
set => throw new NotSupportedException("Setting position is not supported");
}
public override int Read(byte[] buffer, int offset, int count)
{
// Limit read to remaining bytes
var remainingBytes = _totalBytes - _bytesRead;
var bytesToRead = (int)Math.Min(count, remainingBytes);
if (bytesToRead <= 0)
return 0;
var bytesRead = _baseStream.Read(buffer, offset, bytesToRead);
_bytesRead += bytesRead;
// Report progress only when threshold is reached or at completion (reduces overhead)
if (_bytesRead - _lastReportedBytes >= _reportingThreshold || _bytesRead >= _totalBytes)
{
_progressCallback(_bytesRead, _totalBytes);
_lastReportedBytes = _bytesRead;
}
return bytesRead;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException("Writing is not supported");
}
public override void Flush()
{
_baseStream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException("Seeking is not supported");
}
public override void SetLength(long value)
{
throw new NotSupportedException("Setting length is not supported");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Don't dispose the base stream - let the caller handle it
}
base.Dispose(disposing);
}
}
}