using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace io.github.hatayama.uLoopMCP
{
///
/// Manages dynamic buffer allocation and recycling for TCP communication.
/// Provides memory-efficient buffer management with automatic sizing and cleanup.
///
public class DynamicBufferManager : IDisposable
{
private readonly ConcurrentQueue _bufferPool = new();
private readonly object _lockObject = new();
private bool _disposed = false;
// Statistics for monitoring
private int _totalBuffersCreated = 0;
private int _totalBuffersReused = 0;
private int _currentPoolSize = 0;
///
/// Maximum number of buffers to keep in the pool to prevent memory bloat.
///
private const int MAX_POOL_SIZE = 10;
///
/// Gets a buffer of at least the specified size.
/// Reuses existing buffers when possible for memory efficiency.
///
/// The minimum required buffer size
/// A byte array of at least the required size
public byte[] GetBuffer(int requiredSize)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DynamicBufferManager));
}
if (requiredSize <= 0)
{
throw new ArgumentException("Required size must be positive", nameof(requiredSize));
}
if (requiredSize > BufferConfig.MAX_BUFFER_SIZE)
{
throw new ArgumentException($"Required size {requiredSize} exceeds maximum buffer size {BufferConfig.MAX_BUFFER_SIZE}", nameof(requiredSize));
}
// Try to reuse an existing buffer from the pool
byte[] suitableBuffer = null;
var tempBuffers = new List();
// Look for a suitable buffer - stop when found
while (_bufferPool.TryDequeue(out byte[] pooledBuffer))
{
lock (_lockObject)
{
_currentPoolSize--;
}
if (pooledBuffer.Length >= requiredSize && suitableBuffer == null)
{
suitableBuffer = pooledBuffer;
break; // Stop searching once we find a suitable buffer
}
else
{
// Put back buffers that are too small
tempBuffers.Add(pooledBuffer);
}
}
// Put back the buffers that were too small
foreach (var buffer in tempBuffers)
{
lock (_lockObject)
{
if (_currentPoolSize < MAX_POOL_SIZE)
{
_bufferPool.Enqueue(buffer);
_currentPoolSize++;
}
}
}
if (suitableBuffer != null)
{
_totalBuffersReused++;
return suitableBuffer;
}
// No suitable buffer found, create a new one
int newBufferSize = CalculateOptimalBufferSize(requiredSize);
byte[] newBuffer = new byte[newBufferSize];
_totalBuffersCreated++;
return newBuffer;
}
///
/// Returns a buffer to the pool for potential reuse.
/// Only keeps buffers within reasonable size limits to prevent memory bloat.
///
/// The buffer to return to the pool
public void ReturnBuffer(byte[] buffer)
{
if (_disposed || buffer == null)
{
return;
}
// Only pool buffers within reasonable size limits
if (buffer.Length < BufferConfig.MIN_BUFFER_SIZE || buffer.Length > BufferConfig.MAX_BUFFER_SIZE)
{
return;
}
lock (_lockObject)
{
// Limit pool size to prevent memory bloat
if (_currentPoolSize >= MAX_POOL_SIZE)
{
return;
}
_currentPoolSize++;
}
_bufferPool.Enqueue(buffer);
}
///
/// Resizes a buffer to accommodate new data.
/// Creates a new buffer and copies existing data if necessary.
///
/// Reference to the current buffer
/// Length of valid data in the current buffer
/// The new required buffer size
public void ResizeBuffer(ref byte[] buffer, int currentDataLength, int newSize)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DynamicBufferManager));
}
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (currentDataLength < 0)
{
throw new ArgumentException("Current data length cannot be negative", nameof(currentDataLength));
}
if (currentDataLength > buffer.Length)
{
throw new ArgumentException("Current data length exceeds buffer size", nameof(currentDataLength));
}
if (newSize <= buffer.Length)
{
// Current buffer is already large enough
return;
}
if (newSize > BufferConfig.MAX_BUFFER_SIZE)
{
throw new ArgumentException($"New size {newSize} exceeds maximum buffer size {BufferConfig.MAX_BUFFER_SIZE}", nameof(newSize));
}
// Get a new larger buffer
byte[] oldBuffer = buffer;
buffer = GetBuffer(newSize);
// Copy existing data to the new buffer
if (currentDataLength > 0)
{
Array.Copy(oldBuffer, 0, buffer, 0, currentDataLength);
}
// Return the old buffer to the pool
ReturnBuffer(oldBuffer);
}
///
/// Calculates the optimal buffer size for the given required size.
/// Uses growth factor to minimize frequent reallocations.
///
/// The minimum required size
/// The optimal buffer size
private int CalculateOptimalBufferSize(int requiredSize)
{
// Start with initial buffer size
int optimalSize = BufferConfig.INITIAL_BUFFER_SIZE;
// Grow by factor until we meet the requirement
while (optimalSize < requiredSize)
{
optimalSize *= BufferConfig.BUFFER_GROWTH_FACTOR;
// Cap at maximum buffer size
if (optimalSize > BufferConfig.MAX_BUFFER_SIZE)
{
optimalSize = BufferConfig.MAX_BUFFER_SIZE;
break;
}
}
// Ensure we meet the minimum requirement
return Math.Max(optimalSize, requiredSize);
}
///
/// Gets buffer management statistics for monitoring and debugging.
///
/// Statistics about buffer usage
public BufferManagerStats GetStats()
{
lock (_lockObject)
{
return new BufferManagerStats
{
TotalBuffersCreated = _totalBuffersCreated,
TotalBuffersReused = _totalBuffersReused,
CurrentPoolSize = _currentPoolSize,
MaxPoolSize = MAX_POOL_SIZE,
ReuseRate = _totalBuffersCreated > 0 ? (double)_totalBuffersReused / (_totalBuffersCreated + _totalBuffersReused) * 100 : 0
};
}
}
///
/// Clears the buffer pool and releases all pooled buffers.
/// Useful for memory cleanup during low-usage periods.
///
public void ClearPool()
{
if (_disposed)
{
return;
}
lock (_lockObject)
{
int buffersReleased = 0;
while (_bufferPool.TryDequeue(out _))
{
buffersReleased++;
}
_currentPoolSize = 0;
}
}
///
/// Validates buffer parameters for safety checks.
///
/// The buffer to validate
/// The offset within the buffer
/// The length of data
/// True if parameters are valid, false otherwise
public static bool ValidateBufferParameters(byte[] buffer, int offset, int length)
{
if (buffer == null)
{
return false;
}
if (offset < 0 || offset >= buffer.Length)
{
return false;
}
if (length < 0 || offset + length > buffer.Length)
{
return false;
}
return true;
}
///
/// Releases all resources used by the DynamicBufferManager.
///
public void Dispose()
{
if (!_disposed)
{
ClearPool();
_disposed = true;
}
}
}
///
/// Statistics about buffer manager performance and usage.
///
public class BufferManagerStats
{
///
/// Total number of new buffers created.
///
public int TotalBuffersCreated { get; set; }
///
/// Total number of buffers reused from the pool.
///
public int TotalBuffersReused { get; set; }
///
/// Current number of buffers in the pool.
///
public int CurrentPoolSize { get; set; }
///
/// Maximum allowed pool size.
///
public int MaxPoolSize { get; set; }
///
/// Buffer reuse rate as a percentage.
///
public double ReuseRate { get; set; }
///
/// Returns a formatted string representation of the statistics.
///
public override string ToString()
{
return $"BufferManager Stats: Created={TotalBuffersCreated}, Reused={TotalBuffersReused}, " +
$"PoolSize={CurrentPoolSize}/{MaxPoolSize}, ReuseRate={ReuseRate:F1}%";
}
}
}