/// <summary> /// Release taken memory stream (set it back to <see cref="_cachedMemoryReadStream"/>) /// </summary> /// <param name="bufferingReadStream">Buffered stream to release</param> private void ReleaseMemoryReadStream(RegionBinaryReader bufferingReadStream) { Debug.Assert(bufferingReadStream != null); Debug.Assert(Monitor.IsEntered(_readLock)); Debug.Assert(_cachedMemoryReadStream == null); if (bufferingReadStream.BaseStream.InnerStream.Length <= _maxCachedMemoryReadStreamSize) { if (bufferingReadStream.BaseStream.InnerStream.Capacity > _maxCachedMemoryReadStreamSize) { bufferingReadStream.BaseStream.SetOriginLength(0, -1); bufferingReadStream.BaseStream.InnerStream.SetLength(0); bufferingReadStream.BaseStream.InnerStream.Capacity = _maxCachedMemoryReadStreamSize; } _cachedMemoryReadStream = bufferingReadStream; } }
/// <summary> /// Release taken memory stream (set it back to <see cref="_cachedMemoryReadStream"/>) /// </summary> /// <param name="bufferingReadStream">Buffered stream to release</param> private void ReleaseMemoryReadStream(RegionBinaryReader bufferingReadStream) { TurboContract.Requires(bufferingReadStream != null, conditionString: "bufferingReadStream != null"); TurboContract.Assert(Monitor.IsEntered(_readLock), conditionString: "Monitor.IsEntered(_readLock)"); TurboContract.Assert(_cachedMemoryReadStream == null, conditionString: "_cachedMemoryReadStream == null"); if (bufferingReadStream.BaseStream.InnerStream.Length <= _maxCachedMemoryReadStreamSize) { if (bufferingReadStream.BaseStream.InnerStream.Capacity > _maxCachedMemoryReadStreamSize) { bufferingReadStream.BaseStream.SetOriginLength(0, -1); bufferingReadStream.BaseStream.InnerStream.SetLength(0); bufferingReadStream.BaseStream.InnerStream.Capacity = _maxCachedMemoryReadStreamSize; } _cachedMemoryReadStream = bufferingReadStream; } }
/// <summary> /// Take or peek item inside readLock /// Steps: /// - Reads item from disk /// - Reads item from disk in exclusive mode (lock on _writeLock) /// - Reads item from writeBuffer (with lock on _writeLock) /// </summary> private bool TryTakeOrPeek(out T item, bool take) { lock (_readLock) { if (_isDisposed) { throw new ObjectDisposedException(this.GetType().Name); } RegionBinaryReader memoryBuffer = GetMemoryReadStream(); Debug.Assert(memoryBuffer.BaseStream.Length == 0); Debug.Assert(memoryBuffer.BaseStream.InnerStream.Length == 0); try { if (TryTakeOrPeekItemFromDisk(out item, memoryBuffer, take)) { return(true); } // Should enter write lock to observe fully saved items lock (_writeLock) { // Retry read from disk if (TryTakeOrPeekItemFromDisk(out item, memoryBuffer, take)) { return(true); } // Now attempt to read from write buffer if (_writeBuffer != null && TryTakeOrPeekFromWriteBuffer(out item, take)) { return(true); } } } finally { ReleaseMemoryReadStream(memoryBuffer); } return(false); } }
/// <summary> /// Read signle item from disk and deserialize it /// </summary> /// <param name="item">Taken item</param> /// <param name="buffer">Buffer</param> /// <param name="take">True = take, False = peek</param> /// <returns>Success or not</returns> private bool TryTakeOrPeekItemFromDisk(out T item, RegionBinaryReader buffer, bool take) { Debug.Assert(buffer != null); Debug.Assert(Monitor.IsEntered(_readLock)); buffer.BaseStream.SetOriginLength(0, -1); int itemSize = 0; if (!TryTakeOrPeekSingleItemBytesFromDisk(buffer.BaseStream.InnerStream, out itemSize, take)) // Read from disk { item = default(T); return(false); } buffer.BaseStream.SetOriginLength(ItemHeaderSize, itemSize); Debug.Assert(buffer.BaseStream.Length == itemSize); item = _serializer.Deserialize(buffer); // Deserialize return(true); }
// ============= MemoryReadStream ================= /// <summary> /// Gets cached memory stream to read /// </summary> /// <returns>BinaryReader with stream for in-memory reading</returns> private RegionBinaryReader GetMemoryReadStream(int itemCount = 1) { Debug.Assert(Monitor.IsEntered(_readLock)); Debug.Assert(itemCount > 0); RegionBinaryReader result = _cachedMemoryReadStream; _cachedMemoryReadStream = null; if (result == null) { int expectedItemSize = _serializer.ExpectedSizeInBytes; if (expectedItemSize <= 0) { expectedItemSize = InitialCacheSizePerItem; } result = new RegionBinaryReader(Math.Min(MaxCachedMemoryStreamSize, Math.Max(0, itemCount * (expectedItemSize + ItemHeaderSize)))); // Max used to protect from overflow } else { result.BaseStream.SetOriginLength(0, -1); result.BaseStream.SetLength(0); } return(result); }
/// <summary> /// Take or peek item inside readLock through read buffer. Also populate readBuffer with items /// Steps: /// - Reads item from readBuffer; /// - Reads items from disk /// - Reads items from disk in exclusive mode (lock on _writeLock) /// - Reads items from writeBuffer (with lock on _writeLock) /// </summary> private bool TryTakeOrPeekThroughReadBuffer(out T item, bool take) { Debug.Assert(_readBuffer != null); Debug.Assert(_maxReadBufferSize > 0); lock (_readLock) { if (_isDisposed) { throw new ObjectDisposedException(this.GetType().Name); } // retry read from buffer if (TryTakeOrPeekFromReadBuffer(out item, take)) { return(true); } // Read buffer is empty => should read from disk RegionBinaryReader memoryBuffer = GetMemoryReadStream(); Debug.Assert(memoryBuffer.BaseStream.Length == 0); Debug.Assert(memoryBuffer.BaseStream.InnerStream.Length == 0); try { int itemTransfered = 0; T tmpItem = default(T); while (itemTransfered < _maxReadBufferSize && TryTakeOrPeekItemFromDisk(out tmpItem, memoryBuffer, take: true)) // take = true as we transfer items to buffer { if (itemTransfered == 0) { item = tmpItem; } if (itemTransfered > 0 || !take) // First item should always be ours { _readBuffer.Enqueue(tmpItem); } itemTransfered++; } if (itemTransfered < _maxReadBufferSize) { // Should enter write lock to observe fully saved items lock (_writeLock) { // Retry read from disk while (itemTransfered < _maxReadBufferSize && TryTakeOrPeekItemFromDisk(out tmpItem, memoryBuffer, take: true)) // take = true as we transfer items to buffer { if (itemTransfered == 0) { item = tmpItem; } if (itemTransfered > 0 || !take) // First item should always be ours { _readBuffer.Enqueue(tmpItem); } itemTransfered++; } // attempt to read from write buffer if (itemTransfered < _maxReadBufferSize && _writeBuffer != null) { while (itemTransfered < _maxReadBufferSize && TryTakeOrPeekFromWriteBuffer(out tmpItem, take: true)) // take = true as we transfer items to buffer { if (itemTransfered == 0) { item = tmpItem; } if (itemTransfered > 0 || !take) // First item should always be ours { _readBuffer.Enqueue(tmpItem); } itemTransfered++; } } } } return(itemTransfered > 0); } finally { ReleaseMemoryReadStream(memoryBuffer); } } }
/// <summary> /// NonPersistentDiskQueueSegment constructor /// </summary> /// <param name="segmentNumber">Segment number</param> /// <param name="fileName">Full file name for the segment</param> /// <param name="serializer">Items serializing/deserializing logic</param> /// <param name="capacity">Maximum number of stored items inside the segement (overall capacity)</param> /// <param name="writeBufferSize">Determines the number of items, that are stored in memory before save them to disk (-1 - set to default value, 0 - disable write buffer)</param> /// <param name="cachedMemoryWriteStreamSize">Maximum size of the cached byte stream that used to serialize items in memory (-1 - set to default value, 0 - disable byte stream caching)</param> /// <param name="readBufferSize">Determines the number of items, that are stored in memory for read purposes (-1 - set to default value, 0 - disable read buffer)</param> /// <param name="cachedMemoryReadStreamSize">Maximum size of the cached byte stream that used to deserialize items in memory (-1 - set to default value, 0 - disable byte stream caching)</param> public NonPersistentDiskQueueSegment(long segmentNumber, string fileName, IDiskQueueItemSerializer <T> serializer, int capacity, int writeBufferSize, int cachedMemoryWriteStreamSize, int readBufferSize, int cachedMemoryReadStreamSize) : base(segmentNumber, capacity, 0, 0) { if (string.IsNullOrEmpty(fileName)) { throw new ArgumentNullException(nameof(fileName)); } if (serializer == null) { throw new ArgumentNullException(nameof(serializer)); } if (File.Exists(fileName)) { throw new ArgumentException($"Can't create NonPersistentDiskQueueSegment on existing file '{fileName}'", nameof(fileName)); } _fileName = fileName; _serializer = serializer; try { _writeStream = new FileStream(_fileName, FileMode.CreateNew, FileAccess.Write, FileShare.Read); _readStream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); _writeLock = new object(); _readLock = new object(); _writeBufferSize = 0; _maxWriteBufferSize = writeBufferSize >= 0 ? writeBufferSize : DefaultWriteBufferSize; if (writeBufferSize < 0 && serializer.ExpectedSizeInBytes > 0) { _maxWriteBufferSize = checked (Math.Max(DefaultWriteBufferSize, 4096 / Math.Min(serializer.ExpectedSizeInBytes + ItemHeaderSize, MaxChacheSizePerItem))); } if (_maxWriteBufferSize > 0) { _writeBuffer = new ConcurrentQueue <T>(); } _cachedMemoryWriteStream = null; _maxCachedMemoryWriteStreamSize = cachedMemoryWriteStreamSize; if (cachedMemoryWriteStreamSize < 0) { checked { int expectedItemSize = MaxChacheSizePerItem; if (serializer.ExpectedSizeInBytes > 0) { expectedItemSize = Math.Min(serializer.ExpectedSizeInBytes + ItemHeaderSize, MaxChacheSizePerItem); } _maxCachedMemoryWriteStreamSize = Math.Min(MaxCachedMemoryStreamSize, (_maxWriteBufferSize + 1) * expectedItemSize); } } _maxReadBufferSize = readBufferSize >= 0 ? readBufferSize : Math.Max(_writeBufferSize / 2, DefaultReadBufferSize); if (_maxReadBufferSize > 0) { _readBuffer = new ConcurrentQueue <T>(); } _cachedMemoryReadStream = null; _maxCachedMemoryReadStreamSize = cachedMemoryReadStreamSize; if (cachedMemoryReadStreamSize < 0) { checked { int expectedItemSize = MaxChacheSizePerItem; if (serializer.ExpectedSizeInBytes > 0) { expectedItemSize = Math.Min(serializer.ExpectedSizeInBytes + ItemHeaderSize, MaxChacheSizePerItem); } _maxCachedMemoryReadStreamSize = Math.Min(MaxCachedMemoryStreamSize, expectedItemSize); } } // Prepare file header WriteSegmentHeader(_writeStream, Number, Capacity); _readStream.Seek(0, SeekOrigin.End); // Offset readStream after the header _isDisposed = false; Debug.Assert(_maxWriteBufferSize >= 0); Debug.Assert(_maxCachedMemoryWriteStreamSize >= 0); Debug.Assert(_maxReadBufferSize >= 0); Debug.Assert(_maxCachedMemoryReadStreamSize >= 0); Debug.Assert(_writeStream.Position == _readStream.Position); } catch { // Should close streams to make created files available to delete if (_writeStream != null) { _writeStream.Dispose(); } if (_readStream != null) { _readStream.Dispose(); } throw; } }