// 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 { /// Stores targets registered with a source. /// Specifies the type of data accepted by the targets. /// This type is not thread-safe. [DebuggerDisplay("Count={Count}")] [DebuggerTypeProxy(typeof(TargetRegistry<>.DebugView))] internal sealed class TargetRegistry { /// /// Information about a registered target. This class represents a self-sufficient node in a linked list. /// internal sealed class LinkedTargetInfo { /// Initializes the LinkedTargetInfo. /// The target block reference for this entry. /// The link options. internal LinkedTargetInfo(ITargetBlock 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; } /// The target block reference for this entry. internal readonly ITargetBlock Target; /// The value of the PropagateCompletion link option. internal readonly bool PropagateCompletion; /// Number of remaining messages to propagate. /// This counter is initialized to the MaxMessages option and /// gets decremented after each successful propagation. internal int RemainingMessages; /// The previous node in the list. internal LinkedTargetInfo Previous; /// The next node in the list. internal LinkedTargetInfo Next; } /// A reference to the owning source block. private readonly ISourceBlock _owningSource; /// A mapping of targets to information about them. private readonly Dictionary, LinkedTargetInfo> _targetInformation; /// The first node of an ordered list of targets. Messages should be offered to targets starting from First and following Next. private LinkedTargetInfo _firstTarget; /// 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. private LinkedTargetInfo _lastTarget; /// Number of links with positive RemainingMessages counters. /// This is an optimization that allows us to skip dictionary lookup when this counter is 0. private int _linksWithRemainingMessages; /// Initializes the registry. internal TargetRegistry(ISourceBlock owningSource) { Contract.Requires(owningSource != null, "The TargetRegistry instance must be owned by a source block."); _owningSource = owningSource; _targetInformation = new Dictionary, LinkedTargetInfo>(); } /// Adds a target to the registry. /// The target to add. /// The link options. internal void Add(ref ITargetBlock 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 } /// Gets whether the registry contains a particular target. /// The target. /// true if the registry contains the target; otherwise, false. internal bool Contains(ITargetBlock target) { return _targetInformation.ContainsKey(target); } /// Removes the target from the registry. /// The target to remove. /// /// Only remove the target if it's configured to be unlinked after one propagation. /// internal void Remove(ITargetBlock 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); } /// Actually removes the target from the registry. /// The target to remove. /// /// Only remove the target if it's configured to be unlinked after one propagation. /// private void Remove_Slow(ITargetBlock 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--; } } } /// Clears the target registry entry points while allowing subsequent traversals of the linked list. 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; } /// Propagated completion to the targets of the given linked list. /// The head of a saved linked list. 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); } } /// Gets the first node of the ordered target list. internal LinkedTargetInfo FirstTargetNode { get { return _firstTarget; } } /// Adds a LinkedTargetInfo node to the doubly-linked list. /// The node to be added. /// Whether to append or to prepend the node. 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."); } /// Removes the LinkedTargetInfo node from the doubly-linked list. /// The node to be removed. 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."); } /// Gets the number of items in the registry. private int Count { get { return _targetInformation.Count; } } /// Converts the linked list of targets to an array for rendering in a debugger. private ITargetBlock[] TargetsForDebugger { get { var targets = new ITargetBlock[Count]; int i = 0; for (LinkedTargetInfo node = _firstTarget; node != null; node = node.Next) { targets[i++] = node.Target; } return targets; } } /// Provides a nop passthrough for use with TargetRegistry. [DebuggerDisplay("{DebuggerDisplayContent,nq}")] [DebuggerTypeProxy(typeof(TargetRegistry<>.NopLinkPropagator.DebugView))] private sealed class NopLinkPropagator : IPropagatorBlock, ISourceBlock, IDebuggerDisplay { /// The source that encapsulates this block. private readonly ISourceBlock _owningSource; /// The target with which this block is associated. private readonly ITargetBlock _target; /// Initializes the passthrough. /// The source that encapsulates this block. /// The target to which messages should be forwarded. internal NopLinkPropagator(ISourceBlock owningSource, ITargetBlock 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; } /// DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock 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); } /// T ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) { return _owningSource.ConsumeMessage(messageHeader, this, out messageConsumed); } /// bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) { return _owningSource.ReserveMessage(messageHeader, this); } /// void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) { _owningSource.ReleaseReservation(messageHeader, this); } /// Task IDataflowBlock.Completion { get { return _owningSource.Completion; } } /// void IDataflowBlock.Complete() { _target.Complete(); } /// void IDataflowBlock.Fault(Exception exception) { _target.Fault(exception); } /// IDisposable ISourceBlock.LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } /// The data to display in the debugger display attribute. [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); } } /// Gets the data to display in the debugger display attribute for this instance. object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } /// Provides a debugger type proxy for a passthrough. private sealed class DebugView { /// The passthrough. private readonly NopLinkPropagator _passthrough; /// Initializes the debug view. /// The passthrough to view. public DebugView(NopLinkPropagator passthrough) { Contract.Requires(passthrough != null, "Need a propagator with which to construct the debug view."); _passthrough = passthrough; } /// The linked target for this block. public ITargetBlock LinkedTarget { get { return _passthrough._target; } } } } /// Provides a debugger type proxy for the target registry. private sealed class DebugView { /// The registry being debugged. private readonly TargetRegistry _registry; /// Initializes the type proxy. /// The target registry. public DebugView(TargetRegistry registry) { Contract.Requires(registry != null, "Need a registry with which to construct the debug view."); _registry = registry; } /// Gets a list of all targets to show in the debugger. [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public ITargetBlock[] Targets { get { return _registry.TargetsForDebugger; } } } } }