You've already forked linux-packaging-mono
Imported Upstream version 4.3.2.467
Former-commit-id: 9c2cb47f45fa221e661ab616387c9cda183f283d
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// ActionOnDispose.cs
|
||||
//
|
||||
//
|
||||
// Implemention of IDisposable that runs a delegate on Dispose.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
/// <summary>Provider of disposables that run actions.</summary>
|
||||
internal sealed class Disposables
|
||||
{
|
||||
/// <summary>An IDisposable that does nothing.</summary>
|
||||
internal readonly static IDisposable Nop = new NopDisposable();
|
||||
|
||||
/// <summary>Creates an IDisposable that runs an action when disposed.</summary>
|
||||
/// <typeparam name="T1">Specifies the type of the first argument.</typeparam>
|
||||
/// <typeparam name="T2">Specifies the type of the second argument.</typeparam>
|
||||
/// <param name="action">The action to invoke.</param>
|
||||
/// <param name="arg1">The first argument.</param>
|
||||
/// <param name="arg2">The second argument.</param>
|
||||
/// <returns>The created disposable.</returns>
|
||||
internal static IDisposable Create<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2)
|
||||
{
|
||||
Contract.Requires(action != null, "Non-null disposer action required.");
|
||||
return new Disposable<T1, T2>(action, arg1, arg2);
|
||||
}
|
||||
|
||||
/// <summary>Creates an IDisposable that runs an action when disposed.</summary>
|
||||
/// <typeparam name="T1">Specifies the type of the first argument.</typeparam>
|
||||
/// <typeparam name="T2">Specifies the type of the second argument.</typeparam>
|
||||
/// <typeparam name="T3">Specifies the type of the third argument.</typeparam>
|
||||
/// <param name="action">The action to invoke.</param>
|
||||
/// <param name="arg1">The first argument.</param>
|
||||
/// <param name="arg2">The second argument.</param>
|
||||
/// <param name="arg3">The third argument.</param>
|
||||
/// <returns>The created disposable.</returns>
|
||||
internal static IDisposable Create<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3)
|
||||
{
|
||||
Contract.Requires(action != null, "Non-null disposer action required.");
|
||||
return new Disposable<T1, T2, T3>(action, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
/// <summary>A disposable that's a nop.</summary>
|
||||
[DebuggerDisplay("Disposed = true")]
|
||||
private sealed class NopDisposable : IDisposable
|
||||
{
|
||||
void IDisposable.Dispose() { }
|
||||
}
|
||||
|
||||
/// <summary>An IDisposable that will run a delegate when disposed.</summary>
|
||||
[DebuggerDisplay("Disposed = {Disposed}")]
|
||||
private sealed class Disposable<T1, T2> : IDisposable
|
||||
{
|
||||
/// <summary>First state argument.</summary>
|
||||
private readonly T1 _arg1;
|
||||
/// <summary>Second state argument.</summary>
|
||||
private readonly T2 _arg2;
|
||||
/// <summary>The action to run when disposed. Null if disposed.</summary>
|
||||
private Action<T1, T2> _action;
|
||||
|
||||
/// <summary>Initializes the ActionOnDispose.</summary>
|
||||
/// <param name="action">The action to run when disposed.</param>
|
||||
/// <param name="arg1">The first argument.</param>
|
||||
/// <param name="arg2">The second argument.</param>
|
||||
internal Disposable(Action<T1, T2> action, T1 arg1, T2 arg2)
|
||||
{
|
||||
Contract.Requires(action != null, "Non-null action needed for disposable");
|
||||
_action = action;
|
||||
_arg1 = arg1;
|
||||
_arg2 = arg2;
|
||||
}
|
||||
|
||||
/// <summary>Gets whether the IDisposable has been disposed.</summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
private bool Disposed { get { return _action == null; } }
|
||||
|
||||
/// <summary>Invoke the action.</summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Action<T1, T2> toRun = _action;
|
||||
if (toRun != null &&
|
||||
Interlocked.CompareExchange(ref _action, null, toRun) == toRun)
|
||||
{
|
||||
toRun(_arg1, _arg2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>An IDisposable that will run a delegate when disposed.</summary>
|
||||
[DebuggerDisplay("Disposed = {Disposed}")]
|
||||
private sealed class Disposable<T1, T2, T3> : IDisposable
|
||||
{
|
||||
/// <summary>First state argument.</summary>
|
||||
private readonly T1 _arg1;
|
||||
/// <summary>Second state argument.</summary>
|
||||
private readonly T2 _arg2;
|
||||
/// <summary>Third state argument.</summary>
|
||||
private readonly T3 _arg3;
|
||||
/// <summary>The action to run when disposed. Null if disposed.</summary>
|
||||
private Action<T1, T2, T3> _action;
|
||||
|
||||
/// <summary>Initializes the ActionOnDispose.</summary>
|
||||
/// <param name="action">The action to run when disposed.</param>
|
||||
/// <param name="arg1">The first argument.</param>
|
||||
/// <param name="arg2">The second argument.</param>
|
||||
/// <param name="arg3">The third argument.</param>
|
||||
internal Disposable(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3)
|
||||
{
|
||||
Contract.Requires(action != null, "Non-null action needed for disposable");
|
||||
_action = action;
|
||||
_arg1 = arg1;
|
||||
_arg2 = arg2;
|
||||
_arg3 = arg3;
|
||||
}
|
||||
|
||||
/// <summary>Gets whether the IDisposable has been disposed.</summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
private bool Disposed { get { return _action == null; } }
|
||||
|
||||
/// <summary>Invoke the action.</summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Action<T1, T2, T3> toRun = _action;
|
||||
if (toRun != null &&
|
||||
Interlocked.CompareExchange(ref _action, null, toRun) == toRun)
|
||||
{
|
||||
toRun(_arg1, _arg2, _arg3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// EnumerableDebugView.cs
|
||||
//
|
||||
//
|
||||
// Debugger type proxy for enumerables.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
/// <summary>Debugger type proxy for an enumerable of T.</summary>
|
||||
internal sealed class EnumerableDebugView<TKey, TValue>
|
||||
{
|
||||
/// <summary>The enumerable being visualized.</summary>
|
||||
private readonly IEnumerable<KeyValuePair<TKey, TValue>> _enumerable;
|
||||
|
||||
/// <summary>Initializes the debug view.</summary>
|
||||
/// <param name="enumerable">The enumerable being debugged.</param>
|
||||
public EnumerableDebugView(IEnumerable<KeyValuePair<TKey, TValue>> enumerable)
|
||||
{
|
||||
Contract.Requires(enumerable != null, "Expected a non-null enumerable.");
|
||||
_enumerable = enumerable;
|
||||
}
|
||||
|
||||
/// <summary>Gets the contents of the list.</summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public KeyValuePair<TKey, TValue>[] Items { get { return _enumerable.ToArray(); } }
|
||||
}
|
||||
|
||||
/// <summary>Debugger type proxy for an enumerable of T.</summary>
|
||||
internal sealed class EnumerableDebugView<T>
|
||||
{
|
||||
/// <summary>The enumerable being visualized.</summary>
|
||||
private readonly IEnumerable<T> _enumerable;
|
||||
|
||||
/// <summary>Initializes the debug view.</summary>
|
||||
/// <param name="enumerable">The enumerable being debugged.</param>
|
||||
public EnumerableDebugView(IEnumerable<T> enumerable)
|
||||
{
|
||||
Contract.Requires(enumerable != null, "Expected a non-null enumerable.");
|
||||
_enumerable = enumerable;
|
||||
}
|
||||
|
||||
/// <summary>Gets the contents of the list.</summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public T[] Items { get { return _enumerable.ToArray(); } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// IDebuggerDisplay.cs
|
||||
//
|
||||
//
|
||||
// An interface implemented by objects that expose their debugger display
|
||||
// attribute content through a property, making it possible for code to query
|
||||
// for the same content.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
/// <summary>Implemented to provide customizable data for debugger displays.</summary>
|
||||
internal interface IDebuggerDisplay
|
||||
{
|
||||
/// <summary>The object to be displayed as the content of a DebuggerDisplayAttribute.</summary>
|
||||
/// <remarks>
|
||||
/// The property returns an object to allow the debugger to interpret arbitrary .NET objects.
|
||||
/// The return value may be, but need not be limited to be, a string.
|
||||
/// </remarks>
|
||||
object Content { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// ImmutableList.cs
|
||||
//
|
||||
//
|
||||
// An immutable data structure that supports adding, removing, and enumerating elements.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
/// <summary>Provides a simple, immutable list.</summary>
|
||||
/// <typeparam name="T">Specifies the type of the data stored in the list.</typeparam>
|
||||
[DebuggerDisplay("Count={Count}")]
|
||||
[DebuggerTypeProxy(typeof(EnumerableDebugView<>))]
|
||||
internal sealed class ImmutableList<T> : IEnumerable<T>
|
||||
{
|
||||
/// <summary>An empty list.</summary>
|
||||
private readonly static ImmutableList<T> _empty = new ImmutableList<T>();
|
||||
/// <summary>The immutable data in this list instance.</summary>
|
||||
private readonly T[] _array;
|
||||
|
||||
/// <summary>Gets the empty list.</summary>
|
||||
public static ImmutableList<T> Empty { get { return _empty; } }
|
||||
|
||||
/// <summary>Initializes the immutable list to be empty.</summary>
|
||||
private ImmutableList() : this(new T[0]) { }
|
||||
|
||||
/// <summary>Initializes the immutable list with the specified elements.</summary>
|
||||
/// <param name="elements">The element array to use for this list's data.</param>
|
||||
private ImmutableList(T[] elements)
|
||||
{
|
||||
Contract.Requires(elements != null, "List requires an array to wrap.");
|
||||
_array = elements;
|
||||
}
|
||||
|
||||
/// <summary>Creates a new immutable list from this list and the additional element.</summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <returns>The new list.</returns>
|
||||
public ImmutableList<T> Add(T item)
|
||||
{
|
||||
// Copy the elements from this list and the item
|
||||
// to a new list that's returned.
|
||||
var newArray = new T[_array.Length + 1];
|
||||
Array.Copy(_array, 0, newArray, 0, _array.Length);
|
||||
newArray[newArray.Length - 1] = item;
|
||||
return new ImmutableList<T>(newArray);
|
||||
}
|
||||
|
||||
/// <summary>Creates a new immutable list from this list and without the specified element.</summary>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <returns>The new list.</returns>
|
||||
public ImmutableList<T> Remove(T item)
|
||||
{
|
||||
// Get the index of the element. If it's not in the list, just return this list.
|
||||
int index = Array.IndexOf(_array, item);
|
||||
if (index < 0) return this;
|
||||
|
||||
// It's in the list, so if it's the only one, just return the empty list
|
||||
if (_array.Length == 1) return Empty;
|
||||
|
||||
// Otherwise, copy the other elements to a new list that's returned.
|
||||
var newArray = new T[_array.Length - 1];
|
||||
Array.Copy(_array, 0, newArray, 0, index);
|
||||
Array.Copy(_array, index + 1, newArray, index, _array.Length - index - 1);
|
||||
return new ImmutableList<T>(newArray);
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of elements in this list.</summary>
|
||||
public int Count { get { return _array.Length; } }
|
||||
|
||||
/// <summary>Gets whether the list contains the specified item.</summary>
|
||||
/// <param name="item">The item to lookup.</param>
|
||||
/// <returns>true if the list contains the item; otherwise, false.</returns>
|
||||
public bool Contains(T item) { return Array.IndexOf(_array, item) >= 0; }
|
||||
|
||||
/// <summary>Returns an enumerator that iterates through the collection.</summary>
|
||||
public IEnumerator<T> GetEnumerator() { return ((IEnumerable<T>)_array).GetEnumerator(); }
|
||||
/// <summary>Returns an enumerator that iterates through the collection.</summary>
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// Padding.cs
|
||||
//
|
||||
//
|
||||
// Helper structs for padding over CPU cache lines to avoid false sharing.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
/// <summary>A placeholder class for common padding constants and eventually routines.</summary>
|
||||
internal static class Padding
|
||||
{
|
||||
/// <summary>A size greater than or equal to the size of the most common CPU cache lines.</summary>
|
||||
internal const int CACHE_LINE_SIZE = 128;
|
||||
}
|
||||
|
||||
/// <summary>Padding structure used to minimize false sharing in SingleProducerSingleConsumerQueue{T}.</summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = Padding.CACHE_LINE_SIZE - sizeof(Int32))] // Based on common case of 64-byte cache lines
|
||||
internal struct PaddingForInt32
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Value type that contains single Int64 value padded on both sides.</summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 2 * Padding.CACHE_LINE_SIZE)]
|
||||
internal struct PaddedInt64
|
||||
{
|
||||
[FieldOffset(Padding.CACHE_LINE_SIZE)]
|
||||
internal Int64 Value;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,230 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// QueuedMap.cs
|
||||
//
|
||||
//
|
||||
// A key-value pair queue, where pushing an existing key into the collection overwrites
|
||||
// the existing value.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a data structure that supports pushing and popping key/value pairs.
|
||||
/// Pushing a key/value pair for which the key already exists results in overwriting
|
||||
/// the existing key entry's value.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Specifies the type of keys in the map.</typeparam>
|
||||
/// <typeparam name="TValue">Specifies the type of values in the map.</typeparam>
|
||||
/// <remarks>This type is not thread-safe.</remarks>
|
||||
[DebuggerDisplay("Count = {Count}")]
|
||||
[DebuggerTypeProxy(typeof(EnumerableDebugView<,>))]
|
||||
internal sealed class QueuedMap<TKey, TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// A queue structure that uses an array-based list to store its items
|
||||
/// and that supports overwriting elements at specific indices.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the items storedin the queue</typeparam>
|
||||
/// <remarks>This type is not thread-safe.</remarks>
|
||||
private sealed class ArrayBasedLinkedQueue<T>
|
||||
{
|
||||
/// <summary>Terminator index.</summary>
|
||||
private const int TERMINATOR_INDEX = -1;
|
||||
/// <summary>
|
||||
/// The queue where the items will be stored.
|
||||
/// The key of each entry is the index of the next entry in the queue.
|
||||
/// </summary>
|
||||
private readonly List<KeyValuePair<int, T>> _storage;
|
||||
/// <summary>Index of the first queue item.</summary>
|
||||
private int _headIndex = TERMINATOR_INDEX;
|
||||
/// <summary>Index of the last queue item.</summary>
|
||||
private int _tailIndex = TERMINATOR_INDEX;
|
||||
/// <summary>Index of the first free slot.</summary>
|
||||
private int _freeIndex = TERMINATOR_INDEX;
|
||||
|
||||
/// <summary>Initializes the Queue instance.</summary>
|
||||
internal ArrayBasedLinkedQueue()
|
||||
{
|
||||
_storage = new List<KeyValuePair<int, T>>();
|
||||
}
|
||||
|
||||
/// <summary>Initializes the Queue instance.</summary>
|
||||
/// <param name="capacity">The capacity of the internal storage.</param>
|
||||
internal ArrayBasedLinkedQueue(int capacity)
|
||||
{
|
||||
_storage = new List<KeyValuePair<int, T>>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>Enqueues an item.</summary>
|
||||
/// <param name="item">The item to be enqueued.</param>
|
||||
/// <returns>The index of the slot where item was stored.</returns>
|
||||
internal int Enqueue(T item)
|
||||
{
|
||||
int newIndex;
|
||||
|
||||
// If there is a free slot, reuse it
|
||||
if (_freeIndex != TERMINATOR_INDEX)
|
||||
{
|
||||
Debug.Assert(0 <= _freeIndex && _freeIndex < _storage.Count, "Index is out of range.");
|
||||
newIndex = _freeIndex;
|
||||
_freeIndex = _storage[_freeIndex].Key;
|
||||
_storage[newIndex] = new KeyValuePair<int, T>(TERMINATOR_INDEX, item);
|
||||
}
|
||||
// If there is no free slot, add one
|
||||
else
|
||||
{
|
||||
newIndex = _storage.Count;
|
||||
_storage.Add(new KeyValuePair<int, T>(TERMINATOR_INDEX, item));
|
||||
}
|
||||
|
||||
if (_headIndex == TERMINATOR_INDEX)
|
||||
{
|
||||
// Point _headIndex to newIndex if the queue was empty
|
||||
Debug.Assert(_tailIndex == TERMINATOR_INDEX, "If head indicates empty, so too should tail.");
|
||||
_headIndex = newIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Point the tail slot to newIndex if the queue was not empty
|
||||
Debug.Assert(_tailIndex != TERMINATOR_INDEX, "If head does not indicate empty, neither should tail.");
|
||||
_storage[_tailIndex] = new KeyValuePair<int, T>(newIndex, _storage[_tailIndex].Value);
|
||||
}
|
||||
|
||||
// Point the tail slot newIndex
|
||||
_tailIndex = newIndex;
|
||||
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
/// <summary>Tries to dequeue an item.</summary>
|
||||
/// <param name="item">The item that is dequeued.</param>
|
||||
internal bool TryDequeue(out T item)
|
||||
{
|
||||
// If the queue is empty, just initialize the output item and return false
|
||||
if (_headIndex == TERMINATOR_INDEX)
|
||||
{
|
||||
Debug.Assert(_tailIndex == TERMINATOR_INDEX, "If head indicates empty, so too should tail.");
|
||||
item = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there are items in the queue, start with populating the output item
|
||||
Debug.Assert(0 <= _headIndex && _headIndex < _storage.Count, "Head is out of range.");
|
||||
item = _storage[_headIndex].Value;
|
||||
|
||||
// Move the popped slot to the head of the free list
|
||||
int newHeadIndex = _storage[_headIndex].Key;
|
||||
_storage[_headIndex] = new KeyValuePair<int, T>(_freeIndex, default(T));
|
||||
_freeIndex = _headIndex;
|
||||
_headIndex = newHeadIndex;
|
||||
if (_headIndex == TERMINATOR_INDEX) _tailIndex = TERMINATOR_INDEX;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Replaces the item of a given slot.</summary>
|
||||
/// <param name="index">The index of the slot where the value should be replaced.</param>
|
||||
/// <param name="item">The item to be places.</param>
|
||||
internal void Replace(int index, T item)
|
||||
{
|
||||
Debug.Assert(0 <= index && index < _storage.Count, "Index is out of range.");
|
||||
#if DEBUG
|
||||
// Also assert that index does not belong to the list of free slots
|
||||
for (int idx = _freeIndex; idx != TERMINATOR_INDEX; idx = _storage[idx].Key)
|
||||
Debug.Assert(idx != index, "Index should not belong to the list of free slots.");
|
||||
#endif
|
||||
_storage[index] = new KeyValuePair<int, T>(_storage[index].Key, item);
|
||||
}
|
||||
|
||||
internal bool IsEmpty { get { return _headIndex == TERMINATOR_INDEX; } }
|
||||
}
|
||||
|
||||
/// <summary>The queue of elements.</summary>
|
||||
private readonly ArrayBasedLinkedQueue<KeyValuePair<TKey, TValue>> _queue;
|
||||
/// <summary>A map from key to index into the list.</summary>
|
||||
/// <remarks>The correctness of this map relies on the list only having elements removed from its end.</remarks>
|
||||
private readonly Dictionary<TKey, int> _mapKeyToIndex;
|
||||
|
||||
/// <summary>Initializes the QueuedMap.</summary>
|
||||
internal QueuedMap()
|
||||
{
|
||||
_queue = new ArrayBasedLinkedQueue<KeyValuePair<TKey, TValue>>();
|
||||
_mapKeyToIndex = new Dictionary<TKey, int>();
|
||||
}
|
||||
|
||||
/// <summary>Initializes the QueuedMap.</summary>
|
||||
/// <param name="capacity">The initial capacity of the data structure.</param>
|
||||
internal QueuedMap(int capacity)
|
||||
{
|
||||
_queue = new ArrayBasedLinkedQueue<KeyValuePair<TKey, TValue>>(capacity);
|
||||
_mapKeyToIndex = new Dictionary<TKey, int>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>Pushes a key/value pair into the data structure.</summary>
|
||||
/// <param name="key">The key for the pair.</param>
|
||||
/// <param name="value">The value for the pair.</param>
|
||||
internal void Push(TKey key, TValue value)
|
||||
{
|
||||
// Try to get the index of the key in the queue. If it's there, replace the value.
|
||||
int indexOfKeyInQueue;
|
||||
if (!_queue.IsEmpty && _mapKeyToIndex.TryGetValue(key, out indexOfKeyInQueue))
|
||||
{
|
||||
_queue.Replace(indexOfKeyInQueue, new KeyValuePair<TKey, TValue>(key, value));
|
||||
}
|
||||
// If it's not there, add it to the queue and then add the mapping.
|
||||
else
|
||||
{
|
||||
indexOfKeyInQueue = _queue.Enqueue(new KeyValuePair<TKey, TValue>(key, value));
|
||||
_mapKeyToIndex.Add(key, indexOfKeyInQueue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Try to pop the next element from the data structure.</summary>
|
||||
/// <param name="item">The popped pair.</param>
|
||||
/// <returns>true if an item could be popped; otherwise, false.</returns>
|
||||
internal bool TryPop(out KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
bool popped = _queue.TryDequeue(out item);
|
||||
if (popped) _mapKeyToIndex.Remove(item.Key);
|
||||
return popped;
|
||||
}
|
||||
|
||||
/// <summary>Tries to pop one or more elements from the data structure.</summary>
|
||||
/// <param name="items">The items array into which the popped elements should be stored.</param>
|
||||
/// <param name="arrayOffset">The offset into the array at which to start storing popped items.</param>
|
||||
/// <param name="count">The number of items to be popped.</param>
|
||||
/// <returns>The number of items popped, which may be less than the requested number if fewer existed in the data structure.</returns>
|
||||
internal int PopRange(KeyValuePair<TKey, TValue>[] items, int arrayOffset, int count)
|
||||
{
|
||||
// As this data structure is internal, only assert incorrect usage.
|
||||
// If this were to ever be made public, these would need to be real argument checks.
|
||||
Contract.Requires(items != null, "Requires non-null array to store into.");
|
||||
Contract.Requires(count >= 0 && arrayOffset >= 0, "Count and offset must be non-negative");
|
||||
Contract.Requires(arrayOffset + count >= 0, "Offset plus count overflowed");
|
||||
Contract.Requires(arrayOffset + count <= items.Length, "Range must be within array size");
|
||||
|
||||
int actualCount = 0;
|
||||
for (int i = arrayOffset; actualCount < count; i++, actualCount++)
|
||||
{
|
||||
KeyValuePair<TKey, TValue> item;
|
||||
if (TryPop(out item)) items[i] = item;
|
||||
else break;
|
||||
}
|
||||
|
||||
return actualCount;
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of items in the data structure.</summary>
|
||||
internal int Count { get { return _mapKeyToIndex.Count; } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// ReorderingBuffer.cs
|
||||
//
|
||||
//
|
||||
// An intermediate buffer that ensures messages are output in the right order.
|
||||
// Used by blocks (e.g. TransformBlock, TransformManyBlock) when operating in
|
||||
// parallel modes that could result in messages being processed out of order.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
/// <summary>Base interface for reordering buffers.</summary>
|
||||
internal interface IReorderingBuffer
|
||||
{
|
||||
/// <summary>Informs the reordering buffer not to expect the message with the specified id.</summary>
|
||||
/// <param name="id">The id of the message to be ignored.</param>
|
||||
void IgnoreItem(long id);
|
||||
}
|
||||
|
||||
/// <summary>Provides a buffer that reorders items according to their incoming IDs.</summary>
|
||||
/// <typeparam name="TOutput">Specifies the type of data stored in the items being reordered.</typeparam>
|
||||
/// <remarks>
|
||||
/// This type expects the first item to be ID==0 and for all subsequent items
|
||||
/// to increase IDs sequentially.
|
||||
/// </remarks>
|
||||
[DebuggerDisplay("Count={CountForDebugging}")]
|
||||
[DebuggerTypeProxy(typeof(ReorderingBuffer<>.DebugView))]
|
||||
internal sealed class ReorderingBuffer<TOutput> : IReorderingBuffer
|
||||
{
|
||||
/// <summary>The source that owns this reordering buffer.</summary>
|
||||
private readonly object _owningSource;
|
||||
/// <summary>A reordering buffer used when parallelism is employed and items may be completed out-of-order.</summary>
|
||||
/// <remarks>Also serves as the sync object to protect the contents of this class.</remarks>
|
||||
private readonly Dictionary<long, KeyValuePair<bool, TOutput>> _reorderingBuffer = new Dictionary<long, KeyValuePair<bool, TOutput>>();
|
||||
/// <summary>Action used to output items in order.</summary>
|
||||
private readonly Action<object, TOutput> _outputAction;
|
||||
/// <summary>The ID of the next item that should be released from the reordering buffer.</summary>
|
||||
private long _nextReorderedIdToOutput = 0;
|
||||
|
||||
/// <summary>Gets the object used to synchronize all access to the reordering buffer's internals.</summary>
|
||||
private object ValueLock { get { return _reorderingBuffer; } }
|
||||
|
||||
/// <summary>Initializes the reordering buffer.</summary>
|
||||
/// <param name="owningSource">The source that owns this reordering buffer.</param>
|
||||
/// <param name="outputAction">The action to invoke when the next in-order item is available to be output.</param>
|
||||
internal ReorderingBuffer(object owningSource, Action<object, TOutput> outputAction)
|
||||
{
|
||||
// Validate and store internal arguments
|
||||
Contract.Requires(owningSource != null, "Buffer must be associated with a source.");
|
||||
Contract.Requires(outputAction != null, "Action required for when items are to be released.");
|
||||
_owningSource = owningSource;
|
||||
_outputAction = outputAction;
|
||||
}
|
||||
|
||||
/// <summary>Stores the next item as it completes processing.</summary>
|
||||
/// <param name="id">The ID of the item.</param>
|
||||
/// <param name="item">The completed item.</param>
|
||||
/// <param name="itemIsValid">Specifies whether the item is valid (true) or just a placeholder (false).</param>
|
||||
internal void AddItem(long id, TOutput item, bool itemIsValid)
|
||||
{
|
||||
Contract.Requires(id != Common.INVALID_REORDERING_ID, "This ID should never have been handed out.");
|
||||
Common.ContractAssertMonitorStatus(ValueLock, held: false);
|
||||
|
||||
// This may be called concurrently, so protect the buffer...
|
||||
lock (ValueLock)
|
||||
{
|
||||
// If this is the next item we need in our ordering, output it.
|
||||
if (_nextReorderedIdToOutput == id)
|
||||
{
|
||||
OutputNextItem(item, itemIsValid);
|
||||
}
|
||||
// Otherwise, we're using reordering and we're not ready for this item yet, so store
|
||||
// it until we can use it.
|
||||
else
|
||||
{
|
||||
Debug.Assert((ulong)id > (ulong)_nextReorderedIdToOutput, "Duplicate id.");
|
||||
_reorderingBuffer.Add(id, new KeyValuePair<bool, TOutput>(itemIsValid, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified id is next to be output, and if it is
|
||||
/// and if the item is "trusted" (meaning it may be output into the output
|
||||
/// action as-is), adds it.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the item.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="isTrusted">
|
||||
/// Whether to allow the item to be output directly if it is the next item.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// null if the item was added.
|
||||
/// true if the item was not added but is next in line.
|
||||
/// false if the item was not added and is not next in line.
|
||||
/// </returns>
|
||||
internal bool? AddItemIfNextAndTrusted(long id, TOutput item, bool isTrusted)
|
||||
{
|
||||
Contract.Requires(id != Common.INVALID_REORDERING_ID, "This ID should never have been handed out.");
|
||||
Common.ContractAssertMonitorStatus(ValueLock, held: false);
|
||||
|
||||
lock (ValueLock)
|
||||
{
|
||||
// If this is in the next item, try to take the fast path.
|
||||
if (_nextReorderedIdToOutput == id)
|
||||
{
|
||||
// If we trust this data structure to be stored as-is,
|
||||
// output it immediately. Otherwise, return that it is
|
||||
// next to be output.
|
||||
if (isTrusted)
|
||||
{
|
||||
OutputNextItem(item, itemIsValid: true);
|
||||
return null;
|
||||
}
|
||||
else return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Informs the reordering buffer not to expect the message with the specified id.</summary>
|
||||
/// <param name="id">The id of the message to be ignored.</param>
|
||||
public void IgnoreItem(long id)
|
||||
{
|
||||
AddItem(id, default(TOutput), itemIsValid: false);
|
||||
}
|
||||
|
||||
/// <summary>Outputs the item. The item must have already been confirmed to have the next ID.</summary>
|
||||
/// <param name="theNextItem">The item to output.</param>
|
||||
/// <param name="itemIsValid">Whether the item is valid.</param>
|
||||
private void OutputNextItem(TOutput theNextItem, bool itemIsValid)
|
||||
{
|
||||
Common.ContractAssertMonitorStatus(ValueLock, held: true);
|
||||
|
||||
// Note that we're now looking for a different item, and pass this one through.
|
||||
// Then release any items which may be pending.
|
||||
_nextReorderedIdToOutput++;
|
||||
if (itemIsValid) _outputAction(_owningSource, theNextItem);
|
||||
|
||||
// Try to get the next available item from the buffer and output it. Continue to do so
|
||||
// until we run out of items in the reordering buffer or don't yet have the next ID buffered.
|
||||
KeyValuePair<bool, TOutput> nextOutputItemWithValidity;
|
||||
while (_reorderingBuffer.TryGetValue(_nextReorderedIdToOutput, out nextOutputItemWithValidity))
|
||||
{
|
||||
_reorderingBuffer.Remove(_nextReorderedIdToOutput);
|
||||
_nextReorderedIdToOutput++;
|
||||
if (nextOutputItemWithValidity.Key) _outputAction(_owningSource, nextOutputItemWithValidity.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets a item count for debugging purposes.</summary>
|
||||
private int CountForDebugging { get { return _reorderingBuffer.Count; } }
|
||||
|
||||
/// <summary>Provides a debugger type proxy for the buffer.</summary>
|
||||
private sealed class DebugView
|
||||
{
|
||||
/// <summary>The buffer being debugged.</summary>
|
||||
private readonly ReorderingBuffer<TOutput> _buffer;
|
||||
|
||||
/// <summary>Initializes the debug view.</summary>
|
||||
/// <param name="buffer">The buffer being debugged.</param>
|
||||
public DebugView(ReorderingBuffer<TOutput> buffer)
|
||||
{
|
||||
Contract.Requires(buffer != null, "Need a buffer with which to construct the debug view.");
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
/// <summary>Gets a dictionary of buffered items and their reordering IDs.</summary>
|
||||
public Dictionary<long, KeyValuePair<Boolean, TOutput>> ItemsBuffered { get { return _buffer._reorderingBuffer; } }
|
||||
/// <summary>Gets the next ID required for outputting.</summary>
|
||||
public long NextIdRequired { get { return _buffer._nextReorderedIdToOutput; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,413 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// SpscTargetCore.cs
|
||||
//
|
||||
//
|
||||
// A fast single-producer-single-consumer core for a target block.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
#pragma warning disable 0420 // turn off warning for passing volatiles to interlocked operations
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
// SpscTargetCore provides a fast target core for use in blocks that will only have single-producer-single-consumer
|
||||
// semantics. Blocks configured with the default DOP==1 will be single consumer, so whether this core may be
|
||||
// used is largely up to whether the block is also single-producer. The ExecutionDataflowBlockOptions.SingleProducerConstrained
|
||||
// option can be used by a developer to inform a block that it will only be accessed by one producer at a time,
|
||||
// and a block like ActionBlock can utilize that knowledge to choose this target instead of the default TargetCore.
|
||||
// However, there are further constraints that might prevent this core from being used.
|
||||
// - If the user specifies a CancellationToken, this core can't be used, as the cancellation request
|
||||
// could come in concurrently with the single producer accessing the block, thus resulting in multiple producers.
|
||||
// - If the user specifies a bounding capacity, this core can't be used, as the consumer processing items
|
||||
// needs to synchronize with producers around the change in bounding count, and the consumer is again
|
||||
// in effect another producer.
|
||||
// - If the block has a source half (e.g. TransformBlock) and that source could potentially call back
|
||||
// to the target half to, for example, notify it of exceptions occurring, again there would potentially
|
||||
// be multiple producers.
|
||||
// Thus, when and how this SpscTargetCore may be applied is significantly constrained.
|
||||
|
||||
/// <summary>
|
||||
/// Provides a core implementation of <see cref="ITargetBlock{TInput}"/> for use when there's only a single producer posting data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">Specifies the type of data accepted by the <see cref="TargetCore{TInput}"/>.</typeparam>
|
||||
[DebuggerDisplay("{DebuggerDisplayContent,nq}")]
|
||||
internal sealed class SpscTargetCore<TInput>
|
||||
{
|
||||
/// <summary>The target block using this helper.</summary>
|
||||
private readonly ITargetBlock<TInput> _owningTarget;
|
||||
/// <summary>The messages in this target.</summary>
|
||||
private readonly SingleProducerSingleConsumerQueue<TInput> _messages = new SingleProducerSingleConsumerQueue<TInput>();
|
||||
/// <summary>The options to use to configure this block. The target core assumes these options are immutable.</summary>
|
||||
private readonly ExecutionDataflowBlockOptions _dataflowBlockOptions;
|
||||
/// <summary>An action to invoke for every accepted message.</summary>
|
||||
private readonly Action<TInput> _action;
|
||||
|
||||
/// <summary>Exceptions that may have occurred and gone unhandled during processing. This field is lazily initialized.</summary>
|
||||
private volatile List<Exception> _exceptions;
|
||||
/// <summary>Whether to stop accepting new messages.</summary>
|
||||
private volatile bool _decliningPermanently;
|
||||
/// <summary>A task has reserved the right to run the completion routine.</summary>
|
||||
private volatile bool _completionReserved;
|
||||
/// <summary>
|
||||
/// The Task currently active to process the block. This field is used to synchronize between producer and consumer,
|
||||
/// and it should not be set to null once the block completes, as doing so would allow for races where the producer
|
||||
/// gets another consumer task queued even though the block has completed.
|
||||
/// </summary>
|
||||
private volatile Task _activeConsumer;
|
||||
/// <summary>A task representing the completion of the block. This field is lazily initialized.</summary>
|
||||
private TaskCompletionSource<VoidResult> _completionTask;
|
||||
|
||||
/// <summary>Initialize the SPSC target core.</summary>
|
||||
/// <param name="owningTarget">The owning target block.</param>
|
||||
/// <param name="action">The action to be invoked for every message.</param>
|
||||
/// <param name="dataflowBlockOptions">The options to use to configure this block. The target core assumes these options are immutable.</param>
|
||||
internal SpscTargetCore(
|
||||
ITargetBlock<TInput> owningTarget, Action<TInput> action, ExecutionDataflowBlockOptions dataflowBlockOptions)
|
||||
{
|
||||
Contract.Requires(owningTarget != null, "Expected non-null owningTarget");
|
||||
Contract.Requires(action != null, "Expected non-null action");
|
||||
Contract.Requires(dataflowBlockOptions != null, "Expected non-null dataflowBlockOptions");
|
||||
|
||||
_owningTarget = owningTarget;
|
||||
_action = action;
|
||||
_dataflowBlockOptions = dataflowBlockOptions;
|
||||
}
|
||||
|
||||
internal bool Post(TInput messageValue)
|
||||
{
|
||||
if (_decliningPermanently)
|
||||
return false;
|
||||
|
||||
// Store the offered message into the queue.
|
||||
_messages.Enqueue(messageValue);
|
||||
|
||||
Interlocked.MemoryBarrier(); // ensure the read of _activeConsumer doesn't move up before the writes in Enqueue
|
||||
|
||||
// Make sure there's an active task available to handle processing this message. If we find the task
|
||||
// is null, we'll try to schedule one using an interlocked operation. If we find the task is non-null,
|
||||
// then there must be a task actively running. If there's a race where the task is about to complete
|
||||
// and nulls out its reference (using a barrier), it'll subsequently check whether there are any messages in the queue,
|
||||
// and since we put the messages into the queue before now, it'll find them and use an interlocked
|
||||
// to re-launch itself.
|
||||
if (_activeConsumer == null)
|
||||
{
|
||||
ScheduleConsumerIfNecessary(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
|
||||
internal DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, bool consumeToAccept)
|
||||
{
|
||||
// If we're not required to go back to the source to consume the offered message, try fast path.
|
||||
return !consumeToAccept && Post(messageValue) ?
|
||||
DataflowMessageStatus.Accepted :
|
||||
OfferMessage_Slow(messageHeader, messageValue, source, consumeToAccept);
|
||||
}
|
||||
|
||||
/// <summary>Implements the slow path for OfferMessage.</summary>
|
||||
/// <param name="messageHeader">The message header for the offered value.</param>
|
||||
/// <param name="messageValue">The offered value.</param>
|
||||
/// <param name="source">The source offering the message. This may be null.</param>
|
||||
/// <param name="consumeToAccept">true if we need to call back to the source to consume the message; otherwise, false if we can simply accept it directly.</param>
|
||||
/// <returns>The status of the message.</returns>
|
||||
private DataflowMessageStatus OfferMessage_Slow(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, bool consumeToAccept)
|
||||
{
|
||||
// If we're declining permanently, let the caller know.
|
||||
if (_decliningPermanently)
|
||||
{
|
||||
return DataflowMessageStatus.DecliningPermanently;
|
||||
}
|
||||
|
||||
// If the message header is invalid, throw.
|
||||
if (!messageHeader.IsValid)
|
||||
{
|
||||
throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader");
|
||||
}
|
||||
|
||||
// If the caller has requested we consume the message using ConsumeMessage, do so.
|
||||
if (consumeToAccept)
|
||||
{
|
||||
if (source == null) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept");
|
||||
bool consumed;
|
||||
messageValue = source.ConsumeMessage(messageHeader, _owningTarget, out consumed);
|
||||
if (!consumed) return DataflowMessageStatus.NotAvailable;
|
||||
}
|
||||
|
||||
// See the "fast path" comments in Post
|
||||
_messages.Enqueue(messageValue);
|
||||
Interlocked.MemoryBarrier(); // ensure the read of _activeConsumer doesn't move up before the writes in Enqueue
|
||||
if (_activeConsumer == null)
|
||||
{
|
||||
ScheduleConsumerIfNecessary(isReplica: false);
|
||||
}
|
||||
return DataflowMessageStatus.Accepted;
|
||||
}
|
||||
|
||||
/// <summary>Schedules a consumer task if there's none currently running.</summary>
|
||||
/// <param name="isReplica">Whether the new consumer is being scheduled to replace a currently running consumer.</param>
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
|
||||
private void ScheduleConsumerIfNecessary(bool isReplica)
|
||||
{
|
||||
// If there's currently no active task...
|
||||
if (_activeConsumer == null)
|
||||
{
|
||||
// Create a new consumption task and try to set it as current as long as there's still no other task
|
||||
var newConsumer = new Task(
|
||||
state => ((SpscTargetCore<TInput>)state).ProcessMessagesLoopCore(),
|
||||
this, CancellationToken.None, Common.GetCreationOptionsForTask(isReplica));
|
||||
if (Interlocked.CompareExchange(ref _activeConsumer, newConsumer, null) == null)
|
||||
{
|
||||
// We won the race. This task is now the consumer.
|
||||
|
||||
#if FEATURE_TRACING
|
||||
DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
|
||||
if (etwLog.IsEnabled())
|
||||
{
|
||||
etwLog.TaskLaunchedForMessageHandling(
|
||||
_owningTarget, newConsumer, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, _messages.Count);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start the task. In the erroneous case where the scheduler throws an exception,
|
||||
// just allow it to propagate. Our other option would be to fault the block with
|
||||
// that exception, but in order for the block to complete we need to schedule a consumer
|
||||
// task to do so, and it's very likely that if the scheduler is throwing an exception
|
||||
// now, it would do so again.
|
||||
newConsumer.Start(_dataflowBlockOptions.TaskScheduler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Task body used to process messages.</summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
private void ProcessMessagesLoopCore()
|
||||
{
|
||||
Debug.Assert(
|
||||
_activeConsumer != null && _activeConsumer.Id == Task.CurrentId,
|
||||
"This method should only be called when it's the active consumer.");
|
||||
|
||||
int messagesProcessed = 0;
|
||||
int maxMessagesToProcess = _dataflowBlockOptions.ActualMaxMessagesPerTask;
|
||||
|
||||
// Continue processing as long as there's more processing to be done
|
||||
bool continueProcessing = true;
|
||||
while (continueProcessing)
|
||||
{
|
||||
continueProcessing = false;
|
||||
TInput nextMessage = default(TInput);
|
||||
try
|
||||
{
|
||||
// While there are more messages to be processed, process each one.
|
||||
// NOTE: This loop is critical for performance. It must be super lean.
|
||||
while (
|
||||
_exceptions == null &&
|
||||
messagesProcessed < maxMessagesToProcess &&
|
||||
_messages.TryDequeue(out nextMessage))
|
||||
{
|
||||
messagesProcessed++; // done before _action invoked in case it throws exception
|
||||
_action(nextMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
// If the exception is for cancellation, just ignore it.
|
||||
// Otherwise, store it, and the finally block will handle completion.
|
||||
if (!Common.IsCooperativeCancellation(exc))
|
||||
{
|
||||
_decliningPermanently = true; // stop accepting from producers
|
||||
Common.StoreDataflowMessageValueIntoExceptionData<TInput>(exc, nextMessage, false);
|
||||
StoreException(exc);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// If more messages just arrived and we should still process them,
|
||||
// loop back around and keep going.
|
||||
if (!_messages.IsEmpty && _exceptions == null && (messagesProcessed < maxMessagesToProcess))
|
||||
{
|
||||
continueProcessing = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If messages are being declined and we're empty, or if there's an exception,
|
||||
// then there's no more work to be done and we should complete the block.
|
||||
bool wasDecliningPermanently = _decliningPermanently;
|
||||
if ((wasDecliningPermanently && _messages.IsEmpty) || _exceptions != null)
|
||||
{
|
||||
// Complete the block, as long as we're not already completing or completed.
|
||||
if (!_completionReserved) // no synchronization necessary; this can't happen concurrently
|
||||
{
|
||||
_completionReserved = true;
|
||||
CompleteBlockOncePossible();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mark that we're exiting.
|
||||
Task previousConsumer = Interlocked.Exchange(ref _activeConsumer, null);
|
||||
Debug.Assert(previousConsumer != null && previousConsumer.Id == Task.CurrentId,
|
||||
"The running task should have been denoted as the active task.");
|
||||
|
||||
// Now that we're no longer the active task, double
|
||||
// check to make sure there's really nothing to do,
|
||||
// which could include processing more messages or completing.
|
||||
// If there is more to do, schedule a task to try to do it.
|
||||
// This is to handle a race with Post/Complete/Fault and this
|
||||
// task completing.
|
||||
if (!_messages.IsEmpty || // messages to be processed
|
||||
(!wasDecliningPermanently && _decliningPermanently) || // potentially completion to be processed
|
||||
_exceptions != null) // exceptions/completion to be processed
|
||||
{
|
||||
ScheduleConsumerIfNecessary(isReplica: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of messages waiting to be processed.</summary>
|
||||
internal int InputCount { get { return _messages.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// Completes the target core. If an exception is provided, the block will end up in a faulted state.
|
||||
/// If Complete is invoked more than once, or if it's invoked after the block is already
|
||||
/// completing, all invocations after the first are ignored.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception to be stored.</param>
|
||||
internal void Complete(Exception exception)
|
||||
{
|
||||
// If we're not yet declining permanently...
|
||||
if (!_decliningPermanently)
|
||||
{
|
||||
// Mark us as declining permanently, and then kick off a processing task
|
||||
// if we need one. It's this processing task's job to complete the block
|
||||
// once all data has been consumed and/or we're in a valid state for completion.
|
||||
if (exception != null) StoreException(exception);
|
||||
_decliningPermanently = true;
|
||||
ScheduleConsumerIfNecessary(isReplica: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the exceptions list is initialized and stores the exception into the list using a lock.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception to store.</param>
|
||||
private void StoreException(Exception exception)
|
||||
{
|
||||
// Ensure that the _exceptions field has been initialized.
|
||||
// We need to synchronize the initialization and storing of
|
||||
// the exception because this method could be accessed concurrently
|
||||
// by the producer and consumer, a producer calling Fault and the
|
||||
// processing task processing the user delegate which might throw.
|
||||
lock (LazyInitializer.EnsureInitialized(ref _exceptions, () => new List<Exception>()))
|
||||
{
|
||||
_exceptions.Add(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the block. This must only be called once, and only once all of the completion conditions are met.
|
||||
/// </summary>
|
||||
private void CompleteBlockOncePossible()
|
||||
{
|
||||
Debug.Assert(_completionReserved, "Should only invoke once completion has been reserved.");
|
||||
|
||||
// Dump any messages that might remain in the queue, which could happen if we completed due to exceptions.
|
||||
TInput dumpedMessage;
|
||||
while (_messages.TryDequeue(out dumpedMessage)) ;
|
||||
|
||||
// Complete the completion task
|
||||
bool result;
|
||||
if (_exceptions != null)
|
||||
{
|
||||
Exception[] exceptions;
|
||||
lock (_exceptions) exceptions = _exceptions.ToArray();
|
||||
result = CompletionSource.TrySetException(exceptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = CompletionSource.TrySetResult(default(VoidResult));
|
||||
}
|
||||
Debug.Assert(result, "Expected completion task to not yet be completed");
|
||||
// We explicitly do not set the _activeTask to null here, as that would
|
||||
// allow for races where a producer calling OfferMessage could end up
|
||||
// seeing _activeTask as null and queueing a new consumer task even
|
||||
// though the block has completed.
|
||||
|
||||
#if FEATURE_TRACING
|
||||
DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
|
||||
if (etwLog.IsEnabled())
|
||||
{
|
||||
etwLog.DataflowBlockCompleted(_owningTarget);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
|
||||
internal Task Completion { get { return CompletionSource.Task; } }
|
||||
|
||||
/// <summary>Gets the lazily-initialized completion source.</summary>
|
||||
private TaskCompletionSource<VoidResult> CompletionSource
|
||||
{
|
||||
get { return LazyInitializer.EnsureInitialized(ref _completionTask, () => new TaskCompletionSource<VoidResult>()); }
|
||||
}
|
||||
|
||||
/// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
|
||||
internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } }
|
||||
|
||||
/// <summary>Gets information about this helper to be used for display in a debugger.</summary>
|
||||
/// <returns>Debugging information about this target.</returns>
|
||||
internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); }
|
||||
|
||||
/// <summary>Gets the object to display in the debugger display attribute.</summary>
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
|
||||
private object DebuggerDisplayContent
|
||||
{
|
||||
get
|
||||
{
|
||||
var displayTarget = _owningTarget as IDebuggerDisplay;
|
||||
return string.Format("Block=\"{0}\"",
|
||||
displayTarget != null ? displayTarget.Content : _owningTarget);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Provides a wrapper for commonly needed debugging information.</summary>
|
||||
internal sealed class DebuggingInformation
|
||||
{
|
||||
/// <summary>The target being viewed.</summary>
|
||||
private readonly SpscTargetCore<TInput> _target;
|
||||
|
||||
/// <summary>Initializes the debugging helper.</summary>
|
||||
/// <param name="target">The target being viewed.</param>
|
||||
internal DebuggingInformation(SpscTargetCore<TInput> target) { _target = target; }
|
||||
|
||||
/// <summary>Gets the number of messages waiting to be processed.</summary>
|
||||
internal int InputCount { get { return _target.InputCount; } }
|
||||
/// <summary>Gets the messages waiting to be processed.</summary>
|
||||
internal IEnumerable<TInput> InputQueue { get { return _target._messages.ToList(); } }
|
||||
|
||||
/// <summary>Gets the current number of outstanding input processing operations.</summary>
|
||||
internal Int32 CurrentDegreeOfParallelism { get { return _target._activeConsumer != null && !_target.Completion.IsCompleted ? 1 : 0; } }
|
||||
/// <summary>Gets the DataflowBlockOptions used to configure this block.</summary>
|
||||
internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _target._dataflowBlockOptions; } }
|
||||
/// <summary>Gets whether the block is declining further messages.</summary>
|
||||
internal bool IsDecliningPermanently { get { return _target._decliningPermanently; } }
|
||||
/// <summary>Gets whether the block is completed.</summary>
|
||||
internal bool IsCompleted { get { return _target.Completion.IsCompleted; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,418 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
//
|
||||
// TargetRegistry.cs
|
||||
//
|
||||
//
|
||||
// A store of registered targets with a target block.
|
||||
//
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal
|
||||
{
|
||||
/// <summary>Stores targets registered with a source.</summary>
|
||||
/// <typeparam name="T">Specifies the type of data accepted by the targets.</typeparam>
|
||||
/// <remarks>This type is not thread-safe.</remarks>
|
||||
[DebuggerDisplay("Count={Count}")]
|
||||
[DebuggerTypeProxy(typeof(TargetRegistry<>.DebugView))]
|
||||
internal sealed class TargetRegistry<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a registered target. This class represents a self-sufficient node in a linked list.
|
||||
/// </summary>
|
||||
internal sealed class LinkedTargetInfo
|
||||
{
|
||||
/// <summary>Initializes the LinkedTargetInfo.</summary>
|
||||
/// <param name="target">The target block reference for this entry.</param>
|
||||
/// <param name="linkOptions">The link options.</param>
|
||||
internal LinkedTargetInfo(ITargetBlock<T> target, DataflowLinkOptions linkOptions)
|
||||
{
|
||||
Contract.Requires(target != null, "The target that is supposed to be linked must not be null.");
|
||||
Contract.Requires(linkOptions != null, "The linkOptions must not be null.");
|
||||
|
||||
Target = target;
|
||||
PropagateCompletion = linkOptions.PropagateCompletion;
|
||||
RemainingMessages = linkOptions.MaxMessages;
|
||||
}
|
||||
|
||||
/// <summary>The target block reference for this entry.</summary>
|
||||
internal readonly ITargetBlock<T> Target;
|
||||
/// <summary>The value of the PropagateCompletion link option.</summary>
|
||||
internal readonly bool PropagateCompletion;
|
||||
/// <summary>Number of remaining messages to propagate.
|
||||
/// This counter is initialized to the MaxMessages option and
|
||||
/// gets decremented after each successful propagation.</summary>
|
||||
internal int RemainingMessages;
|
||||
/// <summary>The previous node in the list.</summary>
|
||||
internal LinkedTargetInfo Previous;
|
||||
/// <summary>The next node in the list.</summary>
|
||||
internal LinkedTargetInfo Next;
|
||||
}
|
||||
|
||||
/// <summary>A reference to the owning source block.</summary>
|
||||
private readonly ISourceBlock<T> _owningSource;
|
||||
/// <summary>A mapping of targets to information about them.</summary>
|
||||
private readonly Dictionary<ITargetBlock<T>, LinkedTargetInfo> _targetInformation;
|
||||
/// <summary>The first node of an ordered list of targets. Messages should be offered to targets starting from First and following Next.</summary>
|
||||
private LinkedTargetInfo _firstTarget;
|
||||
/// <summary>The last node of the ordered list of targets. This field is used purely as a perf optimization to avoid traversing the list for each Add.</summary>
|
||||
private LinkedTargetInfo _lastTarget;
|
||||
/// <summary>Number of links with positive RemainingMessages counters.
|
||||
/// This is an optimization that allows us to skip dictionary lookup when this counter is 0.</summary>
|
||||
private int _linksWithRemainingMessages;
|
||||
|
||||
/// <summary>Initializes the registry.</summary>
|
||||
internal TargetRegistry(ISourceBlock<T> owningSource)
|
||||
{
|
||||
Contract.Requires(owningSource != null, "The TargetRegistry instance must be owned by a source block.");
|
||||
|
||||
_owningSource = owningSource;
|
||||
_targetInformation = new Dictionary<ITargetBlock<T>, LinkedTargetInfo>();
|
||||
}
|
||||
|
||||
/// <summary>Adds a target to the registry.</summary>
|
||||
/// <param name="target">The target to add.</param>
|
||||
/// <param name="linkOptions">The link options.</param>
|
||||
internal void Add(ref ITargetBlock<T> target, DataflowLinkOptions linkOptions)
|
||||
{
|
||||
Contract.Requires(target != null, "The target that is supposed to be linked must not be null.");
|
||||
Contract.Requires(linkOptions != null, "The link options must not be null.");
|
||||
|
||||
LinkedTargetInfo targetInfo;
|
||||
|
||||
// If the target already exists in the registry, replace it with a new NopLinkPropagator to maintain uniqueness
|
||||
if (_targetInformation.TryGetValue(target, out targetInfo)) target = new NopLinkPropagator(_owningSource, target);
|
||||
|
||||
// Add the target to both stores, the list and the dictionary, which are used for different purposes
|
||||
var node = new LinkedTargetInfo(target, linkOptions);
|
||||
AddToList(node, linkOptions.Append);
|
||||
_targetInformation.Add(target, node);
|
||||
|
||||
// Increment the optimization counter if needed
|
||||
Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
|
||||
if (node.RemainingMessages > 0) _linksWithRemainingMessages++;
|
||||
#if FEATURE_TRACING
|
||||
DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
|
||||
if (etwLog.IsEnabled())
|
||||
{
|
||||
etwLog.DataflowBlockLinking(_owningSource, target);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Gets whether the registry contains a particular target.</summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <returns>true if the registry contains the target; otherwise, false.</returns>
|
||||
internal bool Contains(ITargetBlock<T> target)
|
||||
{
|
||||
return _targetInformation.ContainsKey(target);
|
||||
}
|
||||
|
||||
/// <summary>Removes the target from the registry.</summary>
|
||||
/// <param name="target">The target to remove.</param>
|
||||
/// <param name="onlyIfReachedMaxMessages">
|
||||
/// Only remove the target if it's configured to be unlinked after one propagation.
|
||||
/// </param>
|
||||
internal void Remove(ITargetBlock<T> target, bool onlyIfReachedMaxMessages = false)
|
||||
{
|
||||
Contract.Requires(target != null, "Target to remove is required.");
|
||||
|
||||
// If we are implicitly unlinking and there is nothing to be unlinked implicitly, bail
|
||||
Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
|
||||
if (onlyIfReachedMaxMessages && _linksWithRemainingMessages == 0) return;
|
||||
|
||||
// Otherwise take the slow path
|
||||
Remove_Slow(target, onlyIfReachedMaxMessages);
|
||||
}
|
||||
|
||||
/// <summary>Actually removes the target from the registry.</summary>
|
||||
/// <param name="target">The target to remove.</param>
|
||||
/// <param name="onlyIfReachedMaxMessages">
|
||||
/// Only remove the target if it's configured to be unlinked after one propagation.
|
||||
/// </param>
|
||||
private void Remove_Slow(ITargetBlock<T> target, bool onlyIfReachedMaxMessages)
|
||||
{
|
||||
Contract.Requires(target != null, "Target to remove is required.");
|
||||
|
||||
// Make sure we've intended to go the slow route
|
||||
Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
|
||||
Debug.Assert(!onlyIfReachedMaxMessages || _linksWithRemainingMessages > 0, "We shouldn't have ended on the slow path.");
|
||||
|
||||
// If the target is registered...
|
||||
LinkedTargetInfo node;
|
||||
if (_targetInformation.TryGetValue(target, out node))
|
||||
{
|
||||
Debug.Assert(node != null, "The LinkedTargetInfo node referenced in the Dictionary must be non-null.");
|
||||
|
||||
// Remove the target, if either there's no constraint on the removal
|
||||
// or if this was the last remaining message.
|
||||
if (!onlyIfReachedMaxMessages || node.RemainingMessages == 1)
|
||||
{
|
||||
RemoveFromList(node);
|
||||
_targetInformation.Remove(target);
|
||||
|
||||
// Decrement the optimization counter if needed
|
||||
if (node.RemainingMessages == 0) _linksWithRemainingMessages--;
|
||||
Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
|
||||
#if FEATURE_TRACING
|
||||
DataflowEtwProvider etwLog = DataflowEtwProvider.Log;
|
||||
if (etwLog.IsEnabled())
|
||||
{
|
||||
etwLog.DataflowBlockUnlinking(_owningSource, target);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// If the target is to stay and we are counting the remaining messages for this link, decrement the counter
|
||||
else if (node.RemainingMessages > 0)
|
||||
{
|
||||
Debug.Assert(node.RemainingMessages > 1, "The target should have been removed, because there are no remaining messages.");
|
||||
node.RemainingMessages--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Clears the target registry entry points while allowing subsequent traversals of the linked list.</summary>
|
||||
internal LinkedTargetInfo ClearEntryPoints()
|
||||
{
|
||||
// Save _firstTarget so we can return it
|
||||
LinkedTargetInfo firstTarget = _firstTarget;
|
||||
|
||||
// Clear out the entry points
|
||||
_firstTarget = _lastTarget = null;
|
||||
_targetInformation.Clear();
|
||||
Debug.Assert(_linksWithRemainingMessages >= 0, "_linksWithRemainingMessages must be non-negative at any time.");
|
||||
_linksWithRemainingMessages = 0;
|
||||
|
||||
return firstTarget;
|
||||
}
|
||||
|
||||
/// <summary>Propagated completion to the targets of the given linked list.</summary>
|
||||
/// <param name="firstTarget">The head of a saved linked list.</param>
|
||||
internal void PropagateCompletion(LinkedTargetInfo firstTarget)
|
||||
{
|
||||
Debug.Assert(_owningSource.Completion.IsCompleted, "The owning source must have completed before propagating completion.");
|
||||
|
||||
// Cache the owning source's completion task to avoid calling the getter many times
|
||||
Task owningSourceCompletion = _owningSource.Completion;
|
||||
|
||||
// Propagate completion to those targets that have requested it
|
||||
for (LinkedTargetInfo node = firstTarget; node != null; node = node.Next)
|
||||
{
|
||||
if (node.PropagateCompletion) Common.PropagateCompletion(owningSourceCompletion, node.Target, Common.AsyncExceptionHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the first node of the ordered target list.</summary>
|
||||
internal LinkedTargetInfo FirstTargetNode { get { return _firstTarget; } }
|
||||
|
||||
/// <summary>Adds a LinkedTargetInfo node to the doubly-linked list.</summary>
|
||||
/// <param name="node">The node to be added.</param>
|
||||
/// <param name="append">Whether to append or to prepend the node.</param>
|
||||
internal void AddToList(LinkedTargetInfo node, bool append)
|
||||
{
|
||||
Contract.Requires(node != null, "Requires a node to be added.");
|
||||
|
||||
// If the list is empty, assign the ends to point to the new node and we are done
|
||||
if (_firstTarget == null && _lastTarget == null)
|
||||
{
|
||||
_firstTarget = _lastTarget = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(_firstTarget != null && _lastTarget != null, "Both first and last node must either be null or non-null.");
|
||||
Debug.Assert(_lastTarget.Next == null, "The last node must not have a successor.");
|
||||
Debug.Assert(_firstTarget.Previous == null, "The first node must not have a predecessor.");
|
||||
|
||||
if (append)
|
||||
{
|
||||
// Link the new node to the end of the existing list
|
||||
node.Previous = _lastTarget;
|
||||
_lastTarget.Next = node;
|
||||
_lastTarget = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link the new node to the front of the existing list
|
||||
node.Next = _firstTarget;
|
||||
_firstTarget.Previous = node;
|
||||
_firstTarget = node;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(_firstTarget != null && _lastTarget != null, "Both first and last node must be non-null after AddToList.");
|
||||
}
|
||||
|
||||
/// <summary>Removes the LinkedTargetInfo node from the doubly-linked list.</summary>
|
||||
/// <param name="node">The node to be removed.</param>
|
||||
internal void RemoveFromList(LinkedTargetInfo node)
|
||||
{
|
||||
Contract.Requires(node != null, "Node to remove is required.");
|
||||
Debug.Assert(_firstTarget != null && _lastTarget != null, "Both first and last node must be non-null before RemoveFromList.");
|
||||
|
||||
LinkedTargetInfo previous = node.Previous;
|
||||
LinkedTargetInfo next = node.Next;
|
||||
|
||||
// Remove the node by linking the adjacent nodes
|
||||
if (node.Previous != null)
|
||||
{
|
||||
node.Previous.Next = next;
|
||||
node.Previous = null;
|
||||
}
|
||||
|
||||
if (node.Next != null)
|
||||
{
|
||||
node.Next.Previous = previous;
|
||||
node.Next = null;
|
||||
}
|
||||
|
||||
// Adjust the list ends
|
||||
if (_firstTarget == node) _firstTarget = next;
|
||||
if (_lastTarget == node) _lastTarget = previous;
|
||||
|
||||
Debug.Assert((_firstTarget != null) == (_lastTarget != null), "Both first and last node must either be null or non-null after RemoveFromList.");
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of items in the registry.</summary>
|
||||
private int Count { get { return _targetInformation.Count; } }
|
||||
|
||||
/// <summary>Converts the linked list of targets to an array for rendering in a debugger.</summary>
|
||||
private ITargetBlock<T>[] TargetsForDebugger
|
||||
{
|
||||
get
|
||||
{
|
||||
var targets = new ITargetBlock<T>[Count];
|
||||
int i = 0;
|
||||
for (LinkedTargetInfo node = _firstTarget; node != null; node = node.Next)
|
||||
{
|
||||
targets[i++] = node.Target;
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Provides a nop passthrough for use with TargetRegistry.</summary>
|
||||
[DebuggerDisplay("{DebuggerDisplayContent,nq}")]
|
||||
[DebuggerTypeProxy(typeof(TargetRegistry<>.NopLinkPropagator.DebugView))]
|
||||
private sealed class NopLinkPropagator : IPropagatorBlock<T, T>, ISourceBlock<T>, IDebuggerDisplay
|
||||
{
|
||||
/// <summary>The source that encapsulates this block.</summary>
|
||||
private readonly ISourceBlock<T> _owningSource;
|
||||
/// <summary>The target with which this block is associated.</summary>
|
||||
private readonly ITargetBlock<T> _target;
|
||||
|
||||
/// <summary>Initializes the passthrough.</summary>
|
||||
/// <param name="owningSource">The source that encapsulates this block.</param>
|
||||
/// <param name="target">The target to which messages should be forwarded.</param>
|
||||
internal NopLinkPropagator(ISourceBlock<T> owningSource, ITargetBlock<T> target)
|
||||
{
|
||||
Contract.Requires(owningSource != null, "Propagator must be associated with a source.");
|
||||
Contract.Requires(target != null, "Target to propagate to is required.");
|
||||
|
||||
// Store the arguments
|
||||
_owningSource = owningSource;
|
||||
_target = target;
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
|
||||
DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source, Boolean consumeToAccept)
|
||||
{
|
||||
Debug.Assert(source == _owningSource, "Only valid to be used with the source for which it was created.");
|
||||
return _target.OfferMessage(messageHeader, messageValue, this, consumeToAccept);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
|
||||
T ISourceBlock<T>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target, out Boolean messageConsumed)
|
||||
{
|
||||
return _owningSource.ConsumeMessage(messageHeader, this, out messageConsumed);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
|
||||
bool ISourceBlock<T>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
|
||||
{
|
||||
return _owningSource.ReserveMessage(messageHeader, this);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
|
||||
void ISourceBlock<T>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
|
||||
{
|
||||
_owningSource.ReleaseReservation(messageHeader, this);
|
||||
}
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
|
||||
Task IDataflowBlock.Completion { get { return _owningSource.Completion; } }
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
|
||||
void IDataflowBlock.Complete() { _target.Complete(); }
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
|
||||
void IDataflowBlock.Fault(Exception exception) { _target.Fault(exception); }
|
||||
|
||||
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
|
||||
IDisposable ISourceBlock<T>.LinkTo(ITargetBlock<T> target, DataflowLinkOptions linkOptions) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
|
||||
|
||||
/// <summary>The data to display in the debugger display attribute.</summary>
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
|
||||
private object DebuggerDisplayContent
|
||||
{
|
||||
get
|
||||
{
|
||||
var displaySource = _owningSource as IDebuggerDisplay;
|
||||
var displayTarget = _target as IDebuggerDisplay;
|
||||
return string.Format("{0} Source=\"{1}\", Target=\"{2}\"",
|
||||
Common.GetNameForDebugger(this),
|
||||
displaySource != null ? displaySource.Content : _owningSource,
|
||||
displayTarget != null ? displayTarget.Content : _target);
|
||||
}
|
||||
}
|
||||
/// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
|
||||
object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
|
||||
|
||||
/// <summary>Provides a debugger type proxy for a passthrough.</summary>
|
||||
private sealed class DebugView
|
||||
{
|
||||
/// <summary>The passthrough.</summary>
|
||||
private readonly NopLinkPropagator _passthrough;
|
||||
|
||||
/// <summary>Initializes the debug view.</summary>
|
||||
/// <param name="passthrough">The passthrough to view.</param>
|
||||
public DebugView(NopLinkPropagator passthrough)
|
||||
{
|
||||
Contract.Requires(passthrough != null, "Need a propagator with which to construct the debug view.");
|
||||
_passthrough = passthrough;
|
||||
}
|
||||
|
||||
/// <summary>The linked target for this block.</summary>
|
||||
public ITargetBlock<T> LinkedTarget { get { return _passthrough._target; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Provides a debugger type proxy for the target registry.</summary>
|
||||
private sealed class DebugView
|
||||
{
|
||||
/// <summary>The registry being debugged.</summary>
|
||||
private readonly TargetRegistry<T> _registry;
|
||||
|
||||
/// <summary>Initializes the type proxy.</summary>
|
||||
/// <param name="registry">The target registry.</param>
|
||||
public DebugView(TargetRegistry<T> registry)
|
||||
{
|
||||
Contract.Requires(registry != null, "Need a registry with which to construct the debug view.");
|
||||
_registry = registry;
|
||||
}
|
||||
|
||||
/// <summary>Gets a list of all targets to show in the debugger.</summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public ITargetBlock<T>[] Targets { get { return _registry.TargetsForDebugger; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Threading.Tasks.Dataflow.Internal.Threading
|
||||
{
|
||||
internal delegate void TimerCallback(object state);
|
||||
|
||||
internal sealed class Timer : CancellationTokenSource, IDisposable
|
||||
{
|
||||
internal Timer(TimerCallback callback, object state, int dueTime, int period)
|
||||
{
|
||||
Debug.Assert(period == -1, "This stub implementation only supports dueTime.");
|
||||
Task.Delay(dueTime, Token).ContinueWith((t, s) =>
|
||||
{
|
||||
var tuple = (Tuple<TimerCallback, object>)s;
|
||||
tuple.Item1(tuple.Item2);
|
||||
}, Tuple.Create(callback, state), CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
|
||||
public new void Dispose() { base.Cancel(); }
|
||||
}
|
||||
|
||||
internal sealed class Thread
|
||||
{
|
||||
internal static bool Yield() { return true; }
|
||||
}
|
||||
|
||||
internal delegate void WaitCallback(object state);
|
||||
|
||||
internal sealed class ThreadPool
|
||||
{
|
||||
private static readonly SynchronizationContext _ctx = new SynchronizationContext();
|
||||
|
||||
internal static void QueueUserWorkItem(WaitCallback callback, object state)
|
||||
{
|
||||
_ctx.Post(s =>
|
||||
{
|
||||
var tuple = (Tuple<WaitCallback, object>)s;
|
||||
tuple.Item1(tuple.Item2);
|
||||
}, Tuple.Create(callback, state));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user