コード例 #1
0
        /// <summary>
        /// This method is the meat of the lock-free aggregation logic.
        /// </summary>
        /// <param name="metricValue">Already filtered and conveted value to be tracked.
        ///     We know that the value is not Double.NaN and not null and it passed trought any filters.</param>
        private void TrackFilteredConvertedValue(TBufferedValue metricValue)
        {
            // Get reference to the current buffer:
            MetricValuesBufferBase <TBufferedValue> buffer = _metricValuesBuffer;

            // Get the index at which to store metricValue into the buffer:
            int index = buffer.IncWriteIndex();

            // Check to see whether we are past the end of the buffer.
            // If we are, it means that some *other* thread hit exactly the end (wrote the last value that fits into the buffer) and is currently flushing.
            // If we are, we will spin and wait.
            if (index >= buffer.Capacity)
            {
#if DEBUG
                int startMillis = Environment.TickCount;
#endif
                var spinWait = new SpinWait();

                // It could be that the thread that was flushing is done and has updated the buffer pointer.
                // We refresh our local reference and see if we now have a valid index into the buffer.
                buffer = _metricValuesBuffer;
                index  = buffer.IncWriteIndex();

                while (index >= buffer.Capacity)
                {
                    // Still not valid index into the buffer. Spin and try again.
                    spinWait.SpinOnce();
#if DEBUG
                    unchecked
                    {
                        Interlocked.Increment(ref s_countBufferWaitSpinCycles);
                    }
#endif
                    // In tests (including stress tests) we always finished wating before 100 cycles. However, this is a protection
                    // against en extreme case on a slow machine. We will back off and sleep for a few millisecs to give th emachine
                    // a chance to finisah current tasks.
                    if (spinWait.Count % 100 == 0)
                    {
                        Task.Delay(10).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult();
                    }

                    // Check to see whether the thread that was flushing is done and has updated the buffer pointer.
                    // We refresh our local reference and see if we now have a valid index into the buffer.
                    buffer = _metricValuesBuffer;
                    index  = buffer.IncWriteIndex();
                }
#if DEBUG
                unchecked
                {
                    int periodMillis      = Environment.TickCount - startMillis;
                    int currentSpinMillis = s_timeBufferWaitSpinMillis;
                    int prevSpinMillis    = Interlocked.CompareExchange(ref s_timeBufferWaitSpinMillis, currentSpinMillis + periodMillis, currentSpinMillis);
                    while (prevSpinMillis != currentSpinMillis)
                    {
                        currentSpinMillis = s_timeBufferWaitSpinMillis;
                        prevSpinMillis    = Interlocked.CompareExchange(ref s_timeBufferWaitSpinMillis, currentSpinMillis + periodMillis, currentSpinMillis);
                    }

                    Interlocked.Increment(ref s_countBufferWaitSpinEvents);
                }
#endif
            }

            // Ok, so now we know that (0 <= index = buffer.Capacity). Write the value to the buffer:
            buffer.WriteValue(index, metricValue);

            // If this was the last value that fits into the buffer, we must flush the buffer:
            if (index == buffer.Capacity - 1)
            {
                // Before we begin flushing (which is can take time), we update the _metricValuesBuffer to a fresh buffer that is ready to take values.
                // That way threads do notneed to spin and wait until we flush and can begin writing values.

                // We try to recycle a previous buffer to lower stress on GC and to lower Gen-2 heap fragmentation.
                // The lifetime of an buffer can easily be a minute or so and then it can get into Gen-2 GC heap.
                // If we then, keep throwing such buffers away we can fragment the Gen-2 heap. To avoid this we employ
                // a simple form of best-effort object pooling.

                // Get buffer from pool and reset the pool:
                MetricValuesBufferBase <TBufferedValue> newBufer = Interlocked.Exchange(ref _metricValuesBufferRecycle, null);

                if (newBufer != null)
                {
                    // If we were succesful in getting a recycled buffer from the pool, we will try to use it as the new buffer.
                    // If we successfully the the recycled buffer to be the new buffer, we will reset it to prepare for data.
                    // Otherwise we will just throw it away.

                    MetricValuesBufferBase <TBufferedValue> prevBuffer = Interlocked.CompareExchange(ref _metricValuesBuffer, newBufer, buffer);
                    if (prevBuffer == buffer)
                    {
                        newBufer.ResetIndices();
                    }
                }
                else
                {
                    // If we were succesful in getting a recycled buffer from the pool, we will create a new one.

                    newBufer = InvokeMetricValuesBufferFactory();
                    Interlocked.CompareExchange(ref _metricValuesBuffer, newBufer, buffer);
                }

                // Ok, now we have either set a new buffer that is ready to be used, or we have determined using CompareExchange
                // that another thread set a new buffer and we do not need to do it here.

                // Now we can actually flush the buffer:

                UpdateAggregate(buffer);

                // The buffer is now flushed. If the slot for the best-effor object pooling is free, use it:
                Interlocked.CompareExchange(ref _metricValuesBufferRecycle, buffer, null);
            }
        }