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}%"; } } }