Imported Upstream version 4.3.2.467

Former-commit-id: 9c2cb47f45fa221e661ab616387c9cda183f283d
This commit is contained in:
Xamarin Public Jenkins
2016-02-22 11:00:01 -05:00
parent f302175246
commit f3e3aab35a
4097 changed files with 122406 additions and 82300 deletions

View File

@@ -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

View File

@@ -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(); } }
}
}

View File

@@ -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; }
}
}

View File

@@ -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(); }
}
}

View File

@@ -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;
}
}

View File

@@ -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; } }
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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; } }
}
}
}

View File

@@ -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));
}
}
}