// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #pragma warning disable 0420 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // // // A lock-free, concurrent stack primitive, and its associated debugger view type. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.ConstrainedExecution; using System.Runtime.Serialization; using System.Security; using System.Threading; namespace System.Collections.Concurrent { // A stack that uses CAS operations internally to maintain thread-safety in a lock-free // manner. Attempting to push or pop concurrently from the stack will not trigger waiting, // although some optimistic concurrency and retry is used, possibly leading to lack of // fairness and/or livelock. The stack uses spinning and backoff to add some randomization, // in hopes of statistically decreasing the possibility of livelock. // // Note that we currently allocate a new node on every push. This avoids having to worry // about potential ABA issues, since the CLR GC ensures that a memory address cannot be // reused before all references to it have died. /// /// Represents a thread-safe last-in, first-out collection of objects. /// /// Specifies the type of elements in the stack. /// /// All public and protected members of are thread-safe and may be used /// concurrently from multiple threads. /// [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView<>))] internal class ConcurrentStack : IProducerConsumerCollection, IReadOnlyCollection { /// /// A simple (internal) node type used to store elements of concurrent stacks and queues. /// private class Node { internal readonly T m_value; // Value of the node. internal Node m_next; // Next pointer. /// /// Constructs a new node with the specified value and no next node. /// /// The value of the node. internal Node(T value) { m_value = value; m_next = null; } } private volatile Node m_head; // The stack is a singly linked list, and only remembers the head. private const int BACKOFF_MAX_YIELDS = 8; // Arbitrary number to cap backoff. /// /// Initializes a new instance of the /// class. /// public ConcurrentStack() { } /// /// Gets the number of elements contained in the . /// /// The number of elements contained in the . /// /// For determining whether the collection contains any items, use of the /// property is recommended rather than retrieving the number of items from the /// property and comparing it to 0. /// public int Count { // Counts the number of entries in the stack. This is an O(n) operation. The answer may be out // of date before returning, but guarantees to return a count that was once valid. Conceptually, // the implementation snaps a copy of the list and then counts the entries, though physically // this is not what actually happens. get { int count = 0; // Just whip through the list and tally up the number of nodes. We rely on the fact that // node next pointers are immutable after being enqueued for the first time, even as // they are being dequeued. If we ever changed this (e.g. to pool nodes somehow), // we'd need to revisit this implementation. for (Node curr = m_head; curr != null; curr = curr.m_next) { count++; //we don't handle overflow, to be consistent with existing generic collection types in CLR } return count; } } /// /// Gets a value indicating whether access to the is /// synchronized with the SyncRoot. /// /// true if access to the is synchronized /// with the SyncRoot; otherwise, false. For , this property always /// returns false. bool ICollection.IsSynchronized { // Gets a value indicating whether access to this collection is synchronized. Always returns // false. The reason is subtle. While access is in face thread safe, it's not the case that // locking on the SyncRoot would have prevented concurrent pushes and pops, as this property // would typically indicate; that's because we internally use CAS operations vs. true locks. get { return false; } } /// /// Gets an object that can be used to synchronize access to the . This property is not supported. /// /// The SyncRoot property is not supported object ICollection.SyncRoot { get { throw new NotSupportedException(Environment.GetResourceString("ConcurrentCollection_SyncRoot_NotSupported")); } } /// /// Copies the elements of the to an , starting at a particular /// index. /// /// The one-dimensional that is the destination of /// the elements copied from the /// . The must /// have zero-based indexing. /// The zero-based index in at which copying /// begins. /// is a null reference (Nothing in /// Visual Basic). /// is less than /// zero. /// /// is multidimensional. -or- /// does not have zero-based indexing. -or- /// is equal to or greater than the length of the /// -or- The number of elements in the source is /// greater than the available space from to the end of the destination /// . -or- The type of the source cannot be cast automatically to the type of the /// destination . /// void ICollection.CopyTo(Array array, int index) { // Validate arguments. if (array == null) { throw new ArgumentNullException(nameof(array)); } // We must be careful not to corrupt the array, so we will first accumulate an // internal list of elements that we will then copy to the array. This requires // some extra allocation, but is necessary since we don't know up front whether // the array is sufficiently large to hold the stack's contents. ((ICollection)ToList()).CopyTo(array, index); } /// /// Copies the elements to an existing one-dimensional , starting at the specified array index. /// /// The one-dimensional that is the destination of /// the elements copied from the /// . The must have zero-based /// indexing. /// The zero-based index in at which copying /// begins. /// is a null reference (Nothing in /// Visual Basic). /// is less than /// zero. /// is equal to or greater than the /// length of the /// -or- The number of elements in the source is greater than the /// available space from to the end of the destination . /// public void CopyTo(T[] array, int index) { if (array == null) { throw new ArgumentNullException(nameof(array)); } // We must be careful not to corrupt the array, so we will first accumulate an // internal list of elements that we will then copy to the array. This requires // some extra allocation, but is necessary since we don't know up front whether // the array is sufficiently large to hold the stack's contents. ToList().CopyTo(array, index); } /// /// Inserts an object at the top of the . /// /// The object to push onto the . The value can be /// a null reference (Nothing in Visual Basic) for reference types. /// public void Push(T item) { // Pushes a node onto the front of the stack thread-safely. Internally, this simply // swaps the current head pointer using a (thread safe) CAS operation to accomplish // lock freedom. If the CAS fails, we add some back off to statistically decrease // contention at the head, and then go back around and retry. Node newNode = new Node(item); newNode.m_next = m_head; if (Interlocked.CompareExchange(ref m_head, newNode, newNode.m_next) == newNode.m_next) { return; } // If we failed, go to the slow path and loop around until we succeed. PushCore(newNode, newNode); } /// /// Push one or many nodes into the stack, if head and tails are equal then push one node to the stack other wise push the list between head /// and tail to the stack /// /// The head pointer to the new list /// The tail pointer to the new list private void PushCore(Node head, Node tail) { SpinWait spin = new SpinWait(); // Keep trying to CAS the exising head with the new node until we succeed. do { spin.SpinOnce(); // Reread the head and link our new node. tail.m_next = m_head; } while (Interlocked.CompareExchange( ref m_head, head, tail.m_next) != tail.m_next); } /// /// Attempts to pop and return the object at the top of the . /// /// /// When this method returns, if the operation was successful, contains the /// object removed. If no object was available to be removed, the value is unspecified. /// /// true if an element was removed and returned from the top of the /// succesfully; otherwise, false. public bool TryPop(out T result) { Node head = m_head; //stack is empty if (head == null) { result = default(T); return false; } if (Interlocked.CompareExchange(ref m_head, head.m_next, head) == head) { result = head.m_value; return true; } // Fall through to the slow path. return TryPopCore(out result); } /// /// Local helper function to Pop an item from the stack, slow path /// /// The popped item /// True if succeeded, false otherwise private bool TryPopCore(out T result) { Node poppedNode; if (TryPopCore(1, out poppedNode) == 1) { result = poppedNode.m_value; return true; } result = default(T); return false; } /// /// Slow path helper for TryPop. This method assumes an initial attempt to pop an element /// has already occurred and failed, so it begins spinning right away. /// /// The number of items to pop. /// /// When this method returns, if the pop succeeded, contains the removed object. If no object was /// available to be removed, the value is unspecified. This parameter is passed uninitialized. /// /// True if an element was removed and returned; otherwise, false. private int TryPopCore(int count, out Node poppedHead) { SpinWait spin = new SpinWait(); // Try to CAS the head with its current next. We stop when we succeed or // when we notice that the stack is empty, whichever comes first. Node head; Node next; int backoff = 1; Random r = new Random(Environment.TickCount & Int32.MaxValue); // avoid the case where TickCount could return Int32.MinValue while (true) { head = m_head; // Is the stack empty? if (head == null) { poppedHead = null; return 0; } next = head; int nodesCount = 1; for (; nodesCount < count && next.m_next != null; nodesCount++) { next = next.m_next; } // Try to swap the new head. If we succeed, break out of the loop. if (Interlocked.CompareExchange(ref m_head, next.m_next, head) == head) { // Return the popped Node. poppedHead = head; return nodesCount; } // We failed to CAS the new head. Spin briefly and retry. for (int i = 0; i < backoff; i++) { spin.SpinOnce(); } backoff = spin.NextSpinWillYield ? r.Next(1, BACKOFF_MAX_YIELDS) : backoff * 2; } } /// /// Copies the items stored in the to a new array. /// /// A new array containing a snapshot of elements copied from the . public T[] ToArray() { return ToList().ToArray(); } /// /// Returns an array containing a snapshot of the list's contents, using /// the target list node as the head of a region in the list. /// /// An array of the list's contents. private List ToList() { List list = new List(); Node curr = m_head; while (curr != null) { list.Add(curr.m_value); curr = curr.m_next; } return list; } /// /// Returns an enumerator that iterates through the . /// /// An enumerator for the . /// /// The enumeration represents a moment-in-time snapshot of the contents /// of the stack. It does not reflect any updates to the collection after /// was called. The enumerator is safe to use /// concurrently with reads from and writes to the stack. /// public IEnumerator GetEnumerator() { // Returns an enumerator for the stack. This effectively takes a snapshot // of the stack's contents at the time of the call, i.e. subsequent modifications // (pushes or pops) will not be reflected in the enumerator's contents. //If we put yield-return here, the iterator will be lazily evaluated. As a result a snapshot of //the stack is not taken when GetEnumerator is initialized but when MoveNext() is first called. //This is inconsistent with existing generic collections. In order to prevent it, we capture the //value of m_head in a buffer and call out to a helper method return GetEnumerator(m_head); } private IEnumerator GetEnumerator(Node head) { Node current = head; while (current != null) { yield return current.m_value; current = current.m_next; } } /// /// Returns an enumerator that iterates through a collection. /// /// An that can be used to iterate through /// the collection. /// /// The enumeration represents a moment-in-time snapshot of the contents of the stack. It does not /// reflect any updates to the collection after /// was called. The enumerator is safe to use concurrently with reads /// from and writes to the stack. /// IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } }