/// <summary> /// Closes the shared memory queue currently opened. /// </summary> public void Close() { if (mMemoryMappedViewAccessor != null) { if (mQueueHeader != null) { mMemoryMappedViewAccessor.SafeMemoryMappedViewHandle.ReleasePointer(); mQueueHeader = null; } mMemoryMappedViewAccessor.Dispose(); mMemoryMappedViewAccessor = null; } if (mMemoryMappedFile != null) { mMemoryMappedFile.Dispose(); mMemoryMappedFile = null; } mQueueHeader = null; mFirstBlockInMemory = null; mFirstBlockUnderRead = null; mNumberOfBlocks = 0; mBufferSize = 0; mBlockSize = 0; mInitialized = false; }
/// <summary> /// Creates a new shared memory queue (only supported on the full .NET framework). /// </summary> /// <param name="name">Name of the shared memory region to create the queue in.</param> /// <param name="bufferSize">Size of a buffer in the shared memory queue (in bytes).</param> /// <param name="numberOfBlocks">Number of blocks the queue should keep.</param> /// <exception cref="NotSupportedException">This method is not supported on the current platform/framework.</exception> /// <remarks> /// This method creates a new queue in a shared memory region specified by the given name and the given size. /// </remarks> public void Create(string name, int bufferSize, int numberOfBlocks) { #if NETFRAMEWORK if (name == null) { throw new ArgumentNullException(nameof(name)); } if (bufferSize < 0) { throw new ArgumentOutOfRangeException(nameof(bufferSize), "The block size must be positive."); } if (numberOfBlocks < 0) { throw new ArgumentOutOfRangeException(nameof(numberOfBlocks), "The number of blocks must be positive."); } // close currently opened queue, if necessary Close(); mNumberOfBlocks = numberOfBlocks; mBufferSize = bufferSize; // ensure that the block sizes are a multiple of the cache line size to avoid false sharing mBlockSize = (sizeof(QueueBlock) + mBufferSize + CacheLineSize - 1) & ~(CacheLineSize - 1); mQueueHeaderSize = (sizeof(QueueHeader) + CacheLineSize - 1) & ~(CacheLineSize - 1); long dataSize = (long)mNumberOfBlocks * mBlockSize; long totalBufferSize = mQueueHeaderSize + dataSize; // create the shared memory region string queueName = $"{name} - Shared Memory"; mMemoryMappedFile = MemoryMappedFile.CreateNew( queueName, totalBufferSize, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, sMemoryMappedFileSecurity, HandleInheritability.None); mMemoryMappedViewAccessor = mMemoryMappedFile.CreateViewAccessor(); // get pointer to the buffer byte *ptr = null; mMemoryMappedViewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); mQueueHeader = (QueueHeader *)ptr; mFirstBlockInMemory = (QueueBlock *)((byte *)mQueueHeader + mQueueHeaderSize); // init the queue InitQueue(); // the queue is initialized now mFirstBlockUnderRead = null; mInitialized = true; #else throw new NotSupportedException("The Created() method is only supported on the full .NET framework."); #endif }
/// <summary> /// Pushes a sequence of blocks onto the top of the 'used block stack'. /// </summary> /// <param name="firstBlock">First block to push onto the 'used block stack'.</param> /// <param name="lastBlock">Last block to push onto the 'used block stack'.</param> /// <param name="overflowCount">Number of lost single blocks or block sequences since the last successful transferred block.</param> private void PushUsedBlocks(QueueBlock *firstBlock, QueueBlock *lastBlock, int overflowCount) { lastBlock->OverflowCount = overflowCount; // last block is the first block of the pushed sequence int firstBlockIndex = (int)(((byte *)firstBlock - (byte *)mFirstBlockInMemory) / mBlockSize); while (true) { int firstIndex = lastBlock->NextIndex = mQueueHeader->UsedStackHeaderIndex; if (Interlocked.CompareExchange(ref mQueueHeader->UsedStackHeaderIndex, firstBlockIndex, firstIndex) == firstIndex) { break; } } }
/// <summary> /// Opens an existing queue in the shared memory region with the specified name. /// </summary> /// <param name="name">Name of the shared memory region to open.</param> public void Open(string name) { // close currently opened queue, if necessary Close(); try { // open the shared memory region the queue resides in string queueName = $"{name} - Shared Memory"; mMemoryMappedFile = MemoryMappedFile.OpenExisting(queueName, MemoryMappedFileRights.ReadWrite); mMemoryMappedViewAccessor = mMemoryMappedFile.CreateViewAccessor(); // get pointer to the buffer in shared memory byte *ptr = null; mMemoryMappedViewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); mQueueHeader = (QueueHeader *)ptr; // check the queue's signature // (for an explanation why 'ALVA' see InitQueue() if (mQueueHeader->Signature[0] != 'A' || mQueueHeader->Signature[1] != 'L' || mQueueHeader->Signature[2] != 'V' || mQueueHeader->Signature[3] != 'A') { throw new InvalidDataException("Shared region does not start with the magic word 'ALVA'."); } // read administrative information from the queue's header mNumberOfBlocks = mQueueHeader->NumberOfBlocks; mBufferSize = mQueueHeader->BufferSize; mBlockSize = mQueueHeader->BlockSize; mQueueHeaderSize = (sizeof(QueueHeader) + CacheLineSize - 1) & ~(CacheLineSize - 1); mFirstBlockInMemory = (QueueBlock *)((byte *)mQueueHeader + mQueueHeaderSize); // the queue is initialized now! mFirstBlockUnderRead = null; mInitialized = true; } catch (Exception) { Close(); throw; } }
/// <summary> /// Pushes a block onto the top of the 'used block stack'. /// </summary> /// <param name="block">Block to push onto the 'used block stack'.</param> /// <param name="overflowCount">Number of lost single blocks or block sequences since the last successful transferred block.</param> private void PushUsedBlock(QueueBlock *block, int overflowCount) { Debug.Assert(block->MagicNumber == QueueBlock.MagicNumberValue); Debug.Assert(block->NextIndex == -1); block->OverflowCount = overflowCount; Debug.Assert(((byte *)block - (byte *)mFirstBlockInMemory) % mBlockSize == 0); int blockIndex = (int)(((byte *)block - (byte *)mFirstBlockInMemory) / mBlockSize); Debug.Assert(blockIndex < mNumberOfBlocks); while (true) { int firstIndex = block->NextIndex = mQueueHeader->UsedStackHeaderIndex; if (Interlocked.CompareExchange(ref mQueueHeader->UsedStackHeaderIndex, blockIndex, firstIndex) == firstIndex) { break; } } }
/// <summary> /// Begins reading a new block. /// </summary> /// <param name="validSize">Receives the number of valid bytes in the block.</param> /// <param name="overflowCount">Receives the number of lost single blocks or block sequences since the last reading attempt.</param> /// <param name="blocksFollowingInSequence"> /// Receives <c>true</c>, if there are more blocks in the queue that belong to the block; /// otherwise false. /// </param> /// <returns> /// Pointer to the data buffer within the retrieved block; /// null if no block is available for reading. /// </returns> /// <exception cref="InvalidOperationException">The queue was not initialized using <see cref="Create"/> or <see cref="Open"/>.</exception> public void *BeginReading(out int validSize, out int overflowCount, out bool blocksFollowingInSequence) { // abort, if the queue is not initialized if (!mInitialized) { throw new InvalidOperationException("Queue is not initialized, call Create() or Open() to initialize it."); } if (mFirstBlockUnderRead != null) { // there are some blocks to read left from the last flush // => take one of these blocks // prepare stuff to return validSize = mFirstBlockUnderRead->DataSize; overflowCount = mFirstBlockUnderRead->OverflowCount; var block = mFirstBlockUnderRead; var buffer = (void *)((byte)block + sizeof(QueueBlock)); // proceed with the next block if (mFirstBlockUnderRead->NextIndex >= 0) { Debug.Assert(mFirstBlockUnderRead->NextIndex < mNumberOfBlocks); mFirstBlockUnderRead = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)mFirstBlockUnderRead->NextIndex * mBlockSize); Debug.Assert(mFirstBlockUnderRead->MagicNumber == QueueBlock.MagicNumberValue); } else { mFirstBlockUnderRead = null; } blocksFollowingInSequence = mFirstBlockUnderRead != null; Debug.Assert(((long)buffer & 15) == 0); block->NextIndex = -1; return(buffer); } // there are no blocks to read left from the last flush // => try to flush the queue, re-initialize the buffer and proceed... while (true) { int blockIndex = mQueueHeader->UsedStackHeaderIndex; if (blockIndex < 0) { // no block on the 'used block stack' validSize = 0; overflowCount = 0; blocksFollowingInSequence = false; return(null); } // flush the 'used block stack' Debug.Assert(blockIndex < mNumberOfBlocks); if (Interlocked.CompareExchange(ref mQueueHeader->UsedStackHeaderIndex, -1, blockIndex) != blockIndex) { continue; } // reverse the sequence of blocks to restore the original order var block = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)blockIndex * mBlockSize); int currentBlockIndex = blockIndex; int previousIndex = -1; while (true) { Debug.Assert(block->MagicNumber == QueueBlock.MagicNumberValue); Debug.Assert(block->NextIndex < 0 || block->NextIndex < mNumberOfBlocks); int nextIndex = block->NextIndex; block->NextIndex = previousIndex; previousIndex = currentBlockIndex; currentBlockIndex = nextIndex; if (currentBlockIndex < 0) { break; } block = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)currentBlockIndex * mBlockSize); } Debug.Assert(previousIndex < mNumberOfBlocks); mFirstBlockUnderRead = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)previousIndex * mBlockSize); validSize = mFirstBlockUnderRead->DataSize; overflowCount = mFirstBlockUnderRead->OverflowCount; void *buffer = (byte *)mFirstBlockUnderRead + sizeof(QueueBlock); // proceed with the next block if (mFirstBlockUnderRead->NextIndex >= 0) { Debug.Assert(mFirstBlockUnderRead->NextIndex < mNumberOfBlocks); mFirstBlockUnderRead = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)mFirstBlockUnderRead->NextIndex * mBlockSize); } else { mFirstBlockUnderRead = null; } blocksFollowingInSequence = mFirstBlockUnderRead != null; Debug.Assert(((long)buffer & 15) == 0); block->NextIndex = -1; return(buffer); } }