//******************************************************************************************************
// IsolatedQueue.cs - Gbtc
//
// Copyright © 2013, 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:
// ----------------------------------------------------------------------------------------------------
// 1/4/2013 - Steven E. Chisholm
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
namespace GSF.Collections
{
///
/// Provides a buffer of point data where reads are isolated from writes.
/// However, reads must be synchronized with other reads and writes must be synchronized with other writes.
///
///
/// This class is about twice as fast as a ConcurrentQueue. However, it has some decent overhead, so
/// small lists may not make much sense.
///
/// This also uses jagged arrays, and will automatically deallocate memory so is useful for
/// structures that vary in size with time.
///
public class IsolatedQueue
{
///
/// Represents an individual node that allows for items to be added and removed from the
/// queue independently and without locks.
///
class IsolatedNode
{
private readonly int m_lastBlock;
private volatile int m_tail;
private volatile int m_head;
private readonly T[] m_blocks;
///
/// Creates a
///
/// the number of items in each node.
public IsolatedNode(int count)
{
m_tail = 0;
m_head = 0;
m_blocks = new T[count];
m_lastBlock = m_blocks.Length;
}
///
/// Gets if the current node is out of entries.
///
public bool DequeueMustMoveToNextNode
{
get
{
return m_tail == m_lastBlock;
}
}
///
/// Gets if there are items that can be dequeued
///
public bool CanDequeue
{
get
{
return m_head != m_tail;
}
}
///
/// Gets if this list can be enqueued
///
public bool CanEnqueue
{
get
{
return m_head != m_lastBlock;
}
}
///
/// Gets the number of items that can be enqueued.
///
public int AvailableEnqueueLength
{
get
{
return m_lastBlock - m_head;
}
}
///
/// Resets the queue. This operation must be synchronized external
/// from this class with the read and write operations. Therefore it
/// is only recommended to call this once the item has been returned to a
/// buffer pool of some sorts.
///
public void Reset()
{
if (m_tail != m_head)
Array.Clear(m_blocks, m_tail, m_head - m_tail);
m_tail = 0;
m_head = 0;
}
///
/// Adds the following item to the queue. Be sure to check if it is full first.
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Enqueue(T item)
{
m_blocks[m_head] = item;
//No memory barior here since .NET 2.0 ensures that writes will not be reordered.
m_head = m_head + 1;
}
///
/// Adds the following items to the queue. Be sure to check if it is full first.
/// Adjust the length if not the right size.
///
///
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Enqueue(T[] items, int offset, int length)
{
Array.Copy(items, offset, m_blocks, m_head, length);
//No memory barior here since .NET 2.0 ensures that writes will not be reordered.
m_head = m_head + length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Dequeue()
{
T item = m_blocks[m_tail];
m_blocks[m_tail] = default(T);
//No memory barior here since .NET 2.0 ensures that writes will not be reordered.
m_tail = m_tail + 1;
return item;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public int Dequeue(T[] items, ref int startingIndex, ref int length)
{
int count = Math.Min(m_head - m_tail, length);
Array.Copy(m_blocks, m_tail, items, startingIndex, count);
Array.Clear(m_blocks, m_tail, count);
m_tail += count;
startingIndex += count;
length -= count;
return count;
}
}
private readonly ConcurrentQueue m_blocks;
private readonly ConcurrentQueue m_pooledNodes;
private IsolatedNode m_currentHead;
private IsolatedNode m_currentTail;
private readonly int m_unitCount;
private long m_enqueueCount;
private long m_dequeueCount;
///
/// Creates an
///
public IsolatedQueue()
{
m_unitCount = 128;
m_pooledNodes = new ConcurrentQueue();
m_blocks = new ConcurrentQueue();
}
///
/// The number of elements in the queue
///
public long Count
{
get
{
//ToDo: Figure out how to make this value always correct on a 32-bit computer.
return m_enqueueCount - m_dequeueCount;
}
}
///
/// Addes the provided items to the .
///
/// the items to add
/// the offset postion
/// the length
[MethodImpl(MethodImplOptions.NoInlining)]
public void Enqueue(T[] items, int offset, int length)
{
items.ValidateParameters(offset, length);
while (true)
{
//If the header is empty. Get a new one.
if (m_currentHead == null || !m_currentHead.CanEnqueue)
{
m_currentHead = GetNode();
m_currentHead.Reset();
Thread.MemoryBarrier();
m_blocks.Enqueue(m_currentHead);
m_enqueueCount++;
}
//If the remainder will fit, queue it all.
int enqueueLength = m_currentHead.AvailableEnqueueLength;
if (length <= enqueueLength)
{
m_currentHead.Enqueue(items, offset, length);
m_enqueueCount += length;
return;
}
//Some will not fit. Enqueue what will fit, then repeat.
m_currentHead.Enqueue(items, offset, enqueueLength);
m_enqueueCount += enqueueLength;
offset += enqueueLength;
length -= enqueueLength;
}
}
///
/// Addes the provided item to the .
///
///
[MethodImpl(MethodImplOptions.NoInlining)]
public void Enqueue(T item)
{
if (m_currentHead != null && m_currentHead.CanEnqueue)
{
m_currentHead.Enqueue(item);
m_enqueueCount++;
return;
}
EnqueueSlower(item);
}
[MethodImpl(MethodImplOptions.NoInlining)]
void EnqueueSlower(T item)
{
if (m_currentHead == null || !m_currentHead.CanEnqueue)
{
m_currentHead = GetNode();
m_currentHead.Reset();
Thread.MemoryBarrier();
m_blocks.Enqueue(m_currentHead);
m_enqueueCount++;
}
m_currentHead.Enqueue(item);
}
///
/// Attempts to dequeue the specified item from the
///
/// an output for the item
///
[MethodImpl(MethodImplOptions.NoInlining)]
public bool TryDequeue(out T item)
{
if (m_currentTail != null && m_currentTail.CanDequeue)
{
item = m_currentTail.Dequeue();
m_dequeueCount++;
return true;
}
return TryDequeueSlower(out item);
}
///
/// 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);
int itemCount = 0;
TryAgain:
if (m_currentTail == null)
{
if (!m_blocks.TryDequeue(out m_currentTail))
{
return 0;
}
}
if (m_currentTail.CanDequeue)
{
//Dequeue as many items as possible
itemCount += m_currentTail.Dequeue(items, ref startingIndex, ref length);
//If no more items can fit in parameter: items, then exit
if (length == 0)
{
m_dequeueCount += itemCount;
return itemCount;
}
}
//If tail position of node is not at the end, all of the items in the Isolated Queue have been read.
//Note: because of a race condition, it may not still be empty,
//but cordinating that closely may cause drastically decrease performance.
if (!m_currentTail.DequeueMustMoveToNextNode)
{
m_dequeueCount += itemCount;
return itemCount;
}
//Don't reset the node on return since it is still
//possible for the enqueue thread to be using it.
//Note: If the enqueue thread pulls it off the queue
//immediately, this is ok since it will be coordinated at that point.
ReleaseNode(m_currentTail);
if (!m_blocks.TryDequeue(out m_currentTail))
{
m_dequeueCount += itemCount;
return itemCount;
}
goto TryAgain;
}
[MethodImpl(MethodImplOptions.NoInlining)]
bool TryDequeueSlower(out T item)
{
if (m_currentTail == null)
{
if (!m_blocks.TryDequeue(out m_currentTail))
{
item = default(T);
return false;
}
}
else if (m_currentTail.DequeueMustMoveToNextNode)
{
//Don't reset the node on return since it is still
//possible for the enqueue thread to be using it.
//Note: If the enqueue thread pulls it off the queue
//immediately, this is ok since it will be coordinated at that point.
ReleaseNode(m_currentTail);
if (!m_blocks.TryDequeue(out m_currentTail))
{
item = default(T);
return false;
}
}
if (m_currentTail.CanDequeue)
{
item = m_currentTail.Dequeue();
m_dequeueCount++;
return true;
}
item = default(T);
return false;
}
///
/// Removes a node from the pool. If one does not exist, one is created.
///
///
IsolatedNode GetNode()
{
IsolatedNode item;
if (m_pooledNodes.TryDequeue(out item))
return item;
return new IsolatedNode(m_unitCount);
}
///
/// Addes an item back to the queue.
///
///
void ReleaseNode(IsolatedNode resource)
{
//if the queue has too many node items. just let the old one get garbage collected.
if (m_pooledNodes.Count < 1000)
{
m_pooledNodes.Enqueue(resource);
}
}
}
}