예제 #1
0
        /*
         * //----------------------------------------------------------------------------
         * // 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();
            }
        }
예제 #2
0
        /// <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);
            }
        }
예제 #3
0
 internal static void SignalFrameForCleanup(MlosProxy.FrameHeader frame, int frameLength)
 {
     frame.Length.Store(-frameLength);
 }
예제 #4
0
 internal static void SignalFrameIsReady(
     MlosProxy.FrameHeader frame,
     int frameLength)
 {
     frame.Length.Store(frameLength);
 }
예제 #5
0
        /// <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);
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        /// <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;
            }
        }