using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; namespace MECF.Framework.Common.Communications.Tcp.Buffer { /// /// A manager to handle buffers for the socket connections. /// /// /// When used in an async call a buffer is pinned. Large numbers of pinned buffers /// cause problem with the GC (in particular it causes heap fragmentation). /// This class maintains a set of large segments and gives clients pieces of these /// segments that they can use for their buffers. The alternative to this would be to /// create many small arrays which it then maintained. This methodology should be slightly /// better than the many small array methodology because in creating only a few very /// large objects it will force these objects to be placed on the LOH. Since the /// objects are on the LOH they are at this time not subject to compacting which would /// require an update of all GC roots as would be the case with lots of smaller arrays /// that were in the normal heap. /// public class SegmentBufferManager : ISegmentBufferManager { private const int TrialsCount = 100; private static SegmentBufferManager _defaultBufferManager; private readonly int _segmentChunks; private readonly int _chunkSize; private readonly int _segmentSize; private readonly bool _allowedToCreateMemory; private readonly ConcurrentStack> _buffers = new ConcurrentStack>(); private readonly List _segments; private readonly object _creatingNewSegmentLock = new object(); public static SegmentBufferManager Default { get { // default to 1024 1kb buffers if people don't want to manage it on their own; if (_defaultBufferManager == null) _defaultBufferManager = new SegmentBufferManager(1024, 1024, 1); return _defaultBufferManager; } } public static void SetDefaultBufferManager(SegmentBufferManager manager) { if (manager == null) throw new ArgumentNullException("manager"); _defaultBufferManager = manager; } public int ChunkSize { get { return _chunkSize; } } public int SegmentsCount { get { return _segments.Count; } } public int SegmentChunksCount { get { return _segmentChunks; } } public int AvailableBuffers { get { return _buffers.Count; } } public int TotalBufferSize { get { return _segments.Count * _segmentSize; } } public SegmentBufferManager(int segmentChunks, int chunkSize) : this(segmentChunks, chunkSize, 1) { } public SegmentBufferManager(int segmentChunks, int chunkSize, int initialSegments) : this(segmentChunks, chunkSize, initialSegments, true) { } /// /// Constructs a new object /// /// The number of chunks to create per segment /// The size of a chunk in bytes /// The initial number of segments to create /// If false when empty and checkout is called an exception will be thrown public SegmentBufferManager(int segmentChunks, int chunkSize, int initialSegments, bool allowedToCreateMemory) { if (segmentChunks <= 0) throw new ArgumentException("segmentChunks"); if (chunkSize <= 0) throw new ArgumentException("chunkSize"); if (initialSegments < 0) throw new ArgumentException("initialSegments"); _segmentChunks = segmentChunks; _chunkSize = chunkSize; _segmentSize = _segmentChunks * _chunkSize; _segments = new List(); _allowedToCreateMemory = true; for (int i = 0; i < initialSegments; i++) { CreateNewSegment(true); } _allowedToCreateMemory = allowedToCreateMemory; } private void CreateNewSegment(bool forceCreation) { if (!_allowedToCreateMemory) throw new UnableToCreateMemoryException(); lock (_creatingNewSegmentLock) { if (!forceCreation && _buffers.Count > _segmentChunks / 2) return; var bytes = new byte[_segmentSize]; _segments.Add(bytes); for (int i = 0; i < _segmentChunks; i++) { var chunk = new ArraySegment(bytes, i * _chunkSize, _chunkSize); _buffers.Push(chunk); } } } public ArraySegment BorrowBuffer() { int trial = 0; while (trial < TrialsCount) { ArraySegment result; if (_buffers.TryPop(out result)) return result; CreateNewSegment(false); trial++; } throw new UnableToAllocateBufferException(); } public IEnumerable> BorrowBuffers(int count) { var result = new ArraySegment[count]; var trial = 0; var totalReceived = 0; try { while (trial < TrialsCount) { ArraySegment piece; while (totalReceived < count) { if (!_buffers.TryPop(out piece)) break; result[totalReceived] = piece; ++totalReceived; } if (totalReceived == count) return result; CreateNewSegment(false); trial++; } throw new UnableToAllocateBufferException(); } catch { if (totalReceived > 0) ReturnBuffers(result.Take(totalReceived)); throw; } } public void ReturnBuffer(ArraySegment buffer) { if (ValidateBuffer(buffer)) { _buffers.Push(buffer); } } public void ReturnBuffers(IEnumerable> buffers) { if (buffers == null) throw new ArgumentNullException("buffers"); foreach (var buf in buffers) { if (ValidateBuffer(buf)) { _buffers.Push(buf); } } } public void ReturnBuffers(params ArraySegment[] buffers) { if (buffers == null) throw new ArgumentNullException("buffers"); foreach (var buf in buffers) { if (ValidateBuffer(buf)) { _buffers.Push(buf); } } } private bool ValidateBuffer(ArraySegment buffer) { if (buffer.Array == null || buffer.Count == 0 || buffer.Array.Length < buffer.Offset + buffer.Count) return false; if (buffer.Count != _chunkSize) return false; return true; } } }