//******************************************************************************************************
// ConcurrentIsolatedQueue.cs - Gbtc
//
// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 11/28/2016 - Steven E. Chisholm
// Generated original version of source code based on IsolatedQueue
//
//******************************************************************************************************
using System;
using System.Threading;
namespace GSF.Collections
{
///
/// Provides a buffer of point data where reads are isolated from writes.
/// This class differs from because writes can be concurrent.
/// Reads on the other hand have to be externally synchronized.
///
public class ConcurrentIsolatedQueue
where T : class
{
private static readonly DynamicObjectPool Pool = new DynamicObjectPool(() => new IsolatedNode(), 10);
class IsolatedNode
{
///
/// The slots to put stuff.
///
public readonly T[] Slots;
///
/// The next node of the linked list.
///
public IsolatedNode NextNode;
///
/// The base index of this node
///
public long PositionIndex;
///
/// Creates a
///
public IsolatedNode()
{
Slots = new T[NodeSize];
}
}
private const int NodeSize = 128;
private const int ShiftBits = 7;
private const int BitMask = 127;
private IsolatedNode m_currentHead;
private IsolatedNode m_currentTail;
private long m_enqueueCount;
private long m_dequeueCount;
///
/// Creates an
///
public ConcurrentIsolatedQueue()
{
m_currentHead = m_currentTail = new IsolatedNode();
m_currentHead.PositionIndex = 0;
}
///
/// The number of elements in the queue.
///
///
/// Note: Due to the nature of concurrency. This is a representative number.
/// and does not mean the exact number of items in the queue unless both Enqueue and Dequeue
/// are not currently processing.
///
public int Count
{
get
{
if (Environment.Is64BitProcess)
{
return Math.Max(0, (int)(m_enqueueCount - m_dequeueCount));
}
return Math.Max(0, (int)(Interlocked.Read(ref m_enqueueCount) - Interlocked.Read(ref m_dequeueCount)));
}
}
///
/// Adds the provided item to the . This method is thread safe.
///
/// cannot be null
public void Enqueue(T item)
{
if (item == null)
throw new ArgumentNullException(nameof(item));
//I must acquire the current head before I increment m_enqueueCount
//This effectively takes a snapshot before the increment operation occurs
//If I waited until after the increment, then it's possible that multiple
//nodes have passed and I'd never be able to find the one needed to
//assign this item.
IsolatedNode currentNode = m_currentHead;
Thread.MemoryBarrier();
long index = Interlocked.Increment(ref m_enqueueCount) - 1;
long positionIndex = index >> ShiftBits;
int arrayIndex = (int)(index & BitMask);
while (true)
{
// ReSharper disable once PossibleNullReferenceException
if (currentNode.PositionIndex == positionIndex)
{
currentNode.Slots[arrayIndex] = item;
return;
}
if (currentNode.NextNode == null)
{
Grow(currentNode);
}
currentNode = currentNode.NextNode;
}
}
private void Grow(IsolatedNode currentNode)
{
//Each thread is in a race to grow the current node. This is to ensure that
//Enqueue will never block.
//The person who wins the race will assign m_current with the node they placed.
//while it's possible that a race condition could assign m_currentHead out of sequence
//eventually it will catch back up and there will be little performance penalty in the mean time.
//Note: There is a very small chance of ConcurrentQueue blocking here. But that's ok.
IsolatedNode nextNode = Pool.Dequeue();
nextNode.PositionIndex = currentNode.PositionIndex + 1;
if (Interlocked.CompareExchange(ref currentNode.NextNode, nextNode, null) == null)
{
m_currentHead = nextNode;
}
else
{
Pool.Enqueue(nextNode);
}
}
///
/// Attempts to dequeue the specified item from the . This method is NOT thread safe.
///
/// an output for the item
///
///
/// During a race condition, the queue might not be completely empty when TryDequeue returns false. Instead this method returns false
/// rather than blocking and waiting on the race condition to satisfy.
///
public bool TryDequeue(out T item)
{
long positionIndex = m_dequeueCount >> ShiftBits;
int arrayIndex = (int)(m_dequeueCount & BitMask);
TryAgain:
if (m_currentTail.PositionIndex == positionIndex)
{
item = m_currentTail.Slots[arrayIndex];
if (item == null)
return false;
m_currentTail.Slots[arrayIndex] = null;
if (Environment.Is64BitProcess)
m_dequeueCount++;
else
Interlocked.Increment(ref m_dequeueCount);
return true;
}
if (m_currentTail.NextNode == null)
{
item = null;
return false;
}
IsolatedNode node = m_currentTail;
m_currentTail = m_currentTail.NextNode;
node.NextNode = null;
Pool.Enqueue(node);
goto TryAgain;
}
///
/// Adds the provided items to the .
///
/// the items to add
/// the offset position
/// the length
public void Enqueue(T[] items, int offset, int length)
{
items.ValidateParameters(offset, length);
for (int x = 0; x < length; x++)
{
Enqueue(items[offset + x]);
}
}
///
/// Dequeues all of the items into the provided array
///
/// where to put the items
/// the starting index
/// the maximum number of times to store
/// the number of items dequeued
public int Dequeue(T[] items, int startingIndex, int length)
{
items.ValidateParameters(startingIndex, length);
for (int x = 0; x < length; x++)
{
T item;
if (TryDequeue(out item))
{
items[x] = item;
}
else
{
return x;
}
}
return length;
}
}
}