/// <summary> /// Wait for the frame become available. /// </summary> /// <returns>Returns an offset to the frame buffer.</returns> /// <remarks> /// Reader function. /// If the wait has been aborted, it returns uint32_t::max. /// </remarks> internal uint WaitForFrame() { uint readPosition; TChannelSpinPolicy channelSpinPolicy = default; // Create AtomicUInt32 proxy once. // StdTypesProxy.AtomicUInt32 atomicReadPosition = Sync.ReadPosition; StdTypesProxy.AtomicUInt32 atomicReaderInWaitingStateCount = Sync.ReaderInWaitingStateCount; StdTypesProxy.AtomicBool atomicTerminateChannel = Sync.TerminateChannel; MlosProxy.FrameHeader frame = default; uint shouldWait = 0; // int spinIndex = 0; while (true) { // Wait for the frame become available. // Spin on current frame (ReadOffset). // readPosition = atomicReadPosition.Load(); uint readOffset = readPosition % Size; frame = Frame(readOffset); int frameLength = frame.Length.Load(); if (frameLength > 0) { // Writer had updated the length. // Frame is ready and available for the reader. // Advance ReadIndex to end of the frame, and allow other reads to process next frame. // uint expectedReadPosition = readPosition; uint nextReadPosition = readPosition + (uint)(frameLength & (~1)); if (atomicReadPosition.LoadRelaxed() != expectedReadPosition || atomicReadPosition.CompareExchange(nextReadPosition, expectedReadPosition) != expectedReadPosition) { // Other reader advanced ReadPosition therefore it will process the frame. // channelSpinPolicy.FailedToAcquireReadRegion(); continue; } // Current reader owns the frame. Wait untill the reader completes the write. // while ((frameLength & 1) == 1) { channelSpinPolicy.WaitForFrameCompletion(); frameLength = frame.Length.Load(); } break; } channelSpinPolicy.WaitForNewFrame(); // if ((++spinIndex & 0xff) == 0) { // No frame yet, spin if the channel is stil active. // if (atomicTerminateChannel.LoadRelaxed()) { return(uint.MaxValue); } } // Wait for the synchronization primitive. // if (shouldWait != 0) { ChannelPolicy.WaitForFrame(); atomicReaderInWaitingStateCount.FetchSub(shouldWait); shouldWait = 0; } else { // Before reader enters wait state it will increase ReaderInWaitingState count and then check if are there any messages in the channel. // shouldWait = 1; atomicReaderInWaitingStateCount.FetchAdd(shouldWait); } // If (frameLength < 0) there is active cleaning up on this frame by the writer. // The read offset had already advanced, so retry. // } // Reset InWaitingState counter. // atomicReaderInWaitingStateCount.FetchSub(shouldWait); // Reader acquired read region, frame is ready. // return(readPosition % Size); }
/// <summary> /// Function returns an offset to the acquired region that can we safely use for write operation. /// </summary> /// <param name="frameLength"></param> /// <returns> /// An offset to the acquired region. /// If reader has been aborted, return uint32_t::max. /// </returns> /// <remarks> /// There is no guarantee that the acquired region is contiguous (it might be overlapping). /// However it ensures that the next write offset will not be greater than the buffer margin, /// so the next writer can write an empty FrameHeader. /// </remarks> private uint AcquireRegionForWrite(ref int frameLength) { // Create AtomicUInt32 proxies once. // StdTypesProxy.AtomicUInt32 atomicFreePosition = Sync.FreePosition; StdTypesProxy.AtomicUInt32 atomicWritePosition = Sync.WritePosition; StdTypesProxy.AtomicBool atomicTerminateChannel = Sync.TerminateChannel; TChannelSpinPolicy channelSpinPolicy = default; while (true) { // FreePosition is expected to be less than WritePosition unless WritePosition has overflow. // To preserve this order, we read FreePosition first. Otherwise, it might advance if we had read WritePosition first. // uint freePosition = atomicFreePosition.Load(); uint writePosition = atomicWritePosition.LoadRelaxed(); // Check if there is enough bytes to write frame (full frame or a link). // Always keep the distance to free offset, at least a size of FrameHeader. // If WritePosition overflown, then (writePosition - freePosition) is still positive value. // if (!(writePosition - freePosition < margin - frameLength)) { // Not enough free space to acquire region. // // Check if the channel is still active. // if (atomicTerminateChannel.LoadRelaxed()) { return(uint.MaxValue); } // Advance free position to reclaim memory for writes. // Retry after that, as another writer might acquire just the released region. // AdvanceFreePosition(); continue; } // If the end of the requested frame is located in the buffer margin, extend the acquired region. // uint frameLengthAdj = 0; // NextWritePosition is at the frame end. // NextWriteOffset (calculated from NextWritePosition) must be aligned to sizeof(uint32_t) // therefore frame length must be also aligned. // uint nextWritePosition = (uint)(writePosition + frameLength); // Ensure that after a full frame there is enough space for the next frame header. // Otherwise, we will not be able to store next frame, because the frame header will not fit in the buffer. // uint nextWriteOffset = nextWritePosition % Size; if (nextWriteOffset >= margin) { // Update frameLength, as we acquired more than requested. // frameLengthAdj = Size - nextWriteOffset; nextWritePosition += frameLengthAdj; } uint expectedWritePosition = writePosition; if (atomicWritePosition.LoadRelaxed() != expectedWritePosition || atomicWritePosition.CompareExchange(nextWritePosition, expectedWritePosition) != expectedWritePosition) { // Failed to advance write offset, another writer acquired this region. // channelSpinPolicy.FailedToAcquireWriteRegion(); continue; } frameLength += (int)frameLengthAdj; // The region should be empty except for free links. // Frame links are always stored in offset aligned to sizeof int. // uint writeOffset = writePosition % Size; return(writeOffset); } }