/* * //---------------------------------------------------------------------------- * // NAME: SharedCircularBufferChannel::ReaderThreadLoop * // * // PURPOSE: * // Reader loop, receives frames and processes them. * // * // RETURNS: * // * // NOTES: * // * void SharedCircularBufferChannel::ReaderThreadLoop( * CircularBuffer& buffer, * DispatchEntry* dispatchTable, * size_t dispatchEntryCount) * { * // Receiver thread. * // * while (true) * { * if (WaitAndDispatchFrame(buffer, dispatchTable, dispatchEntryCount)) * { * continue; * } * * // The wait has been interrupted, we are done. * // * break; * } * } * * } * } */ /// <inheritdoc/> public void SendMessage <TMessage>(ref TMessage msg) where TMessage : ICodegenType { // Calculate frame size. // int frameLength = FrameHeader.TypeSize + (int)CodegenTypeExtensions.GetSerializedSize(msg); frameLength = Utils.Align(frameLength, sizeof(int)); // Acquire a write region to write the frame. // uint writeOffset = AcquireWriteRegionForFrame(ref frameLength); if (writeOffset == uint.MaxValue) { // The write has been interrupted. // return; } MlosProxy.FrameHeader frame = Frame(writeOffset); // Optimization. Store the frame length with incomplete bit. // frame.Length.Store(frameLength | 1); // Store type index and hash. // frame.CodegenTypeIndex = msg.CodegenTypeIndex(); frame.CodegenTypeHash = msg.CodegenTypeHash(); // Copy the structure to the buffer. // IntPtr payload = Payload(writeOffset); CodegenTypeExtensions.Serialize(msg, payload); // Frame is ready for the reader. // SignalFrameIsReady(frame, frameLength); // If there are readers in the waiting state, we need to notify them. // if (HasReadersInWaitingState()) { ChannelPolicy.NotifyExternalReader(); } }
/// <summary> /// Acquire a region to write the frame. /// </summary> /// <param name="frameLength"></param> /// <returns>Returns an offset to acquired memory region that can hold a full frame.</returns> /// <remarks>The acquired region is contiguous.</remarks> internal uint AcquireWriteRegionForFrame(ref int frameLength) { uint expectedFrameLength = (uint)frameLength; // Acquire writing region in the buffer. // while (true) { // Align frame length to the integer. // Otherwise the next frame might have unaligned offset for Length field. // frameLength = (int)expectedFrameLength; // Acquire region for writes. Function might adjust frame length. // uint writeOffset = AcquireRegionForWrite(ref frameLength); if (writeOffset == uint.MaxValue) { // The reader has terminated, abandon the write. // return(writeOffset); } // We might acquired a circular region, // check if we can store a full frame without overlapping the buffer. // if (writeOffset + frameLength > Size) { // There is not enough space in the buffer to write the full frame. // Create an empty frame to adjust the write offset to the beginning of the buffer. // Retry the whole operation until we write a full frame. // MlosProxy.FrameHeader frame = Frame(writeOffset); frame.CodegenTypeIndex = 0; SignalFrameIsReady(frame, frameLength); continue; } // Acquired a region that we can write a full frame. // return(writeOffset); } }
internal static void SignalFrameForCleanup(MlosProxy.FrameHeader frame, int frameLength) { frame.Length.Store(-frameLength); }
internal static void SignalFrameIsReady( MlosProxy.FrameHeader frame, int frameLength) { frame.Length.Store(frameLength); }
/// <inheritdoc/> public bool WaitAndDispatchFrame(DispatchEntry[] dispatchTable) { TChannelPolicy channelPolicy = default; uint readOffset = WaitForFrame(); if (readOffset == uint.MaxValue) { // Invalid offset, the wait was interrupted. // return(false); } uint dispatchEntryCount = (uint)dispatchTable.Length; // Verify frame and call dispatcher. // MlosProxy.FrameHeader frame = Frame(readOffset); uint codegenTypeIndex = frame.CodegenTypeIndex; ulong codegenTypeHash = frame.CodegenTypeHash; int frameLength = frame.Length.LoadRelaxed(); // Check if this is valid frame or just the link to the beginning of the buffer. // if (codegenTypeIndex != 0 && codegenTypeIndex <= dispatchEntryCount) { // Use hash to check the message. // ulong expectedCodegenTypeHash = dispatchTable[codegenTypeIndex - 1].CodegenTypeHash; bool isMessageValid = ((uint)frameLength < Size) && (expectedCodegenTypeHash == codegenTypeHash); if (isMessageValid) { unsafe { // Call dispatcher only if type hash is correct. // isMessageValid = dispatchTable[codegenTypeIndex - 1].Callback(Payload(readOffset), frameLength); } } if (!isMessageValid) { // Received invalid frame, channel policy decides what how to handle it. // channelPolicy.ReceivedInvalidFrame(); } // Cleanup. Writer requires clean memory buffer (frameLength != 0). // Clear the whole frame except the length. // ClearPayload(readOffset, frameLength); } else { // Just a link frame, clean circular region. // ClearOverlappedPayload(readOffset, frameLength, Size); } // Mark frame that processing is completed (negative length). // SignalFrameForCleanup(frame, frameLength); return(true); }
/// <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> /// Follows free links until we reach read position. /// </summary> /// <remarks> /// While we follow the links, the method is not cleaning the memory. /// The memory is cleared by the reader after processing the frame. /// The whole memory region is clean except locations where negative frame length values are stored /// to signal that the message has been read and the frame is free-able. /// Those locations are always aligned to the size of uint32_t. The current reader continues to spin if it reads negative frame length. /// </remarks> internal void AdvanceFreePosition() { // Create AtomicUInt32 proxies once. // StdTypesProxy.AtomicUInt32 atomicFreePosition = Sync.FreePosition; StdTypesProxy.AtomicUInt32 atomicReadPosition = Sync.ReadPosition; // Move free position and allow the writer to advance. // uint freePosition = atomicFreePosition.Load(); uint readPosition = atomicReadPosition.LoadRelaxed(); if (freePosition == readPosition) { // Free position points to the current read position. // return; } // For diagnostic purposes, following the free links we should get the same distance. // uint distance = readPosition - freePosition; // Follow the free links up to a current read position. // Cleanup is completed when free position is equal to read position. // However by the time this cleanup is completed, // the reader threads might process more frames and advance read position. // while (freePosition != readPosition) { // Load a frame from the beginning of the free region. // However, other writer threads might already advance the free position. // In this case, local free position points to the write region and // we will fail to advance free offset. // uint freeOffset = freePosition % Size; MlosProxy.FrameHeader frame = Frame(freeOffset); int frameLength = frame.Length.Load(); if (frameLength >= 0) { // Frame is currently processed or has been already cleared. // Other writer thread advanced free position, there is no point to check using compare_exchange_weak. // Local free offset is now the write region. // return; } // Advance free position. The frame length is negative. // uint expectedFreePosition = freePosition; uint nextFreePosition = freePosition - (uint)frameLength; if (atomicFreePosition.LoadRelaxed() != expectedFreePosition || atomicFreePosition.CompareExchange(nextFreePosition, expectedFreePosition) != expectedFreePosition) { // Advanced by another writer, local free offset is now the write region. // return; } freePosition = nextFreePosition; // Frame length is negative. // distance += (uint)frameLength; } }