From d17c5407a4e537a024939ec619c7a73540e3560e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alexander=20K=C3=B6plinger?= Date: Mon, 16 Nov 2015 16:23:09 +0100 Subject: [PATCH] [System.Threading.Tasks.Dataflow] Replace implementation with CoreFx version We were seeing some random test failures on Jenkins with our Dataflow implementation. Replacing it with Microsoft's CoreFx version fixed those and makes us more compatible. While we'd ideally not ship this assembly at all with Mono (it doesn't ship with .NET), we shipped it in the past and as such people might rely on it so we can't remove it. The CoreFx commit this version was taken is 905a1940bcda0afdca2f14ceb2b0161ebc4d1d02. --- .../CoreFxSources/Base/DataflowBlock.cs | 2813 +++++++++++++++++ .../Base/DataflowBlockOptions.cs | 414 +++ .../CoreFxSources/Base/DataflowLinkOptions.cs | 113 + .../Base/DataflowMessageHeader.cs | 92 + .../Base/DataflowMessageStatus.cs | 47 + .../CoreFxSources/Base/IDataflowBlock.cs | 31 + .../CoreFxSources/Base/IPropagatorBlock.cs | 22 + .../Base/IReceivableSourceBlock.cs | 31 + .../CoreFxSources/Base/ISourceBlock.cs | 39 + .../CoreFxSources/Base/ITargetBlock.cs | 24 + .../CoreFxSources/Blocks/ActionBlock.cs | 383 +++ .../CoreFxSources/Blocks/BatchBlock.cs | 1206 +++++++ .../CoreFxSources/Blocks/BatchedJoinBlock.cs | 783 +++++ .../CoreFxSources/Blocks/BroadcastBlock.cs | 1262 ++++++++ .../CoreFxSources/Blocks/BufferBlock.cs | 492 +++ .../CoreFxSources/Blocks/JoinBlock.cs | 1482 +++++++++ .../CoreFxSources/Blocks/TransformBlock.cs | 427 +++ .../Blocks/TransformManyBlock.cs | 644 ++++ .../CoreFxSources/Blocks/WriteOnceBlock.cs | 568 ++++ .../CoreFxSources/Internal/ActionOnDispose.cs | 142 + .../CoreFxSources/Internal/Common.cs | 694 ++++ .../CoreFxSources/Internal/ConcurrentQueue.cs | 947 ++++++ .../Internal/DataflowEtwProvider.cs | 235 ++ .../Internal/EnumerableDebugView.cs | 57 + .../Internal/IDebuggerDisplay.cs | 27 + .../Internal/IProducerConsumerCollection.cs | 112 + .../CoreFxSources/Internal/ImmutableList.cs | 89 + .../CoreFxSources/Internal/Padding.cs | 37 + .../Internal/ProducerConsumerQueues.cs | 558 ++++ .../CoreFxSources/Internal/QueuedMap.cs | 230 ++ .../Internal/ReorderingBuffer.cs | 184 ++ .../CoreFxSources/Internal/SourceCore.cs | 1035 ++++++ .../CoreFxSources/Internal/SpscTargetCore.cs | 413 +++ .../CoreFxSources/Internal/TargetCore.cs | 881 ++++++ .../CoreFxSources/Internal/TargetRegistry.cs | 418 +++ .../CoreFxSources/Internal/Threading.cs | 52 + .../CoreFxSources/Resources/Strings.resx | 177 ++ .../System.Threading.Tasks.Dataflow/Makefile | 3 +- .../System.Threading.Tasks.Dataflow/README.md | 6 + .../System.Threading.Tasks.Dataflow/SR.cs | 27 + ...ystem.Threading.Tasks.Dataflow.dll.sources | 80 +- .../ActionBlock.cs | 150 - .../AsyncExecutingMessageBox.cs | 121 - .../BatchBlock.cs | 376 --- .../BatchedJoinBlock.cs | 270 -- .../BatchedJoinBlock`3.cs | 294 -- .../BroadcastBlock.cs | 146 - .../BroadcastOutgoingQueue.cs | 212 -- .../BufferBlock.cs | 132 - .../ChooserBlock.cs | 179 -- .../CompletionHelper.cs | 199 -- .../DataflowBlock.cs | 311 -- .../DataflowBlockOptions.cs | 94 - .../DataflowLinkOptions.cs | 55 - .../DataflowMessageHeader.cs | 73 - .../DataflowMessageStatus.cs | 41 - .../ExecutingMessageBox.cs | 67 - .../ExecutingMessageBoxBase.cs | 163 - .../ExecutionDataflowBlockOptions.cs | 55 - .../GroupingDataflowBlockOptions.cs | 57 - .../IDataflowBlock.cs | 30 - .../IPropagatorBlock.cs | 28 - .../IReceivableSourceBlock.cs | 30 - .../ISourceBlock.cs | 31 - .../ITargetBlock.cs | 30 - .../JoinBlock.cs | 256 -- .../JoinBlock`3.cs | 292 -- .../JoinTarget.cs | 84 - .../MessageBox.cs | 332 -- .../NameHelper.cs | 44 - .../NullTargetBlock.cs | 67 - .../ObservableDataflowBlock.cs | 83 - .../ObserverDataflowBlock.cs | 51 - .../OutgoingQueue.cs | 281 -- .../OutgoingQueueBase.cs | 177 -- .../OutputAvailableBlock.cs | 97 - .../PassingMessageBox.cs | 54 - .../PredicateBlock.cs | 131 - .../PropagatorWrapperBlock.cs | 92 - .../ReceiveBlock.cs | 149 - .../SendBlock.cs | 188 -- .../TargetCollection.cs | 523 --- .../TransformBlock.cs | 203 -- .../TransformManyBlock.cs | 223 -- .../WriteOnceBlock.cs | 150 - ....Threading.Tasks.Dataflow_test.dll.sources | 2 - .../CompletionHelperTest.cs | 81 - 87 files changed, 17231 insertions(+), 6750 deletions(-) create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowBlockOptions.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowLinkOptions.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowMessageHeader.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowMessageStatus.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IDataflowBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IPropagatorBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IReceivableSourceBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/ISourceBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/ITargetBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/ActionBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BatchBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BatchedJoinBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BroadcastBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BufferBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/JoinBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/TransformBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/TransformManyBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/WriteOnceBlock.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ActionOnDispose.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Common.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ConcurrentQueue.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/DataflowEtwProvider.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/EnumerableDebugView.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/IDebuggerDisplay.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/IProducerConsumerCollection.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ImmutableList.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Padding.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ProducerConsumerQueues.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/QueuedMap.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ReorderingBuffer.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/SourceCore.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/SpscTargetCore.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/TargetCore.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/TargetRegistry.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Threading.cs create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Resources/Strings.resx create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/README.md create mode 100644 mcs/class/System.Threading.Tasks.Dataflow/SR.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ActionBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/AsyncExecutingMessageBox.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchedJoinBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchedJoinBlock`3.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BroadcastBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BroadcastOutgoingQueue.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BufferBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ChooserBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/CompletionHelper.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowBlockOptions.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowLinkOptions.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowMessageHeader.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowMessageStatus.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutingMessageBox.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutingMessageBoxBase.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutionDataflowBlockOptions.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/GroupingDataflowBlockOptions.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IDataflowBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IPropagatorBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IReceivableSourceBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ISourceBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ITargetBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinBlock`3.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinTarget.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/MessageBox.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/NameHelper.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/NullTargetBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ObservableDataflowBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ObserverDataflowBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutgoingQueue.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutgoingQueueBase.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutputAvailableBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PassingMessageBox.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PredicateBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PropagatorWrapperBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ReceiveBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/SendBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TargetCollection.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TransformBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TransformManyBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/WriteOnceBlock.cs delete mode 100644 mcs/class/System.Threading.Tasks.Dataflow/Test/System.Threading.Tasks.Dataflow/CompletionHelperTest.cs diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowBlock.cs new file mode 100644 index 00000000000..a083e9d9beb --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowBlock.cs @@ -0,0 +1,2813 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// DataflowBlock.cs +// +// +// Common functionality for ITargetBlock, ISourceBlock, and IPropagatorBlock. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Security; +using System.Threading.Tasks.Dataflow.Internal; +using System.Threading.Tasks.Dataflow.Internal.Threading; + +namespace System.Threading.Tasks.Dataflow +{ + /// + /// Provides a set of static (Shared in Visual Basic) methods for working with dataflow blocks. + /// + public static class DataflowBlock + { + #region LinkTo + /// Links the to the specified . + /// The source from which to link. + /// The to which to connect the source. + /// An IDisposable that, upon calling Dispose, will unlink the source from the target. + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + public static IDisposable LinkTo( + this ISourceBlock source, + ITargetBlock target) + { + // Validate arguments + if (source == null) throw new ArgumentNullException("source"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + // This method exists purely to pass default DataflowLinkOptions + // to increase usability of the "90%" case. + return source.LinkTo(target, DataflowLinkOptions.Default); + } + + /// Links the to the specified using the specified filter. + /// The source from which to link. + /// The to which to connect the source. + /// The filter a message must pass in order for it to propagate from the source to the target. + /// An IDisposable that, upon calling Dispose, will unlink the source from the target. + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + public static IDisposable LinkTo( + this ISourceBlock source, + ITargetBlock target, + Predicate predicate) + { + // All argument validation handled by delegated method. + return LinkTo(source, target, DataflowLinkOptions.Default, predicate); + } + + /// Links the to the specified using the specified filter. + /// The source from which to link. + /// The to which to connect the source. + /// The filter a message must pass in order for it to propagate from the source to the target. + /// The options to use to configure the link. + /// An IDisposable that, upon calling Dispose, will unlink the source from the target. + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + public static IDisposable LinkTo( + this ISourceBlock source, + ITargetBlock target, + DataflowLinkOptions linkOptions, + Predicate predicate) + { + // Validate arguments + if (source == null) throw new ArgumentNullException("source"); + if (target == null) throw new ArgumentNullException("target"); + if (linkOptions == null) throw new ArgumentNullException("linkOptions"); + if (predicate == null) throw new ArgumentNullException("predicate"); + Contract.EndContractBlock(); + + // Create the filter, which links to the real target, and then + // link the real source to this intermediate filter. + var filter = new FilteredLinkPropagator(source, target, predicate); + return source.LinkTo(filter, linkOptions); + } + + /// Provides a synchronous filter for use in filtered LinkTos. + /// Specifies the type of data being filtered. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(FilteredLinkPropagator<>.DebugView))] + private sealed class FilteredLinkPropagator : IPropagatorBlock, IDebuggerDisplay + { + /// The source connected with this filter. + private readonly ISourceBlock _source; + /// The target with which this block is associated. + private readonly ITargetBlock _target; + /// The predicate provided by the user. + private readonly Predicate _userProvidedPredicate; + + /// Initializes the filter passthrough. + /// The source connected to this filter. + /// The target to which filtered messages should be passed. + /// The predicate to run for each messsage. + internal FilteredLinkPropagator(ISourceBlock source, ITargetBlock target, Predicate predicate) + { + Contract.Requires(source != null, "Filtered link requires a source to filter on."); + Contract.Requires(target != null, "Filtered link requires a target to filter to."); + Contract.Requires(predicate != null, "Filtered link requires a predicate to filter with."); + + // Store the arguments + _source = source; + _target = target; + _userProvidedPredicate = predicate; + } + + /// Runs the user-provided predicate over an item in the correct execution context. + /// The item to evaluate. + /// true if the item passed the filter; otherwise, false. + private bool RunPredicate(T item) + { + Contract.Requires(_userProvidedPredicate != null, "User-provided predicate is required."); + + return _userProvidedPredicate(item); // avoid state object allocation if execution context isn't needed + } + + /// Manually closes over state necessary in FilteredLinkPropagator. + private sealed class PredicateContextState + { + /// The input to be filtered. + internal readonly T Input; + /// The predicate function. + internal readonly Predicate Predicate; + /// The result of the filtering operation. + internal bool Output; + + /// Initializes the predicate state. + /// The input to be filtered. + /// The predicate function. + internal PredicateContextState(T input, Predicate predicate) + { + Contract.Requires(predicate != null, "A predicate with which to filter is required."); + this.Input = input; + this.Predicate = predicate; + } + + /// Runs the predicate function over the input and stores the result into the output. + internal void Run() + { + Contract.Requires(Predicate != null, "Non-null predicate required"); + Output = Predicate(Input); + } + } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments. Some targets may have a null source, but FilteredLinkPropagator + // is an internal target that should only ever have source non-null. + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null) throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + // Run the filter. + bool passedFilter = RunPredicate(messageValue); + + // If the predicate matched, pass the message along to the real target. + if (passedFilter) + { + return _target.OfferMessage(messageHeader, messageValue, this, consumeToAccept); + } + // Otherwise, decline. + else return DataflowMessageStatus.Declined; + } + + /// + T ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + // This message should have only made it to the target if it passes the filter, so we shouldn't need to check again. + // The real source will also be doing verifications, so we don't need to validate args here. + Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed."); + return _source.ConsumeMessage(messageHeader, this, out messageConsumed); + } + + /// + bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // This message should have only made it to the target if it passes the filter, so we shouldn't need to check again. + // The real source will also be doing verifications, so we don't need to validate args here. + Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed."); + return _source.ReserveMessage(messageHeader, this); + } + + /// + void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // This message should have only made it to the target if it passes the filter, so we shouldn't need to check again. + // The real source will also be doing verifications, so we don't need to validate args here. + Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed."); + _source.ReleaseReservation(messageHeader, this); + } + + /// + Task IDataflowBlock.Completion { get { return _source.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 = _source as IDebuggerDisplay; + var displayTarget = _target as IDebuggerDisplay; + return string.Format("{0} Source=\"{1}\", Target=\"{2}\"", + Common.GetNameForDebugger(this), + displaySource != null ? displaySource.Content : _source, + 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 filter. + private sealed class DebugView + { + /// The filter. + private readonly FilteredLinkPropagator _filter; + + /// Initializes the debug view. + /// The filter to view. + public DebugView(FilteredLinkPropagator filter) + { + Contract.Requires(filter != null, "Need a filter with which to construct the debug view."); + _filter = filter; + } + + /// The linked target for this filter. + public ITargetBlock LinkedTarget { get { return _filter._target; } } + } + } + #endregion + + #region Post and SendAsync + /// Posts an item to the . + /// Specifies the type of data accepted by the target block. + /// The target block. + /// The item being offered to the target. + /// true if the item was accepted by the target block; otherwise, false. + /// + /// This method will return once the target block has decided to accept or decline the item, + /// but unless otherwise dictated by special semantics of the target block, it does not wait + /// for the item to actually be processed (for example, + /// will return from Post as soon as it has stored the posted item into its input queue). From the perspective + /// of the block's processing, Post is asynchronous. For target blocks that support postponing offered messages, + /// or for blocks that may do more processing in their Post implementation, consider using + /// SendAsync, + /// which will return immediately and will enable the target to postpone the posted message and later consume it + /// after SendAsync returns. + /// + public static Boolean Post(this ITargetBlock target, TInput item) + { + if (target == null) throw new ArgumentNullException("target"); + return target.OfferMessage(Common.SingleMessageHeader, item, source: null, consumeToAccept: false) == DataflowMessageStatus.Accepted; + } + + /// Asynchronously offers a message to the target message block, allowing for postponement. + /// Specifies the type of the data to post to the target. + /// The target to which to post the data. + /// The item being offered to the target. + /// + /// A that represents the asynchronous send. If the target + /// accepts and consumes the offered element during the call to SendAsync, upon return + /// from the call the resulting will be completed and its Result + /// property will return true. If the target declines the offered element during the call, upon return from the call the resulting will + /// be completed and its Result property will return false. If the target + /// postpones the offered element, the element will be buffered until such time that the target consumes or releases it, at which + /// point the Task will complete, with its indicating whether the message was consumed. If the target + /// never attempts to consume or release the message, the returned task will never complete. + /// + /// The is null (Nothing in Visual Basic). + public static Task SendAsync(this ITargetBlock target, TInput item) + { + return SendAsync(target, item, CancellationToken.None); + } + + /// Asynchronously offers a message to the target message block, allowing for postponement. + /// Specifies the type of the data to post to the target. + /// The target to which to post the data. + /// The item being offered to the target. + /// The cancellation token with which to request cancellation of the send operation. + /// + /// + /// A that represents the asynchronous send. If the target + /// accepts and consumes the offered element during the call to SendAsync, upon return + /// from the call the resulting will be completed and its Result + /// property will return true. If the target declines the offered element during the call, upon return from the call the resulting will + /// be completed and its Result property will return false. If the target + /// postpones the offered element, the element will be buffered until such time that the target consumes or releases it, at which + /// point the Task will complete, with its indicating whether the message was consumed. If the target + /// never attempts to consume or release the message, the returned task will never complete. + /// + /// + /// If cancellation is requested before the target has successfully consumed the sent data, + /// the returned task will complete in the Canceled state and the data will no longer be available to the target. + /// + /// + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public static Task SendAsync(this ITargetBlock target, TInput item, CancellationToken cancellationToken) + { + // Validate arguments. No validation necessary for item. + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + // Fast path check for cancellation + if (cancellationToken.IsCancellationRequested) + return Common.CreateTaskFromCancellation(cancellationToken); + + SendAsyncSource source; + + // Fast path: try to offer the item synchronously. This first try is done + // without any form of cancellation, and thus consumeToAccept can be the better-performing "false". + try + { + switch (target.OfferMessage(Common.SingleMessageHeader, item, source: null, consumeToAccept: false)) + { + // If the message is immediately accepted, return a cached completed task with a true result + case DataflowMessageStatus.Accepted: + return Common.CompletedTaskWithTrueResult; + + // If the target is declining permanently, return a cached completed task with a false result + case DataflowMessageStatus.DecliningPermanently: + return Common.CompletedTaskWithFalseResult; + +#if DEBUG + case DataflowMessageStatus.Postponed: + Debug.Assert(false, "A message should never be postponed when no source has been provided"); + break; + + case DataflowMessageStatus.NotAvailable: + Debug.Assert(false, "The message should never be missed, as it's offered to only this one target"); + break; +#endif + } + + // Slow path: the target did not accept the synchronous post, nor did it decline it. + // Create a source for the send, launch the offering, and return the representative task. + // This ctor attempts to register a cancellation notification which would throw if the + // underlying CTS has been disposed of. Therefore, keep it inside the try/catch block. + source = new SendAsyncSource(target, item, cancellationToken); + } + catch (Exception exc) + { + // If the target throws from OfferMessage, return a faulted task + Common.StoreDataflowMessageValueIntoExceptionData(exc, item); + return Common.CreateTaskFromException(exc); + } + + Debug.Assert(source != null, "The SendAsyncSource instance must have been constructed."); + source.OfferToTarget(); // synchronous to preserve message ordering + return source.Task; + } + + /// + /// Provides a source used by SendAsync that will buffer a single message and signal when it's been accepted or declined. + /// + /// This source must only be passed to a single target, and must only be used once. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(SendAsyncSource<>.DebugView))] + private sealed class SendAsyncSource : TaskCompletionSource, ISourceBlock, IDebuggerDisplay + { + /// The target to offer to. + private readonly ITargetBlock _target; + /// The buffered message. + private readonly TOutput _messageValue; + + /// CancellationToken used to cancel the send. + private CancellationToken _cancellationToken; + /// Registration with the cancellation token. + private CancellationTokenRegistration _cancellationRegistration; + /// The cancellation/completion state of the source. + private int _cancellationState; // one of the CANCELLATION_STATE_* constant values, defaulting to NONE + + // Cancellation states: + // _cancellationState starts out as NONE, and will remain that way unless a CancellationToken + // is provided in the initial OfferToTarget call. As such, unless a token is provided, + // all synchronization related to cancellation will be avoided. Once a token is provided, + // the state transitions to REGISTERED. If cancellation then is requested or if the target + // calls back to consume the message, the state will transition to COMPLETING prior to + // actually committing the action; if it can't transition to COMPLETING, then the action doesn't + // take effect (e.g. if cancellation raced with the target consuming, such that the cancellation + // action was able to transition to COMPLETING but the consumption wasn't, then ConsumeMessage + // would return false indicating that the message could not be consumed). The only additional + // complication here is around reservations. If a target reserves a message, _cancellationState + // transitions to RESERVED. A subsequent ConsumeMessage call can successfully transition from + // RESERVED to COMPLETING, but cancellation can't; cancellation can only transition from REGISTERED + // to COMPLETING. If the reservation on the message is instead released, _cancellationState + // will transition back to REGISTERED. + + /// No cancellation registration is used. + private const int CANCELLATION_STATE_NONE = 0; + /// A cancellation token has been registered. + private const int CANCELLATION_STATE_REGISTERED = 1; + /// The message has been reserved. Only used if a cancellation token is in play. + private const int CANCELLATION_STATE_RESERVED = 2; + /// Completion is now in progress. Only used if a cancellation token is in play. + private const int CANCELLATION_STATE_COMPLETING = 3; + + /// Initializes the source. + /// The target to offer to. + /// The message to offer and buffer. + /// The cancellation token with which to cancel the send. + internal SendAsyncSource(ITargetBlock target, TOutput messageValue, CancellationToken cancellationToken) + { + Contract.Requires(target != null, "A valid target to send to is required."); + _target = target; + _messageValue = messageValue; + + // If a cancelable CancellationToken is used, update our cancellation state + // and register with the token. Only if CanBeCanceled is true due we want + // to pay the subsequent costs around synchronization between cancellation + // requests and the target coming back to consume the message. + if (cancellationToken.CanBeCanceled) + { + _cancellationToken = cancellationToken; + _cancellationState = CANCELLATION_STATE_REGISTERED; + + try + { + _cancellationRegistration = cancellationToken.Register( + _cancellationCallback, new WeakReference>(this)); + } + catch + { + // Suppress finalization. Finalization is only required if the target drops a reference + // to the source before the source has completed, and we'll never offer to the target. + GC.SuppressFinalize(this); + + // Propagate the exception + throw; + } + } + } + + /// Finalizer that completes the returned task if all references to this source are dropped. + ~SendAsyncSource() + { + // CompleteAsDeclined uses synchronization, which is dangerous for a finalizer + // during shutdown or appdomain unload. + if (!Environment.HasShutdownStarted) + { + CompleteAsDeclined(runAsync: true); + } + } + + /// Completes the source in an "Accepted" state. + /// true to accept asynchronously; false to accept synchronously. + private void CompleteAsAccepted(bool runAsync) + { + RunCompletionAction(state => + { + try { ((SendAsyncSource)state).TrySetResult(true); } + catch (ObjectDisposedException) { } + }, this, runAsync); + } + + /// Completes the source in an "Declined" state. + /// true to decline asynchronously; false to decline synchronously. + private void CompleteAsDeclined(bool runAsync) + { + RunCompletionAction(state => + { + // The try/catch for ObjectDisposedException handles the case where the + // user disposes of the returned task before we're done with it. + try { ((SendAsyncSource)state).TrySetResult(false); } + catch (ObjectDisposedException) { } + }, this, runAsync); + } + + /// Completes the source in faulted state. + /// The exception with which to fault. + /// true to fault asynchronously; false to fault synchronously. + private void CompleteAsFaulted(Exception exception, bool runAsync) + { + RunCompletionAction(state => + { + var tuple = (Tuple, Exception>)state; + try { tuple.Item1.TrySetException(tuple.Item2); } + catch (ObjectDisposedException) { } + }, Tuple.Create, Exception>(this, exception), runAsync); + } + + /// Completes the source in canceled state. + /// true to fault asynchronously; false to fault synchronously. + private void CompleteAsCanceled(bool runAsync) + { + RunCompletionAction(state => + { + try { ((SendAsyncSource)state).TrySetCanceled(); } + catch (ObjectDisposedException) { } + }, this, runAsync); + } + + /// Executes a completion action. + /// The action to execute, passed the state. + /// The state to pass into the delegate. + /// true to execute the action asynchronously; false to execute it synchronously. + /// + /// async should be true if this is being called on a path that has the target on the stack, e.g. + /// the target is calling to ConsumeMessage. We don't want to block the target indefinitely + /// with any synchronous continuations off of the returned send async task. + /// + [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")] + private void RunCompletionAction(Action completionAction, object completionActionState, bool runAsync) + { + Contract.Requires(completionAction != null, "Completion action to run is required."); + + // Suppress finalization. Finalization is only required if the target drops a reference + // to the source before the source has completed, and here we're completing the source. + GC.SuppressFinalize(this); + + // Dispose of the cancellation registration if there is one + if (_cancellationState != CANCELLATION_STATE_NONE) + { + Debug.Assert(_cancellationRegistration != default(CancellationTokenRegistration), + "If we're not in NONE, we must have a cancellation token we've registered with."); + _cancellationRegistration.Dispose(); + } + + // If we're meant to run asynchronously, launch a task. + if (runAsync) + { + System.Threading.Tasks.Task.Factory.StartNew( + completionAction, completionActionState, + CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + // Otherwise, execute directly. + else + { + completionAction(completionActionState); + } + } + + /// Offers the message to the target asynchronously. + private void OfferToTargetAsync() + { + System.Threading.Tasks.Task.Factory.StartNew( + state => ((SendAsyncSource)state).OfferToTarget(), this, + CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + + /// Cached delegate used to cancel a send in response to a cancellation request. + private readonly static Action _cancellationCallback = CancellationHandler; + + /// Attempts to cancel the source passed as state in response to a cancellation request. + /// + /// A weak reference to the SendAsyncSource. A weak reference is used to prevent the source + /// from being rooted in a long-lived token. + /// + private static void CancellationHandler(object state) + { + SendAsyncSource source = Common.UnwrapWeakReference>(state); + if (source != null) + { + Debug.Assert(source._cancellationState != CANCELLATION_STATE_NONE, + "If cancellation is in play, we must have already moved out of the NONE state."); + + // Try to reserve completion, and if we can, complete as canceled. Note that we can only + // achieve cancellation when in the REGISTERED state, and not when in the RESERVED state, + // as if a target has reserved the message, we must allow the message to be consumed successfully. + if (source._cancellationState == CANCELLATION_STATE_REGISTERED && // fast check to avoid the interlocked if we can + Interlocked.CompareExchange(ref source._cancellationState, CANCELLATION_STATE_COMPLETING, CANCELLATION_STATE_REGISTERED) == CANCELLATION_STATE_REGISTERED) + { + // We've reserved completion, so proceed to cancel the task. + source.CompleteAsCanceled(true); + } + } + } + + /// Offers the message to the target synchronously. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal void OfferToTarget() + { + try + { + // Offer the message to the target. If there's no cancellation in play, we can just allow the target + // to accept the message directly. But if a CancellationToken is in use, the target needs to come + // back to us to get the data; that way, we can ensure we don't race between returning a canceled task but + // successfully completing the send. + bool consumeToAccept = _cancellationState != CANCELLATION_STATE_NONE; + + switch (_target.OfferMessage( + Common.SingleMessageHeader, _messageValue, this, consumeToAccept: consumeToAccept)) + { + // If the message is immediately accepted, complete the task as accepted + case DataflowMessageStatus.Accepted: + if (!consumeToAccept) + { + // Cancellation wasn't in use, and the target accepted the message directly, + // so complete the task as accepted. + CompleteAsAccepted(runAsync: false); + } + else + { + // If cancellation is in use, then since the target accepted, + // our state better reflect that we're completing. + Debug.Assert(_cancellationState == CANCELLATION_STATE_COMPLETING, + "The message was accepted, so we should have started completion."); + } + break; + + // If the message is immediately declined, complete the task as declined + case DataflowMessageStatus.Declined: + case DataflowMessageStatus.DecliningPermanently: + CompleteAsDeclined(runAsync: false); + break; +#if DEBUG + case DataflowMessageStatus.NotAvailable: + Debug.Assert(false, "The message should never be missed, as it's offered to only this one target"); + break; + // If the message was postponed, the source may or may not be complete yet. Nothing to validate. + // Treat an improper DataflowMessageStatus as postponed and do nothing. +#endif + } + } + // A faulty target might throw from OfferMessage. If that happens, + // we'll try to fault the returned task. A really faulty target might + // both throw from OfferMessage and call ConsumeMessage, + // in which case it's possible we might not be able to propagate the exception + // out to the caller through the task if ConsumeMessage wins the race, + // which is likely if the exception doesn't occur until after ConsumeMessage is + // called. If that happens, we just eat the exception. + catch (Exception exc) + { + Common.StoreDataflowMessageValueIntoExceptionData(exc, _messageValue); + CompleteAsFaulted(exc, runAsync: false); + } + } + + /// Called by the target to consume the buffered message. + TOutput ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + // If the task has already completed, there's nothing to consume. This could happen if + // cancellation was already requested and completed the task as a result. + if (Task.IsCompleted) + { + messageConsumed = false; + return default(TOutput); + } + + // If the message being asked for is not the same as the one that's buffered, + // something is wrong. Complete as having failed to transfer the message. + bool validMessage = (messageHeader.Id == Common.SINGLE_MESSAGE_ID); + + if (validMessage) + { + int curState = _cancellationState; + Debug.Assert( + curState == CANCELLATION_STATE_NONE || curState == CANCELLATION_STATE_REGISTERED || + curState == CANCELLATION_STATE_RESERVED || curState == CANCELLATION_STATE_COMPLETING, + "The current cancellation state is not valid."); + + // If we're not dealing with cancellation, then if we're currently registered or reserved, try to transition + // to completing. If we're able to, allow the message to be consumed, and we're done. At this point, we + // support transitioning out of REGISTERED or RESERVED. + if (curState == CANCELLATION_STATE_NONE || // no synchronization necessary if there's no cancellation + (curState != CANCELLATION_STATE_COMPLETING && // fast check to avoid unnecessary synchronization + Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_COMPLETING, curState) == curState)) + { + CompleteAsAccepted(runAsync: true); + messageConsumed = true; + return _messageValue; + } + } + + // Consumption failed + messageConsumed = false; + return default(TOutput); + } + + /// Called by the target to reserve the buffered message. + bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + // If the task has already completed, such as due to cancellation, there's nothing to reserve. + if (Task.IsCompleted) return false; + + // As long as the message is the one being requested and cancellation hasn't been requested, allow it to be reserved. + bool reservable = (messageHeader.Id == Common.SINGLE_MESSAGE_ID); + return reservable && + (_cancellationState == CANCELLATION_STATE_NONE || // avoid synchronization when cancellation is not in play + Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_RESERVED, CANCELLATION_STATE_REGISTERED) == CANCELLATION_STATE_REGISTERED); + } + + /// Called by the target to release a reservation on the buffered message. + void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + // If this is not the message we posted, bail + if (messageHeader.Id != Common.SINGLE_MESSAGE_ID) + throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget); + + // If the task has already completed, there's nothing to release. + if (Task.IsCompleted) return; + + // If a cancellation token is being used, revert our state back to registered. In the meantime + // cancellation could have been requested, so check to see now if cancellation was requested + // and process it if it was. + if (_cancellationState != CANCELLATION_STATE_NONE) + { + if (Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_REGISTERED, CANCELLATION_STATE_RESERVED) != CANCELLATION_STATE_RESERVED) + throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget); + if (_cancellationToken.IsCancellationRequested) + CancellationHandler(new WeakReference>(this)); // same code as registered with the CancellationToken + } + + // Start the process over by reoffering the message asynchronously. + OfferToTargetAsync(); + } + + /// + Task IDataflowBlock.Completion { get { return Task; } } + + /// + IDisposable ISourceBlock.LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } + /// + void IDataflowBlock.Complete() { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } + /// + void IDataflowBlock.Fault(Exception exception) { 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 displayTarget = _target as IDebuggerDisplay; + return string.Format("{0} Message={1}, Target=\"{2}\"", + Common.GetNameForDebugger(this), + _messageValue, + 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 the source. + private sealed class DebugView + { + /// The source. + private readonly SendAsyncSource _source; + + /// Initializes the debug view. + /// The source to view. + public DebugView(SendAsyncSource source) + { + Contract.Requires(source != null, "Need a source with which to construct the debug view."); + _source = source; + } + + /// The target to which we're linked. + public ITargetBlock Target { get { return _source._target; } } + /// The message buffered by the source. + public TOutput Message { get { return _source._messageValue; } } + /// The Task represented the posting of the message. + public Task Completion { get { return _source.Task; } } + } + } + #endregion + + #region TryReceive, ReceiveAsync, and Receive + #region TryReceive + /// + /// Attempts to synchronously receive an item from the . + /// + /// The source from which to receive. + /// The item received from the source. + /// true if an item could be received; otherwise, false. + /// + /// This method does not wait until the source has an item to provide. + /// It will return whether or not an element was available. + /// + public static bool TryReceive(this IReceivableSourceBlock source, out TOutput item) + { + if (source == null) throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return source.TryReceive(null, out item); + } + #endregion + + #region ReceiveAsync + /// Asynchronously receives a value from the specified source. + /// Specifies the type of data contained in the source. + /// The source from which to asynchronously receive. + /// + /// A that represents the asynchronous receive operation. When an item is successfully received from the source, + /// the returned task will be completed and its Result will return the received item. If an item cannot be retrieved, + /// because the source is empty and completed, the returned task will be canceled. + /// + /// The is null (Nothing in Visual Basic). + public static Task ReceiveAsync( + this ISourceBlock source) + { + // Argument validation handled by target method + return ReceiveAsync(source, Common.InfiniteTimeSpan, CancellationToken.None); + } + + /// Asynchronously receives a value from the specified source. + /// Specifies the type of data contained in the source. + /// The source from which to asynchronously receive. + /// The which may be used to cancel the receive operation. + /// + /// A that represents the asynchronous receive operation. When an item is successfully received from the source, + /// the returned task will be completed and its Result will return the received item. If an item cannot be retrieved, + /// either because cancellation is requested or the source is empty and completed, the returned task will be canceled. + /// + /// The is null (Nothing in Visual Basic). + public static Task ReceiveAsync( + this ISourceBlock source, CancellationToken cancellationToken) + { + // Argument validation handled by target method + return ReceiveAsync(source, Common.InfiniteTimeSpan, cancellationToken); + } + + /// Asynchronously receives a value from the specified source. + /// Specifies the type of data contained in the source. + /// The source from which to asynchronously receive. + /// A that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// + /// A that represents the asynchronous receive operation. When an item is successfully received from the source, + /// the returned task will be completed and its Result will return the received item. If an item cannot be retrieved, + /// either because the timeout expires or the source is empty and completed, the returned task will be canceled. + /// + /// The is null (Nothing in Visual Basic). + /// + /// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than . + /// + public static Task ReceiveAsync( + this ISourceBlock source, TimeSpan timeout) + { + // Argument validation handled by target method + return ReceiveAsync(source, timeout, CancellationToken.None); + } + + /// Asynchronously receives a value from the specified source. + /// Specifies the type of data contained in the source. + /// The source from which to asynchronously receive. + /// A that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// The which may be used to cancel the receive operation. + /// + /// A that represents the asynchronous receive operation. When an item is successfully received from the source, + /// the returned task will be completed and its Result will return the received item. If an item cannot be retrieved, + /// either because the timeout expires, cancellation is requested, or the source is empty and completed, the returned task will be canceled. + /// + /// The is null (Nothing in Visual Basic). + /// + /// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than . + /// + public static Task ReceiveAsync( + this ISourceBlock source, TimeSpan timeout, CancellationToken cancellationToken) + { + // Validate arguments + + + if (source == null) throw new ArgumentNullException("source"); + if (!Common.IsValidTimeout(timeout)) throw new ArgumentOutOfRangeException("timeout", SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + + // Return the task representing the core receive operation + return ReceiveCore(source, true, timeout, cancellationToken); + } + #endregion + + #region Receive + /// Synchronously receives an item from the source. + /// Specifies the type of data contained in the source. + /// The source from which to receive. + /// The received item. + /// The is null (Nothing in Visual Basic). + /// No item could be received from the source. + public static TOutput Receive( + this ISourceBlock source) + { + // Argument validation handled by target method + return Receive(source, Common.InfiniteTimeSpan, CancellationToken.None); + } + + /// Synchronously receives an item from the source. + /// Specifies the type of data contained in the source. + /// The source from which to receive. + /// The which may be used to cancel the receive operation. + /// The received item. + /// The is null (Nothing in Visual Basic). + /// No item could be received from the source. + /// The operation was canceled before an item was received from the source. + /// + /// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent cancellation request occurs. + /// + public static TOutput Receive( + this ISourceBlock source, CancellationToken cancellationToken) + { + // Argument validation handled by target method + return Receive(source, Common.InfiniteTimeSpan, cancellationToken); + } + + /// Synchronously receives an item from the source. + /// Specifies the type of data contained in the source. + /// The source from which to receive. + /// A that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// The received item. + /// + /// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than . + /// + /// The is null (Nothing in Visual Basic). + /// No item could be received from the source. + /// The specified timeout expired before an item was received from the source. + /// + /// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent timeout occurs. + /// + public static TOutput Receive( + this ISourceBlock source, TimeSpan timeout) + { + // Argument validation handled by target method + return Receive(source, timeout, CancellationToken.None); + } + + /// Synchronously receives an item from the source. + /// Specifies the type of data contained in the source. + /// The source from which to receive. + /// A that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// The which may be used to cancel the receive operation. + /// The received item. + /// The is null (Nothing in Visual Basic). + /// + /// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than . + /// + /// No item could be received from the source. + /// The specified timeout expired before an item was received from the source. + /// The operation was canceled before an item was received from the source. + /// + /// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent timeout or cancellation request occurs. + /// + [SuppressMessage("Microsoft.Usage", "CA2200:RethrowToPreserveStackDetails")] + public static TOutput Receive( + this ISourceBlock source, TimeSpan timeout, CancellationToken cancellationToken) + { + // Validate arguments + if (source == null) throw new ArgumentNullException("source"); + if (!Common.IsValidTimeout(timeout)) throw new ArgumentOutOfRangeException("timeout", SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + + // Do fast path checks for both cancellation and data already existing. + cancellationToken.ThrowIfCancellationRequested(); + TOutput fastCheckedItem; + var receivableSource = source as IReceivableSourceBlock; + if (receivableSource != null && receivableSource.TryReceive(null, out fastCheckedItem)) + { + return fastCheckedItem; + } + + // Get a TCS to represent the receive operation and wait for it to complete. + // If it completes successfully, return the result. Otherwise, throw the + // original inner exception representing the cause. This could be an OCE. + Task task = ReceiveCore(source, false, timeout, cancellationToken); + try + { + return task.GetAwaiter().GetResult(); // block until the result is available + } + catch + { + // Special case cancellation in order to ensure the exception contains the token. + // The public TrySetCanceled, used by ReceiveCore, is parameterless and doesn't + // accept the token to use. Thus the exception that we're catching here + // won't contain the cancellation token we want propagated. + if (task.IsCanceled) cancellationToken.ThrowIfCancellationRequested(); + + // If we get here, propagate the original exception. + throw; + } + } + #endregion + + #region Shared by Receive and ReceiveAsync + /// Receives an item from the source. + /// Specifies the type of data contained in the source. + /// The source from which to receive. + /// Whether to first attempt using TryReceive to get a value from the source. + /// A that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// The which may be used to cancel the receive operation. + /// A Task for the receive operation. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private static Task ReceiveCore( + this ISourceBlock source, bool attemptTryReceive, TimeSpan timeout, CancellationToken cancellationToken) + { + Contract.Requires(source != null, "Need a source from which to receive."); + + // If cancellation has been requested, we're done before we've even started, cancel this receive. + if (cancellationToken.IsCancellationRequested) + { + return Common.CreateTaskFromCancellation(cancellationToken); + } + + if (attemptTryReceive) + { + // If we're able to directly and immediately receive an item, use that item to complete the receive. + var receivableSource = source as IReceivableSourceBlock; + if (receivableSource != null) + { + try + { + TOutput fastCheckedItem; + if (receivableSource.TryReceive(null, out fastCheckedItem)) + { + return Task.FromResult(fastCheckedItem); + } + } + catch (Exception exc) + { + return Common.CreateTaskFromException(exc); + } + } + } + + int millisecondsTimeout = (int)timeout.TotalMilliseconds; + if (millisecondsTimeout == 0) + { + return Common.CreateTaskFromException(ReceiveTarget.CreateExceptionForTimeout()); + } + + return ReceiveCoreByLinking(source, millisecondsTimeout, cancellationToken); + } + + /// The reason for a ReceiveCoreByLinking call failing. + private enum ReceiveCoreByLinkingCleanupReason + { + /// The Receive operation completed successfully, obtaining a value from the source. + Success = 0, + /// The timer expired before a value could be received. + Timer = 1, + /// The cancellation token had cancellation requested before a value could be received. + Cancellation = 2, + /// The source completed before a value could be received. + SourceCompletion = 3, + /// An error occurred while linking up the target. + SourceProtocolError = 4, + /// An error during cleanup after completion for another reason. + ErrorDuringCleanup = 5 + } + + /// Cancels a CancellationTokenSource passed as the object state argument. + private static readonly Action _cancelCts = state => ((CancellationTokenSource)state).Cancel(); + + /// Receives an item from the source by linking a temporary target from it. + /// Specifies the type of data contained in the source. + /// The source from which to receive. + /// The number of milliseconds to wait, or -1 to wait indefinitely. + /// The which may be used to cancel the receive operation. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private static Task ReceiveCoreByLinking(ISourceBlock source, int millisecondsTimeout, CancellationToken cancellationToken) + { + // Create a target to link from the source + var target = new ReceiveTarget(); + + // Keep cancellation registrations inside the try/catch in case the underlying CTS is disposed in which case an exception is thrown + try + { + // Create a cancellation token that will be canceled when either the provided token + // is canceled or the source block completes. + if (cancellationToken.CanBeCanceled) + { + target._externalCancellationToken = cancellationToken; + target._regFromExternalCancellationToken = cancellationToken.Register(_cancelCts, target._cts); + } + + // We need to cleanup if one of a few things happens: + // - The target completes successfully due to receiving data. + // - The user-specified timeout occurs, such that we should bail on the receive. + // - The cancellation token has cancellation requested, such that we should bail on the receive. + // - The source completes, since it won't send any more data. + // Note that there's a potential race here, in that the cleanup delegate could be executed + // from the timer before the timer variable is set, but that's ok, because then timer variable + // will just show up as null in the cleanup and there will be nothing to dispose (nor will anything + // need to be disposed, since it's the timer that fired. Timer.Dispose is also thread-safe to be + // called multiple times concurrently.) + if (millisecondsTimeout > 0) + { + target._timer = new Timer( + ReceiveTarget.CachedLinkingTimerCallback, target, + millisecondsTimeout, Timeout.Infinite); + } + + if (target._cts.Token.CanBeCanceled) + { + target._cts.Token.Register( + ReceiveTarget.CachedLinkingCancellationCallback, target); // we don't have to cleanup this registration, as this cts is short-lived + } + + // Link the target to the source + IDisposable unlink = source.LinkTo(target, DataflowLinkOptions.UnlinkAfterOneAndPropagateCompletion); + target._unlink = unlink; + + // If completion has started, there is a chance it started after we linked. + // In that case, we must dispose of the unlinker. + // If completion started before we linked, the cleanup code will try to unlink. + // So we are racing to dispose of the unlinker. + if (Volatile.Read(ref target._cleanupReserved)) + { + IDisposable disposableUnlink = Interlocked.CompareExchange(ref target._unlink, null, unlink); + if (disposableUnlink != null) disposableUnlink.Dispose(); + } + } + catch (Exception exception) + { + target._receivedException = exception; + target.TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason.SourceProtocolError); + // If we lose the race here, we may end up eating this exception. + } + + return target.Task; + } + + /// Provides a TaskCompletionSource that is also a dataflow target for use in ReceiveCore. + /// Specifies the type of data offered to the target. + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + private sealed class ReceiveTarget : TaskCompletionSource, ITargetBlock, IDebuggerDisplay + { + /// Cached delegate used in ReceiveCoreByLinking on the created timer. Passed the ReceiveTarget as the argument. + /// The C# compiler will not cache this delegate by default due to it being a generic method on a non-generic class. + internal readonly static TimerCallback CachedLinkingTimerCallback = state => + { + var receiveTarget = (ReceiveTarget)state; + receiveTarget.TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason.Timer); + }; + + /// Cached delegate used in ReceiveCoreByLinking on the cancellation token. Passed the ReceiveTarget as the state argument. + /// The C# compiler will not cache this delegate by default due to it being a generic method on a non-generic class. + internal readonly static Action CachedLinkingCancellationCallback = state => + { + var receiveTarget = (ReceiveTarget)state; + receiveTarget.TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason.Cancellation); + }; + + /// The received value if we accepted a value from the source. + private T _receivedValue; + + /// The cancellation token source representing both external and internal cancellation. + internal readonly CancellationTokenSource _cts = new CancellationTokenSource(); + /// Indicates a code path is already on route to complete the target. 0 is false, 1 is true. + internal bool _cleanupReserved; // must only be accessed under IncomingLock + /// The external token that cancels the internal token. + internal CancellationToken _externalCancellationToken; + /// The registration on the external token that cancels the internal token. + internal CancellationTokenRegistration _regFromExternalCancellationToken; + /// The timer that fires when the timeout has been exceeded. + internal Timer _timer; + /// The unlinker from removing this target from the source from which we're receiving. + internal IDisposable _unlink; + /// The received exception if an error occurred. + internal Exception _receivedException; + + /// Gets the sync obj used to synchronize all activity on this target. + internal object IncomingLock { get { return _cts; } } + + /// Initializes the target. + internal ReceiveTarget() { } + + /// Offers a message to be used to complete the TaskCompletionSource. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + DataflowMessageStatus status = DataflowMessageStatus.NotAvailable; + + // If we're already one our way to being done, don't accept anything. + // This is a fast-path check prior to taking the incoming lock; + // _cleanupReserved only ever goes from false to true. + if (Volatile.Read(ref _cleanupReserved)) return DataflowMessageStatus.DecliningPermanently; + + lock (IncomingLock) + { + // Check again now that we've taken the lock + if (_cleanupReserved) return DataflowMessageStatus.DecliningPermanently; + + try + { + // Accept the message if possible and complete this task with the message's value. + bool consumed = true; + T acceptedValue = consumeToAccept ? source.ConsumeMessage(messageHeader, this, out consumed) : messageValue; + if (consumed) + { + status = DataflowMessageStatus.Accepted; + _receivedValue = acceptedValue; + _cleanupReserved = true; + } + } + catch (Exception exc) + { + // An error occurred. Take ourselves out of the game. + status = DataflowMessageStatus.DecliningPermanently; + Common.StoreDataflowMessageValueIntoExceptionData(exc, messageValue); + _receivedException = exc; + _cleanupReserved = true; + } + } + + // Do any cleanup outside of the lock. The right to cleanup was reserved above for these cases. + if (status == DataflowMessageStatus.Accepted) + { + CleanupAndComplete(ReceiveCoreByLinkingCleanupReason.Success); + } + else if (status == DataflowMessageStatus.DecliningPermanently) // should only be the case if an error occurred + { + CleanupAndComplete(ReceiveCoreByLinkingCleanupReason.SourceProtocolError); + } + + return status; + } + + /// + /// Attempts to reserve the right to cleanup and complete, and if successfully, + /// continues to cleanup and complete. + /// + /// The reason we're completing and cleaning up. + /// true if successful in completing; otherwise, false. + internal bool TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason reason) + { + // If cleanup was already reserved, bail. + if (Volatile.Read(ref _cleanupReserved)) return false; + + // Atomically using IncomingLock try to reserve the completion routine. + lock (IncomingLock) + { + if (_cleanupReserved) return false; + _cleanupReserved = true; + } + + // We've reserved cleanup and completion, so do it. + CleanupAndComplete(reason); + return true; + } + + /// Cleans up the target for completion. + /// The reason we're completing and cleaning up. + /// This method must only be called once on this instance. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] + private void CleanupAndComplete(ReceiveCoreByLinkingCleanupReason reason) + { + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + Debug.Assert(Volatile.Read(ref _cleanupReserved), "Should only be called once by whomever reserved the right."); + + // Unlink from the source. If we're cleaning up because the source + // completed, this is unnecessary, as the source should have already + // emptied out its target registry, or at least be in the process of doing so. + // We are racing with the linking code - only one can dispose of the unlinker. + IDisposable unlink = _unlink; + if (reason != ReceiveCoreByLinkingCleanupReason.SourceCompletion && unlink != null) + { + IDisposable disposableUnlink = Interlocked.CompareExchange(ref _unlink, null, unlink); + if (disposableUnlink != null) + { + // If an error occurs, fault the target and override the reason to + // continue executing, i.e. do the remaining cleanup without completing + // the target the way we originally intended to. + try + { + disposableUnlink.Dispose(); // must not be holding IncomingLock, or could deadlock + } + catch (Exception exc) + { + _receivedException = exc; + reason = ReceiveCoreByLinkingCleanupReason.SourceProtocolError; + } + } + } + + // Cleanup the timer. (Even if we're here because of the timer firing, we still + // want to aggressively dispose of the timer.) + if (_timer != null) _timer.Dispose(); + + // Cancel the token everyone is listening to. We also want to unlink + // from the user-provided cancellation token to prevent a leak. + // We do *not* dispose of the cts itself here, as there could be a race + // with the code registering this cleanup delegate with cts; not disposing + // is ok, though, because there's no resources created by the CTS + // that needs to be cleaned up since we're not using the wait handle. + // This is also why we don't use CreateLinkedTokenSource, as that combines + // both disposing of the token source and disposal of the connection link + // into a single dispose operation. + // if we're here because of cancellation, no need to cancel again + if (reason != ReceiveCoreByLinkingCleanupReason.Cancellation) + { + // if the source complete without receiving a value, we check the cancellation one more time + if (reason == ReceiveCoreByLinkingCleanupReason.SourceCompletion && + (_externalCancellationToken.IsCancellationRequested || _cts.IsCancellationRequested)) + { + reason = ReceiveCoreByLinkingCleanupReason.Cancellation; + } + _cts.Cancel(); + } + _regFromExternalCancellationToken.Dispose(); + + // No need to dispose of the cts, either, as we're not accessing its WaitHandle + // nor was it created as a linked token source. Disposing it could also be dangerous + // if other code tries to access it after we dispose of it... best to leave it available. + + // Complete the task based on the reason + switch (reason) + { + // Task final state: RanToCompletion + case ReceiveCoreByLinkingCleanupReason.Success: + System.Threading.Tasks.Task.Factory.StartNew(state => + { + // Complete with the received value + var target = (ReceiveTarget)state; + try { target.TrySetResult(target._receivedValue); } + catch (ObjectDisposedException) { /* benign race if returned task is already disposed */ } + }, this, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); + break; + + // Task final state: Canceled + case ReceiveCoreByLinkingCleanupReason.Cancellation: + System.Threading.Tasks.Task.Factory.StartNew(state => + { + // Complete as canceled + var target = (ReceiveTarget)state; + try { target.TrySetCanceled(); } + catch (ObjectDisposedException) { /* benign race if returned task is already disposed */ } + }, this, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); + break; + default: + Debug.Assert(false, "Invalid linking cleanup reason specified."); + goto case ReceiveCoreByLinkingCleanupReason.Cancellation; + + // Task final state: Faulted + case ReceiveCoreByLinkingCleanupReason.SourceCompletion: + if (_receivedException == null) _receivedException = CreateExceptionForSourceCompletion(); + goto case ReceiveCoreByLinkingCleanupReason.SourceProtocolError; + case ReceiveCoreByLinkingCleanupReason.Timer: + if (_receivedException == null) _receivedException = CreateExceptionForTimeout(); + goto case ReceiveCoreByLinkingCleanupReason.SourceProtocolError; + case ReceiveCoreByLinkingCleanupReason.SourceProtocolError: + case ReceiveCoreByLinkingCleanupReason.ErrorDuringCleanup: + Debug.Assert(_receivedException != null, "We need an exception with which to fault the task."); + System.Threading.Tasks.Task.Factory.StartNew(state => + { + // Complete with the received exception + var target = (ReceiveTarget)state; + try { target.TrySetException(target._receivedException ?? new Exception()); } + catch (ObjectDisposedException) { /* benign race if returned task is already disposed */ } + }, this, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); + break; + } + } + + /// Creates an exception to use when a source completed before receiving a value. + /// The initialized exception. + internal static Exception CreateExceptionForSourceCompletion() + { + return Common.InitializeStackTrace(new InvalidOperationException(SR.InvalidOperation_DataNotAvailableForReceive)); + } + + /// Creates an exception to use when a timeout occurs before receiving a value. + /// The initialized exception. + internal static Exception CreateExceptionForTimeout() + { + return Common.InitializeStackTrace(new TimeoutException()); + } + + /// + void IDataflowBlock.Complete() + { + TryCleanupAndComplete(ReceiveCoreByLinkingCleanupReason.SourceCompletion); + } + + /// + void IDataflowBlock.Fault(Exception exception) { ((IDataflowBlock)this).Complete(); } + + /// + Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0} IsCompleted={1}", + Common.GetNameForDebugger(this), base.Task.IsCompleted); + } + } + /// Gets the data to display in the debugger display attribute for this instance. + object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } + } + #endregion + #endregion + + #region OutputAvailableAsync + /// + /// Provides a + /// that asynchronously monitors the source for available output. + /// + /// Specifies the type of data contained in the source. + /// The source to monitor. + /// + /// A that informs of whether and when + /// more output is available. When the task completes, if its is true, more output + /// is available in the source (though another consumer of the source may retrieve the data). + /// If it returns false, more output is not and will never be available, due to the source + /// completing prior to output being available. + /// + public static Task OutputAvailableAsync(this ISourceBlock source) + { + return OutputAvailableAsync(source, CancellationToken.None); + } + + /// + /// Provides a + /// that asynchronously monitors the source for available output. + /// + /// Specifies the type of data contained in the source. + /// The source to monitor. + /// The cancellation token with which to cancel the asynchronous operation. + /// + /// A that informs of whether and when + /// more output is available. When the task completes, if its is true, more output + /// is available in the source (though another consumer of the source may retrieve the data). + /// If it returns false, more output is not and will never be available, due to the source + /// completing prior to output being available. + /// + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] + public static Task OutputAvailableAsync( + this ISourceBlock source, CancellationToken cancellationToken) + { + // Validate arguments + if (source == null) throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + // Fast path for cancellation + if (cancellationToken.IsCancellationRequested) + return Common.CreateTaskFromCancellation(cancellationToken); + + // In a method like this, normally we would want to check source.Completion.IsCompleted + // and avoid linking completely by simply returning a completed task. However, + // some blocks that are completed still have data available, like WriteOnceBlock, + // which completes as soon as it gets a value and stores that value forever. + // As such, OutputAvailableAsync must link from the source so that the source + // can push data to us if it has it, at which point we can immediately unlink. + + // Create a target task that will complete when it's offered a message (but it won't accept the message) + var target = new OutputAvailableAsyncTarget(); + try + { + // Link from the source. If the source propagates a message during or immediately after linking + // such that our target is already completed, just return its task. + target._unlinker = source.LinkTo(target, DataflowLinkOptions.UnlinkAfterOneAndPropagateCompletion); + + // If the task is already completed (an exception may have occurred, or the source may have propagated + // a message to the target during LinkTo or soon thereafter), just return the task directly. + if (target.Task.IsCompleted) + { + return target.Task; + } + + // If cancellation could be requested, hook everything up to be notified of cancellation requests. + if (cancellationToken.CanBeCanceled) + { + // When cancellation is requested, unlink the target from the source and cancel the target. + target._ctr = cancellationToken.Register(OutputAvailableAsyncTarget.s_cancelAndUnlink, target); + } + + // We can't return the task directly, as the source block will be completing the task synchronously, + // and thus any synchronous continuations would run as part of the source block's call. We don't have to worry + // about cancellation, as we've coded cancellation to complete the task asynchronously, and with the continuation + // set as NotOnCanceled, so the continuation will be canceled immediately when the antecedent is canceled, which + // will thus be asynchronously from the cancellation token source's cancellation call. + return target.Task.ContinueWith( + OutputAvailableAsyncTarget.s_handleCompletion, target, + CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default); + } + catch (Exception exc) + { + // Source.LinkTo could throw, as could cancellationToken.Register if cancellation was already requested + // such that it synchronously invokes the source's unlinker IDisposable, which could throw. + target.TrySetException(exc); + + // Undo the link from the source to the target + target.AttemptThreadSafeUnlink(); + + // Return the now faulted task + return target.Task; + } + } + + /// Provides a target used in OutputAvailableAsync operations. + /// Specifies the type of data in the data source being checked. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + private sealed class OutputAvailableAsyncTarget : TaskCompletionSource, ITargetBlock, IDebuggerDisplay + { + /// + /// Cached continuation delegate that unregisters from cancellation and + /// marshals the antecedent's result to the return value. + /// + internal readonly static Func, object, bool> s_handleCompletion = (antecedent, state) => + { + var target = state as OutputAvailableAsyncTarget; + Debug.Assert(target != null, "Expected non-null target"); + target._ctr.Dispose(); + return antecedent.GetAwaiter().GetResult(); + }; + + /// + /// Cached delegate that cancels the target and unlinks the target from the source. + /// Expects an OutputAvailableAsyncTarget as the state argument. + /// + internal readonly static Action s_cancelAndUnlink = CancelAndUnlink; + + /// Cancels the target and unlinks the target from the source. + /// An OutputAvailableAsyncTarget. + private static void CancelAndUnlink(object state) + { + var target = state as OutputAvailableAsyncTarget; + Debug.Assert(target != null, "Expected a non-null target"); + + // Cancel asynchronously so that we're not completing the task as part of the cts.Cancel() call, + // since synchronous continuations off that task would then run as part of Cancel. + // Take advantage of this task and unlink from there to avoid doing the interlocked operation synchronously. + System.Threading.Tasks.Task.Factory.StartNew(tgt => + { + var thisTarget = (OutputAvailableAsyncTarget)tgt; + thisTarget.TrySetCanceled(); + thisTarget.AttemptThreadSafeUnlink(); + }, + target, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + + /// Disposes of _unlinker if the target has been linked. + internal void AttemptThreadSafeUnlink() + { + // A race is possible. Therefore use an interlocked operation. + IDisposable cachedUnlinker = _unlinker; + if (cachedUnlinker != null && Interlocked.CompareExchange(ref _unlinker, null, cachedUnlinker) == cachedUnlinker) + { + cachedUnlinker.Dispose(); + } + } + + /// The IDisposable used to unlink this target from its source. + internal IDisposable _unlinker; + /// The registration used to unregister this target from the cancellation token. + internal CancellationTokenRegistration _ctr; + + /// Completes the task when offered a message (but doesn't consume the message). + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null) throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + TrySetResult(true); + return DataflowMessageStatus.DecliningPermanently; + } + + /// + void IDataflowBlock.Complete() + { + TrySetResult(false); + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + TrySetResult(false); + } + + /// + Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0} IsCompleted={1}", + Common.GetNameForDebugger(this), base.Task.IsCompleted); + } + } + /// Gets the data to display in the debugger display attribute for this instance. + object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } + } + #endregion + + #region Encapsulate + /// Encapsulates a target and a source into a single propagator. + /// Specifies the type of input expected by the target. + /// Specifies the type of output produced by the source. + /// The target to encapsulate. + /// The source to encapsulate. + /// The encapsulated target and source. + /// + /// This method does not in any way connect the target to the source. It creates a + /// propagator block whose target methods delegate to the specified target and whose + /// source methods delegate to the specified source. Any connection between the target + /// and the source is left for the developer to explicitly provide. The propagator's + /// implementation delegates to the specified source. + /// + public static IPropagatorBlock Encapsulate( + ITargetBlock target, ISourceBlock source) + { + if (target == null) throw new ArgumentNullException("target"); + if (source == null) throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + return new EncapsulatingPropagator(target, source); + } + + /// Provides a dataflow block that encapsulates a target and a source to form a single propagator. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(EncapsulatingPropagator<,>.DebugView))] + private sealed class EncapsulatingPropagator : IPropagatorBlock, IReceivableSourceBlock, IDebuggerDisplay + { + /// The target half. + private ITargetBlock _target; + /// The source half. + private ISourceBlock _source; + + public EncapsulatingPropagator(ITargetBlock target, ISourceBlock source) + { + Contract.Requires(target != null, "The target should never be null; this should be checked by all internal usage."); + Contract.Requires(source != null, "The source should never be null; this should be checked by all internal usage."); + _target = target; + _source = source; + } + + /// + public void Complete() + { + _target.Complete(); + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + _target.Fault(exception); + } + /// + public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock source, bool consumeToAccept) + { + return _target.OfferMessage(messageHeader, messageValue, source, consumeToAccept); + } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + public IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) + { + return _source.LinkTo(target, linkOptions); + } + + /// + public bool TryReceive(Predicate filter, out TOutput item) + { + var receivableSource = _source as IReceivableSourceBlock; + if (receivableSource != null) return receivableSource.TryReceive(filter, out item); + + item = default(TOutput); + return false; + } + + /// + public bool TryReceiveAll(out IList items) + { + var receivableSource = _source as IReceivableSourceBlock; + if (receivableSource != null) return receivableSource.TryReceiveAll(out items); + + items = default(IList); + return false; + } + + /// + public TOutput ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + public bool ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + public void ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displayTarget = _target as IDebuggerDisplay; + var displaySource = _source as IDebuggerDisplay; + return string.Format("{0} Target=\"{1}\", Source=\"{2}\"", + Common.GetNameForDebugger(this), + displayTarget != null ? displayTarget.Content : _target, + displaySource != null ? displaySource.Content : _source); + } + } + /// Gets the data to display in the debugger display attribute for this instance. + object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } + + /// A debug view for the propagator. + private sealed class DebugView + { + /// The propagator being debugged. + private readonly EncapsulatingPropagator _propagator; + + /// Initializes the debug view. + /// The propagator being debugged. + public DebugView(EncapsulatingPropagator propagator) + { + Contract.Requires(propagator != null, "Need a block with which to construct the debug view."); + _propagator = propagator; + } + + /// The target. + public ITargetBlock Target { get { return _propagator._target; } } + /// The source. + public ISourceBlock Source { get { return _propagator._source; } } + } + } + #endregion + + #region Choose + #region Choose + /// Monitors two dataflow sources, invoking the provided handler for whichever source makes data available first. + /// Specifies type of data contained in the first source. + /// Specifies type of data contained in the second source. + /// The first source. + /// The handler to execute on data from the first source. + /// The second source. + /// The handler to execute on data from the second source. + /// + /// + /// A that represents the asynchronous choice. + /// If both sources are completed prior to the choice completing, + /// the resulting task will be canceled. When one of the sources has data available and successfully propagates + /// it to the choice, the resulting task will complete when the handler completes: if the handler throws an exception, + /// the task will end in the state containing the unhandled exception, otherwise the task + /// will end with its set to either 0 or 1 to + /// represent the first or second source, respectively. + /// + /// + /// This method will only consume an element from one of the two data sources, never both. + /// + /// + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + public static Task Choose( + ISourceBlock source1, Action action1, + ISourceBlock source2, Action action2) + { + // All argument validation is handled by the delegated method + return Choose(source1, action1, source2, action2, DataflowBlockOptions.Default); + } + + /// Monitors two dataflow sources, invoking the provided handler for whichever source makes data available first. + /// Specifies type of data contained in the first source. + /// Specifies type of data contained in the second source. + /// The first source. + /// The handler to execute on data from the first source. + /// The second source. + /// The handler to execute on data from the second source. + /// The options with which to configure this choice. + /// + /// + /// A that represents the asynchronous choice. + /// If both sources are completed prior to the choice completing, or if the CancellationToken + /// provided as part of is canceled prior to the choice completing, + /// the resulting task will be canceled. When one of the sources has data available and successfully propagates + /// it to the choice, the resulting task will complete when the handler completes: if the handler throws an exception, + /// the task will end in the state containing the unhandled exception, otherwise the task + /// will end with its set to either 0 or 1 to + /// represent the first or second source, respectively. + /// + /// + /// This method will only consume an element from one of the two data sources, never both. + /// If cancellation is requested after an element has been received, the cancellation request will be ignored, + /// and the relevant handler will be allowed to execute. + /// + /// + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] + public static Task Choose( + ISourceBlock source1, Action action1, + ISourceBlock source2, Action action2, + DataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (source1 == null) throw new ArgumentNullException("source1"); + if (action1 == null) throw new ArgumentNullException("action1"); + if (source2 == null) throw new ArgumentNullException("source2"); + if (action2 == null) throw new ArgumentNullException("action2"); + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + + // Delegate to the shared implementation + return ChooseCore(source1, action1, source2, action2, null, null, dataflowBlockOptions); + } + #endregion + + #region Choose + /// Monitors three dataflow sources, invoking the provided handler for whichever source makes data available first. + /// Specifies type of data contained in the first source. + /// Specifies type of data contained in the second source. + /// Specifies type of data contained in the third source. + /// The first source. + /// The handler to execute on data from the first source. + /// The second source. + /// The handler to execute on data from the second source. + /// The third source. + /// The handler to execute on data from the third source. + /// + /// + /// A that represents the asynchronous choice. + /// If all sources are completed prior to the choice completing, + /// the resulting task will be canceled. When one of the sources has data available and successfully propagates + /// it to the choice, the resulting task will complete when the handler completes: if the handler throws an exception, + /// the task will end in the state containing the unhandled exception, otherwise the task + /// will end with its set to the 0-based index of the source. + /// + /// + /// This method will only consume an element from one of the data sources, never more than one. + /// + /// + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + public static Task Choose( + ISourceBlock source1, Action action1, + ISourceBlock source2, Action action2, + ISourceBlock source3, Action action3) + { + // All argument validation is handled by the delegated method + return Choose(source1, action1, source2, action2, source3, action3, DataflowBlockOptions.Default); + } + + /// Monitors three dataflow sources, invoking the provided handler for whichever source makes data available first. + /// Specifies type of data contained in the first source. + /// Specifies type of data contained in the second source. + /// Specifies type of data contained in the third source. + /// The first source. + /// The handler to execute on data from the first source. + /// The second source. + /// The handler to execute on data from the second source. + /// The third source. + /// The handler to execute on data from the third source. + /// The options with which to configure this choice. + /// + /// + /// A that represents the asynchronous choice. + /// If all sources are completed prior to the choice completing, or if the CancellationToken + /// provided as part of is canceled prior to the choice completing, + /// the resulting task will be canceled. When one of the sources has data available and successfully propagates + /// it to the choice, the resulting task will complete when the handler completes: if the handler throws an exception, + /// the task will end in the state containing the unhandled exception, otherwise the task + /// will end with its set to the 0-based index of the source. + /// + /// + /// This method will only consume an element from one of the data sources, never more than one. + /// If cancellation is requested after an element has been received, the cancellation request will be ignored, + /// and the relevant handler will be allowed to execute. + /// + /// + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] + public static Task Choose( + ISourceBlock source1, Action action1, + ISourceBlock source2, Action action2, + ISourceBlock source3, Action action3, + DataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (source1 == null) throw new ArgumentNullException("source1"); + if (action1 == null) throw new ArgumentNullException("action1"); + if (source2 == null) throw new ArgumentNullException("source2"); + if (action2 == null) throw new ArgumentNullException("action2"); + if (source3 == null) throw new ArgumentNullException("source3"); + if (action3 == null) throw new ArgumentNullException("action3"); + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + + // Delegate to the shared implementation + return ChooseCore(source1, action1, source2, action2, source3, action3, dataflowBlockOptions); + } + #endregion + + #region Choose Shared + /// Monitors dataflow sources, invoking the provided handler for whichever source makes data available first. + /// Specifies type of data contained in the first source. + /// Specifies type of data contained in the second source. + /// Specifies type of data contained in the third source. + /// The first source. + /// The handler to execute on data from the first source. + /// The second source. + /// The handler to execute on data from the second source. + /// The third source. + /// The handler to execute on data from the third source. + /// The options with which to configure this choice. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] + private static Task ChooseCore( + ISourceBlock source1, Action action1, + ISourceBlock source2, Action action2, + ISourceBlock source3, Action action3, + DataflowBlockOptions dataflowBlockOptions) + { + Contract.Requires(source1 != null && action1 != null, "The first source and action should not be null."); + Contract.Requires(source2 != null && action2 != null, "The second source and action should not be null."); + Contract.Requires((source3 == null) == (action3 == null), "The third action should be null iff the third source is null."); + Contract.Requires(dataflowBlockOptions != null, "Options are required."); + bool hasThirdSource = source3 != null; // In the future, if we want higher arities on Choose, we can simply add more such checks on additional arguments + + // Early cancellation check and bail out + if (dataflowBlockOptions.CancellationToken.IsCancellationRequested) + return Common.CreateTaskFromCancellation(dataflowBlockOptions.CancellationToken); + + // Fast path: if any of the sources already has data available that can be received immediately. + Task resultTask; + try + { + TaskScheduler scheduler = dataflowBlockOptions.TaskScheduler; + if (TryChooseFromSource(source1, action1, 0, scheduler, out resultTask) || + TryChooseFromSource(source2, action2, 1, scheduler, out resultTask) || + (hasThirdSource && TryChooseFromSource(source3, action3, 2, scheduler, out resultTask))) + { + return resultTask; + } + } + catch (Exception exc) + { + // In case TryReceive in TryChooseFromSource erroneously throws + return Common.CreateTaskFromException(exc); + } + + // Slow path: link up to all of the sources. Separated out to avoid a closure on the fast path. + return ChooseCoreByLinking(source1, action1, source2, action2, source3, action3, dataflowBlockOptions); + } + + /// + /// Tries to remove data from a receivable source and schedule an action to process that received item. + /// + /// Specifies the type of data to process. + /// The source from which to receive the data. + /// The action to run for the received data. + /// The branch ID associated with this source/action pair. + /// The scheduler to use to process the action. + /// The task created for processing the received item. + /// true if this try attempt satisfies the choose operation; otherwise, false. + private static bool TryChooseFromSource( + ISourceBlock source, Action action, int branchId, TaskScheduler scheduler, + out Task task) + { + // Validate arguments + Contract.Requires(source != null, "Expected a non-null source"); + Contract.Requires(action != null, "Expected a non-null action"); + Contract.Requires(branchId >= 0, "Expected a valid branch ID (> 0)"); + Contract.Requires(scheduler != null, "Expected a non-null scheduler"); + + // Try to receive from the source. If we can't, bail. + T result; + var receivableSource = source as IReceivableSourceBlock; + if (receivableSource == null || !receivableSource.TryReceive(out result)) + { + task = null; + return false; + } + + // We successfully received an item. Launch a task to process it. + task = Task.Factory.StartNew(ChooseTarget.s_processBranchFunction, + Tuple.Create, T, int>(action, result, branchId), + CancellationToken.None, Common.GetCreationOptionsForTask(), scheduler); + return true; + } + + /// Monitors dataflow sources, invoking the provided handler for whichever source makes data available first. + /// Specifies type of data contained in the first source. + /// Specifies type of data contained in the second source. + /// Specifies type of data contained in the third source. + /// The first source. + /// The handler to execute on data from the first source. + /// The second source. + /// The handler to execute on data from the second source. + /// The third source. + /// The handler to execute on data from the third source. + /// The options with which to configure this choice. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] + private static Task ChooseCoreByLinking( + ISourceBlock source1, Action action1, + ISourceBlock source2, Action action2, + ISourceBlock source3, Action action3, + DataflowBlockOptions dataflowBlockOptions) + { + Contract.Requires(source1 != null && action1 != null, "The first source and action should not be null."); + Contract.Requires(source2 != null && action2 != null, "The second source and action should not be null."); + Contract.Requires((source3 == null) == (action3 == null), "The third action should be null iff the third source is null."); + Contract.Requires(dataflowBlockOptions != null, "Options are required."); + + bool hasThirdSource = source3 != null; // In the future, if we want higher arities on Choose, we can simply add more such checks on additional arguments + + // Create object to act as both completion marker and sync obj for targets. + var boxedCompleted = new StrongBox(); + + // Set up teardown cancellation. We will request cancellation when a) the supplied options token + // has cancellation requested or b) when we actually complete somewhere in order to tear down + // the rest of our configured set up. + CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(dataflowBlockOptions.CancellationToken, CancellationToken.None); + + // Set up the branches. + TaskScheduler scheduler = dataflowBlockOptions.TaskScheduler; + var branchTasks = new Task[hasThirdSource ? 3 : 2]; + branchTasks[0] = CreateChooseBranch(boxedCompleted, cts, scheduler, 0, source1, action1); + branchTasks[1] = CreateChooseBranch(boxedCompleted, cts, scheduler, 1, source2, action2); + if (hasThirdSource) + { + branchTasks[2] = CreateChooseBranch(boxedCompleted, cts, scheduler, 2, source3, action3); + } + + // Asynchronously wait for all branches to complete, then complete + // a task to be returned to the caller. + var result = new TaskCompletionSource(); + Task.Factory.ContinueWhenAll(branchTasks, tasks => + { + // Process the outcome of all branches. At most one will have completed + // successfully, returning its branch ID. Others may have faulted, + // in which case we need to propagate their exceptions, regardless + // of whether a branch completed successfully. Others may have been + // canceled (or run but found they were not needed), and those + // we just ignore. + List exceptions = null; + int successfulBranchId = -1; + foreach (Task task in tasks) + { + switch (task.Status) + { + case TaskStatus.Faulted: + Common.AddException(ref exceptions, task.Exception, unwrapInnerExceptions: true); + break; + case TaskStatus.RanToCompletion: + int resultBranchId = task.Result; + if (resultBranchId >= 0) + { + Debug.Assert(resultBranchId < tasks.Length, "Expected a valid branch ID"); + Debug.Assert(successfulBranchId == -1, "There should be at most one successful branch."); + successfulBranchId = resultBranchId; + } + else Debug.Assert(resultBranchId == -1, "Expected -1 as a signal of a non-successful branch"); + break; + } + } + + // If we found any exceptions, fault the Choose task. Otherwise, if any branch completed + // successfully, store its result, or if cancellation was request + if (exceptions != null) + { + result.TrySetException(exceptions); + } + else if (successfulBranchId >= 0) + { + result.TrySetResult(successfulBranchId); + } + else + { + result.TrySetCanceled(); + } + + // By now we know that all of the tasks have completed, so there + // can't be any more use of the CancellationTokenSource. + cts.Dispose(); + }, CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default); + return result.Task; + } + + /// Creates a target for a branch of a Choose. + /// Specifies the type of data coming through this branch. + /// A strong box around the completed Task from any target. Also sync obj for access to the targets. + /// The CancellationTokenSource used to issue tear down / cancellation requests. + /// The TaskScheduler on which to scheduler work. + /// The ID of this branch, used to complete the resultTask. + /// The source with which this branch is associated. + /// The action to run for a single element received from the source. + /// A task representing the branch. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private static Task CreateChooseBranch( + StrongBox boxedCompleted, CancellationTokenSource cts, + TaskScheduler scheduler, + int branchId, ISourceBlock source, Action action) + { + // If the cancellation token is already canceled, there is no need to create and link a target. + // Instead, directly return a canceled task. + if (cts.IsCancellationRequested) + return Common.CreateTaskFromCancellation(cts.Token); + + // Proceed with creating and linking a hidden target. Also get the source's completion task, + // as we need it to know when the source completes. Both of these operations + // could throw an exception if the block is faulty. + var target = new ChooseTarget(boxedCompleted, cts.Token); + IDisposable unlink; + try + { + unlink = source.LinkTo(target, DataflowLinkOptions.UnlinkAfterOneAndPropagateCompletion); + } + catch (Exception exc) + { + cts.Cancel(); + return Common.CreateTaskFromException(exc); + } + + // The continuation task below is implicitly capturing the right execution context, + // as CreateChooseBranch is called synchronously from Choose, so we + // don't need to additionally capture and marshal an ExecutionContext. + + return target.Task.ContinueWith(completed => + { + try + { + // If the target ran to completion, i.e. it got a message, + // cancel the other branch(es) and proceed with the user callback. + if (completed.Status == TaskStatus.RanToCompletion) + { + // Cancel the cts to trigger completion of the other branches. + cts.Cancel(); + + // Proceed with the user callback. + action(completed.Result); + + // Return the ID of our branch to indicate. + return branchId; + } + return -1; + } + finally + { + // Unlink from the source. This could throw if the block is faulty, + // in which case our branch's task will fault. If this + // does throw, it'll end up propagating instead of the + // original action's exception if there was one. + unlink.Dispose(); + } + }, CancellationToken.None, Common.GetContinuationOptions(), scheduler); + } + + /// Provides a dataflow target used by Choose to receive data from a single source. + /// Specifies the type of data offered to this target. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + private sealed class ChooseTarget : TaskCompletionSource, ITargetBlock, IDebuggerDisplay + { + /// + /// Delegate used to invoke the action for a branch when that branch is activated + /// on the fast path. + /// + internal static readonly Func s_processBranchFunction = state => + { + Tuple, T, int> actionResultBranch = (Tuple, T, int>)state; + actionResultBranch.Item1(actionResultBranch.Item2); + return actionResultBranch.Item3; + }; + + /// + /// A wrapper for the task that represents the completed branch of this choice. + /// The wrapper is also the sync object used to protect all choice branch's access to shared state. + /// + private StrongBox _completed; + + /// Initializes the target. + /// The completed wrapper shared between all choice branches. + /// The cancellation token used to cancel this target. + internal ChooseTarget(StrongBox completed, CancellationToken cancellationToken) + { + Contract.Requires(completed != null, "Requires a shared target to complete."); + _completed = completed; + + // Handle async cancellation by canceling the target without storing it into _completed. + // _completed must only be set to a RanToCompletion task for a successful branch. + Common.WireCancellationToComplete(cancellationToken, base.Task, + state => + { + var thisChooseTarget = (ChooseTarget)state; + lock (thisChooseTarget._completed) thisChooseTarget.TrySetCanceled(); + }, this); + } + + /// Called when this choice branch is being offered a message. + public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + lock (_completed) + { + // If we or another participating choice has already completed, we're done. + if (_completed.Value != null || base.Task.IsCompleted) return DataflowMessageStatus.DecliningPermanently; + + // Consume the message from the source if necessary + if (consumeToAccept) + { + bool consumed; + messageValue = source.ConsumeMessage(messageHeader, this, out consumed); + if (!consumed) return DataflowMessageStatus.NotAvailable; + } + + // Store the result and signal our success + TrySetResult(messageValue); + _completed.Value = Task; + return DataflowMessageStatus.Accepted; + } + } + + /// + void IDataflowBlock.Complete() + { + lock (_completed) TrySetCanceled(); + } + + /// + void IDataflowBlock.Fault(Exception exception) { ((IDataflowBlock)this).Complete(); } + + /// + Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0} IsCompleted={1}", + Common.GetNameForDebugger(this), base.Task.IsCompleted); + } + } + /// Gets the data to display in the debugger display attribute for this instance. + object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } + } + #endregion + #endregion + + #region AsObservable + /// Creates a new abstraction over the . + /// Specifies the type of data contained in the source. + /// The source to wrap. + /// An IObservable{TOutput} that enables observers to be subscribed to the source. + /// The is null (Nothing in Visual Basic). + public static IObservable AsObservable(this ISourceBlock source) + { + if (source == null) throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + return SourceObservable.From(source); + } + + /// Cached options for non-greedy processing. + private static readonly ExecutionDataflowBlockOptions _nonGreedyExecutionOptions = new ExecutionDataflowBlockOptions { BoundedCapacity = 1 }; + + /// Provides an IObservable veneer over a source block. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(SourceObservable<>.DebugView))] + private sealed class SourceObservable : IObservable, IDebuggerDisplay + { + /// The table that maps source to cached observable. + /// + /// ConditionalWeakTable doesn't do the initialization under a lock, just the publication. + /// This means that if there's a race to create two observables off the same source, we could end + /// up instantiating multiple SourceObservable instances, of which only one will be published. + /// Worst case, we end up with a few additional continuations off of the source's completion task. + /// + private static readonly ConditionalWeakTable, SourceObservable> _table = + new ConditionalWeakTable, SourceObservable>(); + + /// Gets an observable to represent the source block. + /// The source. + /// The observable. + internal static IObservable From(ISourceBlock source) + { + Contract.Requires(source != null, "Requires a source for which to retrieve the observable."); + return _table.GetValue(source, s => new SourceObservable(s)); + } + + /// Object used to synchronize all subscriptions, unsubscriptions, and propagations. + private readonly object _SubscriptionLock = new object(); + /// The wrapped source. + private readonly ISourceBlock _source; + /// + /// The current target. We use the same target until the number of subscribers + /// drops to 0, at which point we substitute in a new target. + /// + private ObserversState _observersState; + + /// Initializes the SourceObservable. + /// The source to wrap. + internal SourceObservable(ISourceBlock source) + { + Contract.Requires(source != null, "The observable requires a source to wrap."); + _source = source; + _observersState = new ObserversState(this); + } + + /// Gets any exceptions from the source block. + /// The aggregate exception of all errors, or null if everything completed successfully. + private AggregateException GetCompletionError() + { + Task sourceCompletionTask = Common.GetPotentiallyNotSupportedCompletionTask(_source); + return sourceCompletionTask != null && sourceCompletionTask.IsFaulted ? + sourceCompletionTask.Exception : null; + } + + /// Subscribes the observer to the source. + /// the observer to subscribe. + /// An IDisposable that may be used to unsubscribe the source. + [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] + IDisposable IObservable.Subscribe(IObserver observer) + { + // Validate arguments + if (observer == null) throw new ArgumentNullException("observer"); + Contract.EndContractBlock(); + Common.ContractAssertMonitorStatus(_SubscriptionLock, held: false); + + Task sourceCompletionTask = Common.GetPotentiallyNotSupportedCompletionTask(_source); + + // Synchronize all observers for this source. + Exception error = null; + lock (_SubscriptionLock) + { + // Fast path for if everything is already done. We need to ensure that both + // the source is complete and that the target has finished propagating data to all observers. + // If there was an error, we grab it here and then we'll complete the observer + // outside of the lock. + if (sourceCompletionTask != null && sourceCompletionTask.IsCompleted && + _observersState.Target.Completion.IsCompleted) + { + error = GetCompletionError(); + } + // Otherwise, we need to subscribe this observer. + else + { + // Hook up the observer. If this is the first observer, link the source to the target. + _observersState.Observers = _observersState.Observers.Add(observer); + if (_observersState.Observers.Count == 1) + { + Debug.Assert(_observersState.Unlinker == null, "The source should not be linked to the target."); + _observersState.Unlinker = _source.LinkTo(_observersState.Target); + if (_observersState.Unlinker == null) + { + _observersState.Observers = ImmutableList>.Empty; + return null; + } + } + + // Return a disposable that will unlink this observer, and if it's the last + // observer for the source, shut off the pipe to observers. + return Disposables.Create((s, o) => s.Unsubscribe(o), this, observer); + } + } + + // Complete the observer. + if (error != null) observer.OnError(error); + else observer.OnCompleted(); + return Disposables.Nop; + } + + /// Unsubscribes the observer. + /// The observer being unsubscribed. + private void Unsubscribe(IObserver observer) + { + Contract.Requires(observer != null, "Expected an observer."); + Common.ContractAssertMonitorStatus(_SubscriptionLock, held: false); + + lock (_SubscriptionLock) + { + ObserversState currentState = _observersState; + Debug.Assert(currentState != null, "Observer state should never be null."); + + // If the observer was already unsubscribed (or is otherwise no longer present in our list), bail. + if (!currentState.Observers.Contains(observer)) return; + + // If this is the last observer being removed, reset to be ready for future subscribers. + if (currentState.Observers.Count == 1) + { + ResetObserverState(); + } + // Otherwise, just remove the observer. Note that we don't remove the observer + // from the current target if this is the last observer. This is done in case the target + // has already taken data from the source: we want that data to end up somewhere, + // and we can't put it back in the source, so we ensure we send it along to the observer. + else + { + currentState.Observers = currentState.Observers.Remove(observer); + } + } + } + + /// Resets the observer state to the original, inactive state. + /// The list of active observers prior to the reset. + private ImmutableList> ResetObserverState() + { + Common.ContractAssertMonitorStatus(_SubscriptionLock, held: true); + + ObserversState currentState = _observersState; + Debug.Assert(currentState != null, "Observer state should never be null."); + Debug.Assert(currentState.Unlinker != null, "The target should be linked."); + Debug.Assert(currentState.Canceler != null, "The target should have set up continuations."); + + // Replace the target with a clean one, unlink and cancel, and return the previous set of observers + ImmutableList> currentObservers = currentState.Observers; + _observersState = new ObserversState(this); + currentState.Unlinker.Dispose(); + currentState.Canceler.Cancel(); + return currentObservers; + } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displaySource = _source as IDebuggerDisplay; + return string.Format("Observers={0}, Block=\"{1}\"", + _observersState.Observers.Count, + displaySource != null ? displaySource.Content : _source); + } + } + /// 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 the observable. + private sealed class DebugView + { + /// The observable being debugged. + private readonly SourceObservable _observable; + + /// Initializes the debug view. + /// The target being debugged. + public DebugView(SourceObservable observable) + { + Contract.Requires(observable != null, "Need a block with which to construct the debug view."); + _observable = observable; + } + + /// Gets an enumerable of the observers. + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public IObserver[] Observers { get { return _observable._observersState.Observers.ToArray(); } } + } + + /// State associated with the current target for propagating data to observers. + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + private sealed class ObserversState + { + /// The owning SourceObservable. + internal readonly SourceObservable Observable; + /// The ActionBlock that consumes data from a source and offers it to targets. + internal readonly ActionBlock Target; + /// Used to cancel continuations when they're no longer necessary. + internal readonly CancellationTokenSource Canceler = new CancellationTokenSource(); + /// + /// A list of the observers currently registered with this target. The list is immutable + /// to enable iteration through the list while the set of observers may be changing. + /// + internal ImmutableList> Observers = ImmutableList>.Empty; + /// Used to unlink the source from this target when the last observer is unsubscribed. + internal IDisposable Unlinker; + /// + /// Temporary list to keep track of SendAsync tasks to TargetObservers with back pressure. + /// This field gets instantiated on demand. It gets populated and cleared within an offering cycle. + /// + private List> _tempSendAsyncTaskList; + + /// Initializes the target instance. + /// The owning observable. + internal ObserversState(SourceObservable observable) + { + Contract.Requires(observable != null, "Observe state must be mapped to a source observable."); + + // Set up the target block + Observable = observable; + Target = new ActionBlock((Func)ProcessItemAsync, DataflowBlock._nonGreedyExecutionOptions); + + // If the target block fails due to an unexpected exception (e.g. it calls back to the source and the source throws an error), + // we fault currently registered observers and reset the observable. + Target.Completion.ContinueWith( + (t, state) => ((ObserversState)state).NotifyObserversOfCompletion(t.Exception), this, + CancellationToken.None, + Common.GetContinuationOptions(TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously), + TaskScheduler.Default); + + // When the source completes, complete the target. Then when the target completes, + // send completion messages to any observers still registered. + Task sourceCompletionTask = Common.GetPotentiallyNotSupportedCompletionTask(Observable._source); + if (sourceCompletionTask != null) + { + sourceCompletionTask.ContinueWith((_1, state1) => + { + var ti = (ObserversState)state1; + ti.Target.Complete(); + ti.Target.Completion.ContinueWith( + (_2, state2) => ((ObserversState)state2).NotifyObserversOfCompletion(), state1, + CancellationToken.None, + Common.GetContinuationOptions(TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.ExecuteSynchronously), + TaskScheduler.Default); + }, this, Canceler.Token, Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), TaskScheduler.Default); + } + } + + /// Forwards an item to all currently subscribed observers. + /// The item to forward. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private Task ProcessItemAsync(TOutput item) + { + Common.ContractAssertMonitorStatus(Observable._SubscriptionLock, held: false); + + ImmutableList> currentObservers; + lock (Observable._SubscriptionLock) currentObservers = Observers; + try + { + foreach (IObserver observer in currentObservers) + { + // If the observer is our own TargetObserver, we SendAsync() to it + // rather than going through IObserver.OnNext() which allows us to + // continue offering to the remaining observers without blocking. + var targetObserver = observer as TargetObserver; + if (targetObserver != null) + { + Task sendAsyncTask = targetObserver.SendAsyncToTarget(item); + if (sendAsyncTask.Status != TaskStatus.RanToCompletion) + { + // Ensure the SendAsyncTaskList is instantiated + if (_tempSendAsyncTaskList == null) _tempSendAsyncTaskList = new List>(); + + // Add the task to the list + _tempSendAsyncTaskList.Add(sendAsyncTask); + } + } + else + { + observer.OnNext(item); + } + } + + // If there are SendAsync tasks to wait on... + if (_tempSendAsyncTaskList != null && _tempSendAsyncTaskList.Count > 0) + { + // Consolidate all SendAsync tasks into one + Task allSendAsyncTasksConsolidated = Task.WhenAll(_tempSendAsyncTaskList); + + // Clear the temp SendAsync task list + _tempSendAsyncTaskList.Clear(); + + // Return the consolidated task + return allSendAsyncTasksConsolidated; + } + } + catch (Exception exc) + { + // Return a faulted task + return Common.CreateTaskFromException(exc); + } + + // All observers accepted normally. + // Return a completed task. + return Common.CompletedTaskWithTrueResult; + } + + /// Notifies all currently registered observers that they should complete. + /// + /// Non-null when an unexpected exception occurs during processing. Faults + /// all subscribed observers and resets the observable back to its original condition. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void NotifyObserversOfCompletion(Exception targetException = null) + { + Contract.Requires(Target.Completion.IsCompleted, "The target must have already completed in order to notify of completion."); + Common.ContractAssertMonitorStatus(Observable._SubscriptionLock, held: false); + + // Send completion notification to all observers. + ImmutableList> currentObservers; + lock (Observable._SubscriptionLock) + { + // Get the currently registered set of observers. Then, if we're being called due to the target + // block failing from an unexpected exception, reset the observer state so that subsequent + // subscribed observers will get a new target block. Finally clear out our observer list. + currentObservers = Observers; + if (targetException != null) Observable.ResetObserverState(); + Observers = ImmutableList>.Empty; + } + + // If there are any observers to complete... + if (currentObservers.Count > 0) + { + // Determine if we should fault or complete the observers + Exception error = targetException ?? Observable.GetCompletionError(); + try + { + // Do it. + if (error != null) + { + foreach (IObserver observer in currentObservers) observer.OnError(error); + } + else + { + foreach (IObserver observer in currentObservers) observer.OnCompleted(); + } + } + catch (Exception exc) + { + // If an observer throws an exception at this point (which it shouldn't do), + // we have little recourse but to let that exception propagate. Since allowing it to + // propagate here would just result in it getting eaten by the owning task, + // we instead have it propagate on the thread pool. + Common.ThrowAsync(exc); + } + } + } + } + } + #endregion + + #region AsObserver + /// Creates a new abstraction over the . + /// Specifies the type of input accepted by the target block. + /// The target to wrap. + /// An observer that wraps the target block. + public static IObserver AsObserver(this ITargetBlock target) + { + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + return new TargetObserver(target); + } + + /// Provides an observer wrapper for a target block. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + private sealed class TargetObserver : IObserver, IDebuggerDisplay + { + /// The wrapped target. + private readonly ITargetBlock _target; + + /// Initializes the observer. + /// The target to wrap. + internal TargetObserver(ITargetBlock target) + { + Contract.Requires(target != null, "A target to observe is required."); + _target = target; + } + + /// Sends the value to the observer. + /// The value to send. + void IObserver.OnNext(TInput value) + { + // Send the value asynchronously... + Task task = SendAsyncToTarget(value); + + // And block until it's received. + task.GetAwaiter().GetResult(); // propagate original (non-aggregated) exception + } + + /// Completes the target. + void IObserver.OnCompleted() + { + _target.Complete(); + } + + /// Forwards the error to the target. + /// The exception to forward. + void IObserver.OnError(Exception error) + { + _target.Fault(error); + } + + /// Sends a value to the underlying target asynchronously. + /// The value to send. + /// A Task{bool} to wait on. + internal Task SendAsyncToTarget(TInput value) + { + return _target.SendAsync(value); + } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displayTarget = _target as IDebuggerDisplay; + return string.Format("Block=\"{0}\"", + displayTarget != null ? displayTarget.Content : _target); + } + } + /// Gets the data to display in the debugger display attribute for this instance. + object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } } + } + #endregion + + #region NullTarget + /// + /// Gets a target block that synchronously accepts all messages offered to it and drops them. + /// + /// The type of the messages this block can accept. + /// A that accepts and subsequently drops all offered messages. + public static ITargetBlock NullTarget() + { + return new NullTargetBlock(); + } + + /// + /// Target block that synchronously accepts all messages offered to it and drops them. + /// + /// The type of the messages this block can accept. + private class NullTargetBlock : ITargetBlock + { + private Task _completion; + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock source, Boolean consumeToAccept) + { + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + Contract.EndContractBlock(); + + // If the source requires an explicit synchronous consumption, do it + if (consumeToAccept) + { + if (source == null) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + bool messageConsumed; + + // If the source throws during this call, let the exception propagate back to the source + source.ConsumeMessage(messageHeader, this, out messageConsumed); + if (!messageConsumed) return DataflowMessageStatus.NotAvailable; + } + + // Always tell the source the message has been accepted + return DataflowMessageStatus.Accepted; + } + + /// + void IDataflowBlock.Complete() { } // No-op + /// + void IDataflowBlock.Fault(Exception exception) { } // No-op + /// + Task IDataflowBlock.Completion + { + get { return LazyInitializer.EnsureInitialized(ref _completion, () => new TaskCompletionSource().Task); } + } + } + #endregion + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowBlockOptions.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowBlockOptions.cs new file mode 100644 index 00000000000..44532dacbf8 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowBlockOptions.cs @@ -0,0 +1,414 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// DataflowBlockOptions.cs +// +// +// DataflowBlockOptions types for configuring dataflow blocks +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace System.Threading.Tasks.Dataflow +{ + /// + /// Provides options used to configure the processing performed by dataflow blocks. + /// + /// + /// is mutable and can be configured through its properties. + /// When specific configuration options are not set, the following defaults are used: + /// + /// + /// Options + /// Default + /// + /// + /// TaskScheduler + /// + /// + /// + /// MaxMessagesPerTask + /// DataflowBlockOptions.Unbounded (-1) + /// + /// + /// CancellationToken + /// + /// + /// + /// BoundedCapacity + /// DataflowBlockOptions.Unbounded (-1) + /// + /// + /// NameFormat + /// "{0} Id={1}" + /// + /// + /// Dataflow blocks capture the state of the options at their construction. Subsequent changes + /// to the provided instance should not affect the behavior + /// of a dataflow block. + /// + [DebuggerDisplay("TaskScheduler = {TaskScheduler}, MaxMessagesPerTask = {MaxMessagesPerTask}, BoundedCapacity = {BoundedCapacity}")] + public class DataflowBlockOptions + { + /// + /// A constant used to specify an unlimited quantity for members + /// that provide an upper bound. This field is constant. + /// + public const Int32 Unbounded = -1; + + /// The scheduler to use for scheduling tasks to process messages. + private TaskScheduler _taskScheduler = TaskScheduler.Default; + /// The cancellation token to monitor for cancellation requests. + private CancellationToken _cancellationToken = CancellationToken.None; + /// The maximum number of messages that may be processed per task. + private Int32 _maxMessagesPerTask = Unbounded; + /// The maximum number of messages that may be buffered by the block. + private Int32 _boundedCapacity = Unbounded; + /// The name format to use for creating a name for a block. + private string _nameFormat = "{0} Id={1}"; // see NameFormat property for a description of format items + + /// A default instance of . + /// + /// Do not change the values of this instance. It is shared by all of our blocks when no options are provided by the user. + /// + internal static readonly DataflowBlockOptions Default = new DataflowBlockOptions(); + + /// Returns this instance if it's the default instance or else a cloned instance. + /// An instance of the options that may be cached by the block. + internal DataflowBlockOptions DefaultOrClone() + { + return (this == Default) ? + this : + new DataflowBlockOptions + { + TaskScheduler = this.TaskScheduler, + CancellationToken = this.CancellationToken, + MaxMessagesPerTask = this.MaxMessagesPerTask, + BoundedCapacity = this.BoundedCapacity, + NameFormat = this.NameFormat + }; + } + + /// Initializes the . + public DataflowBlockOptions() { } + + /// Gets or sets the to use for scheduling tasks. + public TaskScheduler TaskScheduler + { + get { return _taskScheduler; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + if (value == null) throw new ArgumentNullException("value"); + _taskScheduler = value; + } + } + + /// Gets or sets the to monitor for cancellation requests. + public CancellationToken CancellationToken + { + get { return _cancellationToken; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + _cancellationToken = value; + } + } + + /// Gets or sets the maximum number of messages that may be processed per task. + public Int32 MaxMessagesPerTask + { + get { return _maxMessagesPerTask; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + if (value < 1 && value != Unbounded) throw new ArgumentOutOfRangeException("value"); + _maxMessagesPerTask = value; + } + } + + /// Gets a MaxMessagesPerTask value that may be used for comparison purposes. + /// The maximum value, usable for comparison purposes. + /// Unlike MaxMessagesPerTask, this property will always return a positive value. + internal Int32 ActualMaxMessagesPerTask + { + get { return (_maxMessagesPerTask == Unbounded) ? Int32.MaxValue : _maxMessagesPerTask; } + } + + /// Gets or sets the maximum number of messages that may be buffered by the block. + public Int32 BoundedCapacity + { + get { return _boundedCapacity; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + if (value < 1 && value != Unbounded) throw new ArgumentOutOfRangeException("value"); + _boundedCapacity = value; + } + } + + /// + /// Gets or sets the format string to use when a block is queried for its name. + /// + /// + /// The name format may contain up to two format items. {0} will be substituted + /// with the block's name. {1} will be substituted with the block's Id, as is + /// returned from the block's Completion.Id property. + /// + public string NameFormat + { + get { return _nameFormat; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + if (value == null) throw new ArgumentNullException("value"); + _nameFormat = value; + } + } + } + + /// + /// Provides options used to configure the processing performed by dataflow blocks that + /// process each message through the invocation of a user-provided delegate, blocks such + /// as and . + /// + /// + /// is mutable and can be configured through its properties. + /// When specific configuration options are not set, the following defaults are used: + /// + /// + /// Options + /// Default + /// + /// + /// TaskScheduler + /// + /// + /// + /// CancellationToken + /// + /// + /// + /// MaxMessagesPerTask + /// DataflowBlockOptions.Unbounded (-1) + /// + /// + /// BoundedCapacity + /// DataflowBlockOptions.Unbounded (-1) + /// + /// + /// NameFormat + /// "{0} Id={1}" + /// + /// + /// MaxDegreeOfParallelism + /// 1 + /// + /// + /// SingleProducerConstrained + /// false + /// + /// + /// Dataflow block captures the state of the options at their construction. Subsequent changes + /// to the provided instance should not affect the behavior + /// of a dataflow block. + /// + [DebuggerDisplay("TaskScheduler = {TaskScheduler}, MaxMessagesPerTask = {MaxMessagesPerTask}, BoundedCapacity = {BoundedCapacity}, MaxDegreeOfParallelism = {MaxDegreeOfParallelism}")] + public class ExecutionDataflowBlockOptions : DataflowBlockOptions + { + /// A default instance of . + /// + /// Do not change the values of this instance. It is shared by all of our blocks when no options are provided by the user. + /// + internal new static readonly ExecutionDataflowBlockOptions Default = new ExecutionDataflowBlockOptions(); + + /// Returns this instance if it's the default instance or else a cloned instance. + /// An instance of the options that may be cached by the block. + internal new ExecutionDataflowBlockOptions DefaultOrClone() + { + return (this == Default) ? + this : + new ExecutionDataflowBlockOptions + { + TaskScheduler = this.TaskScheduler, + CancellationToken = this.CancellationToken, + MaxMessagesPerTask = this.MaxMessagesPerTask, + BoundedCapacity = this.BoundedCapacity, + NameFormat = this.NameFormat, + MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, + SingleProducerConstrained = this.SingleProducerConstrained + }; + } + + /// The maximum number of tasks that may be used concurrently to process messages. + private Int32 _maxDegreeOfParallelism = 1; + /// Whether the code using this block will only ever have a single producer accessing the block at any given time. + private Boolean _singleProducerConstrained = false; + + /// Initializes the . + public ExecutionDataflowBlockOptions() { } + + /// Gets the maximum number of messages that may be processed by the block concurrently. + public Int32 MaxDegreeOfParallelism + { + get { return _maxDegreeOfParallelism; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + if (value < 1 && value != Unbounded) throw new ArgumentOutOfRangeException("value"); + _maxDegreeOfParallelism = value; + } + } + + /// + /// Gets whether code using the dataflow block is constrained to one producer at a time. + /// + /// + /// This property defaults to false, such that the block may be used by multiple + /// producers concurrently. This property should only be set to true if the code + /// using the block can guarantee that it will only ever be used by one producer + /// (e.g. a source linked to the block) at a time, meaning that methods like Post, + /// Complete, Fault, and OfferMessage will never be called concurrently. Some blocks + /// may choose to capitalize on the knowledge that there will only be one producer at a time + /// in order to provide better performance. + /// + public Boolean SingleProducerConstrained + { + get { return _singleProducerConstrained; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + _singleProducerConstrained = value; + } + } + + /// Gets a MaxDegreeOfParallelism value that may be used for comparison purposes. + /// The maximum value, usable for comparison purposes. + /// Unlike MaxDegreeOfParallelism, this property will always return a positive value. + internal Int32 ActualMaxDegreeOfParallelism + { + get { return (_maxDegreeOfParallelism == Unbounded) ? Int32.MaxValue : _maxDegreeOfParallelism; } + } + + /// Gets whether these dataflow block options allow for parallel execution. + internal Boolean SupportsParallelExecution { get { return _maxDegreeOfParallelism == Unbounded || _maxDegreeOfParallelism > 1; } } + } + + /// + /// Provides options used to configure the processing performed by dataflow blocks that + /// group together multiple messages, blocks such as and + /// . + /// + /// + /// is mutable and can be configured through its properties. + /// When specific configuration options are not set, the following defaults are used: + /// + /// + /// Options + /// Default + /// + /// + /// TaskScheduler + /// + /// + /// + /// CancellationToken + /// + /// + /// + /// MaxMessagesPerTask + /// DataflowBlockOptions.Unbounded (-1) + /// + /// + /// BoundedCapacity + /// DataflowBlockOptions.Unbounded (-1) + /// + /// + /// NameFormat + /// "{0} Id={1}" + /// + /// + /// MaxNumberOfGroups + /// GroupingDataflowBlockOptions.Unbounded (-1) + /// + /// + /// Greedy + /// true + /// + /// + /// Dataflow block capture the state of the options at their construction. Subsequent changes + /// to the provided instance should not affect the behavior + /// of a dataflow block. + /// + [DebuggerDisplay("TaskScheduler = {TaskScheduler}, MaxMessagesPerTask = {MaxMessagesPerTask}, BoundedCapacity = {BoundedCapacity}, Greedy = {Greedy}, MaxNumberOfGroups = {MaxNumberOfGroups}")] + public class GroupingDataflowBlockOptions : DataflowBlockOptions + { + /// A default instance of . + /// + /// Do not change the values of this instance. It is shared by all of our blocks when no options are provided by the user. + /// + internal new static readonly GroupingDataflowBlockOptions Default = new GroupingDataflowBlockOptions(); + + /// Returns this instance if it's the default instance or else a cloned instance. + /// An instance of the options that may be cached by the block. + internal new GroupingDataflowBlockOptions DefaultOrClone() + { + return (this == Default) ? + this : + new GroupingDataflowBlockOptions + { + TaskScheduler = this.TaskScheduler, + CancellationToken = this.CancellationToken, + MaxMessagesPerTask = this.MaxMessagesPerTask, + BoundedCapacity = this.BoundedCapacity, + NameFormat = this.NameFormat, + Greedy = this.Greedy, + MaxNumberOfGroups = this.MaxNumberOfGroups + }; + } + + /// Whether the block should greedily consume offered messages. + private Boolean _greedy = true; + /// The maximum number of groups that should be generated by the block. + private Int64 _maxNumberOfGroups = Unbounded; + + /// Initializes the . + public GroupingDataflowBlockOptions() { } + + /// Gets or sets the Boolean value to use to determine whether to greedily consume offered messages. + public Boolean Greedy + { + get { return _greedy; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + _greedy = value; + } + } + + /// Gets or sets the maximum number of groups that should be generated by the block. + public Int64 MaxNumberOfGroups + { + get { return _maxNumberOfGroups; } + set + { + Debug.Assert(this != Default, "Default instance is supposed to be immutable."); + if (value <= 0 && value != Unbounded) throw new ArgumentOutOfRangeException("value"); + _maxNumberOfGroups = value; + } + } + + /// Gets a MaxNumberOfGroups value that may be used for comparison purposes. + /// The maximum value, usable for comparison purposes. + /// Unlike MaxNumberOfGroups, this property will always return a positive value. + internal Int64 ActualMaxNumberOfGroups + { + get { return (_maxNumberOfGroups == Unbounded) ? Int64.MaxValue : _maxNumberOfGroups; } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowLinkOptions.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowLinkOptions.cs new file mode 100644 index 00000000000..079da23ef2a --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowLinkOptions.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// DataflowLinkOptions.cs +// +// +// DataflowLinkOptions type for configuring links between dataflow blocks +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace System.Threading.Tasks.Dataflow +{ + /// + /// Provides options used to configure a link between dataflow blocks. + /// + /// + /// is mutable and can be configured through its properties. + /// When specific configuration options are not set, the following defaults are used: + /// + /// + /// Options + /// Default + /// + /// + /// PropagateCompletion + /// False + /// + /// + /// MaxMessages + /// DataflowBlockOptions.Unbounded (-1) + /// + /// + /// Append + /// True + /// + /// + /// Dataflow blocks capture the state of the options at linking. Subsequent changes to the provided + /// instance should not affect the behavior of a link. + /// + [DebuggerDisplay("PropagateCompletion = {PropagateCompletion}, MaxMessages = {MaxMessages}, Append = {Append}")] + public class DataflowLinkOptions + { + /// + /// A constant used to specify an unlimited quantity for members + /// that provide an upper bound. This field is a constant tied to . + /// + internal const Int32 Unbounded = DataflowBlockOptions.Unbounded; + + /// Whether the linked target will have completion and faulting notification propagated to it automatically. + private Boolean _propagateCompletion = false; + /// The maximum number of messages that may be consumed across the link. + private Int32 _maxNumberOfMessages = Unbounded; + /// Whether the link should be appended to the source’s list of links, or whether it should be prepended. + private Boolean _append = true; + + /// A default instance of . + /// + /// Do not change the values of this instance. It is shared by all of our blocks when no options are provided by the user. + /// + internal static readonly DataflowLinkOptions Default = new DataflowLinkOptions(); + + /// A cached instance of . + /// + /// Do not change the values of this instance. It is shared by all of our blocks that need to unlink after one message has been consumed. + /// + internal static readonly DataflowLinkOptions UnlinkAfterOneAndPropagateCompletion = new DataflowLinkOptions() { MaxMessages = 1, PropagateCompletion = true }; + + /// Initializes the . + public DataflowLinkOptions() + { + } + + /// Gets or sets whether the linked target will have completion and faulting notification propagated to it automatically. + public Boolean PropagateCompletion + { + get { return _propagateCompletion; } + set + { + Debug.Assert(this != Default && this != UnlinkAfterOneAndPropagateCompletion, "Default and UnlinkAfterOneAndPropagateCompletion instances are supposed to be immutable."); + _propagateCompletion = value; + } + } + + /// Gets or sets the maximum number of messages that may be consumed across the link. + public Int32 MaxMessages + { + get { return _maxNumberOfMessages; } + set + { + Debug.Assert(this != Default && this != UnlinkAfterOneAndPropagateCompletion, "Default and UnlinkAfterOneAndPropagateCompletion instances are supposed to be immutable."); + if (value < 1 && value != Unbounded) throw new ArgumentOutOfRangeException("value"); + _maxNumberOfMessages = value; + } + } + + /// Gets or sets whether the link should be appended to the source’s list of links, or whether it should be prepended. + public Boolean Append + { + get { return _append; } + set + { + Debug.Assert(this != Default && this != UnlinkAfterOneAndPropagateCompletion, "Default and UnlinkAfterOneAndPropagateCompletion instances are supposed to be immutable."); + _append = value; + } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowMessageHeader.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowMessageHeader.cs new file mode 100644 index 00000000000..a09fe50fe7a --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowMessageHeader.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// DataflowMessageHeader.cs +// +// +// A container of data attributes passed between dataflow blocks. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Threading.Tasks.Dataflow.Internal; + +namespace System.Threading.Tasks.Dataflow +{ + /// Provides a container of data attributes for passing between dataflow blocks. + [DebuggerDisplay("Id = {Id}")] + public struct DataflowMessageHeader : IEquatable + { + /// The message ID. Needs to be unique within the source. + private readonly long _id; + + /// Initializes the with the specified attributes. + /// The ID of the message. Must be unique within the originating source block. Need not be globally unique. + public DataflowMessageHeader(Int64 id) + { + if (id == default(long)) throw new ArgumentException(SR.Argument_InvalidMessageId, "id"); + Contract.EndContractBlock(); + + _id = id; + } + + /// Gets the validity of the message. + /// True if the ID of the message is different from 0. False if the ID of the message is 0 + public Boolean IsValid { get { return _id != default(long); } } + + /// Gets the ID of the message within the source. + /// The ID contained in the instance. + public Int64 Id { get { return _id; } } + + // These overrides are required by the FX API Guidelines. + // NOTE: When these overrides are present, the compiler doesn't complain about statements + // like 'if (struct == null) ...' which will result in incorrect behavior at runtime. + // The product code should not use them. Instead, it should compare the Id properties. + // To verify that, every once in a while, comment out this region and build the product. + #region Comparison Operators + /// Checks two instances for equality by ID without boxing. + /// Another instance. + /// True if the instances are equal. False otherwise. + public bool Equals(DataflowMessageHeader other) + { + return this == other; + } + + /// Checks boxed instances for equality by ID. + /// A boxed instance. + /// True if the instances are equal. False otherwise. + public override bool Equals(object obj) + { + return obj is DataflowMessageHeader && this == (DataflowMessageHeader)obj; + } + + /// Generates a hash code for the instance. + /// Hash code. + public override int GetHashCode() + { + return (int)Id; + } + + /// Checks two instances for equality by ID. + /// A instance. + /// A instance. + /// True if the instances are equal. False otherwise. + public static bool operator ==(DataflowMessageHeader left, DataflowMessageHeader right) + { + return left.Id == right.Id; + } + + /// Checks two instances for non-equality by ID. + /// A instance. + /// A instance. + /// True if the instances are not equal. False otherwise. + public static bool operator !=(DataflowMessageHeader left, DataflowMessageHeader right) + { + return left.Id != right.Id; + } + #endregion + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowMessageStatus.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowMessageStatus.cs new file mode 100644 index 00000000000..17a71c2c577 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/DataflowMessageStatus.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// DataflowMessageStatus.cs +// +// +// Status about the propagation of a message. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +namespace System.Threading.Tasks.Dataflow +{ + /// Represents the status of a when passed between dataflow blocks. + public enum DataflowMessageStatus + { + /// + /// Indicates that the accepted the message. Once a target has accepted a message, + /// it is wholly owned by the target. + /// + Accepted = 0x0, + + /// + /// Indicates that the declined the message. The still owns the message. + /// + Declined = 0x1, + + /// + /// Indicates that the postponed the message for potential consumption at a later time. + /// The still owns the message. + /// + Postponed = 0x2, + + /// + /// Indicates that the tried to accept the message from the , but the + /// message was no longer available. + /// + NotAvailable = 0x3, + + /// + /// Indicates that the declined the message. The still owns the message. + /// Additionally, the will decline all future messages sent by the source. + /// + DecliningPermanently = 0x4 + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IDataflowBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IDataflowBlock.cs new file mode 100644 index 00000000000..d9177701618 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IDataflowBlock.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// IDataflowBlock.cs +// +// +// The base interface for all dataflow blocks. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +namespace System.Threading.Tasks.Dataflow +{ + /// Represents a dataflow block. + public interface IDataflowBlock + { + // IMPLEMENT IMPLICITLY + + /// + Task Completion { get; } + + /// + void Complete(); + + // IMPLEMENT EXPLICITLY + + /// + void Fault(Exception exception); + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IPropagatorBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IPropagatorBlock.cs new file mode 100644 index 00000000000..d8a83358dc1 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IPropagatorBlock.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// IPropagatorBlock.cs +// +// +// The base interface for all propagator blocks. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +namespace System.Threading.Tasks.Dataflow +{ + /// Represents a dataflow block that is both a target for data and a source of data. + /// Specifies the type of data accepted by the . + /// Specifies the type of data supplied by the . + public interface IPropagatorBlock : ITargetBlock, ISourceBlock + { + // No additional members beyond those inherited from ITargetBlock and ISourceBlock + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IReceivableSourceBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IReceivableSourceBlock.cs new file mode 100644 index 00000000000..15688cb6e69 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/IReceivableSourceBlock.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// IReceivableSourceBlock.cs +// +// +// The base interface for all source blocks. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; + +namespace System.Threading.Tasks.Dataflow +{ + /// Represents a dataflow block that supports receiving of messages without linking. + /// Specifies the type of data supplied by the . + public interface IReceivableSourceBlock : ISourceBlock + { + // IMPLEMENT IMPLICITLY + + /// + bool TryReceive(Predicate filter, out TOutput item); + + // IMPLEMENT IMPLICITLY IF BLOCK SUPPORTS RECEIVING MORE THAN ONE ITEM, OTHERWISE EXPLICITLY + + /// + bool TryReceiveAll(out IList items); + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/ISourceBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/ISourceBlock.cs new file mode 100644 index 00000000000..34f904c5964 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/ISourceBlock.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// ISourceBlock.cs +// +// +// The base interface for all source blocks. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Threading.Tasks.Dataflow +{ + /// Represents a dataflow block that is a source of data. + /// Specifies the type of data supplied by the . + public interface ISourceBlock : IDataflowBlock + { + // IMPLEMENT IMPLICITLY + + /// + IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions); + + // IMPLEMENT EXPLICITLY + + /// + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")] + TOutput ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed); + + /// + Boolean ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target); + + /// + void ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target); + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/ITargetBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/ITargetBlock.cs new file mode 100644 index 00000000000..4d2614c0808 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Base/ITargetBlock.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// ITargetBlock.cs +// +// +// The base interface for all target blocks. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +namespace System.Threading.Tasks.Dataflow +{ + /// Represents a dataflow block that is a target for data. + /// Specifies the type of data accepted by the . + public interface ITargetBlock : IDataflowBlock + { + // IMPLEMENT EXPLICITLY + + /// + DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock source, Boolean consumeToAccept); + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/ActionBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/ActionBlock.cs new file mode 100644 index 00000000000..67824a9a950 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/ActionBlock.cs @@ -0,0 +1,383 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// ActionBlock.cs +// +// +// A target block that executes an action for each message. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Threading.Tasks.Dataflow.Internal; + +namespace System.Threading.Tasks.Dataflow +{ + /// Provides a dataflow block that invokes a provided delegate for every data element received. + /// Specifies the type of data operated on by this . + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(ActionBlock<>.DebugView))] + public sealed class ActionBlock : ITargetBlock, IDebuggerDisplay + { + /// The core implementation of this message block when in default mode. + private readonly TargetCore _defaultTarget; + /// The core implementation of this message block when in SPSC mode. + private readonly SpscTargetCore _spscTarget; + + /// Initializes the with the specified . + /// The action to invoke with each data element received. + /// The is null (Nothing in Visual Basic). + public ActionBlock(Action action) : + this((Delegate)action, ExecutionDataflowBlockOptions.Default) + { } + + /// Initializes the with the specified and . + /// The action to invoke with each data element received. + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + public ActionBlock(Action action, ExecutionDataflowBlockOptions dataflowBlockOptions) : + this((Delegate)action, dataflowBlockOptions) + { } + + /// Initializes the with the specified . + /// The action to invoke with each data element received. + /// The is null (Nothing in Visual Basic). + public ActionBlock(Func action) : + this((Delegate)action, ExecutionDataflowBlockOptions.Default) + { } + + /// Initializes the with the specified and . + /// The action to invoke with each data element received. + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + public ActionBlock(Func action, ExecutionDataflowBlockOptions dataflowBlockOptions) : + this((Delegate)action, dataflowBlockOptions) + { } + + /// Initializes the with the specified delegate and options. + /// The action to invoke with each data element received. + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + private ActionBlock(Delegate action, ExecutionDataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (action == null) throw new ArgumentNullException("action"); + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + Contract.Ensures((_spscTarget != null) ^ (_defaultTarget != null), "One and only one of the two targets must be non-null after construction"); + Contract.EndContractBlock(); + + // Ensure we have options that can't be changed by the caller + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Based on the mode, initialize the target. If the user specifies SingleProducerConstrained, + // we'll try to employ an optimized mode under a limited set of circumstances. + var syncAction = action as Action; + if (syncAction != null && + dataflowBlockOptions.SingleProducerConstrained && + dataflowBlockOptions.MaxDegreeOfParallelism == 1 && + !dataflowBlockOptions.CancellationToken.CanBeCanceled && + dataflowBlockOptions.BoundedCapacity == DataflowBlockOptions.Unbounded) + { + // Initialize the SPSC fast target to handle the bulk of the processing. + // The SpscTargetCore is only supported when BoundedCapacity, CancellationToken, + // and MaxDOP are all their default values. It's also only supported for sync + // delegates and not for async delegates. + _spscTarget = new SpscTargetCore(this, syncAction, dataflowBlockOptions); + } + else + { + // Initialize the TargetCore which handles the bulk of the processing. + // The default target core can handle all options and delegate flavors. + + if (syncAction != null) // sync + { + _defaultTarget = new TargetCore(this, + messageWithId => ProcessMessage(syncAction, messageWithId), + null, dataflowBlockOptions, TargetCoreOptions.RepresentsBlockCompletion); + } + else // async + { + var asyncAction = action as Func; + Debug.Assert(asyncAction != null, "action is of incorrect delegate type"); + _defaultTarget = new TargetCore(this, + messageWithId => ProcessMessageWithTask(asyncAction, messageWithId), + null, dataflowBlockOptions, TargetCoreOptions.RepresentsBlockCompletion | TargetCoreOptions.UsesAsyncCompletion); + } + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, Completion, state => ((TargetCore)state).Complete(exception: null, dropPendingMessages: true), _defaultTarget); + } +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// Processes the message with a user-provided action. + /// The action to use to process the message. + /// The message to be processed. + private void ProcessMessage(Action action, KeyValuePair messageWithId) + { + try + { + action(messageWithId.Key); + } + catch (Exception exc) + { + // If this exception represents cancellation, swallow it rather than shutting down the block. + if (!Common.IsCooperativeCancellation(exc)) throw; + } + finally + { + // We're done synchronously processing an element, so reduce the bounding count + // that was incrementing when this element was enqueued. + if (_defaultTarget.IsBounded) _defaultTarget.ChangeBoundingCount(-1); + } + } + + /// Processes the message with a user-provided action that returns a task. + /// The action to use to process the message. + /// The message to be processed. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ProcessMessageWithTask(Func action, KeyValuePair messageWithId) + { + Contract.Requires(action != null, "action needed for processing"); + + // Run the action to get the task that represents the operation's completion + Task task = null; + Exception caughtException = null; + try + { + task = action(messageWithId.Key); + } + catch (Exception exc) { caughtException = exc; } + + // If no task is available, we're done. + if (task == null) + { + // If we didn't get a task because an exception occurred, + // store it (if the exception was cancellation, just ignore it). + if (caughtException != null && !Common.IsCooperativeCancellation(caughtException)) + { + Common.StoreDataflowMessageValueIntoExceptionData(caughtException, messageWithId.Key); + _defaultTarget.Complete(caughtException, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false); + } + + // Signal that we're done this async operation. + _defaultTarget.SignalOneAsyncMessageCompleted(boundingCountChange: -1); + return; + } + else if (task.IsCompleted) + { + AsyncCompleteProcessMessageWithTask(task); + } + else + { + // Otherwise, join with the asynchronous operation when it completes. + task.ContinueWith((completed, state) => + { + ((ActionBlock)state).AsyncCompleteProcessMessageWithTask(completed); + }, this, CancellationToken.None, Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), TaskScheduler.Default); + } + } + + /// Completes the processing of an asynchronous message. + /// The completed task. + private void AsyncCompleteProcessMessageWithTask(Task completed) + { + Contract.Requires(completed != null, "Need completed task for processing"); + Contract.Requires(completed.IsCompleted, "The task to be processed must be completed by now."); + + // If the task faulted, store its errors. We must add the exception before declining + // and signaling completion, as the exception is part of the operation, and the completion conditions + // depend on this. + if (completed.IsFaulted) + { + _defaultTarget.Complete(completed.Exception, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: true); + } + + // Regardless of faults, note that we're done processing. There are + // no outputs to keep track of for action block, so we always decrement + // the bounding count here (the callee will handle checking whether + // we're actually in a bounded mode). + _defaultTarget.SignalOneAsyncMessageCompleted(boundingCountChange: -1); + } + + /// + public void Complete() + { + if (_defaultTarget != null) + { + _defaultTarget.Complete(exception: null, dropPendingMessages: false); + } + else + { + _spscTarget.Complete(exception: null); + } + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + if (_defaultTarget != null) + { + _defaultTarget.Complete(exception, dropPendingMessages: true); + } + else + { + _spscTarget.Complete(exception); + } + } + + /// + public Task Completion + { + get { return _defaultTarget != null ? _defaultTarget.Completion : _spscTarget.Completion; } + } + + /// Posts an item to the . + /// The item being offered to the target. + /// true if the item was accepted by the target block; otherwise, false. + /// + /// This method will return once the target block has decided to accept or decline the item, + /// but unless otherwise dictated by special semantics of the target block, it does not wait + /// for the item to actually be processed (for example, + /// will return from Post as soon as it has stored the posted item into its input queue). From the perspective + /// of the block's processing, Post is asynchronous. For target blocks that support postponing offered messages, + /// or for blocks that may do more processing in their Post implementation, consider using + /// SendAsync, + /// which will return immediately and will enable the target to postpone the posted message and later consume it + /// after SendAsync returns. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Post(TInput item) + { + // Even though this method is available with the exact same functionality as an extension method + // on ITargetBlock, using that extension method goes through an interface call on ITargetBlock, + // which for very high-throughput scenarios shows up as noticeable overhead on certain architectures. + // We can eliminate that call for direct ActionBlock usage by providing the same method as an instance method. + + return _defaultTarget != null ? + _defaultTarget.OfferMessage(Common.SingleMessageHeader, item, null, false) == DataflowMessageStatus.Accepted : + _spscTarget.Post(item); + } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock source, Boolean consumeToAccept) + { + return _defaultTarget != null ? + _defaultTarget.OfferMessage(messageHeader, messageValue, source, consumeToAccept) : + _spscTarget.OfferMessage(messageHeader, messageValue, source, consumeToAccept); + } + + /// + public int InputCount + { + get { return _defaultTarget != null ? _defaultTarget.InputCount : _spscTarget.InputCount; } + } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger. + private int InputCountForDebugger + { + get { return _defaultTarget != null ? _defaultTarget.GetDebuggingInformation().InputCount : _spscTarget.InputCount; } + } + + /// + public override string ToString() + { + return Common.GetNameForDebugger(this, _defaultTarget != null ? _defaultTarget.DataflowBlockOptions : _spscTarget.DataflowBlockOptions); + } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, InputCount={1}", + Common.GetNameForDebugger(this, _defaultTarget != null ? _defaultTarget.DataflowBlockOptions : _spscTarget.DataflowBlockOptions), + InputCountForDebugger); + } + } + /// 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 the Call. + private sealed class DebugView + { + /// The action block being viewed. + private readonly ActionBlock _actionBlock; + /// The action block's default target being viewed. + private readonly TargetCore.DebuggingInformation _defaultDebugInfo; + /// The action block's SPSC target being viewed. + private readonly SpscTargetCore.DebuggingInformation _spscDebugInfo; + + /// Initializes the debug view. + /// The target being debugged. + public DebugView(ActionBlock actionBlock) + { + Contract.Requires(actionBlock != null, "Need a block with which to construct the debug view."); + _actionBlock = actionBlock; + if (_actionBlock._defaultTarget != null) + { + _defaultDebugInfo = actionBlock._defaultTarget.GetDebuggingInformation(); + } + else + { + _spscDebugInfo = actionBlock._spscTarget.GetDebuggingInformation(); + } + } + + /// Gets the messages waiting to be processed. + public IEnumerable InputQueue + { + get { return _defaultDebugInfo != null ? _defaultDebugInfo.InputQueue : _spscDebugInfo.InputQueue; } + } + /// Gets any postponed messages. + public QueuedMap, DataflowMessageHeader> PostponedMessages + { + get { return _defaultDebugInfo != null ? _defaultDebugInfo.PostponedMessages : null; } + } + + /// Gets the number of outstanding input operations. + public Int32 CurrentDegreeOfParallelism + { + get { return _defaultDebugInfo != null ? _defaultDebugInfo.CurrentDegreeOfParallelism : _spscDebugInfo.CurrentDegreeOfParallelism; } + } + + /// Gets the ExecutionDataflowBlockOptions used to configure this block. + public ExecutionDataflowBlockOptions DataflowBlockOptions + { + get { return _defaultDebugInfo != null ? _defaultDebugInfo.DataflowBlockOptions : _spscDebugInfo.DataflowBlockOptions; } + } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently + { + get { return _defaultDebugInfo != null ? _defaultDebugInfo.IsDecliningPermanently : _spscDebugInfo.IsDecliningPermanently; } + } + /// Gets whether the block is completed. + public bool IsCompleted + { + get { return _defaultDebugInfo != null ? _defaultDebugInfo.IsCompleted : _spscDebugInfo.IsCompleted; } + } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_actionBlock); } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BatchBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BatchBlock.cs new file mode 100644 index 00000000000..8a12745236f --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BatchBlock.cs @@ -0,0 +1,1206 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// BatchBlock.cs +// +// +// A propagator block that groups individual messages into arrays of messages. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Security; +using System.Threading.Tasks.Dataflow.Internal; + +namespace System.Threading.Tasks.Dataflow +{ + /// Provides a dataflow block that batches inputs into arrays. + /// Specifies the type of data put into batches. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(BatchBlock<>.DebugView))] + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + public sealed class BatchBlock : IPropagatorBlock, IReceivableSourceBlock, IDebuggerDisplay + { + /// The target half of this batch. + private readonly BatchBlockTargetCore _target; + /// The source half of this batch. + private readonly SourceCore _source; + + /// Initializes this with the specified batch size. + /// The number of items to group into a batch. + /// The must be positive. + public BatchBlock(Int32 batchSize) : + this(batchSize, GroupingDataflowBlockOptions.Default) + { } + + /// Initializes this with the specified batch size, declining option, and block options. + /// The number of items to group into a batch. + /// The options with which to configure this . + /// The must be positive. + /// The must be no greater than the value of the BoundedCapacity option if a non-default value has been set. + /// The is null (Nothing in Visual Basic). + public BatchBlock(Int32 batchSize, GroupingDataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (batchSize < 1) throw new ArgumentOutOfRangeException("batchSize", SR.ArgumentOutOfRange_GenericPositive); + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + if (dataflowBlockOptions.BoundedCapacity > 0 && dataflowBlockOptions.BoundedCapacity < batchSize) throw new ArgumentOutOfRangeException("batchSize", SR.ArgumentOutOfRange_BatchSizeMustBeNoGreaterThanBoundedCapacity); + Contract.EndContractBlock(); + + // Ensure we have options that can't be changed by the caller + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Initialize bounding actions + Action, int> onItemsRemoved = null; + Func, T[], IList, int> itemCountingFunc = null; + if (dataflowBlockOptions.BoundedCapacity > 0) + { + onItemsRemoved = (owningSource, count) => ((BatchBlock)owningSource)._target.OnItemsRemoved(count); + itemCountingFunc = (owningSource, singleOutputItem, multipleOutputItems) => BatchBlockTargetCore.CountItems(singleOutputItem, multipleOutputItems); + } + + // Initialize source + _source = new SourceCore(this, dataflowBlockOptions, + owningSource => ((BatchBlock)owningSource)._target.Complete(exception: null, dropPendingMessages: true, releaseReservedMessages: false), + onItemsRemoved, itemCountingFunc); + + // Initialize target + _target = new BatchBlockTargetCore(this, batchSize, batch => _source.AddMessage(batch), dataflowBlockOptions); + + // When the target is done, let the source know it won't be getting any more data + _target.Completion.ContinueWith(delegate { _source.Complete(); }, + CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((BatchBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, _source.Completion, state => ((BatchBlockTargetCore)state).Complete(exception: null, dropPendingMessages: true, releaseReservedMessages: false), _target); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// + public void Complete() { _target.Complete(exception: null, dropPendingMessages: false, releaseReservedMessages: false); } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + _target.Complete(exception, dropPendingMessages: true, releaseReservedMessages: false); + } + + /// + /// Triggers the to initiate a batching operation even if the number + /// of currently queued or postponed items is less than the . + /// + /// + /// In greedy mode, a batch will be generated from queued items even if fewer exist than the batch size. + /// In non-greedy mode, a batch will be generated asynchronously from postponed items even if + /// fewer than the batch size can be consumed. + /// + public void TriggerBatch() { _target.TriggerBatch(); } + + /// + public IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) + { + return _source.LinkTo(target, linkOptions); + } + + /// + public Boolean TryReceive(Predicate filter, out T[] item) + { + return _source.TryReceive(filter, out item); + } + + /// + public bool TryReceiveAll(out IList items) { return _source.TryReceiveAll(out items); } + + /// + public int OutputCount { get { return _source.OutputCount; } } + + /// + public Task Completion { get { return _source.Completion; } } + + /// Gets the size of the batches generated by this . + /// + /// If the number of items provided to the block is not evenly divisible by the batch size provided + /// to the block's constructor, the block's final batch may contain fewer than the requested number of items. + /// + public Int32 BatchSize { get { return _target.BatchSize; } } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + return _target.OfferMessage(messageHeader, messageValue, source, consumeToAccept); + } + + /// + T[] ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// Gets the number of messages waiting to be offered. This must only be used from the debugger as it avoids taking necessary locks. + private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, BatchSize={1}, OutputCount={2}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + BatchSize, + OutputCountForDebugger); + } + } + /// 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 the Batch. + private sealed class DebugView + { + /// The batch block being viewed. + private BatchBlock _batchBlock; + /// The target half being viewed. + private readonly BatchBlockTargetCore.DebuggingInformation _targetDebuggingInformation; + /// The source half of the block being viewed. + private readonly SourceCore.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The batch being viewed. + public DebugView(BatchBlock batchBlock) + { + Contract.Requires(batchBlock != null, "Need a block with which to construct the debug view"); + _batchBlock = batchBlock; + _targetDebuggingInformation = batchBlock._target.GetDebuggingInformation(); + _sourceDebuggingInformation = batchBlock._source.GetDebuggingInformation(); + } + + /// Gets the messages waiting to be processed. + public IEnumerable InputQueue { get { return _targetDebuggingInformation.InputQueue; } } + /// Gets the messages waiting to be received. + public IEnumerable OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } + /// Gets the number of batches that have been completed. + public long BatchesCompleted { get { return _targetDebuggingInformation.NumberOfBatchesCompleted; } } + + /// Gets the task being used for input processing. + public Task TaskForInputProcessing { get { return _targetDebuggingInformation.TaskForInputProcessing; } } + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public GroupingDataflowBlockOptions DataflowBlockOptions { get { return _targetDebuggingInformation.DataflowBlockOptions; } } + /// Gets the size of batches generated by the block. + public int BatchSize { get { return _batchBlock.BatchSize; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _targetDebuggingInformation.IsDecliningPermanently; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_batchBlock); } } + + /// Gets the messages postponed by this batch. + public QueuedMap, DataflowMessageHeader> PostponedMessages { get { return _targetDebuggingInformation.PostponedMessages; } } + /// Gets the set of all targets linked from this block. + public TargetRegistry LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the set of all targets linked from this block. + public ITargetBlock NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + + /// Provides the core target implementation for a Batch. + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + private sealed class BatchBlockTargetCore + { + /// The messages in this target. + private readonly Queue _messages = new Queue(); + /// A task representing the completion of the block. + private readonly TaskCompletionSource _completionTask = new TaskCompletionSource(); + + /// Gets the object used as the incoming lock. + private object IncomingLock { get { return _completionTask; } } + + /// The target that owns this target core. + private readonly BatchBlock _owningBatch; + /// The batch size. + private readonly int _batchSize; + /// State used when in non-greedy mode. + private readonly NonGreedyState _nonGreedyState; + /// Bounding state for when the block is executing in bounded mode. + private readonly BoundingState _boundingState; + /// The options associated with this block. + private readonly GroupingDataflowBlockOptions _dataflowBlockOptions; + /// The action invoked with a completed batch. + private readonly Action _batchCompletedAction; + + /// Whether to stop accepting new messages. + private bool _decliningPermanently; + /// Whether we've completed at least one batch. + private long _batchesCompleted; + /// Whether someone has reserved the right to call CompleteBlockOncePossible. + private bool _completionReserved; + + /// State used only when in non-greedy mode. + private sealed class NonGreedyState + { + /// Collection of postponed messages. + internal readonly QueuedMap, DataflowMessageHeader> PostponedMessages; + /// A temporary array used to store data retrieved from PostponedMessages. + internal readonly KeyValuePair, DataflowMessageHeader>[] PostponedMessagesTemp; + /// A temporary list used in non-greedy mode when consuming postponed messages to store successfully reserved messages. + internal readonly List, KeyValuePair>> ReservedSourcesTemp; + /// Whether the next batching operation should accept fewer than BatchSize items. + /// This value may be read not under a lock, but it must only be written to protected by the IncomingLock. + internal bool AcceptFewerThanBatchSize; + /// The task used to process messages. + internal Task TaskForInputProcessing; + + /// Initializes the NonGreedyState. + /// The batch size used by the BatchBlock. + internal NonGreedyState(int batchSize) + { + // A non-greedy batch requires at least batchSize sources to be successful. + // Thus, we initialize our collections to be able to store at least that many elements + // in order to avoid unnecessary allocations below that point. + Contract.Requires(batchSize > 0, "A positive batch size is required"); + PostponedMessages = new QueuedMap, DataflowMessageHeader>(batchSize); + PostponedMessagesTemp = new KeyValuePair, DataflowMessageHeader>[batchSize]; + ReservedSourcesTemp = new List, KeyValuePair>>(batchSize); + } + } + + /// Initializes this target core with the specified configuration. + /// The owning batch target. + /// The number of items to group into a batch. + /// The delegate to invoke when a batch is completed. + /// The options with which to configure this . Assumed to be immutable. + /// The must be positive. + /// The is null (Nothing in Visual Basic). + internal BatchBlockTargetCore(BatchBlock owningBatch, Int32 batchSize, Action batchCompletedAction, GroupingDataflowBlockOptions dataflowBlockOptions) + { + Contract.Requires(owningBatch != null, "This batch target core must be associated with a batch block."); + Contract.Requires(batchSize >= 1, "Batch sizes must be positive."); + Contract.Requires(batchCompletedAction != null, "Completion action must be specified."); + Contract.Requires(dataflowBlockOptions != null, "Options required to configure the block."); + + // Store arguments + _owningBatch = owningBatch; + _batchSize = batchSize; + _batchCompletedAction = batchCompletedAction; + _dataflowBlockOptions = dataflowBlockOptions; + + // We'll be using _nonGreedyState even if we are greedy with bounding + bool boundingEnabled = dataflowBlockOptions.BoundedCapacity > 0; + if (!_dataflowBlockOptions.Greedy || boundingEnabled) _nonGreedyState = new NonGreedyState(batchSize); + if (boundingEnabled) _boundingState = new BoundingState(dataflowBlockOptions.BoundedCapacity); + } + + /// + /// Triggers a batching operation even if the number of currently queued or postponed items is less than the . + /// + internal void TriggerBatch() + { + lock (IncomingLock) + { + // If we shouldn't be doing any more work, bail. Otherwise, note that we're willing to + // accept fewer items in the next batching operation, and ensure processing is kicked off. + if (!_decliningPermanently && !_dataflowBlockOptions.CancellationToken.IsCancellationRequested) + { + if (_nonGreedyState == null) + { + MakeBatchIfPossible(evenIfFewerThanBatchSize: true); + } + else + { + _nonGreedyState.AcceptFewerThanBatchSize = true; + ProcessAsyncIfNecessary(); + } + } + CompleteBlockIfPossible(); + } + } + + /// + internal DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + lock (IncomingLock) + { + // If we shouldn't be accepting more messages, don't. + if (_decliningPermanently) + { + CompleteBlockIfPossible(); + return DataflowMessageStatus.DecliningPermanently; + } + + // We can directly accept the message if: + // 1) we are being greedy AND we are not bounding, OR + // 2) we are being greedy AND we are bounding AND there is room available AND there are no postponed messages AND we are not currently processing. + // (If there were any postponed messages, we would need to postpone so that ordering would be maintained.) + // (We should also postpone if we are currently processing, because there may be a race between consuming postponed messages and + // accepting new ones directly into the queue.) + if (_dataflowBlockOptions.Greedy && + (_boundingState == null + || + (_boundingState.CountIsLessThanBound && _nonGreedyState.PostponedMessages.Count == 0 && _nonGreedyState.TaskForInputProcessing == null))) + { + // Consume the message from the source if necessary + if (consumeToAccept) + { + Debug.Assert(source != null, "We must have thrown if source == null && consumeToAccept == true."); + + bool consumed; + messageValue = source.ConsumeMessage(messageHeader, _owningBatch, out consumed); + if (!consumed) return DataflowMessageStatus.NotAvailable; + } + + // Once consumed, enqueue it. + _messages.Enqueue(messageValue); + if (_boundingState != null) _boundingState.CurrentCount += 1; // track this new item against our bound + + // Now start declining if the number of batches we've already made plus + // the number we can make from data already enqueued meets our quota. + if (!_decliningPermanently && + (_batchesCompleted + (_messages.Count / _batchSize)) >= _dataflowBlockOptions.ActualMaxNumberOfGroups) + { + _decliningPermanently = true; + } + + // Now that we have a message, see if we can make forward progress. + MakeBatchIfPossible(evenIfFewerThanBatchSize: false); + + CompleteBlockIfPossible(); + return DataflowMessageStatus.Accepted; + } + // Otherwise, we try to postpone if a source was provided + else if (source != null) + { + Debug.Assert(_nonGreedyState != null, "_nonGreedyState must have been initialized during construction in non-greedy mode."); + + // We always postpone using _nonGreedyState even if we are being greedy with bounding + _nonGreedyState.PostponedMessages.Push(source, messageHeader); + + // In non-greedy mode, we need to see if batch could be completed + if (!_dataflowBlockOptions.Greedy) ProcessAsyncIfNecessary(); + + return DataflowMessageStatus.Postponed; + } + // We can't do anything else about this message + return DataflowMessageStatus.Declined; + } + } + + /// Completes/faults the block. + /// In general, it is not safe to pass releaseReservedMessages:true, because releasing of reserved messages + /// is done without taking a lock. We pass releaseReservedMessages:true only when an exception has been + /// caught inside the message processing loop which is a single instance at any given moment. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal void Complete(Exception exception, bool dropPendingMessages, bool releaseReservedMessages, bool revertProcessingState = false) + { + // Ensure that no new messages may be added + lock (IncomingLock) + { + // Faulting from outside is allowed until we start declining permanently. + // Faulting from inside is allowed at any time. + if (exception != null && (!_decliningPermanently || releaseReservedMessages)) + { + // Record the exception in the source. + // The source, which exposes its Completion to the public will take this + // into account and will complete in Faulted state. + _owningBatch._source.AddException(exception); + } + + // Drop pending messages if requested + if (dropPendingMessages) _messages.Clear(); + } + + // Release reserved messages if requested. + // This must be done from outside the lock. + if (releaseReservedMessages) + { + try { ReleaseReservedMessages(throwOnFirstException: false); } + catch (Exception e) { _owningBatch._source.AddException(e); } + } + + // Triggering completion requires the lock + lock (IncomingLock) + { + // Revert the dirty processing state if requested + if (revertProcessingState) + { + Debug.Assert(_nonGreedyState != null && _nonGreedyState.TaskForInputProcessing != null, + "The processing state must be dirty when revertProcessingState==true."); + _nonGreedyState.TaskForInputProcessing = null; + } + + // Trigger completion + _decliningPermanently = true; + CompleteBlockIfPossible(); + } + } + + /// + internal Task Completion { get { return _completionTask.Task; } } + + /// Gets the size of the batches generated by this . + internal Int32 BatchSize { get { return _batchSize; } } + + /// Gets whether the target has had cancellation requested or an exception has occurred. + private bool CanceledOrFaulted + { + get + { + return _dataflowBlockOptions.CancellationToken.IsCancellationRequested || _owningBatch._source.HasExceptions; + } + } + + /// Returns the available capacity to bring in postponed items. The exact values above _batchSize don't matter. + private int BoundedCapacityAvailable + { + get + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + return _boundingState != null ? + _dataflowBlockOptions.BoundedCapacity - _boundingState.CurrentCount : + _batchSize; + } + } + + /// Completes the block once all completion conditions are met. + private void CompleteBlockIfPossible() + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + if (!_completionReserved) + { + bool currentlyProcessing = _nonGreedyState != null && _nonGreedyState.TaskForInputProcessing != null; + bool completedAllDesiredBatches = _batchesCompleted >= _dataflowBlockOptions.ActualMaxNumberOfGroups; + bool noMoreMessages = _decliningPermanently && _messages.Count < _batchSize; + + bool complete = !currentlyProcessing && (completedAllDesiredBatches || noMoreMessages || CanceledOrFaulted); + if (complete) + { + _completionReserved = true; + + // Make sure the target is declining + _decliningPermanently = true; + + // If we still have straggling items remaining, make them into their own batch even though there are fewer than batchSize + if (_messages.Count > 0) MakeBatchIfPossible(evenIfFewerThanBatchSize: true); + + // We need to complete the block, but we may have arrived here from an external + // call to the block. To avoid running arbitrary code in the form of + // completion task continuations in that case, do it in a separate task. + Task.Factory.StartNew(thisTargetCore => + { + var targetCore = (BatchBlockTargetCore)thisTargetCore; + + // Release any postponed messages + List exceptions = null; + if (targetCore._nonGreedyState != null) + { + // Note: No locks should be held at this point + Common.ReleaseAllPostponedMessages(targetCore._owningBatch, + targetCore._nonGreedyState.PostponedMessages, + ref exceptions); + } + + if (exceptions != null) + { + // It is important to migrate these exceptions to the source part of the owning batch, + // because that is the completion task that is publically exposed. + targetCore._owningBatch._source.AddExceptions(exceptions); + } + + // Target's completion task is only available internally with the sole purpose + // of releasing the task that completes the parent. Hence the actual reason + // for completing this task doesn't matter. + targetCore._completionTask.TrySetResult(default(VoidResult)); + }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + } + + /// + /// Gets whether we should launch further synchronous or asynchronous processing + /// to create batches. + /// + private bool BatchesNeedProcessing + { + get + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + // If we're currently processing asynchronously, let that async task + // handle all work; nothing more to do here. If we're not currently processing + // but cancellation has been requested, don't do more work either. + bool completedAllDesiredBatches = _batchesCompleted >= _dataflowBlockOptions.ActualMaxNumberOfGroups; + bool currentlyProcessing = _nonGreedyState != null && _nonGreedyState.TaskForInputProcessing != null; + if (completedAllDesiredBatches || currentlyProcessing || CanceledOrFaulted) return false; + + // Now, if it's possible to create a batch from queued items or if there are enough + // postponed items to attempt a batch, batches need processing. + int neededMessageCountToCompleteBatch = _batchSize - _messages.Count; + int boundedCapacityAvailable = BoundedCapacityAvailable; + + // We have items queued up sufficient to make up a batch + if (neededMessageCountToCompleteBatch <= 0) return true; + + if (_nonGreedyState != null) + { + // We can make a triggered batch using postponed messages + if (_nonGreedyState.AcceptFewerThanBatchSize && + (_messages.Count > 0 || (_nonGreedyState.PostponedMessages.Count > 0 && boundedCapacityAvailable > 0))) + return true; + + if (_dataflowBlockOptions.Greedy) + { + // We are in greedy mode and we have postponed messages. + // (In greedy mode we only postpone due to lack of bounding capacity.) + // And now we have capacity to consume some postponed messages. + // (In greedy mode we can/should consume as many postponed messages as we can even + // if those messages are insufficient to make up a batch.) + if (_nonGreedyState.PostponedMessages.Count > 0 && boundedCapacityAvailable > 0) return true; + } + else + { + // We are in non-greedy mode and we have enough postponed messages and bounding capacity to make a full batch + if (_nonGreedyState.PostponedMessages.Count >= neededMessageCountToCompleteBatch && + boundedCapacityAvailable >= neededMessageCountToCompleteBatch) + return true; + } + } + + // There is no other reason to kick off a processing task + return false; + } + } + + /// Called when new messages are available to be processed. + /// Whether this call is the continuation of a previous message loop. + private void ProcessAsyncIfNecessary(bool isReplacementReplica = false) + { + Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode."); + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + if (BatchesNeedProcessing) + { + ProcessAsyncIfNecessary_Slow(isReplacementReplica); + } + } + + /// + /// Slow path for ProcessAsyncIfNecessary. + /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined. + /// + private void ProcessAsyncIfNecessary_Slow(bool isReplacementReplica) + { + Contract.Requires(BatchesNeedProcessing, "There must be a batch that needs processing."); + + // Create task and store into _taskForInputProcessing prior to scheduling the task + // so that _taskForInputProcessing will be visibly set in the task loop. + _nonGreedyState.TaskForInputProcessing = new Task(thisBatchTarget => ((BatchBlockTargetCore)thisBatchTarget).ProcessMessagesLoopCore(), this, + Common.GetCreationOptionsForTask(isReplacementReplica)); + +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TaskLaunchedForMessageHandling( + _owningBatch, _nonGreedyState.TaskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, + _messages.Count + (_nonGreedyState != null ? _nonGreedyState.PostponedMessages.Count : 0)); + } +#endif + + // Start the task handling scheduling exceptions + Exception exception = Common.StartTaskSafe(_nonGreedyState.TaskForInputProcessing, _dataflowBlockOptions.TaskScheduler); + if (exception != null) + { + // Get out from under currently held locks. Complete re-acquires the locks it needs. + Task.Factory.StartNew(exc => Complete(exception: (Exception)exc, dropPendingMessages: true, releaseReservedMessages: true, revertProcessingState: true), + exception, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + + + /// Task body used to process messages. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ProcessMessagesLoopCore() + { + Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + try + { + int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask; + int timesThroughLoop = 0; + bool madeProgress; + do + { + // Determine whether a batch has been forced/triggered. + // (If the value is read as false and is set to true immediately afterwards, + // we'll simply force the next time around. The only code that can + // set the value to false is this function, after reading a true value.) + bool triggered = Volatile.Read(ref _nonGreedyState.AcceptFewerThanBatchSize); + + // Retrieve postponed items: + // In non-greedy mode: Reserve + Consume + // In greedy bounded mode: Consume (without a prior reservation) + if (!_dataflowBlockOptions.Greedy) RetrievePostponedItemsNonGreedy(allowFewerThanBatchSize: triggered); + else RetrievePostponedItemsGreedyBounded(allowFewerThanBatchSize: triggered); + + // Try to make a batch if there are enough buffered messages + lock (IncomingLock) + { + madeProgress = MakeBatchIfPossible(evenIfFewerThanBatchSize: triggered); + + // Reset the trigger flag if: + // - We made a batch, regardless of whether it came due to a trigger or not. + // - We tried to make a batch due to a trigger, but were unable to, which + // could happen if we're unable to consume any of the postponed messages. + if (madeProgress || triggered) _nonGreedyState.AcceptFewerThanBatchSize = false; + } + + timesThroughLoop++; + } while (madeProgress && timesThroughLoop < maxMessagesPerTask); + } + catch (Exception exc) + { + Complete(exc, dropPendingMessages: false, releaseReservedMessages: true); + } + finally + { + lock (IncomingLock) + { + // We're no longer processing, so null out the processing task + _nonGreedyState.TaskForInputProcessing = null; + + // However, we may have given up early because we hit our own configured + // processing limits rather than because we ran out of work to do. If that's + // the case, make sure we spin up another task to keep going. + ProcessAsyncIfNecessary(isReplacementReplica: true); + + // If, however, we stopped because we ran out of work to do and we + // know we'll never get more, then complete. + CompleteBlockIfPossible(); + } + } + } + + /// Create a batch from the available items. + /// + /// Whether to make a batch even if there are fewer than BatchSize items available. + /// + /// true if a batch was created and published; otherwise, false. + private bool MakeBatchIfPossible(bool evenIfFewerThanBatchSize) + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + // Is a full batch available? + bool fullBatch = _messages.Count >= _batchSize; + + // If so, or if it's ok to make a batch with fewer than batchSize, make one. + if (fullBatch || (evenIfFewerThanBatchSize && _messages.Count > 0)) + { + var newBatch = new T[fullBatch ? _batchSize : _messages.Count]; + for (int i = 0; i < newBatch.Length; i++) newBatch[i] = _messages.Dequeue(); + _batchCompletedAction(newBatch); + _batchesCompleted++; + if (_batchesCompleted >= _dataflowBlockOptions.ActualMaxNumberOfGroups) _decliningPermanently = true; + return true; + } + // No batch could be created + else return false; + } + + /// Retrieves postponed items in non-greedy mode if we have enough to make a batch. + /// Whether we'll accept consuming fewer elements than the defined batch size. + private void RetrievePostponedItemsNonGreedy(bool allowFewerThanBatchSize) + { + Contract.Requires(!_dataflowBlockOptions.Greedy, "This method may only be used in non-greedy mode."); + Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // Shortcuts just to keep the code cleaner + QueuedMap, DataflowMessageHeader> postponed = _nonGreedyState.PostponedMessages; + KeyValuePair, DataflowMessageHeader>[] postponedTemp = _nonGreedyState.PostponedMessagesTemp; + List, KeyValuePair>> reserved = _nonGreedyState.ReservedSourcesTemp; + + // Clear the temporary buffer. This is safe to do without a lock because + // it is only accessed by the serial message loop. + reserved.Clear(); + + int poppedInitially; + int boundedCapacityAvailable; + lock (IncomingLock) + { + // The queue must be empty between batches in non-greedy mode + Debug.Assert(_messages.Count == 0, "The queue must be empty between batches in non-greedy mode"); + + // If there are not enough postponed items (or if we're not allowing consumption), there's nothing more to be done + boundedCapacityAvailable = BoundedCapacityAvailable; + if (_decliningPermanently || + postponed.Count == 0 || + boundedCapacityAvailable <= 0 || + (!allowFewerThanBatchSize && (postponed.Count < _batchSize || boundedCapacityAvailable < _batchSize))) + return; + + // Grab an initial batch of postponed messages. + poppedInitially = postponed.PopRange(postponedTemp, 0, _batchSize); + Debug.Assert(allowFewerThanBatchSize ? poppedInitially > 0 : poppedInitially == _batchSize, + "We received fewer than we expected based on the previous check."); + } // Release the lock. We must not hold it while calling Reserve/Consume/Release. + + // Try to reserve the initial batch of messages. + for (int i = 0; i < poppedInitially; i++) + { + KeyValuePair, DataflowMessageHeader> sourceAndMessage = postponedTemp[i]; + if (sourceAndMessage.Key.ReserveMessage(sourceAndMessage.Value, _owningBatch)) + { + var reservedMessage = new KeyValuePair(sourceAndMessage.Value, default(T)); + var reservedSourceAndMessage = new KeyValuePair, KeyValuePair>(sourceAndMessage.Key, reservedMessage); + reserved.Add(reservedSourceAndMessage); + } + } + Array.Clear(postponedTemp, 0, postponedTemp.Length); // clear out the temp array so as not to hold onto messages too long + + // If we didn't reserve enough to make a batch, start picking off postponed messages + // one by one until we either have enough reserved or we run out of messages + while (reserved.Count < _batchSize) + { + KeyValuePair, DataflowMessageHeader> sourceAndMessage; + lock (IncomingLock) + { + if (!postponed.TryPop(out sourceAndMessage)) break; + } // Release the lock. We must not hold it while calling Reserve/Consume/Release. + if (sourceAndMessage.Key.ReserveMessage(sourceAndMessage.Value, _owningBatch)) + { + var reservedMessage = new KeyValuePair(sourceAndMessage.Value, default(T)); + var reservedSourceAndMessage = new KeyValuePair, KeyValuePair>(sourceAndMessage.Key, reservedMessage); + reserved.Add(reservedSourceAndMessage); + } + } + + Debug.Assert(reserved.Count <= _batchSize, "Expected the number of reserved sources to be <= the number needed for a batch."); + + // We've now reserved what we can. Either consume them all or release them all. + if (reserved.Count > 0) + { + // TriggerBatch adds a complication here. It's possible that while we've been reserving + // messages, Post has been used to queue up a bunch of messages to the batch, + // and that if the batch has a max group count and enough messages were posted, + // we could now be declining. In that case, if we don't specially handle the situation, + // we could consume messages that we won't be able to turn into a batch, since MaxNumberOfGroups + // implies the block will only ever output a maximum number of batches. To handle this, + // we start declining before consuming, now that we know we'll have enough to form a batch. + // (If an exception occurs after we do this, we'll be shutting down the block anyway.) + // This is also why we still reserve/consume rather than just consume in forced mode, + // so that we only consume if we're able to turn what we consume into a batch. + bool shouldProceedToConsume = true; + if (allowFewerThanBatchSize) + { + lock (IncomingLock) + { + if (!_decliningPermanently && + (_batchesCompleted + 1) >= _dataflowBlockOptions.ActualMaxNumberOfGroups) + // Note that this logic differs from the other location where we do a similar check. + // Here we want to know whether we're one shy of meeting our quota, because we'll accept + // any size batch. Elsewhere, we need to know whether we have the right number of messages + // queued up. + { + shouldProceedToConsume = !_decliningPermanently; + _decliningPermanently = true; + } + } + } + + if (shouldProceedToConsume && (allowFewerThanBatchSize || reserved.Count == _batchSize)) + { + ConsumeReservedMessagesNonGreedy(); + } + else + { + ReleaseReservedMessages(throwOnFirstException: true); + } + } + + // Clear out the reserved list, so as not to hold onto values longer than necessary. + // We don't do this in case of failure, because the higher-level exception handler + // accesses the list to try to release reservations. + reserved.Clear(); + } + + /// Retrieves postponed items in greedy bounded mode. + /// Whether we'll accept consuming fewer elements than the defined batch size. + private void RetrievePostponedItemsGreedyBounded(bool allowFewerThanBatchSize) + { + Contract.Requires(_dataflowBlockOptions.Greedy, "This method may only be used in greedy mode."); + Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode."); + Contract.Requires(_boundingState != null, "Bounding state is required when in bounded mode."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // Shortcuts just to keep the code cleaner + QueuedMap, DataflowMessageHeader> postponed = _nonGreedyState.PostponedMessages; + KeyValuePair, DataflowMessageHeader>[] postponedTemp = _nonGreedyState.PostponedMessagesTemp; + List, KeyValuePair>> reserved = _nonGreedyState.ReservedSourcesTemp; + + // Clear the temporary buffer. This is safe to do without a lock because + // it is only accessed by the serial message loop. + reserved.Clear(); + + int poppedInitially; + int boundedCapacityAvailable; + int itemCountNeededToCompleteBatch; + lock (IncomingLock) + { + // If there are not enough postponed items (or if we're not allowing consumption), there's nothing more to be done + boundedCapacityAvailable = BoundedCapacityAvailable; + itemCountNeededToCompleteBatch = _batchSize - _messages.Count; + if (_decliningPermanently || + postponed.Count == 0 || + boundedCapacityAvailable <= 0) + return; + + // Grab an initial batch of postponed messages. + if (boundedCapacityAvailable < itemCountNeededToCompleteBatch) itemCountNeededToCompleteBatch = boundedCapacityAvailable; + poppedInitially = postponed.PopRange(postponedTemp, 0, itemCountNeededToCompleteBatch); + Debug.Assert(poppedInitially > 0, "We received fewer than we expected based on the previous check."); + } // Release the lock. We must not hold it while calling Reserve/Consume/Release. + + // Treat popped messages as reserved. + // We don't have to formally reserve because we are in greedy mode. + for (int i = 0; i < poppedInitially; i++) + { + KeyValuePair, DataflowMessageHeader> sourceAndMessage = postponedTemp[i]; + var reservedMessage = new KeyValuePair(sourceAndMessage.Value, default(T)); + var reservedSourceAndMessage = new KeyValuePair, KeyValuePair>(sourceAndMessage.Key, reservedMessage); + reserved.Add(reservedSourceAndMessage); + } + Array.Clear(postponedTemp, 0, postponedTemp.Length); // clear out the temp array so as not to hold onto messages too long + + // If we didn't reserve enough to make a batch, start picking off postponed messages + // one by one until we either have enough reserved or we run out of messages + while (reserved.Count < itemCountNeededToCompleteBatch) + { + KeyValuePair, DataflowMessageHeader> sourceAndMessage; + lock (IncomingLock) + { + if (!postponed.TryPop(out sourceAndMessage)) break; + } // Release the lock. We must not hold it while calling Reserve/Consume/Release. + + var reservedMessage = new KeyValuePair(sourceAndMessage.Value, default(T)); + var reservedSourceAndMessage = new KeyValuePair, KeyValuePair>(sourceAndMessage.Key, reservedMessage); + reserved.Add(reservedSourceAndMessage); + } + + Debug.Assert(reserved.Count <= itemCountNeededToCompleteBatch, "Expected the number of reserved sources to be <= the number needed for a batch."); + + // We've gotten as many postponed messages as we can. Try to consume them. + if (reserved.Count > 0) + { + // TriggerBatch adds a complication here. It's possible that while we've been reserving + // messages, Post has been used to queue up a bunch of messages to the batch, + // and that if the batch has a max group count and enough messages were posted, + // we could now be declining. In that case, if we don't specially handle the situation, + // we could consume messages that we won't be able to turn into a batch, since MaxNumberOfGroups + // implies the block will only ever output a maximum number of batches. To handle this, + // we start declining before consuming, now that we know we'll have enough to form a batch. + // (If an exception occurs after we do this, we'll be shutting down the block anyway.) + // This is also why we still reserve/consume rather than just consume in forced mode, + // so that we only consume if we're able to turn what we consume into a batch. + bool shouldProceedToConsume = true; + if (allowFewerThanBatchSize) + { + lock (IncomingLock) + { + if (!_decliningPermanently && + (_batchesCompleted + 1) >= _dataflowBlockOptions.ActualMaxNumberOfGroups) + // Note that this logic differs from the other location where we do a similar check. + // Here we want to know whether we're one shy of meeting our quota, because we'll accept + // any size batch. Elsewhere, we need to know whether we have the right number of messages + // queued up. + { + shouldProceedToConsume = !_decliningPermanently; + _decliningPermanently = true; + } + } + } + + if (shouldProceedToConsume) + { + ConsumeReservedMessagesGreedyBounded(); + } + } + + // Clear out the reserved list, so as not to hold onto values longer than necessary. + // We don't do this in case of failure, because the higher-level exception handler + // accesses the list to try to release reservations. + reserved.Clear(); + } + + /// + /// Consumes all of the reserved messages stored in the non-greedy state's temporary reserved source list. + /// + private void ConsumeReservedMessagesNonGreedy() + { + Contract.Requires(!_dataflowBlockOptions.Greedy, "This method may only be used in non-greedy mode."); + Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode."); + Contract.Requires(_nonGreedyState.ReservedSourcesTemp != null, "ReservedSourcesTemp should have been initialized."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // Consume the reserved items and store the data. + List, KeyValuePair>> reserved = _nonGreedyState.ReservedSourcesTemp; + for (int i = 0; i < reserved.Count; i++) + { + // We can only store the data into _messages while holding the IncomingLock, we + // don't want to allocate extra objects for each batch, and we don't want to + // take and release the lock for each individual item... but we do need to use + // the consumed message rather than the initial one. To handle this, because KeyValuePair is immutable, + // we store a new KVP with the newly consumed message back into the temp list, so that we can + // then enumerate the temp list en mass while taking the lock once afterwards. + KeyValuePair, KeyValuePair> sourceAndMessage = reserved[i]; + reserved[i] = default(KeyValuePair, KeyValuePair>); // in case of exception from ConsumeMessage + bool consumed; + T consumedValue = sourceAndMessage.Key.ConsumeMessage(sourceAndMessage.Value.Key, _owningBatch, out consumed); + if (!consumed) + { + // The protocol broke down, so throw an exception, as this is fatal. Before doing so, though, + // null out all of the messages we've already consumed, as a higher-level event handler + // should try to release everything in the reserved list. + for (int prev = 0; prev < i; prev++) reserved[prev] = default(KeyValuePair, KeyValuePair>); + throw new InvalidOperationException(SR.InvalidOperation_FailedToConsumeReservedMessage); + } + + var consumedMessage = new KeyValuePair(sourceAndMessage.Value.Key, consumedValue); + var consumedSourceAndMessage = new KeyValuePair, KeyValuePair>(sourceAndMessage.Key, consumedMessage); + reserved[i] = consumedSourceAndMessage; + } + lock (IncomingLock) + { + // Increment the bounding count with the number of consumed messages + if (_boundingState != null) _boundingState.CurrentCount += reserved.Count; + + // Enqueue the consumed mesasages + foreach (KeyValuePair, KeyValuePair> sourceAndMessage in reserved) + { + _messages.Enqueue(sourceAndMessage.Value.Value); + } + } + } + + /// + /// Consumes all of the reserved messages stored in the non-greedy state's temporary reserved source list. + /// + private void ConsumeReservedMessagesGreedyBounded() + { + Contract.Requires(_dataflowBlockOptions.Greedy, "This method may only be used in greedy mode."); + Contract.Requires(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode."); + Contract.Requires(_nonGreedyState.ReservedSourcesTemp != null, "ReservedSourcesTemp should have been initialized."); + Contract.Requires(_boundingState != null, "Bounded state is required for bounded mode."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // Consume the reserved items and store the data. + int consumedCount = 0; + List, KeyValuePair>> reserved = _nonGreedyState.ReservedSourcesTemp; + for (int i = 0; i < reserved.Count; i++) + { + // We can only store the data into _messages while holding the IncomingLock, we + // don't want to allocate extra objects for each batch, and we don't want to + // take and release the lock for each individual item... but we do need to use + // the consumed message rather than the initial one. To handle this, because KeyValuePair is immutable, + // we store a new KVP with the newly consumed message back into the temp list, so that we can + // then enumerate the temp list en mass while taking the lock once afterwards. + KeyValuePair, KeyValuePair> sourceAndMessage = reserved[i]; + reserved[i] = default(KeyValuePair, KeyValuePair>); // in case of exception from ConsumeMessage + bool consumed; + T consumedValue = sourceAndMessage.Key.ConsumeMessage(sourceAndMessage.Value.Key, _owningBatch, out consumed); + if (consumed) + { + var consumedMessage = new KeyValuePair(sourceAndMessage.Value.Key, consumedValue); + var consumedSourceAndMessage = new KeyValuePair, KeyValuePair>(sourceAndMessage.Key, consumedMessage); + reserved[i] = consumedSourceAndMessage; + + // Keep track of the actually consumed messages + consumedCount++; + } + } + lock (IncomingLock) + { + // Increment the bounding count with the number of consumed messages + if (_boundingState != null) _boundingState.CurrentCount += consumedCount; + + // Enqueue the consumed mesasages + foreach (KeyValuePair, KeyValuePair> sourceAndMessage in reserved) + { + // If we didn't consume this message, the KeyValuePai will be default, i.e. the source will be null + if (sourceAndMessage.Key != null) _messages.Enqueue(sourceAndMessage.Value.Value); + } + } + } + + /// + /// Releases all of the reserved messages stored in the non-greedy state's temporary reserved source list. + /// + /// + /// Whether to allow an exception from a release to propagate immediately, + /// or to delay propagation until all releases have been attempted. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal void ReleaseReservedMessages(bool throwOnFirstException) + { + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + Debug.Assert(_nonGreedyState != null, "Non-greedy state is required for non-greedy mode."); + Debug.Assert(_nonGreedyState.ReservedSourcesTemp != null, "Should have been initialized"); + + List exceptions = null; + + List, KeyValuePair>> reserved = _nonGreedyState.ReservedSourcesTemp; + for (int i = 0; i < reserved.Count; i++) + { + KeyValuePair, KeyValuePair> sourceAndMessage = reserved[i]; + reserved[i] = default(KeyValuePair, KeyValuePair>); + ISourceBlock source = sourceAndMessage.Key; + KeyValuePair message = sourceAndMessage.Value; + if (source != null && message.Key.IsValid) + { + try { source.ReleaseReservation(message.Key, _owningBatch); } + catch (Exception e) + { + if (throwOnFirstException) throw; + if (exceptions == null) exceptions = new List(1); + exceptions.Add(e); + } + } + } + + if (exceptions != null) throw new AggregateException(exceptions); + } + + /// Notifies the block that one or more items was removed from the queue. + /// The number of items removed. + internal void OnItemsRemoved(int numItemsRemoved) + { + Contract.Requires(numItemsRemoved > 0, "Should only be called for a positive number of items removed."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // If we're bounding, we need to know when an item is removed so that we + // can update the count that's mirroring the actual count in the source's queue, + // and potentially kick off processing to start consuming postponed messages. + if (_boundingState != null) + { + lock (IncomingLock) + { + // Decrement the count, which mirrors the count in the source half + Debug.Assert(_boundingState.CurrentCount - numItemsRemoved >= 0, + "It should be impossible to have a negative number of items."); + _boundingState.CurrentCount -= numItemsRemoved; + + ProcessAsyncIfNecessary(); + CompleteBlockIfPossible(); + } + } + } + + /// Counts the input items in a single output item or in a list of output items. + /// A single output item. Only considered if multipleOutputItems == null. + /// A list of output items. May be null. + internal static int CountItems(T[] singleOutputItem, IList multipleOutputItems) + { + // If multipleOutputItems == null, then singleOutputItem is the subject of counting + if (multipleOutputItems == null) return singleOutputItem.Length; + + // multipleOutputItems != null. Count the elements in each item. + int count = 0; + foreach (T[] item in multipleOutputItems) count += item.Length; + return count; + } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int InputCountForDebugger { get { return _messages.Count; } } + + /// Gets information about this helper to be used for display in a debugger. + /// Debugging information about this target. + internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); } + + /// Gets the object to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displayBatch = _owningBatch as IDebuggerDisplay; + return string.Format("Block=\"{0}\"", + displayBatch != null ? displayBatch.Content : _owningBatch); + } + } + + /// Provides a wrapper for commonly needed debugging information. + internal sealed class DebuggingInformation + { + /// The target being viewed. + private BatchBlockTargetCore _target; + + /// Initializes the debugging helper. + /// The target being viewed. + public DebuggingInformation(BatchBlockTargetCore target) { _target = target; } + + /// Gets the messages waiting to be processed. + public IEnumerable InputQueue { get { return _target._messages.ToList(); } } + /// Gets the task being used for input processing. + public Task TaskForInputProcessing { get { return _target._nonGreedyState != null ? _target._nonGreedyState.TaskForInputProcessing : null; } } + /// Gets the collection of postponed messages. + public QueuedMap, DataflowMessageHeader> PostponedMessages { get { return _target._nonGreedyState != null ? _target._nonGreedyState.PostponedMessages : null; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _target._decliningPermanently; } } + /// Gets the DataflowBlockOptions used to configure this block. + public GroupingDataflowBlockOptions DataflowBlockOptions { get { return _target._dataflowBlockOptions; } } + /// Gets the number of batches that have been completed. + public long NumberOfBatchesCompleted { get { return _target._batchesCompleted; } } + } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BatchedJoinBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BatchedJoinBlock.cs new file mode 100644 index 00000000000..f1cf99073a0 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BatchedJoinBlock.cs @@ -0,0 +1,783 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// BatchedJoinBlock.cs +// +// +// A propagator block that groups individual messages of multiple types +// into tuples of arrays of those messages. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Threading.Tasks.Dataflow.Internal; + +namespace System.Threading.Tasks.Dataflow +{ + /// + /// Provides a dataflow block that batches a specified number of inputs of potentially differing types + /// provided to one or more of its targets. + /// + /// Specifies the type of data accepted by the block's first target. + /// Specifies the type of data accepted by the block's second target. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(BatchedJoinBlock<,>.DebugView))] + public sealed class BatchedJoinBlock : IReceivableSourceBlock, IList>>, IDebuggerDisplay + { + /// The size of the batches generated by this BatchedJoin. + private readonly int _batchSize; + /// State shared among the targets. + private readonly BatchedJoinBlockTargetSharedResources _sharedResources; + /// The target providing inputs of type T1. + private readonly BatchedJoinBlockTarget _target1; + /// The target providing inputs of type T2. + private readonly BatchedJoinBlockTarget _target2; + /// The source side. + private readonly SourceCore, IList>> _source; + + /// Initializes this with the specified configuration. + /// The number of items to group into a batch. + /// The must be positive. + public BatchedJoinBlock(Int32 batchSize) : + this(batchSize, GroupingDataflowBlockOptions.Default) + { } + + /// Initializes this with the specified configuration. + /// The number of items to group into a batch. + /// The options with which to configure this . + /// The must be positive. + /// The is null (Nothing in Visual Basic). + public BatchedJoinBlock(Int32 batchSize, GroupingDataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (batchSize < 1) throw new ArgumentOutOfRangeException("batchSize", SR.ArgumentOutOfRange_GenericPositive); + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + if (!dataflowBlockOptions.Greedy) throw new ArgumentException(SR.Argument_NonGreedyNotSupported, "dataflowBlockOptions"); + if (dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded) throw new ArgumentException(SR.Argument_BoundedCapacityNotSupported, "dataflowBlockOptions"); + Contract.EndContractBlock(); + + // Store arguments + _batchSize = batchSize; + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Configure the source + _source = new SourceCore, IList>>( + this, dataflowBlockOptions, owningSource => ((BatchedJoinBlock)owningSource).CompleteEachTarget()); + + // The action to run when a batch should be created. This is typically called + // when we have a full batch, but it will also be called when we're done receiving + // messages, and thus when there may be a few stragglers we need to make a batch out of. + Action createBatchAction = () => + { + if (_target1.Count > 0 || _target2.Count > 0) + { + _source.AddMessage(Tuple.Create(_target1.GetAndEmptyMessages(), _target2.GetAndEmptyMessages())); + } + }; + + // Configure the targets + _sharedResources = new BatchedJoinBlockTargetSharedResources( + batchSize, dataflowBlockOptions, + createBatchAction, + () => + { + createBatchAction(); + _source.Complete(); + }, + _source.AddException, + Complete); + _target1 = new BatchedJoinBlockTarget(_sharedResources); + _target2 = new BatchedJoinBlockTarget(_sharedResources); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((BatchedJoinBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, _source.Completion, state => ((BatchedJoinBlock)state).CompleteEachTarget(), this); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// Gets the size of the batches generated by this . + public Int32 BatchSize { get { return _batchSize; } } + + /// Gets a target that may be used to offer messages of the first type. + public ITargetBlock Target1 { get { return _target1; } } + + /// Gets a target that may be used to offer messages of the second type. + public ITargetBlock Target2 { get { return _target2; } } + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public IDisposable LinkTo(ITargetBlock, IList>> target, DataflowLinkOptions linkOptions) + { + return _source.LinkTo(target, linkOptions); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public Boolean TryReceive(Predicate, IList>> filter, out Tuple, IList> item) + { + return _source.TryReceive(filter, out item); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public bool TryReceiveAll(out IList, IList>> items) { return _source.TryReceiveAll(out items); } + + /// + public int OutputCount { get { return _source.OutputCount; } } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + public void Complete() + { + Debug.Assert(_target1 != null, "_target1 not initialized"); + Debug.Assert(_target2 != null, "_target2 not initialized"); + + _target1.Complete(); + _target2.Complete(); + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + Debug.Assert(_sharedResources != null, "_sharedResources not initialized"); + Debug.Assert(_sharedResources._incomingLock != null, "_sharedResources._incomingLock not initialized"); + Debug.Assert(_source != null, "_source not initialized"); + + lock (_sharedResources._incomingLock) + { + if (!_sharedResources._decliningPermanently) _source.AddException(exception); + } + Complete(); + } + + /// + Tuple, IList> ISourceBlock, IList>>.ConsumeMessage( + DataflowMessageHeader messageHeader, ITargetBlock, IList>> target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock, IList>>.ReserveMessage( + DataflowMessageHeader messageHeader, ITargetBlock, IList>> target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock, IList>>.ReleaseReservation( + DataflowMessageHeader messageHeader, ITargetBlock, IList>> target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// + /// Invokes Complete on each target + /// + private void CompleteEachTarget() + { + _target1.Complete(); + _target2.Complete(); + } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, BatchSize={1}, OutputCount={2}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + BatchSize, + OutputCountForDebugger); + } + } + /// 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 the Transform. + private sealed class DebugView + { + /// The block being viewed. + private readonly BatchedJoinBlock _batchedJoinBlock; + /// The source half of the block being viewed. + private readonly SourceCore, IList>>.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The batched join being viewed. + public DebugView(BatchedJoinBlock batchedJoinBlock) + { + Contract.Requires(batchedJoinBlock != null, "Need a block with which to construct the debug view."); + _batchedJoinBlock = batchedJoinBlock; + _sourceDebuggingInformation = batchedJoinBlock._source.GetDebuggingInformation(); + } + + /// Gets the messages waiting to be received. + public IEnumerable, IList>> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } + /// Gets the number of batches created. + public long BatchesCreated { get { return _batchedJoinBlock._sharedResources._batchesCreated; } } + /// Gets the number of items remaining to form a batch. + public int RemainingItemsForBatch { get { return _batchedJoinBlock._sharedResources._remainingItemsInBatch; } } + + /// Gets the size of the batches generated by this BatchedJoin. + public Int32 BatchSize { get { return _batchedJoinBlock._batchSize; } } + /// Gets the first target. + public ITargetBlock Target1 { get { return _batchedJoinBlock._target1; } } + /// Gets the second target. + public ITargetBlock Target2 { get { return _batchedJoinBlock._target2; } } + + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_batchedJoinBlock); } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry, IList>> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the target that holds a reservation on the next message, if any. + public ITargetBlock, IList>> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + } + + /// + /// Provides a dataflow block that batches a specified number of inputs of potentially differing types + /// provided to one or more of its targets. + /// + /// Specifies the type of data accepted by the block's first target. + /// Specifies the type of data accepted by the block's second target. + /// Specifies the type of data accepted by the block's third target. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(BatchedJoinBlock<,,>.DebugView))] + [SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")] + public sealed class BatchedJoinBlock : IReceivableSourceBlock, IList, IList>>, IDebuggerDisplay + { + /// The size of the batches generated by this BatchedJoin. + private readonly int _batchSize; + /// State shared among the targets. + private readonly BatchedJoinBlockTargetSharedResources _sharedResources; + /// The target providing inputs of type T1. + private readonly BatchedJoinBlockTarget _target1; + /// The target providing inputs of type T2. + private readonly BatchedJoinBlockTarget _target2; + /// The target providing inputs of type T3. + private readonly BatchedJoinBlockTarget _target3; + /// The source side. + private readonly SourceCore, IList, IList>> _source; + + /// Initializes this with the specified configuration. + /// The number of items to group into a batch. + /// The must be positive. + public BatchedJoinBlock(Int32 batchSize) : + this(batchSize, GroupingDataflowBlockOptions.Default) + { } + + /// Initializes this with the specified configuration. + /// The number of items to group into a batch. + /// The options with which to configure this . + /// The must be positive. + /// The is null (Nothing in Visual Basic). + public BatchedJoinBlock(Int32 batchSize, GroupingDataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (batchSize < 1) throw new ArgumentOutOfRangeException("batchSize", SR.ArgumentOutOfRange_GenericPositive); + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + if (!dataflowBlockOptions.Greedy || + dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded) + { + throw new ArgumentException(SR.Argument_NonGreedyNotSupported, "dataflowBlockOptions"); + } + Contract.EndContractBlock(); + + // Store arguments + _batchSize = batchSize; + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Configure the source + _source = new SourceCore, IList, IList>>( + this, dataflowBlockOptions, owningSource => ((BatchedJoinBlock)owningSource).CompleteEachTarget()); + + // The action to run when a batch should be created. This is typically called + // when we have a full batch, but it will also be called when we're done receiving + // messages, and thus when there may be a few stragglers we need to make a batch out of. + Action createBatchAction = () => + { + if (_target1.Count > 0 || _target2.Count > 0 || _target3.Count > 0) + { + _source.AddMessage(Tuple.Create(_target1.GetAndEmptyMessages(), _target2.GetAndEmptyMessages(), _target3.GetAndEmptyMessages())); + } + }; + + // Configure the targets + _sharedResources = new BatchedJoinBlockTargetSharedResources( + batchSize, dataflowBlockOptions, + createBatchAction, + () => + { + createBatchAction(); + _source.Complete(); + }, + _source.AddException, + Complete); + _target1 = new BatchedJoinBlockTarget(_sharedResources); + _target2 = new BatchedJoinBlockTarget(_sharedResources); + _target3 = new BatchedJoinBlockTarget(_sharedResources); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((BatchedJoinBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, _source.Completion, state => ((BatchedJoinBlock)state).CompleteEachTarget(), this); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// Gets the size of the batches generated by this . + public Int32 BatchSize { get { return _batchSize; } } + + /// Gets a target that may be used to offer messages of the first type. + public ITargetBlock Target1 { get { return _target1; } } + + /// Gets a target that may be used to offer messages of the second type. + public ITargetBlock Target2 { get { return _target2; } } + + /// Gets a target that may be used to offer messages of the third type. + public ITargetBlock Target3 { get { return _target3; } } + + /// + public IDisposable LinkTo(ITargetBlock, IList, IList>> target, DataflowLinkOptions linkOptions) + { + return _source.LinkTo(target, linkOptions); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public Boolean TryReceive(Predicate, IList, IList>> filter, out Tuple, IList, IList> item) + { + return _source.TryReceive(filter, out item); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public bool TryReceiveAll(out IList, IList, IList>> items) { return _source.TryReceiveAll(out items); } + + /// + public int OutputCount { get { return _source.OutputCount; } } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + public void Complete() + { + Debug.Assert(_target1 != null, "_target1 not initialized"); + Debug.Assert(_target2 != null, "_target2 not initialized"); + Debug.Assert(_target3 != null, "_target3 not initialized"); + + _target1.Complete(); + _target2.Complete(); + _target3.Complete(); + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + Debug.Assert(_sharedResources != null, "_sharedResources not initialized"); + Debug.Assert(_sharedResources._incomingLock != null, "_sharedResources._incomingLock not initialized"); + Debug.Assert(_source != null, "_source not initialized"); + + lock (_sharedResources._incomingLock) + { + if (!_sharedResources._decliningPermanently) _source.AddException(exception); + } + Complete(); + } + + /// + Tuple, IList, IList> ISourceBlock, IList, IList>>.ConsumeMessage( + DataflowMessageHeader messageHeader, ITargetBlock, IList, IList>> target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock, IList, IList>>.ReserveMessage( + DataflowMessageHeader messageHeader, ITargetBlock, IList, IList>> target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock, IList, IList>>.ReleaseReservation( + DataflowMessageHeader messageHeader, ITargetBlock, IList, IList>> target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// + /// Invokes Complete on each target + /// + private void CompleteEachTarget() + { + _target1.Complete(); + _target2.Complete(); + _target3.Complete(); + } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, BatchSize={1}, OutputCount={2}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + BatchSize, + OutputCountForDebugger); + } + } + /// 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 the Transform. + private sealed class DebugView + { + /// The block being viewed. + private readonly BatchedJoinBlock _batchedJoinBlock; + /// The source half of the block being viewed. + private readonly SourceCore, IList, IList>>.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The batched join being viewed. + public DebugView(BatchedJoinBlock batchedJoinBlock) + { + Contract.Requires(batchedJoinBlock != null, "Need a block with which to construct the debug view."); + _sourceDebuggingInformation = batchedJoinBlock._source.GetDebuggingInformation(); + _batchedJoinBlock = batchedJoinBlock; + } + + /// Gets the messages waiting to be received. + public IEnumerable, IList, IList>> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } + /// Gets the number of batches created. + public long BatchesCreated { get { return _batchedJoinBlock._sharedResources._batchesCreated; } } + /// Gets the number of items remaining to form a batch. + public int RemainingItemsForBatch { get { return _batchedJoinBlock._sharedResources._remainingItemsInBatch; } } + + /// Gets the size of the batches generated by this BatchedJoin. + public Int32 BatchSize { get { return _batchedJoinBlock._batchSize; } } + /// Gets the first target. + public ITargetBlock Target1 { get { return _batchedJoinBlock._target1; } } + /// Gets the second target. + public ITargetBlock Target2 { get { return _batchedJoinBlock._target2; } } + /// Gets the second target. + public ITargetBlock Target3 { get { return _batchedJoinBlock._target3; } } + + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_batchedJoinBlock); } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry, IList, IList>> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the target that holds a reservation on the next message, if any. + public ITargetBlock, IList, IList>> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + } +} + +namespace System.Threading.Tasks.Dataflow.Internal +{ + /// Provides the target used in a BatchedJoin. + /// Specifies the type of data accepted by this target. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(BatchedJoinBlockTarget<>.DebugView))] + internal sealed class BatchedJoinBlockTarget : ITargetBlock, IDebuggerDisplay + { + /// The shared resources used by all targets associated with the same batched join instance. + private readonly BatchedJoinBlockTargetSharedResources _sharedResources; + /// Whether this target is declining future messages. + private bool _decliningPermanently; + /// Input messages for the next batch. + private IList _messages = new List(); + + /// Initializes the target. + /// The shared resources used by all targets associated with this batched join. + internal BatchedJoinBlockTarget(BatchedJoinBlockTargetSharedResources sharedResources) + { + Contract.Requires(sharedResources != null, "Targets require a shared resources through which to communicate."); + + // Store the shared resources, and register with it to let it know there's + // another target. This is done in a non-thread-safe manner and must be done + // during construction of the batched join instance. + _sharedResources = sharedResources; + sharedResources._remainingAliveTargets++; + } + + /// Gets the number of messages buffered in this target. + internal int Count { get { return _messages.Count; } } + + /// Gets the messages buffered by this target and then empties the collection. + /// The messages from the target. + internal IList GetAndEmptyMessages() + { + Common.ContractAssertMonitorStatus(_sharedResources._incomingLock, held: true); + + IList toReturn = _messages; + _messages = new List(); + return toReturn; + } + + /// + public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + lock (_sharedResources._incomingLock) + { + // If we've already stopped accepting messages, decline permanently + if (_decliningPermanently || + _sharedResources._decliningPermanently) + return DataflowMessageStatus.DecliningPermanently; + + // Consume the message from the source if necessary, and store the message + if (consumeToAccept) + { + Debug.Assert(source != null, "We must have thrown if source == null && consumeToAccept == true."); + + bool consumed; + messageValue = source.ConsumeMessage(messageHeader, this, out consumed); + if (!consumed) return DataflowMessageStatus.NotAvailable; + } + _messages.Add(messageValue); + + // If this message makes a batch, notify the shared resources that a batch has been completed + if (--_sharedResources._remainingItemsInBatch == 0) _sharedResources._batchSizeReachedAction(); + + return DataflowMessageStatus.Accepted; + } + } + + /// + public void Complete() + { + lock (_sharedResources._incomingLock) + { + // If this is the first time Complete is being called, + // note that there's now one fewer targets receiving messages for the batched join. + if (!_decliningPermanently) + { + _decliningPermanently = true; + if (--_sharedResources._remainingAliveTargets == 0) _sharedResources._allTargetsDecliningPermanentlyAction(); + } + } + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + lock (_sharedResources._incomingLock) + { + if (!_decliningPermanently && !_sharedResources._decliningPermanently) _sharedResources._exceptionAction(exception); + } + + _sharedResources._completionAction(); + } + + /// + Task IDataflowBlock.Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0} InputCount={1}", + Common.GetNameForDebugger(this), + _messages.Count); + } + } + /// 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 the Transform. + private sealed class DebugView + { + /// The batched join block target being viewed. + private readonly BatchedJoinBlockTarget _batchedJoinBlockTarget; + + /// Initializes the debug view. + /// The batched join target being viewed. + public DebugView(BatchedJoinBlockTarget batchedJoinBlockTarget) + { + Contract.Requires(batchedJoinBlockTarget != null, "Need a block with which to construct the debug view."); + _batchedJoinBlockTarget = batchedJoinBlockTarget; + } + + /// Gets the messages waiting to be processed. + public IEnumerable InputQueue { get { return _batchedJoinBlockTarget._messages; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently + { + get + { + return _batchedJoinBlockTarget._decliningPermanently || + _batchedJoinBlockTarget._sharedResources._decliningPermanently; + } + } + } + } + + /// Provides a container for resources shared across all targets used by the same BatchedJoinBlock instance. + internal sealed class BatchedJoinBlockTargetSharedResources + { + /// Initializes the shared resources. + /// The size of a batch to create. + /// The options used to configure the shared resources. Assumed to be immutable. + /// The action to invoke when a batch is completed. + /// The action to invoke when no more targets are accepting input. + /// The action to invoke when an exception needs to be logged. + /// The action to invoke when completing, typically invoked due to a call to Fault. + internal BatchedJoinBlockTargetSharedResources( + int batchSize, GroupingDataflowBlockOptions dataflowBlockOptions, + Action batchSizeReachedAction, Action allTargetsDecliningAction, + Action exceptionAction, Action completionAction) + { + Debug.Assert(batchSize >= 1, "A positive batch size is required."); + Debug.Assert(batchSizeReachedAction != null, "Need an action to invoke for each batch."); + Debug.Assert(allTargetsDecliningAction != null, "Need an action to invoke when all targets have declined."); + + _incomingLock = new object(); + _batchSize = batchSize; + + // _remainingAliveTargets will be incremented when targets are added. + // They must be added during construction of the BatchedJoin<...>. + _remainingAliveTargets = 0; + _remainingItemsInBatch = batchSize; + + // Configure what to do when batches are completed and/or all targets start declining + _allTargetsDecliningPermanentlyAction = () => + { + // Invoke the caller's action + allTargetsDecliningAction(); + + // Don't accept any more messages. We should already + // be doing this anyway through each individual target's declining flag, + // so setting it to true is just a precaution and is also helpful + // when onceOnly is true. + _decliningPermanently = true; + }; + _batchSizeReachedAction = () => + { + // Invoke the caller's action + batchSizeReachedAction(); + _batchesCreated++; + + // If this batched join is meant to be used for only a single + // batch, invoke the completion logic. + if (_batchesCreated >= dataflowBlockOptions.ActualMaxNumberOfGroups) _allTargetsDecliningPermanentlyAction(); + + // Otherwise, get ready for the next batch. + else _remainingItemsInBatch = _batchSize; + }; + _exceptionAction = exceptionAction; + _completionAction = completionAction; + } + + /// + /// A lock used to synchronize all incoming messages on all targets. It protects all of the rest + /// of the shared Resources's state and will be held while invoking the delegates. + /// + internal readonly object _incomingLock; + /// The size of the batches to generate. + internal readonly int _batchSize; + + /// The action to invoke when enough elements have been accumulated to make a batch. + internal readonly Action _batchSizeReachedAction; + /// The action to invoke when all targets are declining further messages. + internal readonly Action _allTargetsDecliningPermanentlyAction; + /// The action to invoke when an exception has to be logged. + internal readonly Action _exceptionAction; + /// The action to invoke when the owning block has to be completed. + internal readonly Action _completionAction; + + /// The number of items remaining to form a batch. + internal int _remainingItemsInBatch; + /// The number of targets still alive (i.e. not declining all further messages). + internal int _remainingAliveTargets; + /// Whether all targets should decline all further messages. + internal bool _decliningPermanently; + /// The number of batches created. + internal long _batchesCreated; + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BroadcastBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BroadcastBlock.cs new file mode 100644 index 00000000000..43edb41c3f5 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BroadcastBlock.cs @@ -0,0 +1,1262 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// BroadcastBlock.cs +// +// +// A propagator that broadcasts incoming messages to all targets, overwriting the current +// message in the process. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Security; +using System.Threading.Tasks.Dataflow.Internal; + +namespace System.Threading.Tasks.Dataflow +{ + /// + /// Provides a buffer for storing at most one element at time, overwriting each message with the next as it arrives. + /// Messages are broadcast to all linked targets, all of which may consume a clone of the message. + /// + /// Specifies the type of the data buffered by this dataflow block. + /// + /// exposes at most one element at a time. However, unlike + /// , that element will be overwritten as new elements are provided + /// to the block. ensures that the current element is broadcast to any + /// linked targets before allowing the element to be overwritten. + /// + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(BroadcastBlock<>.DebugView))] + public sealed class BroadcastBlock : IPropagatorBlock, IReceivableSourceBlock, IDebuggerDisplay + { + /// The source side. + private readonly BroadcastingSourceCore _source; + /// Bounding state for when the block is executing in bounded mode. + private readonly BoundingStateWithPostponedAndTask _boundingState; + /// Whether all future messages should be declined. + private bool _decliningPermanently; + /// A task has reserved the right to run the completion routine. + private bool _completionReserved; + /// Gets the lock used to synchronize incoming requests. + private object IncomingLock { get { return _source; } } + + /// Initializes the with the specified cloning function. + /// + /// The function to use to clone the data when offered to other blocks. + /// This may be null to indicate that no cloning need be performed. + /// + public BroadcastBlock(Func cloningFunction) : + this(cloningFunction, DataflowBlockOptions.Default) + { } + + /// Initializes the with the specified cloning function and . + /// + /// The function to use to clone the data when offered to other blocks. + /// This may be null to indicate that no cloning need be performed. + /// + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + public BroadcastBlock(Func cloningFunction, DataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + Contract.EndContractBlock(); + + // Ensure we have options that can't be changed by the caller + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Initialize bounding state if necessary + Action onItemsRemoved = null; + if (dataflowBlockOptions.BoundedCapacity > 0) + { + Debug.Assert(dataflowBlockOptions.BoundedCapacity > 0, "Positive bounding count expected; should have been verified by options ctor"); + onItemsRemoved = OnItemsRemoved; + _boundingState = new BoundingStateWithPostponedAndTask(dataflowBlockOptions.BoundedCapacity); + } + + // Initialize the source side + _source = new BroadcastingSourceCore(this, cloningFunction, dataflowBlockOptions, onItemsRemoved); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((BroadcastBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, _source.Completion, state => ((BroadcastBlock)state).Complete(), this); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// + public void Complete() + { + CompleteCore(exception: null, storeExceptionEvenIfAlreadyCompleting: false); + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: false); + } + + internal void CompleteCore(Exception exception, bool storeExceptionEvenIfAlreadyCompleting, bool revertProcessingState = false) + { + Contract.Requires(storeExceptionEvenIfAlreadyCompleting || !revertProcessingState, + "Indicating dirty processing state may only come with storeExceptionEvenIfAlreadyCompleting==true."); + Contract.EndContractBlock(); + + lock (IncomingLock) + { + // Faulting from outside is allowed until we start declining permanently. + // Faulting from inside is allowed at any time. + if (exception != null && (!_decliningPermanently || storeExceptionEvenIfAlreadyCompleting)) + { + _source.AddException(exception); + } + + // Revert the dirty processing state if requested + if (revertProcessingState) + { + Debug.Assert(_boundingState != null && _boundingState.TaskForInputProcessing != null, + "The processing state must be dirty when revertProcessingState==true."); + _boundingState.TaskForInputProcessing = null; + } + + // Trigger completion if possible + _decliningPermanently = true; + CompleteTargetIfPossible(); + } + } + + /// + public IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) { return _source.LinkTo(target, linkOptions); } + + /// + public Boolean TryReceive(Predicate filter, out T item) { return _source.TryReceive(filter, out item); } + + /// + Boolean IReceivableSourceBlock.TryReceiveAll(out IList items) { return _source.TryReceiveAll(out items); } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + lock (IncomingLock) + { + // If we've already stopped accepting messages, decline permanently + if (_decliningPermanently) + { + CompleteTargetIfPossible(); + return DataflowMessageStatus.DecliningPermanently; + } + + // We can directly accept the message if: + // 1) we are not bounding, OR + // 2) we are bounding AND there is room available AND there are no postponed messages AND we are not currently processing. + // (If there were any postponed messages, we would need to postpone so that ordering would be maintained.) + // (We should also postpone if we are currently processing, because there may be a race between consuming postponed messages and + // accepting new ones directly into the queue.) + if (_boundingState == null + || + (_boundingState.CountIsLessThanBound && _boundingState.PostponedMessages.Count == 0 && _boundingState.TaskForInputProcessing == null)) + { + // Consume the message from the source if necessary + if (consumeToAccept) + { + Debug.Assert(source != null, "We must have thrown if source == null && consumeToAccept == true."); + + bool consumed; + messageValue = source.ConsumeMessage(messageHeader, this, out consumed); + if (!consumed) return DataflowMessageStatus.NotAvailable; + } + + // Once consumed, pass it to the delegate + _source.AddMessage(messageValue); + if (_boundingState != null) _boundingState.CurrentCount += 1; // track this new item against our bound + return DataflowMessageStatus.Accepted; + } + // Otherwise, we try to postpone if a source was provided + else if (source != null) + { + Debug.Assert(_boundingState != null && _boundingState.PostponedMessages != null, + "PostponedMessages must have been initialized during construction in bounding mode."); + + _boundingState.PostponedMessages.Push(source, messageHeader); + return DataflowMessageStatus.Postponed; + } + // We can't do anything else about this message + return DataflowMessageStatus.Declined; + } + } + + /// Notifies the block that one or more items was removed from the queue. + /// The number of items removed. + private void OnItemsRemoved(int numItemsRemoved) + { + Contract.Requires(numItemsRemoved > 0, "Should only be called for a positive number of items removed."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // If we're bounding, we need to know when an item is removed so that we + // can update the count that's mirroring the actual count in the source's queue, + // and potentially kick off processing to start consuming postponed messages. + if (_boundingState != null) + { + lock (IncomingLock) + { + // Decrement the count, which mirrors the count in the source half + Debug.Assert(_boundingState.CurrentCount - numItemsRemoved >= 0, + "It should be impossible to have a negative number of items."); + _boundingState.CurrentCount -= numItemsRemoved; + + ConsumeAsyncIfNecessary(); + CompleteTargetIfPossible(); + } + } + } + + /// Called when postponed messages may need to be consumed. + /// Whether this call is the continuation of a previous message loop. + internal void ConsumeAsyncIfNecessary(bool isReplacementReplica = false) + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + Debug.Assert(_boundingState != null, "Must be in bounded mode."); + + if (!_decliningPermanently && + _boundingState.TaskForInputProcessing == null && + _boundingState.PostponedMessages.Count > 0 && + _boundingState.CountIsLessThanBound) + { + // Create task and store into _taskForInputProcessing prior to scheduling the task + // so that _taskForInputProcessing will be visibly set in the task loop. + _boundingState.TaskForInputProcessing = + new Task(state => ((BroadcastBlock)state).ConsumeMessagesLoopCore(), this, + Common.GetCreationOptionsForTask(isReplacementReplica)); + +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TaskLaunchedForMessageHandling( + this, _boundingState.TaskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, + _boundingState.PostponedMessages.Count); + } +#endif + + // Start the task handling scheduling exceptions + Exception exception = Common.StartTaskSafe(_boundingState.TaskForInputProcessing, _source.DataflowBlockOptions.TaskScheduler); + if (exception != null) + { + // Get out from under currently held locks. Complete re-acquires the locks it needs. + Task.Factory.StartNew(exc => CompleteCore(exception: (Exception)exc, storeExceptionEvenIfAlreadyCompleting: true, revertProcessingState: true), + exception, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + } + + /// Task body used to consume postponed messages. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ConsumeMessagesLoopCore() + { + Contract.Requires(_boundingState != null && _boundingState.TaskForInputProcessing != null, + "May only be called in bounded mode and when a task is in flight."); + Debug.Assert(_boundingState.TaskForInputProcessing.Id == Task.CurrentId, + "This must only be called from the in-flight processing task."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + try + { + int maxMessagesPerTask = _source.DataflowBlockOptions.ActualMaxMessagesPerTask; + for (int i = 0; + i < maxMessagesPerTask && ConsumeAndStoreOneMessageIfAvailable(); + i++) + ; + } + catch (Exception exception) + { + // Prevent the creation of new processing tasks + CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: true); + } + finally + { + lock (IncomingLock) + { + // We're no longer processing, so null out the processing task + _boundingState.TaskForInputProcessing = null; + + // However, we may have given up early because we hit our own configured + // processing limits rather than because we ran out of work to do. If that's + // the case, make sure we spin up another task to keep going. + ConsumeAsyncIfNecessary(isReplacementReplica: true); + + // If, however, we stopped because we ran out of work to do and we + // know we'll never get more, then complete. + CompleteTargetIfPossible(); + } + } + } + + /// + /// Retrieves one postponed message if there's room and if we can consume a postponed message. + /// Stores any consumed message into the source half. + /// + /// true if a message could be consumed and stored; otherwise, false. + /// This must only be called from the asynchronous processing loop. + private bool ConsumeAndStoreOneMessageIfAvailable() + { + Contract.Requires(_boundingState != null && _boundingState.TaskForInputProcessing != null, + "May only be called in bounded mode and when a task is in flight."); + Debug.Assert(_boundingState.TaskForInputProcessing.Id == Task.CurrentId, + "This must only be called from the in-flight processing task."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // Loop through the postponed messages until we get one. + while (true) + { + // Get the next item to retrieve. If there are no more, bail. + KeyValuePair, DataflowMessageHeader> sourceAndMessage; + lock (IncomingLock) + { + if (!_boundingState.CountIsLessThanBound) return false; + if (!_boundingState.PostponedMessages.TryPop(out sourceAndMessage)) return false; + + // Optimistically assume we're going to get the item. This avoids taking the lock + // again if we're right. If we're wrong, we decrement it later under lock. + _boundingState.CurrentCount++; + } + + // Consume the item + bool consumed = false; + try + { + T consumedValue = sourceAndMessage.Key.ConsumeMessage(sourceAndMessage.Value, this, out consumed); + if (consumed) + { + _source.AddMessage(consumedValue); + return true; + } + } + finally + { + // We didn't get the item, so decrement the count to counteract our optimistic assumption. + if (!consumed) + { + lock (IncomingLock) _boundingState.CurrentCount--; + } + } + } + } + + /// Completes the target, notifying the source, once all completion conditions are met. + private void CompleteTargetIfPossible() + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + if (_decliningPermanently && + !_completionReserved && + (_boundingState == null || _boundingState.TaskForInputProcessing == null)) + { + _completionReserved = true; + + // If we're in bounding mode and we have any postponed messages, we need to clear them, + // which means calling back to the source, which means we need to escape the incoming lock. + if (_boundingState != null && _boundingState.PostponedMessages.Count > 0) + { + Task.Factory.StartNew(state => + { + var thisBroadcastBlock = (BroadcastBlock)state; + + // Release any postponed messages + List exceptions = null; + if (thisBroadcastBlock._boundingState != null) + { + // Note: No locks should be held at this point + Common.ReleaseAllPostponedMessages(thisBroadcastBlock, + thisBroadcastBlock._boundingState.PostponedMessages, + ref exceptions); + } + + if (exceptions != null) + { + // It is important to migrate these exceptions to the source part of the owning batch, + // because that is the completion task that is publically exposed. + thisBroadcastBlock._source.AddExceptions(exceptions); + } + + thisBroadcastBlock._source.Complete(); + }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + // Otherwise, we can just decline the source directly. + else + { + _source.Complete(); + } + } + } + + /// + T ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// Gets a value to be used for the DebuggerDisplayAttribute. This must not throw even if HasValue is false. + private bool HasValueForDebugger { get { return _source.GetDebuggingInformation().HasValue; } } + /// Gets a value to be used for the DebuggerDisplayAttribute. This must not throw even if HasValue is false. + private T ValueForDebugger { get { return _source.GetDebuggingInformation().Value; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, HasValue={1}, Value={2}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + HasValueForDebugger, + ValueForDebugger); + } + } + /// 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 the BroadcastBlock. + private sealed class DebugView + { + /// The BroadcastBlock being debugged. + private readonly BroadcastBlock _broadcastBlock; + /// Debug info about the source side of the broadcast. + private readonly BroadcastingSourceCore.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The BroadcastBlock being debugged. + public DebugView(BroadcastBlock broadcastBlock) + { + Contract.Requires(broadcastBlock != null, "Need a block with which to construct the debug view."); + _broadcastBlock = broadcastBlock; + _sourceDebuggingInformation = broadcastBlock._source.GetDebuggingInformation(); + } + + /// Gets the messages waiting to be processed. + public IEnumerable InputQueue { get { return _sourceDebuggingInformation.InputQueue; } } + /// Gets whether the broadcast has a current value. + public bool HasValue { get { return _broadcastBlock.HasValueForDebugger; } } + /// Gets the broadcast's current value. + public T Value { get { return _broadcastBlock.ValueForDebugger; } } + + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public DataflowBlockOptions DataflowBlockOptions { get { return _sourceDebuggingInformation.DataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _broadcastBlock._decliningPermanently; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_broadcastBlock); } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the set of all targets linked from this block. + public ITargetBlock NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + + /// Provides a core implementation for blocks that implement . + /// Specifies the type of data supplied by the . + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + private sealed class BroadcastingSourceCore + { + /// A registry used to store all linked targets and information about them. + private readonly TargetRegistry _targetRegistry; + /// All of the output messages queued up to be received by consumers/targets. + private readonly Queue _messages = new Queue(); + /// A TaskCompletionSource that represents the completion of this block. + private readonly TaskCompletionSource _completionTask = new TaskCompletionSource(); + /// + /// An action to be invoked on the owner block when an item is removed. + /// This may be null if the owner block doesn't need to be notified. + /// + private readonly Action _itemsRemovedAction; + + /// Gets the object to use as the outgoing lock. + private object OutgoingLock { get { return _completionTask; } } + /// Gets the object to use as the value lock. + private object ValueLock { get { return _targetRegistry; } } + + /// The source utilize this helper. + private readonly BroadcastBlock _owningSource; + /// The options used to configure this block's execution. + private readonly DataflowBlockOptions _dataflowBlockOptions; + /// The cloning function to use. + private readonly Func _cloningFunction; + + /// An indicator whether _currentMessage has a value. + private bool _currentMessageIsValid; + /// The message currently being broadcast. + private TOutput _currentMessage; + /// The target that the next message is reserved for, or null if nothing is reserved. + private ITargetBlock _nextMessageReservedFor; + /// Whether this block should again attempt to offer messages to targets. + private bool _enableOffering; + /// Whether all future messages should be declined. + private bool _decliningPermanently; + /// The task used to process the output and offer it to targets. + private Task _taskForOutputProcessing; + /// Exceptions that may have occurred and gone unhandled during processing. + private List _exceptions; + /// Counter for message IDs unique within this source block. + private long _nextMessageId = 1; // We are going to use this value before incrementing. + /// Whether someone has reserved the right to call CompleteBlockOncePossible. + private bool _completionReserved; + + /// Initializes the source core. + /// The source utilizing this core. + /// The function to use to clone the data when offered to other blocks. May be null. + /// The options to use to configure the block. + /// Action to invoke when an item is removed. + internal BroadcastingSourceCore( + BroadcastBlock owningSource, + Func cloningFunction, + DataflowBlockOptions dataflowBlockOptions, + Action itemsRemovedAction) + { + Contract.Requires(owningSource != null, "Must be associated with a broadcast block."); + Contract.Requires(dataflowBlockOptions != null, "Options are required to configure this block."); + + // Store the arguments + _owningSource = owningSource; + _cloningFunction = cloningFunction; + _dataflowBlockOptions = dataflowBlockOptions; + _itemsRemovedAction = itemsRemovedAction; + + // Construct members that depend on the arguments + _targetRegistry = new TargetRegistry(_owningSource); + } + + /// + internal Boolean TryReceive(Predicate filter, out TOutput item) + { + // Take the lock only long enough to get the message, + // synchronizing with other activities on the block. + // We don't want to execute the user-provided cloning delegate + // while holding the lock. + TOutput message; + bool isValid; + lock (OutgoingLock) + { + lock (ValueLock) + { + message = _currentMessage; + isValid = _currentMessageIsValid; + } + } + + // Clone and hand back a message if we have one and if it passes the filter. + // (A null filter means all messages pass.) + if (isValid && (filter == null || filter(message))) + { + item = CloneItem(message); + return true; + } + else + { + item = default(TOutput); + return false; + } + } + + /// + internal Boolean TryReceiveAll(out IList items) + { + // Try to receive the one item this block may have. + // If we can, give back an array of one item. Otherwise, give back null. + TOutput item; + if (TryReceive(null, out item)) + { + items = new TOutput[] { item }; + return true; + } + else + { + items = null; + return false; + } + } + + /// Adds a message to the source block for propagation. + /// The item to be wrapped in a message to be added. + internal void AddMessage(TOutput item) + { + // This method must not take the outgoing lock, as it will be called in situations + // where a derived type's incoming lock is held. The lock leveling structure + // we're employing is such that outgoing may be held while acquiring incoming, but + // of course not the other way around. This is the reason why DataflowSourceBlock + // needs ValueLock as well. Otherwise, it would be pure overhead. + lock (ValueLock) + { + if (_decliningPermanently) return; + _messages.Enqueue(item); + if (_messages.Count == 1) _enableOffering = true; + OfferAsyncIfNecessary(); + } + } + + /// Informs the block that it will not be receiving additional messages. + internal void Complete() + { + lock (ValueLock) + { + _decliningPermanently = true; + + // Complete may be called in a context where an incoming lock is held. We need to + // call CompleteBlockIfPossible, but we can't do so if the incoming lock is held. + // However, now that _decliningPermanently has been set, the timing of + // CompleteBlockIfPossible doesn't matter, so we schedule it to run asynchronously + // and take the necessary locks in a situation where we're sure it won't cause a problem. + Task.Factory.StartNew(state => + { + var thisSourceCore = (BroadcastingSourceCore)state; + lock (thisSourceCore.OutgoingLock) + { + lock (thisSourceCore.ValueLock) + { + thisSourceCore.CompleteBlockIfPossible(); + } + } + }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + + /// Clones the item. + /// The item to clone. + /// The cloned item. + private TOutput CloneItem(TOutput item) + { + return _cloningFunction != null ? + _cloningFunction(item) : + item; + } + + /// Offers the current message to a specific target. + /// The target to which to offer the current message. + private void OfferCurrentMessageToNewTarget(ITargetBlock target) + { + Contract.Requires(target != null, "Target required to offer messages to."); + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + Common.ContractAssertMonitorStatus(ValueLock, held: false); + + // Get the current message if there is one + TOutput currentMessage; + bool isValid; + lock (ValueLock) + { + currentMessage = _currentMessage; + isValid = _currentMessageIsValid; + } + + // If there is no valid message yet, there is nothing to offer + if (!isValid) return; + + // Offer it to the target. + // We must not increment the message ID here. We only do that when we populate _currentMessage, i.e. when we dequeue. + bool useCloning = _cloningFunction != null; + DataflowMessageStatus result = target.OfferMessage(new DataflowMessageHeader(_nextMessageId), currentMessage, _owningSource, consumeToAccept: useCloning); + + // If accepted and the target was linked as "unlinkAfterOne", remove it + if (result == DataflowMessageStatus.Accepted) + { + if (!useCloning) + { + // If accepted and the target was linked as "once", mark it for removal. + // If we were forcing consumption, this removal would have already + // happened in ConsumeMessage. + _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true); + } + } + // If declined permanently, remove it + else if (result == DataflowMessageStatus.DecliningPermanently) + { + _targetRegistry.Remove(target); + } + else Debug.Assert(result != DataflowMessageStatus.NotAvailable, "Messages from a Broadcast should never be missed."); + } + + /// Offers messages to targets. + private bool OfferToTargets() + { + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + Common.ContractAssertMonitorStatus(ValueLock, held: false); + + DataflowMessageHeader header = default(DataflowMessageHeader); + TOutput message = default(TOutput); + int numDequeuedMessages = 0; + lock (ValueLock) + { + // If there's a reservation or there aren't any more messages, + // there's nothing for us to do. If there's no reservation + // and a message is available, dequeue the next one and store it + // as the new current. If we're now at 0 message, disable further + // propagation until more messages arrive. + if (_nextMessageReservedFor == null && _messages.Count > 0) + { + // If there are no targets registered, we might as well empty out the broadcast, + // keeping just the last. Otherwise, it'll happen anyway, but much more expensively. + if (_targetRegistry.FirstTargetNode == null) + { + while (_messages.Count > 1) + { + _messages.Dequeue(); + numDequeuedMessages++; + } + } + + // Get the next message to offer + Debug.Assert(_messages.Count > 0, "There must be at least one message to dequeue."); + _currentMessage = message = _messages.Dequeue(); + numDequeuedMessages++; + _currentMessageIsValid = true; + header = new DataflowMessageHeader(++_nextMessageId); + if (_messages.Count == 0) _enableOffering = false; + } + else + { + _enableOffering = false; + return false; + } + } // must not hold ValueLock when calling out to targets + + // Offer the message + if (header.IsValid) + { + // Notify the owner block that our count has decreased + if (_itemsRemovedAction != null) _itemsRemovedAction(numDequeuedMessages); + + // Offer it to each target, unless a soleTarget was provided, which case just offer it to that one. + TargetRegistry.LinkedTargetInfo cur = _targetRegistry.FirstTargetNode; + while (cur != null) + { + // Note that during OfferMessage, a target may call ConsumeMessage, which may unlink the target + // if the target is registered as "once". Doing so will remove the target from the targets list. + // As such, we avoid using an enumerator over _targetRegistry and instead walk from back to front, + // so that if an element is removed, it won't affect the rest of our walk. + TargetRegistry.LinkedTargetInfo next = cur.Next; + ITargetBlock target = cur.Target; + OfferMessageToTarget(header, message, target); + cur = next; + } + } + return true; + } + + /// Offers the specified message to the specified target. + /// The header of the message to offer. + /// The message to offer. + /// The target to which the message should be offered. + /// + /// This will remove the target from the target registry if the result of the propagation demands it. + /// + private void OfferMessageToTarget(DataflowMessageHeader header, TOutput message, ITargetBlock target) + { + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + Common.ContractAssertMonitorStatus(ValueLock, held: false); + + // Offer the message. If there's a cloning function, we force the target to + // come back to us to consume the message, allowing us the opportunity to run + // the cloning function once we know they want the data. If there is no cloning + // function, there's no reason for them to call back here. + bool useCloning = _cloningFunction != null; + switch (target.OfferMessage(header, message, _owningSource, consumeToAccept: useCloning)) + { + case DataflowMessageStatus.Accepted: + if (!useCloning) + { + // If accepted and the target was linked as "once", mark it for removal. + // If we were forcing consumption, this removal would have already + // happened in ConsumeMessage. + _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true); + } + break; + + case DataflowMessageStatus.DecliningPermanently: + // If declined permanently, mark the target for removal + _targetRegistry.Remove(target); + break; + + case DataflowMessageStatus.NotAvailable: + Debug.Assert(false, "Messages from a Broadcast should never be missed."); + break; + // No action required for Postponed or Declined + } + } + + /// Called when we want to enable asynchronously offering message to targets. + /// Whether this call is the continuation of a previous message loop. + private void OfferAsyncIfNecessary(bool isReplacementReplica = false) + { + Common.ContractAssertMonitorStatus(ValueLock, held: true); + // This method must not take the OutgoingLock. + + bool currentlyProcessing = _taskForOutputProcessing != null; + bool processingToDo = _enableOffering && _messages.Count > 0; + + // If there's any work to be done... + if (!currentlyProcessing && processingToDo && !CanceledOrFaulted) + { + // Create task and store into _taskForOutputProcessing prior to scheduling the task + // so that _taskForOutputProcessing will be visibly set in the task loop. + _taskForOutputProcessing = new Task(thisSourceCore => ((BroadcastingSourceCore)thisSourceCore).OfferMessagesLoopCore(), this, + Common.GetCreationOptionsForTask(isReplacementReplica)); + +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TaskLaunchedForMessageHandling( + _owningSource, _taskForOutputProcessing, DataflowEtwProvider.TaskLaunchedReason.OfferingOutputMessages, _messages.Count); + } +#endif + + // Start the task handling scheduling exceptions + Exception exception = Common.StartTaskSafe(_taskForOutputProcessing, _dataflowBlockOptions.TaskScheduler); + if (exception != null) + { + // First, log the exception while the processing state is dirty which is preventing the block from completing. + // Then revert the proactive processing state changes. + // And last, try to complete the block. + AddException(exception); + _decliningPermanently = true; + _taskForOutputProcessing = null; + + // Get out from under currently held locks - ValueLock is taken, but OutgoingLock may not be. + // Re-take the locks on a separate thread. + Task.Factory.StartNew(state => + { + var thisSourceCore = (BroadcastingSourceCore)state; + lock (thisSourceCore.OutgoingLock) + { + lock (thisSourceCore.ValueLock) + { + thisSourceCore.CompleteBlockIfPossible(); + } + } + }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + } + + /// Task body used to process messages. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void OfferMessagesLoopCore() + { + try + { + int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask; + lock (OutgoingLock) + { + // Offer as many messages as we can + for (int counter = 0; + counter < maxMessagesPerTask && !CanceledOrFaulted; + counter++) + { + if (!OfferToTargets()) break; + } + } + } + catch (Exception exception) + { + _owningSource.CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: true); + } + finally + { + lock (OutgoingLock) + { + lock (ValueLock) + { + // We're no longer processing, so null out the processing task + _taskForOutputProcessing = null; + + // However, we may have given up early because we hit our own configured + // processing limits rather than because we ran out of work to do. If that's + // the case, make sure we spin up another task to keep going. + OfferAsyncIfNecessary(isReplacementReplica: true); + + // If, however, we stopped because we ran out of work to do and we + // know we'll never get more, then complete. + CompleteBlockIfPossible(); + } + } + } + } + + /// Completes the block's processing if there's nothing left to do and never will be. + private void CompleteBlockIfPossible() + { + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + Common.ContractAssertMonitorStatus(ValueLock, held: true); + + if (!_completionReserved) + { + bool currentlyProcessing = _taskForOutputProcessing != null; + bool noMoreMessages = _decliningPermanently && _messages.Count == 0; + + // Are we done forever? + bool complete = !currentlyProcessing && (noMoreMessages || CanceledOrFaulted); + if (complete) + { + CompleteBlockIfPossible_Slow(); + } + } + } + + /// + /// Slow path for CompleteBlockIfPossible. + /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined. + /// + private void CompleteBlockIfPossible_Slow() + { + Contract.Requires(_taskForOutputProcessing == null, "There must be no processing tasks."); + Contract.Requires( + (_decliningPermanently && _messages.Count == 0) || CanceledOrFaulted, + "There must be no more messages or the block must be canceled or faulted."); + + _completionReserved = true; + + // Run asynchronously to get out of the currently held locks + Task.Factory.StartNew(thisSourceCore => ((BroadcastingSourceCore)thisSourceCore).CompleteBlockOncePossible(), + this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + + /// + /// Completes the block. This must only be called once, and only once all of the completion conditions are met. + /// As such, it must only be called from CompleteBlockIfPossible. + /// + private void CompleteBlockOncePossible() + { + TargetRegistry.LinkedTargetInfo linkedTargets; + List exceptions; + + // Clear out the target registry and buffers to help avoid memory leaks. + // We do not clear _currentMessage, which should remain as that message forever. + lock (OutgoingLock) + { + // Save the linked list of targets so that it could be traversed later to propagate completion + linkedTargets = _targetRegistry.ClearEntryPoints(); + lock (ValueLock) + { + _messages.Clear(); + + // Save a local reference to the exceptions list and null out the field, + // so that if the target side tries to add an exception this late, + // it will go to a separate list (that will be ignored.) + exceptions = _exceptions; + _exceptions = null; + } + } + + // If it's due to an exception, finish in a faulted state + if (exceptions != null) + { + _completionTask.TrySetException(exceptions); + } + // It's due to cancellation, finish in a canceled state + else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested) + { + _completionTask.TrySetCanceled(); + } + // Otherwise, finish in a successful state. + else + { + _completionTask.TrySetResult(default(VoidResult)); + } + + // Now that the completion task is completed, we may propagate completion to the linked targets + _targetRegistry.PropagateCompletion(linkedTargets); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCompleted(_owningSource); + } +#endif + } + + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + internal IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) + { + // Validate arguments + if (target == null) throw new ArgumentNullException("target"); + if (linkOptions == null) throw new ArgumentNullException("linkOptions"); + Contract.EndContractBlock(); + + lock (OutgoingLock) + { + // If we've completed or completion has at least started, offer the message to this target, + // and propagate completion if that was requested. + // Then there's nothing more to be done. + if (_completionReserved) + { + OfferCurrentMessageToNewTarget(target); + if (linkOptions.PropagateCompletion) Common.PropagateCompletionOnceCompleted(_completionTask.Task, target); + return Disposables.Nop; + } + + // Otherwise, add the target and then offer it the current + // message. We do this in this order because offering may + // cause the target to be removed if it's unlinkAfterOne, + // and in the reverse order we would end up adding the target + // after it was "removed". + _targetRegistry.Add(ref target, linkOptions); + OfferCurrentMessageToNewTarget(target); + return Common.CreateUnlinker(OutgoingLock, _targetRegistry, target); + } + } + + /// + internal TOutput ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + TOutput valueToClone; + lock (OutgoingLock) // We may currently be calling out under this lock to the target; requires it to be reentrant + { + lock (ValueLock) + { + // If this isn't the next message to be served up, bail + if (messageHeader.Id != _nextMessageId) + { + messageConsumed = false; + return default(TOutput); + } + + // If the caller has the reservation, release the reservation. + // We still allow others to take the message if there's a reservation. + if (_nextMessageReservedFor == target) + { + _nextMessageReservedFor = null; + _enableOffering = true; + } + _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true); + + OfferAsyncIfNecessary(); + CompleteBlockIfPossible(); + + // Return a clone of the consumed message. + valueToClone = _currentMessage; + } + } + + messageConsumed = true; + return CloneItem(valueToClone); + } + + /// + internal Boolean ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + lock (OutgoingLock) + { + // If no one currently holds a reservation... + if (_nextMessageReservedFor == null) + { + lock (ValueLock) + { + // ...and the requested message is next in line, allow it + if (messageHeader.Id == _nextMessageId) + { + _nextMessageReservedFor = target; + _enableOffering = false; + return true; + } + } + } + } + return false; + } + + /// + internal void ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + lock (OutgoingLock) + { + // If someone else holds the reservation, bail. + if (_nextMessageReservedFor != target) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget); + + TOutput messageToReoffer; + lock (ValueLock) + { + // If this is not the message at the head of the queue, bail + if (messageHeader.Id != _nextMessageId) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget); + + // Otherwise, release the reservation, and reoffer the message to all targets. + _nextMessageReservedFor = null; + _enableOffering = true; + messageToReoffer = _currentMessage; + OfferAsyncIfNecessary(); + } + + // We need to explicitly reoffer this message to the releaser, + // as otherwise if the target has join behavior it could end up waiting for an offer from + // this broadcast forever, even though data is in fact available. We could only + // do this if _messages.Count == 0, as if it's > 0 the message will get overwritten + // as part of the asynchronous offering, but for consistency we should always reoffer + // the current message. + OfferMessageToTarget(messageHeader, messageToReoffer, target); + } + } + + /// Gets whether the source has had cancellation requested or an exception has occurred. + private bool CanceledOrFaulted + { + get + { + // Cancellation is honored as soon as the CancellationToken has been signaled. + // Faulting is honored after an exception has been encountered and the owning block + // has invoked Complete on us. + return _dataflowBlockOptions.CancellationToken.IsCancellationRequested || + (Volatile.Read(ref _exceptions) != null && _decliningPermanently); + } + } + + /// Adds an individual exceptionto this source. + /// The exception to add + internal void AddException(Exception exception) + { + Contract.Requires(exception != null, "An exception to add is required."); + Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions."); + lock (ValueLock) + { + Common.AddException(ref _exceptions, exception); + } + } + + /// Adds exceptions to this source. + /// The exceptions to add + internal void AddExceptions(List exceptions) + { + Contract.Requires(exceptions != null, "A list of exceptions to add is required."); + Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions."); + lock (ValueLock) + { + foreach (Exception exception in exceptions) + { + Common.AddException(ref _exceptions, exception); + } + } + } + + /// + internal Task Completion { get { return _completionTask.Task; } } + + /// Gets the DataflowBlockOptions used to configure this block. + internal DataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } } + + /// Gets the object to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displaySource = _owningSource as IDebuggerDisplay; + return string.Format("Block=\"{0}\"", + displaySource != null ? displaySource.Content : _owningSource); + } + } + + /// Gets information about this helper to be used for display in a debugger. + /// Debugging information about this source core. + internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); } + + /// Provides debugging information about the source core. + internal sealed class DebuggingInformation + { + /// The source being viewed. + private BroadcastingSourceCore _source; + + /// Initializes the type proxy. + /// The source being viewed. + public DebuggingInformation(BroadcastingSourceCore source) { _source = source; } + + /// Gets whether the source contains a current message. + public bool HasValue { get { return _source._currentMessageIsValid; } } + /// Gets the value of the source's current message. + public TOutput Value { get { return _source._currentMessage; } } + /// Gets the number of messages waiting to be made current. + public int InputCount { get { return _source._messages.Count; } } + /// Gets the messages available for receiving. + public IEnumerable InputQueue { get { return _source._messages.ToList(); } } + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _source._taskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public DataflowBlockOptions DataflowBlockOptions { get { return _source._dataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _source._decliningPermanently; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _source.Completion.IsCompleted; } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry LinkedTargets { get { return _source._targetRegistry; } } + /// Gets the target that holds a reservation on the next message, if any. + public ITargetBlock NextMessageReservedFor { get { return _source._nextMessageReservedFor; } } + } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BufferBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BufferBlock.cs new file mode 100644 index 00000000000..a2a544b1300 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/BufferBlock.cs @@ -0,0 +1,492 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// BufferBlock.cs +// +// +// A propagator block that provides support for unbounded and bounded FIFO buffers. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Security; +using System.Threading.Tasks.Dataflow.Internal; +using System.Diagnostics.CodeAnalysis; + +namespace System.Threading.Tasks.Dataflow +{ + /// Provides a buffer for storing data. + /// Specifies the type of the data buffered by this dataflow block. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(BufferBlock<>.DebugView))] + public sealed class BufferBlock : IPropagatorBlock, IReceivableSourceBlock, IDebuggerDisplay + { + /// The core logic for the buffer block. + private readonly SourceCore _source; + /// The bounding state for when in bounding mode; null if not bounding. + private readonly BoundingStateWithPostponedAndTask _boundingState; + /// Whether all future messages should be declined on the target. + private bool _targetDecliningPermanently; + /// A task has reserved the right to run the target's completion routine. + private bool _targetCompletionReserved; + /// Gets the lock object used to synchronize incoming requests. + private object IncomingLock { get { return _source; } } + + /// Initializes the . + public BufferBlock() : + this(DataflowBlockOptions.Default) + { } + + /// Initializes the with the specified . + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + public BufferBlock(DataflowBlockOptions dataflowBlockOptions) + { + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + Contract.EndContractBlock(); + + // Ensure we have options that can't be changed by the caller + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Initialize bounding state if necessary + Action, int> onItemsRemoved = null; + if (dataflowBlockOptions.BoundedCapacity > 0) + { + onItemsRemoved = (owningSource, count) => ((BufferBlock)owningSource).OnItemsRemoved(count); + _boundingState = new BoundingStateWithPostponedAndTask(dataflowBlockOptions.BoundedCapacity); + } + + // Initialize the source state + _source = new SourceCore(this, dataflowBlockOptions, + owningSource => ((BufferBlock)owningSource).Complete(), + onItemsRemoved); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((BufferBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, _source.Completion, owningSource => ((BufferBlock)owningSource).Complete(), this); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + lock (IncomingLock) + { + // If we've already stopped accepting messages, decline permanently + if (_targetDecliningPermanently) + { + CompleteTargetIfPossible(); + return DataflowMessageStatus.DecliningPermanently; + } + + // We can directly accept the message if: + // 1) we are not bounding, OR + // 2) we are bounding AND there is room available AND there are no postponed messages AND we are not currently processing. + // (If there were any postponed messages, we would need to postpone so that ordering would be maintained.) + // (We should also postpone if we are currently processing, because there may be a race between consuming postponed messages and + // accepting new ones directly into the queue.) + if (_boundingState == null + || + (_boundingState.CountIsLessThanBound && _boundingState.PostponedMessages.Count == 0 && _boundingState.TaskForInputProcessing == null)) + { + // Consume the message from the source if necessary + if (consumeToAccept) + { + Debug.Assert(source != null, "We must have thrown if source == null && consumeToAccept == true."); + + bool consumed; + messageValue = source.ConsumeMessage(messageHeader, this, out consumed); + if (!consumed) return DataflowMessageStatus.NotAvailable; + } + + // Once consumed, pass it to the source + _source.AddMessage(messageValue); + if (_boundingState != null) _boundingState.CurrentCount++; + + return DataflowMessageStatus.Accepted; + } + // Otherwise, we try to postpone if a source was provided + else if (source != null) + { + Debug.Assert(_boundingState != null && _boundingState.PostponedMessages != null, + "PostponedMessages must have been initialized during construction in bounding mode."); + + _boundingState.PostponedMessages.Push(source, messageHeader); + return DataflowMessageStatus.Postponed; + } + // We can't do anything else about this message + return DataflowMessageStatus.Declined; + } + } + + /// + public void Complete() { CompleteCore(exception: null, storeExceptionEvenIfAlreadyCompleting: false); } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: false); + } + + private void CompleteCore(Exception exception, bool storeExceptionEvenIfAlreadyCompleting, bool revertProcessingState = false) + { + Contract.Requires(storeExceptionEvenIfAlreadyCompleting || !revertProcessingState, + "Indicating dirty processing state may only come with storeExceptionEvenIfAlreadyCompleting==true."); + Contract.EndContractBlock(); + + lock (IncomingLock) + { + // Faulting from outside is allowed until we start declining permanently. + // Faulting from inside is allowed at any time. + if (exception != null && (!_targetDecliningPermanently || storeExceptionEvenIfAlreadyCompleting)) + { + _source.AddException(exception); + } + + // Revert the dirty processing state if requested + if (revertProcessingState) + { + Debug.Assert(_boundingState != null && _boundingState.TaskForInputProcessing != null, + "The processing state must be dirty when revertProcessingState==true."); + _boundingState.TaskForInputProcessing = null; + } + + // Trigger completion + _targetDecliningPermanently = true; + CompleteTargetIfPossible(); + } + } + + /// + public IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) { return _source.LinkTo(target, linkOptions); } + + /// + public Boolean TryReceive(Predicate filter, out T item) { return _source.TryReceive(filter, out item); } + + /// + public Boolean TryReceiveAll(out IList items) { return _source.TryReceiveAll(out items); } + + /// Gets the number of items currently stored in the buffer. + public Int32 Count { get { return _source.OutputCount; } } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + T ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// Notifies the block that one or more items was removed from the queue. + /// The number of items removed. + private void OnItemsRemoved(int numItemsRemoved) + { + Contract.Requires(numItemsRemoved > 0, "A positive number of items to remove is required."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // If we're bounding, we need to know when an item is removed so that we + // can update the count that's mirroring the actual count in the source's queue, + // and potentially kick off processing to start consuming postponed messages. + if (_boundingState != null) + { + lock (IncomingLock) + { + // Decrement the count, which mirrors the count in the source half + Debug.Assert(_boundingState.CurrentCount - numItemsRemoved >= 0, + "It should be impossible to have a negative number of items."); + _boundingState.CurrentCount -= numItemsRemoved; + + ConsumeAsyncIfNecessary(); + CompleteTargetIfPossible(); + } + } + } + + /// Called when postponed messages may need to be consumed. + /// Whether this call is the continuation of a previous message loop. + internal void ConsumeAsyncIfNecessary(bool isReplacementReplica = false) + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + Debug.Assert(_boundingState != null, "Must be in bounded mode."); + + if (!_targetDecliningPermanently && + _boundingState.TaskForInputProcessing == null && + _boundingState.PostponedMessages.Count > 0 && + _boundingState.CountIsLessThanBound) + { + // Create task and store into _taskForInputProcessing prior to scheduling the task + // so that _taskForInputProcessing will be visibly set in the task loop. + _boundingState.TaskForInputProcessing = + new Task(state => ((BufferBlock)state).ConsumeMessagesLoopCore(), this, + Common.GetCreationOptionsForTask(isReplacementReplica)); + +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TaskLaunchedForMessageHandling( + this, _boundingState.TaskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, + _boundingState.PostponedMessages.Count); + } +#endif + + // Start the task handling scheduling exceptions + Exception exception = Common.StartTaskSafe(_boundingState.TaskForInputProcessing, _source.DataflowBlockOptions.TaskScheduler); + if (exception != null) + { + // Get out from under currently held locks. CompleteCore re-acquires the locks it needs. + Task.Factory.StartNew(exc => CompleteCore(exception: (Exception)exc, storeExceptionEvenIfAlreadyCompleting: true, revertProcessingState: true), + exception, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + } + + + /// Task body used to consume postponed messages. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ConsumeMessagesLoopCore() + { + Contract.Requires(_boundingState != null && _boundingState.TaskForInputProcessing != null, + "May only be called in bounded mode and when a task is in flight."); + Debug.Assert(_boundingState.TaskForInputProcessing.Id == Task.CurrentId, + "This must only be called from the in-flight processing task."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + try + { + int maxMessagesPerTask = _source.DataflowBlockOptions.ActualMaxMessagesPerTask; + for (int i = 0; + i < maxMessagesPerTask && ConsumeAndStoreOneMessageIfAvailable(); + i++) + ; + } + catch (Exception exc) + { + // Prevent the creation of new processing tasks + CompleteCore(exc, storeExceptionEvenIfAlreadyCompleting: true); + } + finally + { + lock (IncomingLock) + { + // We're no longer processing, so null out the processing task + _boundingState.TaskForInputProcessing = null; + + // However, we may have given up early because we hit our own configured + // processing limits rather than because we ran out of work to do. If that's + // the case, make sure we spin up another task to keep going. + ConsumeAsyncIfNecessary(isReplacementReplica: true); + + // If, however, we stopped because we ran out of work to do and we + // know we'll never get more, then complete. + CompleteTargetIfPossible(); + } + } + } + + /// + /// Retrieves one postponed message if there's room and if we can consume a postponed message. + /// Stores any consumed message into the source half. + /// + /// true if a message could be consumed and stored; otherwise, false. + /// This must only be called from the asynchronous processing loop. + private bool ConsumeAndStoreOneMessageIfAvailable() + { + Contract.Requires(_boundingState != null && _boundingState.TaskForInputProcessing != null, + "May only be called in bounded mode and when a task is in flight."); + Debug.Assert(_boundingState.TaskForInputProcessing.Id == Task.CurrentId, + "This must only be called from the in-flight processing task."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // Loop through the postponed messages until we get one. + while (true) + { + // Get the next item to retrieve. If there are no more, bail. + KeyValuePair, DataflowMessageHeader> sourceAndMessage; + lock (IncomingLock) + { + if (!_boundingState.CountIsLessThanBound) return false; + if (!_boundingState.PostponedMessages.TryPop(out sourceAndMessage)) return false; + + // Optimistically assume we're going to get the item. This avoids taking the lock + // again if we're right. If we're wrong, we decrement it later under lock. + _boundingState.CurrentCount++; + } + + // Consume the item + bool consumed = false; + try + { + T consumedValue = sourceAndMessage.Key.ConsumeMessage(sourceAndMessage.Value, this, out consumed); + if (consumed) + { + _source.AddMessage(consumedValue); + return true; + } + } + finally + { + // We didn't get the item, so decrement the count to counteract our optimistic assumption. + if (!consumed) + { + lock (IncomingLock) _boundingState.CurrentCount--; + } + } + } + } + + /// Completes the target, notifying the source, once all completion conditions are met. + private void CompleteTargetIfPossible() + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + if (_targetDecliningPermanently && + !_targetCompletionReserved && + (_boundingState == null || _boundingState.TaskForInputProcessing == null)) + { + _targetCompletionReserved = true; + + // If we're in bounding mode and we have any postponed messages, we need to clear them, + // which means calling back to the source, which means we need to escape the incoming lock. + if (_boundingState != null && _boundingState.PostponedMessages.Count > 0) + { + Task.Factory.StartNew(state => + { + var thisBufferBlock = (BufferBlock)state; + + // Release any postponed messages + List exceptions = null; + if (thisBufferBlock._boundingState != null) + { + // Note: No locks should be held at this point + Common.ReleaseAllPostponedMessages(thisBufferBlock, + thisBufferBlock._boundingState.PostponedMessages, + ref exceptions); + } + + if (exceptions != null) + { + // It is important to migrate these exceptions to the source part of the owning batch, + // because that is the completion task that is publically exposed. + thisBufferBlock._source.AddExceptions(exceptions); + } + + thisBufferBlock._source.Complete(); + }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + // Otherwise, we can just decline the source directly. + else + { + _source.Complete(); + } + } + } + + /// Gets the number of messages in the buffer. This must only be used from the debugger as it avoids taking necessary locks. + private int CountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, Count={1}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + CountForDebugger); + } + } + /// 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 the BufferBlock. + private sealed class DebugView + { + /// The buffer block. + private readonly BufferBlock _bufferBlock; + /// The buffer's source half. + private readonly SourceCore.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The BufferBlock being viewed. + public DebugView(BufferBlock bufferBlock) + { + Contract.Requires(bufferBlock != null, "Need a block with which to construct the debug view."); + _bufferBlock = bufferBlock; + _sourceDebuggingInformation = bufferBlock._source.GetDebuggingInformation(); + } + + /// Gets the collection of postponed message headers. + public QueuedMap, DataflowMessageHeader> PostponedMessages + { + get { return _bufferBlock._boundingState != null ? _bufferBlock._boundingState.PostponedMessages : null; } + } + /// Gets the messages in the buffer. + public IEnumerable Queue { get { return _sourceDebuggingInformation.OutputQueue; } } + + /// The task used to process messages. + public Task TaskForInputProcessing { get { return _bufferBlock._boundingState != null ? _bufferBlock._boundingState.TaskForInputProcessing : null; } } + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public DataflowBlockOptions DataflowBlockOptions { get { return _sourceDebuggingInformation.DataflowBlockOptions; } } + + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _bufferBlock._targetDecliningPermanently; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_bufferBlock); } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the set of all targets linked from this block. + public ITargetBlock NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/JoinBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/JoinBlock.cs new file mode 100644 index 00000000000..cbfa49d7e42 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/JoinBlock.cs @@ -0,0 +1,1482 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// JoinBlock.cs +// +// +// Blocks that join multiple messages of different types together into a tuple, +// with one item per type. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Security; +using System.Threading.Tasks.Dataflow.Internal; + +namespace System.Threading.Tasks.Dataflow +{ + /// + /// Provides a dataflow block that joins across multiple dataflow sources, not necessarily of the same type, + /// waiting for one item to arrive for each type before they’re all released together as a tuple of one item per type. + /// + /// Specifies the type of data accepted by the block's first target. + /// Specifies the type of data accepted by the block's second target. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(JoinBlock<,>.DebugView))] + public sealed class JoinBlock : IReceivableSourceBlock>, IDebuggerDisplay + { + /// Resources shared by all targets for this join block. + private readonly JoinBlockTargetSharedResources _sharedResources; + /// The source half of this join. + private readonly SourceCore> _source; + /// The first target. + private readonly JoinBlockTarget _target1; + /// The second target. + private readonly JoinBlockTarget _target2; + + /// Initializes the . + public JoinBlock() : + this(GroupingDataflowBlockOptions.Default) + { } + + /// Initializes the . + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + public JoinBlock(GroupingDataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + Contract.EndContractBlock(); + + // Ensure we have options that can't be changed by the caller + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Initialize bounding state if necessary + Action>, int> onItemsRemoved = null; + if (dataflowBlockOptions.BoundedCapacity > 0) onItemsRemoved = (owningSource, count) => ((JoinBlock)owningSource)._sharedResources.OnItemsRemoved(count); + + // Configure the source + _source = new SourceCore>(this, dataflowBlockOptions, + owningSource => ((JoinBlock)owningSource)._sharedResources.CompleteEachTarget(), + onItemsRemoved); + + // Configure targets + var targets = new JoinBlockTargetBase[2]; + _sharedResources = new JoinBlockTargetSharedResources(this, targets, + () => + { + _source.AddMessage(Tuple.Create(_target1.GetOneMessage(), _target2.GetOneMessage())); + }, + exception => + { + Volatile.Write(ref _sharedResources._hasExceptions, true); + _source.AddException(exception); + }, + dataflowBlockOptions); + targets[0] = _target1 = new JoinBlockTarget(_sharedResources); + targets[1] = _target2 = new JoinBlockTarget(_sharedResources); + + // Let the source know when all targets have completed + Task.Factory.ContinueWhenAll( + new[] { _target1.CompletionTaskInternal, _target2.CompletionTaskInternal }, + _ => _source.Complete(), + CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((JoinBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, _source.Completion, state => ((JoinBlock)state)._sharedResources.CompleteEachTarget(), this); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// + public IDisposable LinkTo(ITargetBlock> target, DataflowLinkOptions linkOptions) + { + return _source.LinkTo(target, linkOptions); + } + + /// + public Boolean TryReceive(Predicate> filter, out Tuple item) + { + return _source.TryReceive(filter, out item); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public bool TryReceiveAll(out IList> items) { return _source.TryReceiveAll(out items); } + + /// + public int OutputCount { get { return _source.OutputCount; } } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + public void Complete() + { + Debug.Assert(_target1 != null, "_target1 not initialized"); + Debug.Assert(_target2 != null, "_target2 not initialized"); + + _target1.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false); + _target2.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false); + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + Debug.Assert(_sharedResources != null, "_sharedResources not initialized"); + Debug.Assert(_sharedResources._exceptionAction != null, "_sharedResources._exceptionAction not initialized"); + + lock (_sharedResources.IncomingLock) + { + if (!_sharedResources._decliningPermanently) _sharedResources._exceptionAction(exception); + } + + Complete(); + } + + /// Gets a target that may be used to offer messages of the first type. + public ITargetBlock Target1 { get { return _target1; } } + + /// Gets a target that may be used to offer messages of the second type. + public ITargetBlock Target2 { get { return _target2; } } + + /// + Tuple ISourceBlock>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock> target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock> target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock> target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, OutputCount={1}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + OutputCountForDebugger); + } + } + /// 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 the JoinBlock. + private sealed class DebugView + { + /// The JoinBlock being viewed. + private readonly JoinBlock _joinBlock; + /// The source half of the block being viewed. + private readonly SourceCore>.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The JoinBlock being viewed. + public DebugView(JoinBlock joinBlock) + { + Contract.Requires(joinBlock != null, "Need a block with which to construct the debug view."); + _joinBlock = joinBlock; + _sourceDebuggingInformation = joinBlock._source.GetDebuggingInformation(); + } + + /// Gets the messages waiting to be received. + public IEnumerable> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } + /// Gets the number of joins created thus far. + public long JoinsCreated { get { return _joinBlock._sharedResources._joinsCreated; } } + + /// Gets the task being used for input processing. + public Task TaskForInputProcessing { get { return _joinBlock._sharedResources._taskForInputProcessing; } } + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the GroupingDataflowBlockOptions used to configure this block. + public GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _joinBlock._sharedResources._decliningPermanently; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_joinBlock); } } + + /// Gets the first target. + public ITargetBlock Target1 { get { return _joinBlock._target1; } } + /// Gets the second target. + public ITargetBlock Target2 { get { return _joinBlock._target2; } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the set of all targets linked from this block. + public ITargetBlock> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + } + + /// + /// Provides a dataflow block that joins across multiple dataflow sources, not necessarily of the same type, + /// waiting for one item to arrive for each type before they’re all released together as a tuple of one item per type. + /// + /// Specifies the type of data accepted by the block's first target. + /// Specifies the type of data accepted by the block's second target. + /// Specifies the type of data accepted by the block's third target. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(JoinBlock<,,>.DebugView))] + [SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")] + public sealed class JoinBlock : IReceivableSourceBlock>, IDebuggerDisplay + { + /// Resources shared by all targets for this join block. + private readonly JoinBlockTargetSharedResources _sharedResources; + /// The source half of this join. + private readonly SourceCore> _source; + /// The first target. + private readonly JoinBlockTarget _target1; + /// The second target. + private readonly JoinBlockTarget _target2; + /// The third target. + private readonly JoinBlockTarget _target3; + + /// Initializes the . + public JoinBlock() : + this(GroupingDataflowBlockOptions.Default) + { } + + /// Initializes the . + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + public JoinBlock(GroupingDataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + Contract.EndContractBlock(); + + // Ensure we have options that can't be changed by the caller + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Initialize bounding state if necessary + Action>, int> onItemsRemoved = null; + if (dataflowBlockOptions.BoundedCapacity > 0) onItemsRemoved = (owningSource, count) => ((JoinBlock)owningSource)._sharedResources.OnItemsRemoved(count); + + // Configure the source + _source = new SourceCore>(this, dataflowBlockOptions, + owningSource => ((JoinBlock)owningSource)._sharedResources.CompleteEachTarget(), + onItemsRemoved); + + // Configure the targets + var targets = new JoinBlockTargetBase[3]; + _sharedResources = new JoinBlockTargetSharedResources(this, targets, + () => _source.AddMessage(Tuple.Create(_target1.GetOneMessage(), _target2.GetOneMessage(), _target3.GetOneMessage())), + exception => + { + Volatile.Write(ref _sharedResources._hasExceptions, true); + _source.AddException(exception); + }, + dataflowBlockOptions); + targets[0] = _target1 = new JoinBlockTarget(_sharedResources); + targets[1] = _target2 = new JoinBlockTarget(_sharedResources); + targets[2] = _target3 = new JoinBlockTarget(_sharedResources); + + // Let the source know when all targets have completed + Task.Factory.ContinueWhenAll( + new[] { _target1.CompletionTaskInternal, _target2.CompletionTaskInternal, _target3.CompletionTaskInternal }, + _ => _source.Complete(), + CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((JoinBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, _source.Completion, state => ((JoinBlock)state)._sharedResources.CompleteEachTarget(), this); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// + public IDisposable LinkTo(ITargetBlock> target, DataflowLinkOptions linkOptions) + { + return _source.LinkTo(target, linkOptions); + } + + /// + public Boolean TryReceive(Predicate> filter, out Tuple item) + { + return _source.TryReceive(filter, out item); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public bool TryReceiveAll(out IList> items) { return _source.TryReceiveAll(out items); } + + /// + public int OutputCount { get { return _source.OutputCount; } } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + public void Complete() + { + Debug.Assert(_target1 != null, "_target1 not initialized"); + Debug.Assert(_target2 != null, "_target2 not initialized"); + Debug.Assert(_target3 != null, "_target3 not initialized"); + + _target1.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false); + _target2.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false); + _target3.CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false); + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + Debug.Assert(_sharedResources != null, "_sharedResources not initialized"); + Debug.Assert(_sharedResources._exceptionAction != null, "_sharedResources._exceptionAction not initialized"); + + lock (_sharedResources.IncomingLock) + { + if (!_sharedResources._decliningPermanently) _sharedResources._exceptionAction(exception); + } + + Complete(); + } + + /// Gets a target that may be used to offer messages of the first type. + public ITargetBlock Target1 { get { return _target1; } } + + /// Gets a target that may be used to offer messages of the second type. + public ITargetBlock Target2 { get { return _target2; } } + + /// Gets a target that may be used to offer messages of the third type. + public ITargetBlock Target3 { get { return _target3; } } + + /// + Tuple ISourceBlock>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock> target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock> target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock> target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0} OutputCount={1}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + OutputCountForDebugger); + } + } + /// 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 the Batch. + private sealed class DebugView + { + /// The JoinBlock being viewed. + private readonly JoinBlock _joinBlock; + /// The source half of the block being viewed. + private readonly SourceCore>.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The JoinBlock being viewed. + public DebugView(JoinBlock joinBlock) + { + Contract.Requires(joinBlock != null, "Need a block with which to construct the debug view."); + _joinBlock = joinBlock; + _sourceDebuggingInformation = joinBlock._source.GetDebuggingInformation(); + } + + /// Gets the messages waiting to be received. + public IEnumerable> OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } + /// Gets the number of joins created thus far. + public long JoinsCreated { get { return _joinBlock._sharedResources._joinsCreated; } } + + /// Gets the task being used for input processing. + public Task TaskForInputProcessing { get { return _joinBlock._sharedResources._taskForInputProcessing; } } + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the GroupingDataflowBlockOptions used to configure this block. + public GroupingDataflowBlockOptions DataflowBlockOptions { get { return (GroupingDataflowBlockOptions)_sourceDebuggingInformation.DataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _joinBlock._sharedResources._decliningPermanently; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_joinBlock); } } + + /// Gets the first target. + public ITargetBlock Target1 { get { return _joinBlock._target1; } } + /// Gets the second target. + public ITargetBlock Target2 { get { return _joinBlock._target2; } } + /// Gets the third target. + public ITargetBlock Target3 { get { return _joinBlock._target3; } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry> LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the set of all targets linked from this block. + public ITargetBlock> NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + } +} + +namespace System.Threading.Tasks.Dataflow.Internal +{ + /// Provides the target used in a Join. + /// Specifies the type of data accepted by this target. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(JoinBlockTarget<>.DebugView))] + internal sealed class JoinBlockTarget : JoinBlockTargetBase, ITargetBlock, IDebuggerDisplay + { + /// The shared resources used by all targets associated with the same join instance. + private readonly JoinBlockTargetSharedResources _sharedResources; + /// A task representing the completion of the block. + private readonly TaskCompletionSource _completionTask = new TaskCompletionSource(); + /// Input messages for the next batch. + private readonly Queue _messages; + /// State used when in non-greedy mode. + private readonly NonGreedyState _nonGreedy; + /// Whether this target is declining future messages. + private bool _decliningPermanently; + + /// State used only when in non-greedy mode. + private sealed class NonGreedyState + { + /// Collection of the last postponed message per source. + internal readonly QueuedMap, DataflowMessageHeader> PostponedMessages = new QueuedMap, DataflowMessageHeader>(); + /// The currently reserved message. + internal KeyValuePair, DataflowMessageHeader> ReservedMessage; + /// The currently consumed message. + internal KeyValuePair ConsumedMessage; + } + + /// Initializes the target. + /// The shared resources used by all targets associated with this join. + internal JoinBlockTarget(JoinBlockTargetSharedResources sharedResources) + { + Contract.Requires(sharedResources != null, "Targets need shared resources through which to communicate."); + + // Store arguments and initialize configuration + GroupingDataflowBlockOptions dbo = sharedResources._dataflowBlockOptions; + _sharedResources = sharedResources; + if (!dbo.Greedy || dbo.BoundedCapacity > 0) _nonGreedy = new NonGreedyState(); + if (dbo.Greedy) _messages = new Queue(); + } + + /// Gets a message buffered by this target. + /// This must be called while holding the shared Resources's incoming lock. + internal T GetOneMessage() + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true); + if (_sharedResources._dataflowBlockOptions.Greedy) + { + Debug.Assert(_messages != null, "_messages must have been initialized in greedy mode"); + Debug.Assert(_messages.Count >= 0, "A message must have been consumed by this point."); + return _messages.Dequeue(); + } + else + { + Debug.Assert(_nonGreedy.ConsumedMessage.Key, "A message must have been consumed by this point."); + T value = _nonGreedy.ConsumedMessage.Value; + _nonGreedy.ConsumedMessage = new KeyValuePair(false, default(T)); + return value; + } + } + + /// Gets whether the target is declining messages. + internal override bool IsDecliningPermanently + { + get + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true); + return _decliningPermanently; + } + } + + /// Gets whether the target has at least one message available. + internal override bool HasAtLeastOneMessageAvailable + { + get + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true); + if (_sharedResources._dataflowBlockOptions.Greedy) + { + Debug.Assert(_messages != null, "_messages must have been initialized in greedy mode"); + return _messages.Count > 0; + } + else + { + return _nonGreedy.ConsumedMessage.Key; + } + } + } + + /// Gets whether the target has at least one postponed message. + internal override bool HasAtLeastOnePostponedMessage + { + get + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true); + return _nonGreedy != null && _nonGreedy.PostponedMessages.Count > 0; + } + } + + /// Gets the number of messages available or postponed. + internal override int NumberOfMessagesAvailableOrPostponed + { + get + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true); + return !_sharedResources._dataflowBlockOptions.Greedy ? _nonGreedy.PostponedMessages.Count : _messages.Count; + } + } + + /// Gets whether this target has the highest number of available/buffered messages. This is only valid in greedy mode. + internal override bool HasTheHighestNumberOfMessagesAvailable + { + get + { + Debug.Assert(_sharedResources._dataflowBlockOptions.Greedy, "This is only valid in greedy mode"); + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true); + + // Note: If there is a tie, we must return true + int count = _messages.Count; + foreach (JoinBlockTargetBase target in _sharedResources._targets) + if (target != this && target.NumberOfMessagesAvailableOrPostponed > count) + return false; // Strictly bigger! + return true; + } + } + + /// Reserves one of the postponed messages. + /// true if a message was reserved; otherwise, false. + internal override bool ReserveOneMessage() + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false); + Debug.Assert(!_sharedResources._dataflowBlockOptions.Greedy, "This is only used in non-greedy mode"); + + KeyValuePair, DataflowMessageHeader> next; + + lock (_sharedResources.IncomingLock) + { + // The queue must be empty between joins in non-greedy mode + Debug.Assert(!HasAtLeastOneMessageAvailable, "The queue must be empty between joins in non-greedy mode"); + + // While we are holding the lock, try to pop a postponed message. + // If there are no postponed messages, we can't do anything. + if (!_nonGreedy.PostponedMessages.TryPop(out next)) return false; + } + + // We'll bail out of this loop either when we have reserved a message (true) + // or when we have exhausted the list of postponed messages (false) + for (; ;) + { + // Try to reserve the popped message + if (next.Key.ReserveMessage(next.Value, this)) + { + _nonGreedy.ReservedMessage = next; + return true; + } + + // We could not reserve that message. + // Try to pop another postponed message and continue looping. + lock (_sharedResources.IncomingLock) + { + // If there are no postponed messages, we can't do anything + if (!_nonGreedy.PostponedMessages.TryPop(out next)) return false; + } + } + } + + /// Consumes a reserved message. + /// true if a message was consumed; otherwise, false. + internal override bool ConsumeReservedMessage() + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false); + Debug.Assert(!_sharedResources._dataflowBlockOptions.Greedy, "This is only used in non-greedy mode"); + Debug.Assert(_nonGreedy.ReservedMessage.Key != null, "This target must have a reserved message"); + + bool consumed; + T consumedValue = _nonGreedy.ReservedMessage.Key.ConsumeMessage(_nonGreedy.ReservedMessage.Value, this, out consumed); + + // Null out our reservation + _nonGreedy.ReservedMessage = default(KeyValuePair, DataflowMessageHeader>); + + // The protocol requires that a reserved message must be consumable, + // but it is possible that the source may misbehave. + // In that case complete the target and signal to the owning block to shut down gracefully. + if (!consumed) + { + _sharedResources._exceptionAction(new InvalidOperationException(SR.InvalidOperation_FailedToConsumeReservedMessage)); + + // Complete this target, which will trigger completion of the owning join block. + CompleteOncePossible(); + + // We need to signal to the caller to stop consuming immediately + return false; + } + else + { + lock (_sharedResources.IncomingLock) + { + // Now that we've consumed it, store its data. + Debug.Assert(!_nonGreedy.ConsumedMessage.Key, "There must be no other consumed message"); + _nonGreedy.ConsumedMessage = new KeyValuePair(true, consumedValue); + // We don't account bounding per target in non-greedy mode. We do it once per batch (in the loop). + + CompleteIfLastJoinIsFeasible(); + } + } + + return true; + } + + /// Consumes up to one postponed message in greedy bounded mode. + internal override bool ConsumeOnePostponedMessage() + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false); + Debug.Assert(_sharedResources._dataflowBlockOptions.Greedy, "This is only used in greedy mode"); + Debug.Assert(_sharedResources._boundingState != null, "This is only used in bounding mode"); + + // We'll bail out of this loop either when we have consumed a message (true) + // or when we have exhausted the list of postponed messages (false) + while (true) + { + KeyValuePair, DataflowMessageHeader> next; + bool hasTheHighestNumberOfMessagesAvailable; + + lock (_sharedResources.IncomingLock) + { + // While we are holding the lock, check bounding capacity and try to pop a postponed message. + // If anything fails, we can't do anything. + hasTheHighestNumberOfMessagesAvailable = HasTheHighestNumberOfMessagesAvailable; + bool boundingCapacityAvailable = _sharedResources._boundingState.CountIsLessThanBound || !hasTheHighestNumberOfMessagesAvailable; + if (_decliningPermanently || _sharedResources._decliningPermanently || + !boundingCapacityAvailable || !_nonGreedy.PostponedMessages.TryPop(out next)) + return false; + } + + // Try to consume the popped message + bool consumed; + T consumedValue = next.Key.ConsumeMessage(next.Value, this, out consumed); + if (consumed) + { + lock (_sharedResources.IncomingLock) + { + // The ranking in highest number of available messages cannot have changed because this task is causing OfferMessage to postpone + if (hasTheHighestNumberOfMessagesAvailable) _sharedResources._boundingState.CurrentCount += 1; // track this new item against our bound + _messages.Enqueue(consumedValue); + + CompleteIfLastJoinIsFeasible(); + return true; + } + } + } + } + + /// + /// Start declining if the number of joins we've already made plus the number we can + /// make from data already enqueued meets our quota. + /// + private void CompleteIfLastJoinIsFeasible() + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: true); + int messageCount = _sharedResources._dataflowBlockOptions.Greedy ? + _messages.Count : + _nonGreedy.ConsumedMessage.Key ? 1 : 0; + if ((_sharedResources._joinsCreated + messageCount) >= _sharedResources._dataflowBlockOptions.ActualMaxNumberOfGroups) + { + _decliningPermanently = true; + + bool allAreDecliningPermanently = true; + foreach (JoinBlockTargetBase target in _sharedResources._targets) + { + if (!target.IsDecliningPermanently) + { + allAreDecliningPermanently = false; + break; + } + } + if (allAreDecliningPermanently) _sharedResources._decliningPermanently = true; + } + } + + /// Releases the reservation on a reserved message. + internal override void ReleaseReservedMessage() + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false); + + // Release only if we have a reserved message. + // Otherwise do nothing. + if (_nonGreedy != null && _nonGreedy.ReservedMessage.Key != null) + { + // Release the reservation and null out our reservation flag even if an exception occurs + try { _nonGreedy.ReservedMessage.Key.ReleaseReservation(_nonGreedy.ReservedMessage.Value, this); } + finally { ClearReservation(); } + } + } + + /// Unconditionally clears a reserved message. + internal override void ClearReservation() + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false); + Debug.Assert(_nonGreedy != null, "Only valid in non-greedy mode."); + + _nonGreedy.ReservedMessage = default(KeyValuePair, DataflowMessageHeader>); + } + + /// Completes the target. + internal override void CompleteOncePossible() + { + Common.ContractAssertMonitorStatus(_sharedResources.IncomingLock, held: false); + + // This target must not have an outstanding reservation + Debug.Assert(_nonGreedy == null || _nonGreedy.ReservedMessage.Key == null, + "Must be in greedy mode, or in non-greedy mode but without any reserved messages."); + + // Clean up any messages that may be stragglers left behind + lock (_sharedResources.IncomingLock) + { + _decliningPermanently = true; + if (_messages != null) _messages.Clear(); + } + + // Release any postponed messages + List exceptions = null; + if (_nonGreedy != null) + { + // Note: No locks should be held at this point + Common.ReleaseAllPostponedMessages(this, _nonGreedy.PostponedMessages, ref exceptions); + } + + if (exceptions != null) + { + // It is important to migrate these exceptions to the source part of the owning join, + // because that is the completion task that is publically exposed. + foreach (Exception exc in exceptions) + { + _sharedResources._exceptionAction(exc); + } + } + + // Targets' completion tasks are only available internally with the sole purpose + // of releasing the task that completes the parent. Hence the actual reason + // for completing this task doesn't matter. + _completionTask.TrySetResult(default(VoidResult)); + } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + lock (_sharedResources.IncomingLock) + { + // If we shouldn't be accepting more messages, don't. + if (_decliningPermanently || _sharedResources._decliningPermanently) + { + _sharedResources.CompleteBlockIfPossible(); + return DataflowMessageStatus.DecliningPermanently; + } + + // We can directly accept the message if: + // 1) we are being greedy AND we are not bounding, OR + // 2) we are being greedy AND we are bounding AND there is room available AND there are no postponed messages AND we are not currently processing. + // (If there were any postponed messages, we would need to postpone so that ordering would be maintained.) + // (We should also postpone if we are currently processing, because there may be a race between consuming postponed messages and + // accepting new ones directly into the queue.) + if (_sharedResources._dataflowBlockOptions.Greedy && + (_sharedResources._boundingState == null + || + ((_sharedResources._boundingState.CountIsLessThanBound || !HasTheHighestNumberOfMessagesAvailable) && + _nonGreedy.PostponedMessages.Count == 0 && _sharedResources._taskForInputProcessing == null))) + { + if (consumeToAccept) + { + Debug.Assert(source != null, "We must have thrown if source == null && consumeToAccept == true."); + + bool consumed; + messageValue = source.ConsumeMessage(messageHeader, this, out consumed); + if (!consumed) return DataflowMessageStatus.NotAvailable; + } + if (_sharedResources._boundingState != null && HasTheHighestNumberOfMessagesAvailable) _sharedResources._boundingState.CurrentCount += 1; // track this new item against our bound + _messages.Enqueue(messageValue); + CompleteIfLastJoinIsFeasible(); + + // Since we're in greedy mode, we can skip asynchronous processing and + // make joins aggressively based on enqueued data. + if (_sharedResources.AllTargetsHaveAtLeastOneMessage) + { + _sharedResources._joinFilledAction(); + _sharedResources._joinsCreated++; + } + + _sharedResources.CompleteBlockIfPossible(); + return DataflowMessageStatus.Accepted; + } + // Otherwise, we try to postpone if a source was provided + else if (source != null) + { + Debug.Assert(_nonGreedy != null, "_nonGreedy must have been initialized during construction in non-greedy mode."); + + // Postpone the message now and kick off an async two-phase consumption. + _nonGreedy.PostponedMessages.Push(source, messageHeader); + _sharedResources.ProcessAsyncIfNecessary(); + return DataflowMessageStatus.Postponed; + } + // We can't do anything else about this message + return DataflowMessageStatus.Declined; + } + } + + /// Completes/faults the block. + /// In general, it is not safe to pass releaseReservedMessages:true, because releasing of reserved messages + /// is done without taking a lock. We pass releaseReservedMessages:true only when an exception has been + /// caught inside the message processing loop which is a single instance at any given moment. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal override void CompleteCore(Exception exception, bool dropPendingMessages, bool releaseReservedMessages) + { + bool greedy = _sharedResources._dataflowBlockOptions.Greedy; + lock (_sharedResources.IncomingLock) + { + // Faulting from outside is allowed until we start declining permanently. + // Faulting from inside is allowed at any time. + if (exception != null && ((!_decliningPermanently && !_sharedResources._decliningPermanently) || releaseReservedMessages)) + { + _sharedResources._exceptionAction(exception); + } + + // Drop pending messages if requested + if (dropPendingMessages && greedy) + { + Debug.Assert(_messages != null, "_messages must be initialized in greedy mode."); + _messages.Clear(); + } + } + + // Release reserved messages if requested. + // This must be done from outside the lock. + if (releaseReservedMessages && !greedy) + { + // Do this on all targets + foreach (JoinBlockTargetBase target in _sharedResources._targets) + { + try { target.ReleaseReservedMessage(); } + catch (Exception e) { _sharedResources._exceptionAction(e); } + } + } + + // Triggering completion requires the lock + lock (_sharedResources.IncomingLock) + { + // Trigger completion + _decliningPermanently = true; + _sharedResources.CompleteBlockIfPossible(); + } + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + CompleteCore(exception, dropPendingMessages: true, releaseReservedMessages: false); + } + + /// + public Task Completion { get { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); } } + /// The completion task on Join targets is only hidden from the public. It still exists for internal purposes. + internal Task CompletionTaskInternal { get { return _completionTask.Task; } } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int InputCountForDebugger { get { return _messages != null ? _messages.Count : _nonGreedy.ConsumedMessage.Key ? 1 : 0; } } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displayJoin = _sharedResources._ownerJoin as IDebuggerDisplay; + return string.Format("{0} InputCount={1}, Join=\"{2}\"", + Common.GetNameForDebugger(this), + InputCountForDebugger, + displayJoin != null ? displayJoin.Content : _sharedResources._ownerJoin); + } + } + /// 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 the Transform. + private sealed class DebugView + { + /// The join block target being viewed. + private readonly JoinBlockTarget _joinBlockTarget; + + /// Initializes the debug view. + /// The join being viewed. + public DebugView(JoinBlockTarget joinBlockTarget) + { + Contract.Requires(joinBlockTarget != null, "Need a target with which to construct the debug view."); + _joinBlockTarget = joinBlockTarget; + } + + /// Gets the messages waiting to be processed. + public IEnumerable InputQueue { get { return _joinBlockTarget._messages; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _joinBlockTarget._decliningPermanently || _joinBlockTarget._sharedResources._decliningPermanently; } } + } + } + + /// Provides a non-generic base type for all join targets. + internal abstract class JoinBlockTargetBase + { + /// Whether the target is postponing messages. + internal abstract bool IsDecliningPermanently { get; } + /// Whether the target has at least one message available. + internal abstract bool HasAtLeastOneMessageAvailable { get; } + /// Whether the target has at least one message postponed. + internal abstract bool HasAtLeastOnePostponedMessage { get; } + /// Gets the number of messages available or postponed. + internal abstract int NumberOfMessagesAvailableOrPostponed { get; } + /// Gets whether the target has the highest number of messages available. (A tie yields true.) + internal abstract bool HasTheHighestNumberOfMessagesAvailable { get; } + + /// Reserves a single message. + /// Whether a message was reserved. + internal abstract bool ReserveOneMessage(); + /// Consumes any previously reserved message. + /// Whether a message was consumed. + internal abstract bool ConsumeReservedMessage(); + /// Consumes up to one postponed message in greedy bounded mode. + /// Whether a message was consumed. + internal abstract bool ConsumeOnePostponedMessage(); + /// Releases any previously reserved message. + internal abstract void ReleaseReservedMessage(); + /// Unconditionally clears a reserved message. This is only invoked in case of an exception. + internal abstract void ClearReservation(); + + /// Access point to the corresponding API method. + public void Complete() { CompleteCore(exception: null, dropPendingMessages: false, releaseReservedMessages: false); } + /// Internal implementation of the corresponding API method. + internal abstract void CompleteCore(Exception exception, bool dropPendingMessages, bool releaseReservedMessages); + /// Completes the target. + internal abstract void CompleteOncePossible(); + } + + /// Provides a container for resources shared across all targets used by the same BatchedJoin instance. + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + internal sealed class JoinBlockTargetSharedResources + { + /// Initializes the shared resources. + /// + /// The join block that owns these shared resources. + /// + /// + /// The array of targets associated with the join. Also doubles as the sync object used to synchronize targets. + /// + /// The delegate to invoke when enough messages have been consumed to fulfill the join. + /// The delegate to invoke when a target encounters an error. + /// The options for the join. + internal JoinBlockTargetSharedResources( + IDataflowBlock ownerJoin, JoinBlockTargetBase[] targets, + Action joinFilledAction, Action exceptionAction, + GroupingDataflowBlockOptions dataflowBlockOptions) + { + Contract.Requires(ownerJoin != null, "Resources must be associated with a join."); + Contract.Requires(targets != null, "Resources must be shared between multiple targets."); + Contract.Requires(joinFilledAction != null, "An action to invoke when a join is created must be provided."); + Contract.Requires(exceptionAction != null, "An action to invoke for faults must be provided."); + Contract.Requires(dataflowBlockOptions != null, "Options must be provided to configure the resources."); + + // Store arguments + _ownerJoin = ownerJoin; + _targets = targets; + _joinFilledAction = joinFilledAction; + _exceptionAction = exceptionAction; + _dataflowBlockOptions = dataflowBlockOptions; + + // Initialize bounding state if necessary + if (dataflowBlockOptions.BoundedCapacity > 0) _boundingState = new BoundingState(dataflowBlockOptions.BoundedCapacity); + } + + // *** Accessible fields and properties + internal readonly IDataflowBlock _ownerJoin; + /// All of the targets associated with the join. + internal readonly JoinBlockTargetBase[] _targets; + /// The delegate to invoke when a target encounters an error. + internal readonly Action _exceptionAction; + /// The delegate to invoke when enough messages have been consumed to fulfill the join. + internal readonly Action _joinFilledAction; + /// The options for the join. + internal readonly GroupingDataflowBlockOptions _dataflowBlockOptions; + /// Bounding state for when the block is executing in bounded mode. + internal readonly BoundingState _boundingState; + /// Whether all targets should decline all further messages. + internal bool _decliningPermanently; + /// The task used to process messages. + internal Task _taskForInputProcessing; + /// Whether any exceptions have been generated and stored into the source core. + internal bool _hasExceptions; + /// The number of joins this block has created. + internal long _joinsCreated; + + // *** Private fields and properties - mutatable + /// A task has reserved the right to run the completion routine. + private bool _completionReserved; + + /// Gets the lock used to synchronize all incoming messages on all targets. + internal object IncomingLock { get { return _targets; } } + + /// Invokes Complete on each target with dropping buffered messages. + internal void CompleteEachTarget() + { + foreach (JoinBlockTargetBase target in _targets) + { + target.CompleteCore(exception: null, dropPendingMessages: true, releaseReservedMessages: false); + } + } + + /// Gets whether all of the targets have at least one message in their queues. + internal bool AllTargetsHaveAtLeastOneMessage + { + get + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + foreach (JoinBlockTargetBase target in _targets) + { + if (!target.HasAtLeastOneMessageAvailable) return false; + } + return true; + } + } + + /// Gets whether all of the targets have at least one message in their queues or have at least one postponed message. + private bool TargetsHaveAtLeastOneMessageQueuedOrPostponed + { + get + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + if (_boundingState == null) + { + foreach (JoinBlockTargetBase target in _targets) + { + if (!target.HasAtLeastOneMessageAvailable && + (_decliningPermanently || target.IsDecliningPermanently || !target.HasAtLeastOnePostponedMessage)) + return false; + } + return true; + } + else + { + // Cache the availability state so we don't evaluate it multiple times + bool boundingCapacityAvailable = _boundingState.CountIsLessThanBound; + + // In bounding mode, we have more complex rules whether we should process input messages: + // 1) In greedy mode if a target has postponed messages and there is bounding capacity + // available, then we should greedily consume messages up to the bounding capacity + // even if that doesn't lead to an output join. + // 2) The ability to make join depends on: + // 2a) message availability for each target, AND + // 2b) availability of bounding space + + bool joinIsPossible = true; + bool joinWillNotAffectBoundingCount = false; + foreach (JoinBlockTargetBase target in _targets) + { + bool targetCanConsumePostponedMessages = !_decliningPermanently && !target.IsDecliningPermanently && target.HasAtLeastOnePostponedMessage; + + // Rule #1 + if (_dataflowBlockOptions.Greedy && targetCanConsumePostponedMessages && (boundingCapacityAvailable || !target.HasTheHighestNumberOfMessagesAvailable)) return true; + + // Rule #2a + bool targetHasMessagesAvailable = target.HasAtLeastOneMessageAvailable; + joinIsPossible &= targetHasMessagesAvailable || targetCanConsumePostponedMessages; + + // Rule #2b + // If there is a target that has at least one queued message, bounding space availability + // is no longer an issue, because 1 item from the input side will be replaced with 1 + // item on the output side. + if (targetHasMessagesAvailable) joinWillNotAffectBoundingCount = true; + } + + // Rule #2 + return joinIsPossible && (joinWillNotAffectBoundingCount || boundingCapacityAvailable); + } + } + } + + /// Retrieves postponed items if we have enough to make a batch. + /// true if input messages for a batch were consumed (all or none); false otherwise. + private bool RetrievePostponedItemsNonGreedy() + { + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // If there are not enough postponed items, we have nothing to do. + lock (IncomingLock) + { + if (!TargetsHaveAtLeastOneMessageQueuedOrPostponed) return false; + } // Release the lock. We must not hold it while calling Reserve/Consume/Release. + + // Try to reserve a postponed message on every target that doesn't already have messages available + bool reservedAll = true; + foreach (JoinBlockTargetBase target in _targets) + { + if (!target.ReserveOneMessage()) + { + reservedAll = false; + break; + } + } + + // If we were able to, consume them all and place the consumed messages into each's queue + if (reservedAll) + { + foreach (JoinBlockTargetBase target in _targets) + { + // If we couldn't consume a message, release reservations wherever possible + if (!target.ConsumeReservedMessage()) + { + reservedAll = false; + break; + } + } + } + + // If we were unable to reserve all messages, release the reservations + if (!reservedAll) + { + foreach (JoinBlockTargetBase target in _targets) + { + target.ReleaseReservedMessage(); + } + } + + return reservedAll; + } + + /// Retrieves up to one postponed item through each target. + /// true if at least one input message was consumed (through any target); false otherwise. + private bool RetrievePostponedItemsGreedyBounded() + { + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // Try to consume a postponed message through each target as possible + bool consumed = false; + foreach (JoinBlockTargetBase target in _targets) + { + // It is sufficient to consume through one target to consider we've made progress + consumed |= target.ConsumeOnePostponedMessage(); + } + + return consumed; + } + + /// Gets whether the target has had cancellation requested or an exception has occurred. + private bool CanceledOrFaulted + { + get + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + return _dataflowBlockOptions.CancellationToken.IsCancellationRequested || _hasExceptions; + } + } + + /// + /// Gets whether the join is in a state where processing can be done, meaning there's data + /// to be processed and the block is in a state where such processing is allowed. + /// + internal bool JoinNeedsProcessing + { + get + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + return + _taskForInputProcessing == null && // not currently processing asynchronously + !CanceledOrFaulted && // not canceled or faulted + TargetsHaveAtLeastOneMessageQueuedOrPostponed; // all targets have work queued or postponed + } + } + + /// Called when new messages are available to be processed. + /// Whether this call is the continuation of a previous message loop. + internal void ProcessAsyncIfNecessary(bool isReplacementReplica = false) + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + if (JoinNeedsProcessing) + { + ProcessAsyncIfNecessary_Slow(isReplacementReplica); + } + } + + /// + /// Slow path for ProcessAsyncIfNecessary. + /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined. + /// + private void ProcessAsyncIfNecessary_Slow(bool isReplacementReplica) + { + Contract.Requires(JoinNeedsProcessing, "There must be a join that needs processing."); + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + // Create task and store into _taskForInputProcessing prior to scheduling the task + // so that _taskForInputProcessing will be visibly set in the task loop. + _taskForInputProcessing = new Task(thisSharedResources => ((JoinBlockTargetSharedResources)thisSharedResources).ProcessMessagesLoopCore(), this, + Common.GetCreationOptionsForTask(isReplacementReplica)); + +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TaskLaunchedForMessageHandling( + _ownerJoin, _taskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, + _targets.Max(t => t.NumberOfMessagesAvailableOrPostponed)); + } +#endif + + // Start the task handling scheduling exceptions + Exception exception = Common.StartTaskSafe(_taskForInputProcessing, _dataflowBlockOptions.TaskScheduler); + if (exception != null) + { + // All of the following actions must be performed under the lock. + // So do them now while the lock is being held. + + // First, log the exception while the processing state is dirty which is preventing the block from completing. + // Then revert the proactive processing state changes. + // And last, try to complete the block. + _exceptionAction(exception); + _taskForInputProcessing = null; + CompleteBlockIfPossible(); + } + } + + /// Completes the join block if possible. + internal void CompleteBlockIfPossible() + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + if (!_completionReserved) + { + // Check whether we're sure we'll never be able to fill another join. + // That could happen if we're not accepting more messages and not all targets have a message... + bool impossibleToCompleteAnotherJoin = _decliningPermanently && !AllTargetsHaveAtLeastOneMessage; + if (!impossibleToCompleteAnotherJoin) + { + //...or that could happen if an individual target isn't accepting messages and doesn't have any messages available + foreach (JoinBlockTargetBase target in _targets) + { + if (target.IsDecliningPermanently && !target.HasAtLeastOneMessageAvailable) + { + impossibleToCompleteAnotherJoin = true; + break; + } + } + } + + // We're done forever if there's no task currently processing and + // either it's impossible we'll have another join or we're canceled. + bool currentlyProcessing = _taskForInputProcessing != null; + bool shouldComplete = !currentlyProcessing && (impossibleToCompleteAnotherJoin || CanceledOrFaulted); + + if (shouldComplete) + { + // Make sure no one else tries to call CompleteBlockOncePossible + _completionReserved = true; + + // Make sure all targets are declining + _decliningPermanently = true; + + // Complete each target asynchronously so as not to invoke synchronous continuations under a lock + Task.Factory.StartNew(state => + { + var sharedResources = (JoinBlockTargetSharedResources)state; + foreach (JoinBlockTargetBase target in sharedResources._targets) target.CompleteOncePossible(); + }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + } + + /// Task body used to process messages. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ProcessMessagesLoopCore() + { + Contract.Requires(!_dataflowBlockOptions.Greedy || _boundingState != null, "This only makes sense in non-greedy or bounding mode"); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + try + { + int timesThroughLoop = 0; + int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask; + bool madeProgress; + do + { + // Retrieve postponed messages. + // In greedy bounded mode, consuming a message through a target is sufficient + // to consider we've made progress, i.e. to stay in the loop. + madeProgress = !_dataflowBlockOptions.Greedy ? + RetrievePostponedItemsNonGreedy() : + RetrievePostponedItemsGreedyBounded(); + + if (madeProgress) + { + // Convert buffered messages into a filled join if each target has at least one buffered message + lock (IncomingLock) + { + if (AllTargetsHaveAtLeastOneMessage) + { + _joinFilledAction(); // Pluck a message from each target + _joinsCreated++; + + // If we are in non-greedy mode, do this once per join + if (!_dataflowBlockOptions.Greedy && _boundingState != null) _boundingState.CurrentCount += 1; + } + } + } + + timesThroughLoop++; + } while (madeProgress && timesThroughLoop < maxMessagesPerTask); + } + catch (Exception exception) + { + // We can trigger completion of the JoinBlock by completing one target. + // It doesn't matter which one. So we always complete the first one. + Debug.Assert(_targets.Length > 0, "A join must have targets."); + _targets[0].CompleteCore(exception, dropPendingMessages: true, releaseReservedMessages: true); + // The finally section will do the block completion. + } + finally + { + lock (IncomingLock) + { + // We're no longer processing, so null out the processing task + _taskForInputProcessing = null; + + // However, we may have given up early because we hit our own configured + // processing limits rather than because we ran out of work to do. If that's + // the case, make sure we spin up another task to keep going. + ProcessAsyncIfNecessary(isReplacementReplica: true); + + // If, however, we stopped because we ran out of work to do and we + // know we'll never get more, then complete. + CompleteBlockIfPossible(); + } + } + } + + /// Notifies the block that one or more items was removed from the queue. + /// The number of items removed. + internal void OnItemsRemoved(int numItemsRemoved) + { + Contract.Requires(numItemsRemoved > 0, "Number of items removed needs to be positive."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // If we're bounding, we need to know when an item is removed so that we + // can update the count that's mirroring the actual count in the source's queue, + // and potentially kick off processing to start consuming postponed messages. + if (_boundingState != null) + { + lock (IncomingLock) + { + // Decrement the count, which mirrors the count in the source half + Debug.Assert(_boundingState.CurrentCount - numItemsRemoved >= 0, + "It should be impossible to have a negative number of items."); + _boundingState.CurrentCount -= numItemsRemoved; + + ProcessAsyncIfNecessary(); + CompleteBlockIfPossible(); + } + } + } + + /// Gets the object to display in the debugger display attribute. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displayJoin = _ownerJoin as IDebuggerDisplay; + return string.Format("Block=\"{0}\"", + displayJoin != null ? displayJoin.Content : _ownerJoin); + } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/TransformBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/TransformBlock.cs new file mode 100644 index 00000000000..97ba13d09f4 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/TransformBlock.cs @@ -0,0 +1,427 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// TransformBlock.cs +// +// +// A propagator block that runs a function on each input to produce a single output. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Threading.Tasks.Dataflow.Internal; +using System.Diagnostics.CodeAnalysis; + +namespace System.Threading.Tasks.Dataflow +{ + /// Provides a dataflow block that invokes a provided delegate for every data element received. + /// Specifies the type of data received and operated on by this . + /// Specifies the type of data output by this . + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(TransformBlock<,>.DebugView))] + public sealed class TransformBlock : IPropagatorBlock, IReceivableSourceBlock, IDebuggerDisplay + { + /// The target side. + private readonly TargetCore _target; + /// Buffer used to reorder outputs that may have completed out-of-order between the target half and the source half. + private readonly ReorderingBuffer _reorderingBuffer; + /// The source side. + private readonly SourceCore _source; + + /// Initializes the with the specified . + /// The function to invoke with each data element received. + /// The is null (Nothing in Visual Basic). + public TransformBlock(Func transform) : + this(transform, null, ExecutionDataflowBlockOptions.Default) + { } + + /// + /// Initializes the with the specified and + /// . + /// + /// The function to invoke with each data element received. + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + public TransformBlock(Func transform, ExecutionDataflowBlockOptions dataflowBlockOptions) : + this(transform, null, dataflowBlockOptions) + { } + + /// Initializes the with the specified . + /// The function to invoke with each data element received. + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public TransformBlock(Func> transform) : + this(null, transform, ExecutionDataflowBlockOptions.Default) + { } + + /// + /// Initializes the with the specified + /// and . + /// + /// The function to invoke with each data element received. + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public TransformBlock(Func> transform, ExecutionDataflowBlockOptions dataflowBlockOptions) : + this(null, transform, dataflowBlockOptions) + { } + + /// + /// Initializes the with the specified + /// and . + /// + /// The synchronous function to invoke with each data element received. + /// The asynchronous function to invoke with each data element received. + /// The options with which to configure this . + /// The and are both null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + private TransformBlock(Func transformSync, Func> transformAsync, ExecutionDataflowBlockOptions dataflowBlockOptions) + { + if (transformSync == null && transformAsync == null) throw new ArgumentNullException("transform"); + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + + Contract.Requires(transformSync == null ^ transformAsync == null, "Exactly one of transformSync and transformAsync must be null."); + Contract.EndContractBlock(); + + // Ensure we have options that can't be changed by the caller + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Initialize onItemsRemoved delegate if necessary + Action, int> onItemsRemoved = null; + if (dataflowBlockOptions.BoundedCapacity > 0) + onItemsRemoved = (owningSource, count) => ((TransformBlock)owningSource)._target.ChangeBoundingCount(-count); + + // Initialize source component. + _source = new SourceCore(this, dataflowBlockOptions, + owningSource => ((TransformBlock)owningSource)._target.Complete(exception: null, dropPendingMessages: true), + onItemsRemoved); + + // If parallelism is employed, we will need to support reordering messages that complete out-of-order + if (dataflowBlockOptions.SupportsParallelExecution) + { + _reorderingBuffer = new ReorderingBuffer(this, (owningSource, message) => ((TransformBlock)owningSource)._source.AddMessage(message)); + } + + // Create the underlying target + if (transformSync != null) // sync + { + _target = new TargetCore(this, + messageWithId => ProcessMessage(transformSync, messageWithId), + _reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.None); + } + else // async + { + Debug.Assert(transformAsync != null, "Incorrect delegate type."); + _target = new TargetCore(this, + messageWithId => ProcessMessageWithTask(transformAsync, messageWithId), + _reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.UsesAsyncCompletion); + } + + // Link up the target half with the source half. In doing so, + // ensure exceptions are propagated, and let the source know no more messages will arrive. + // As the target has completed, and as the target synchronously pushes work + // through the reordering buffer when async processing completes, + // we know for certain that no more messages will need to be sent to the source. + _target.Completion.ContinueWith((completed, state) => + { + var sourceCore = (SourceCore)state; + if (completed.IsFaulted) sourceCore.AddAndUnwrapAggregateException(completed.Exception); + sourceCore.Complete(); + }, _source, CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((TransformBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, Completion, state => ((TargetCore)state).Complete(exception: null, dropPendingMessages: true), _target); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// Processes the message with a user-provided transform function that returns a TOutput. + /// The transform function to use to process the message. + /// The message to be processed. + private void ProcessMessage(Func transform, KeyValuePair messageWithId) + { + // Process the input message to get the output message + TOutput outputItem = default(TOutput); + bool itemIsValid = false; + try + { + outputItem = transform(messageWithId.Key); + itemIsValid = true; + } + catch (Exception exc) + { + // If this exception represents cancellation, swallow it rather than shutting down the block. + if (!Common.IsCooperativeCancellation(exc)) throw; + } + finally + { + // If we were not successful in producing an item, update the bounding + // count to reflect that we're done with this input item. + if (!itemIsValid) _target.ChangeBoundingCount(-1); + + // If there's no reordering buffer (because we're running sequentially), + // simply pass the output message through. Otherwise, there's a reordering buffer, + // so add to it instead (if a reordering buffer is used, we always need + // to output the message to it, even if the operation failed and outputMessage + // is null... this is because the reordering buffer cares about a strict sequence + // of IDs, and it needs to know when a particular ID has completed. It will eliminate + // null messages accordingly.) + if (_reorderingBuffer == null) + { + if (itemIsValid) _source.AddMessage(outputItem); + } + else _reorderingBuffer.AddItem(messageWithId.Value, outputItem, itemIsValid); + } + } + + /// Processes the message with a user-provided transform function that returns a task of TOutput. + /// The transform function to use to process the message. + /// The message to be processed. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ProcessMessageWithTask(Func> transform, KeyValuePair messageWithId) + { + Contract.Requires(transform != null, "Function to invoke is required."); + + // Run the transform function to get the task that represents the operation's completion + Task task = null; + Exception caughtException = null; + try + { + task = transform(messageWithId.Key); + } + catch (Exception exc) { caughtException = exc; } + + // If no task is available, we're done. + if (task == null) + { + // If we didn't get a task because an exception occurred, + // store it (if the exception was cancellation, just ignore it). + if (caughtException != null && !Common.IsCooperativeCancellation(caughtException)) + { + Common.StoreDataflowMessageValueIntoExceptionData(caughtException, messageWithId.Key); + _target.Complete(caughtException, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false); + } + + // If there's a reordering buffer, notify it that this message is done. + if (_reorderingBuffer != null) _reorderingBuffer.IgnoreItem(messageWithId.Value); + + // Signal that we're done this async operation, and remove the bounding + // count for the input item that didn't yield any output. + _target.SignalOneAsyncMessageCompleted(boundingCountChange: -1); + return; + } + + // Otherwise, join with the asynchronous operation when it completes. + task.ContinueWith((completed, state) => + { + var tuple = (Tuple, KeyValuePair>)state; + tuple.Item1.AsyncCompleteProcessMessageWithTask(completed, tuple.Item2); + }, Tuple.Create(this, messageWithId), CancellationToken.None, + Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), TaskScheduler.Default); + } + + /// Completes the processing of an asynchronous message. + /// The completed task storing the output data generated for an input message. + /// The originating message + private void AsyncCompleteProcessMessageWithTask(Task completed, KeyValuePair messageWithId) + { + Contract.Requires(completed != null, "Completed task is required."); + Contract.Requires(completed.IsCompleted, "Task must be completed to be here."); + + bool isBounded = _target.IsBounded; + bool gotOutputItem = false; + TOutput outputItem = default(TOutput); + + switch (completed.Status) + { + case TaskStatus.RanToCompletion: + outputItem = completed.Result; + gotOutputItem = true; + break; + + case TaskStatus.Faulted: + // We must add the exception before declining and signaling completion, as the exception + // is part of the operation, and the completion conditions depend on this. + AggregateException aggregate = completed.Exception; + Common.StoreDataflowMessageValueIntoExceptionData(aggregate, messageWithId.Key, targetInnerExceptions: true); + _target.Complete(aggregate, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: true); + break; + // Nothing special to do for cancellation + } + + // Adjust the bounding count if necessary (we only need to decrement it for faulting + // and cancellation, since in the case of success we still have an item that's now in the output buffer). + // Even though this is more costly (again, only in the non-success case, we do this before we store the + // message, so that if there's a race to remove the element from the source buffer, the count is + // appropriately incremented before it's decremented. + if (!gotOutputItem && isBounded) _target.ChangeBoundingCount(-1); + + // If there's no reordering buffer (because we're running sequentially), + // and we got a message, simply pass the output message through. + if (_reorderingBuffer == null) + { + if (gotOutputItem) _source.AddMessage(outputItem); + } + // Otherwise, there's a reordering buffer, so add to it instead. + // Even if something goes wrong, we need to update the + // reordering buffer, so it knows that an item isn't missing. + else _reorderingBuffer.AddItem(messageWithId.Value, outputItem, itemIsValid: gotOutputItem); + + // Let the target know that one of the asynchronous operations it launched has completed. + _target.SignalOneAsyncMessageCompleted(); + } + + /// + public void Complete() { _target.Complete(exception: null, dropPendingMessages: false); } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + _target.Complete(exception, dropPendingMessages: true); + } + + /// + public IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) + { + return _source.LinkTo(target, linkOptions); + } + + /// + public Boolean TryReceive(Predicate filter, out TOutput item) + { + return _source.TryReceive(filter, out item); + } + + /// + public bool TryReceiveAll(out IList items) { return _source.TryReceiveAll(out items); } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + public int InputCount { get { return _target.InputCount; } } + + /// + public int OutputCount { get { return _source.OutputCount; } } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock source, Boolean consumeToAccept) + { + return _target.OfferMessage(messageHeader, messageValue, source, consumeToAccept); + } + + /// + TOutput ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int InputCountForDebugger { get { return _target.GetDebuggingInformation().InputCount; } } + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, InputCount={1}, OutputCount={2}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + InputCountForDebugger, + OutputCountForDebugger); + } + } + /// 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 the TransformBlock. + private sealed class DebugView + { + /// The transform being viewed. + private readonly TransformBlock _transformBlock; + /// The target half of the block being viewed. + private readonly TargetCore.DebuggingInformation _targetDebuggingInformation; + /// The source half of the block being viewed. + private readonly SourceCore.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The transform being viewed. + public DebugView(TransformBlock transformBlock) + { + Contract.Requires(transformBlock != null, "Need a block with which to construct the debug view."); + _transformBlock = transformBlock; + _targetDebuggingInformation = transformBlock._target.GetDebuggingInformation(); + _sourceDebuggingInformation = transformBlock._source.GetDebuggingInformation(); + } + + /// Gets the messages waiting to be processed. + public IEnumerable InputQueue { get { return _targetDebuggingInformation.InputQueue; } } + /// Gets any postponed messages. + public QueuedMap, DataflowMessageHeader> PostponedMessages { get { return _targetDebuggingInformation.PostponedMessages; } } + /// Gets the messages waiting to be received. + public IEnumerable OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } + + /// Gets the number of outstanding input operations. + public Int32 CurrentDegreeOfParallelism { get { return _targetDebuggingInformation.CurrentDegreeOfParallelism; } } + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _targetDebuggingInformation.DataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _targetDebuggingInformation.IsDecliningPermanently; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_transformBlock); } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the target that holds a reservation on the next message, if any. + public ITargetBlock NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/TransformManyBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/TransformManyBlock.cs new file mode 100644 index 00000000000..0223feb82f4 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/TransformManyBlock.cs @@ -0,0 +1,644 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// TransformManyBlock.cs +// +// +// A propagator block that runs a function on each input to produce zero or more outputs. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Threading.Tasks.Dataflow.Internal; +using System.Collections.ObjectModel; + +namespace System.Threading.Tasks.Dataflow +{ + /// Provides a dataflow block that invokes a provided delegate for every data element received. + /// Specifies the type of data received and operated on by this . + /// Specifies the type of data output by this . + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(TransformManyBlock<,>.DebugView))] + public sealed class TransformManyBlock : IPropagatorBlock, IReceivableSourceBlock, IDebuggerDisplay + { + /// The target side. + private readonly TargetCore _target; + /// + /// Buffer used to reorder output sets that may have completed out-of-order between the target half and the source half. + /// This specialized reordering buffer supports streaming out enumerables if the message is the next in line. + /// + private readonly ReorderingBuffer> _reorderingBuffer; + /// The source side. + private readonly SourceCore _source; + + /// Initializes the with the specified function. + /// + /// The function to invoke with each data element received. All of the data from the returned + /// will be made available as output from this . + /// + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public TransformManyBlock(Func> transform) : + this(transform, null, ExecutionDataflowBlockOptions.Default) + { } + + /// Initializes the with the specified function and . + /// + /// The function to invoke with each data element received. All of the data from the returned in the + /// will be made available as output from this . + /// + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public TransformManyBlock(Func> transform, ExecutionDataflowBlockOptions dataflowBlockOptions) : + this(transform, null, dataflowBlockOptions) + { } + + /// Initializes the with the specified function. + /// + /// The function to invoke with each data element received. All of the data asynchronously returned in the + /// will be made available as output from this . + /// + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public TransformManyBlock(Func>> transform) : + this(null, transform, ExecutionDataflowBlockOptions.Default) + { } + + /// Initializes the with the specified function and . + /// + /// The function to invoke with each data element received. All of the data asynchronously returned in the + /// will be made available as output from this . + /// + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public TransformManyBlock(Func>> transform, ExecutionDataflowBlockOptions dataflowBlockOptions) : + this(null, transform, dataflowBlockOptions) + { } + + /// Initializes the with the specified function and . + /// The synchronous function to invoke with each data element received. + /// The asynchronous function to invoke with each data element received. + /// The options with which to configure this . + /// The and are both null (Nothing in Visual Basic). + /// The is null (Nothing in Visual Basic). + private TransformManyBlock(Func> transformSync, Func>> transformAsync, ExecutionDataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments. It's ok for the filterFunction to be null, but not the other parameters. + if (transformSync == null && transformAsync == null) throw new ArgumentNullException("transform"); + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + + Contract.Requires(transformSync == null ^ transformAsync == null, "Exactly one of transformSync and transformAsync must be null."); + Contract.EndContractBlock(); + + // Ensure we have options that can't be changed by the caller + dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // Initialize onItemsRemoved delegate if necessary + Action, int> onItemsRemoved = null; + if (dataflowBlockOptions.BoundedCapacity > 0) + onItemsRemoved = (owningSource, count) => ((TransformManyBlock)owningSource)._target.ChangeBoundingCount(-count); + + // Initialize source component + _source = new SourceCore(this, dataflowBlockOptions, + owningSource => ((TransformManyBlock)owningSource)._target.Complete(exception: null, dropPendingMessages: true), + onItemsRemoved); + + // If parallelism is employed, we will need to support reordering messages that complete out-of-order. + if (dataflowBlockOptions.SupportsParallelExecution) + { + _reorderingBuffer = new ReorderingBuffer>( + this, (source, messages) => ((TransformManyBlock)source)._source.AddMessages(messages)); + } + + // Create the underlying target and source + if (transformSync != null) // sync + { + // If an enumerable function was provided, we can use synchronous completion, meaning + // that the target will consider a message fully processed as soon as the + // delegate returns. + _target = new TargetCore(this, + messageWithId => ProcessMessage(transformSync, messageWithId), + _reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.None); + } + else // async + { + Debug.Assert(transformAsync != null, "Incorrect delegate type."); + + // If a task-based function was provided, we need to use asynchronous completion, meaning + // that the target won't consider a message completed until the task + // returned from that delegate has completed. + _target = new TargetCore(this, + messageWithId => ProcessMessageWithTask(transformAsync, messageWithId), + _reorderingBuffer, dataflowBlockOptions, TargetCoreOptions.UsesAsyncCompletion); + } + + // Link up the target half with the source half. In doing so, + // ensure exceptions are propagated, and let the source know no more messages will arrive. + // As the target has completed, and as the target synchronously pushes work + // through the reordering buffer when async processing completes, + // we know for certain that no more messages will need to be sent to the source. + _target.Completion.ContinueWith((completed, state) => + { + var sourceCore = (SourceCore)state; + if (completed.IsFaulted) sourceCore.AddAndUnwrapAggregateException(completed.Exception); + sourceCore.Complete(); + }, _source, CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default); + + // It is possible that the source half may fault on its own, e.g. due to a task scheduler exception. + // In those cases we need to fault the target half to drop its buffered messages and to release its + // reservations. This should not create an infinite loop, because all our implementations are designed + // to handle multiple completion requests and to carry over only one. + _source.Completion.ContinueWith((completed, state) => + { + var thisBlock = ((TransformManyBlock)state) as IDataflowBlock; + Debug.Assert(completed.IsFaulted, "The source must be faulted in order to trigger a target completion."); + thisBlock.Fault(completed.Exception); + }, this, CancellationToken.None, Common.GetContinuationOptions() | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, Completion, state => ((TargetCore)state).Complete(exception: null, dropPendingMessages: true), _target); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// Processes the message with a user-provided transform function that returns an enumerable. + /// The transform function to use to process the message. + /// The message to be processed. + private void ProcessMessage(Func> transformFunction, KeyValuePair messageWithId) + { + Contract.Requires(transformFunction != null, "Function to invoke is required."); + + bool userDelegateSucceeded = false; + try + { + // Run the user transform and store the results. + IEnumerable outputItems = transformFunction(messageWithId.Key); + userDelegateSucceeded = true; + StoreOutputItems(messageWithId, outputItems); + } + catch (Exception exc) + { + // If this exception represents cancellation, swallow it rather than shutting down the block. + if (!Common.IsCooperativeCancellation(exc)) throw; + } + finally + { + // If the user delegate failed, store an empty set in order + // to update the bounding count and reordering buffer. + if (!userDelegateSucceeded) StoreOutputItems(messageWithId, null); + } + } + + /// Processes the message with a user-provided transform function that returns an observable. + /// The transform function to use to process the message. + /// The message to be processed. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ProcessMessageWithTask(Func>> function, KeyValuePair messageWithId) + { + Contract.Requires(function != null, "Function to invoke is required."); + + // Run the transform function to get the resulting task + Task> task = null; + Exception caughtException = null; + try + { + task = function(messageWithId.Key); + } + catch (Exception exc) { caughtException = exc; } + + // If no task is available, either because null was returned or an exception was thrown, we're done. + if (task == null) + { + // If we didn't get a task because an exception occurred, store it + // (or if the exception was cancellation, just ignore it). + if (caughtException != null && !Common.IsCooperativeCancellation(caughtException)) + { + Common.StoreDataflowMessageValueIntoExceptionData(caughtException, messageWithId.Key); + _target.Complete(caughtException, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false); + } + + // Notify that we're done with this input and that we got no output for the input. + if (_reorderingBuffer != null) + { + // If there's a reordering buffer, "store" an empty output. This will + // internally both update the output buffer and decrement the bounding count + // accordingly. + StoreOutputItems(messageWithId, null); + _target.SignalOneAsyncMessageCompleted(); + } + else + { + // As a fast path if we're not reordering, decrement the bounding + // count as part of our signaling that we're done, since this will + // internally take the lock only once, whereas the above path will + // take the lock twice. + _target.SignalOneAsyncMessageCompleted(boundingCountChange: -1); + } + return; + } + + // We got back a task. Now wait for it to complete and store its results. + // Unlike with TransformBlock and ActionBlock, We run the continuation on the user-provided + // scheduler as we'll be running user code through enumerating the returned enumerable. + task.ContinueWith((completed, state) => + { + var tuple = (Tuple, KeyValuePair>)state; + tuple.Item1.AsyncCompleteProcessMessageWithTask(completed, tuple.Item2); + }, Tuple.Create(this, messageWithId), + CancellationToken.None, + Common.GetContinuationOptions(TaskContinuationOptions.ExecuteSynchronously), + _source.DataflowBlockOptions.TaskScheduler); + } + + /// Completes the processing of an asynchronous message. + /// The completed task storing the output data generated for an input message. + /// The originating message + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void AsyncCompleteProcessMessageWithTask( + Task> completed, KeyValuePair messageWithId) + { + Contract.Requires(completed != null, "A task should have been provided."); + Contract.Requires(completed.IsCompleted, "The task should have been in a final state."); + + switch (completed.Status) + { + case TaskStatus.RanToCompletion: + IEnumerable outputItems = completed.Result; + try + { + // Get the resulting enumerable and persist it. + StoreOutputItems(messageWithId, outputItems); + } + catch (Exception exc) + { + // Enumerating the user's collection failed. If this exception represents cancellation, + // swallow it rather than shutting down the block. + if (!Common.IsCooperativeCancellation(exc)) + { + // The exception was not for cancellation. We must add the exception before declining + // and signaling completion, as the exception is part of the operation, and the completion + // conditions depend on this. + Common.StoreDataflowMessageValueIntoExceptionData(exc, messageWithId.Key); + _target.Complete(exc, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false); + } + } + break; + + case TaskStatus.Faulted: + // We must add the exception before declining and signaling completion, as the exception + // is part of the operation, and the completion conditions depend on this. + AggregateException aggregate = completed.Exception; + Common.StoreDataflowMessageValueIntoExceptionData(aggregate, messageWithId.Key, targetInnerExceptions: true); + _target.Complete(aggregate, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: true); + goto case TaskStatus.Canceled; + case TaskStatus.Canceled: + StoreOutputItems(messageWithId, null); // notify the reordering buffer and decrement the bounding count + break; + + default: + Debug.Assert(false, "The task should have been in a final state."); + break; + } + + // Let the target know that one of the asynchronous operations it launched has completed. + _target.SignalOneAsyncMessageCompleted(); + } + + /// + /// Stores the output items, either into the reordering buffer or into the source half. + /// Ensures that the bounding count is correctly updated. + /// + /// The message with id. + /// The output items to be persisted. + private void StoreOutputItems( + KeyValuePair messageWithId, IEnumerable outputItems) + { + // If there's a reordering buffer, pass the data along to it. + // The reordering buffer will handle all details, including bounding. + if (_reorderingBuffer != null) + { + StoreOutputItemsReordered(messageWithId.Value, outputItems); + } + // Otherwise, output the data directly. + else if (outputItems != null) + { + // If this is a trusted type, output the data en mass. + if (outputItems is TOutput[] || outputItems is List) + { + StoreOutputItemsNonReorderedAtomic(outputItems); + } + else + { + // Otherwise, we need to take the slow path of enumerating + // each individual item. + StoreOutputItemsNonReorderedWithIteration(outputItems); + } + } + else if (_target.IsBounded) + { + // outputItems is null and there's no reordering buffer + // and we're bounding, so decrement the bounding count to + // signify that the input element we already accounted for + // produced no output + _target.ChangeBoundingCount(count: -1); + } + // else there's no reordering buffer, there are no output items, and we're not bounded, + // so there's nothing more to be done. + } + + /// Stores the next item using the reordering buffer. + /// The ID of the item. + /// The completed item. + private void StoreOutputItemsReordered(long id, IEnumerable item) + { + Contract.Requires(_reorderingBuffer != null, "Expected a reordering buffer"); + Contract.Requires(id != Common.INVALID_REORDERING_ID, "This ID should never have been handed out."); + + // Grab info about the transform + TargetCore target = _target; + bool isBounded = target.IsBounded; + + // Handle invalid items (null enumerables) by delegating to the base + if (item == null) + { + _reorderingBuffer.AddItem(id, null, false); + if (isBounded) target.ChangeBoundingCount(count: -1); + return; + } + + // If we can eagerly get the number of items in the collection, update the bounding count. + // This avoids the cost of updating it once per output item (since each update requires synchronization). + // Even if we're not bounding, we still want to determine whether the item is trusted so that we + // can immediately dump it out once we take the lock if we're the next item. + IList itemAsTrustedList = item as TOutput[]; + if (itemAsTrustedList == null) itemAsTrustedList = item as List; + if (itemAsTrustedList != null && isBounded) + { + UpdateBoundingCountWithOutputCount(count: itemAsTrustedList.Count); + } + + // Determine whether this id is the next item, and if it is and if we have a trusted list, + // try to output it immediately on the fast path. If it can be output, we're done. + // Otherwise, make forward progress based on whether we're next in line. + bool? isNextNullable = _reorderingBuffer.AddItemIfNextAndTrusted(id, itemAsTrustedList, itemAsTrustedList != null); + if (!isNextNullable.HasValue) return; // data was successfully output + bool isNextItem = isNextNullable.Value; + + // By this point, either we're not the next item, in which case we need to make a copy of the + // data and store it, or we are the next item and can store it immediately but we need to enumerate + // the items and store them individually because we don't want to enumerate while holding a lock. + List itemCopy = null; + try + { + // If this is the next item, we can output it now. + if (isNextItem) + { + StoreOutputItemsNonReorderedWithIteration(item); + // here itemCopy remains null, so that base.AddItem will finish our interactions with the reordering buffer + } + else if (itemAsTrustedList != null) + { + itemCopy = itemAsTrustedList.ToList(); + // we already got the count and updated the bounding count previously + } + else + { + // We're not the next item, and we're not trusted, so copy the data into a list. + // We need to enumerate outside of the lock in the base class. + int itemCount = 0; + try + { + itemCopy = item.ToList(); // itemCopy will remain null in the case of exception + itemCount = itemCopy.Count; + } + finally + { + // If we're here successfully, then itemCount is the number of output items + // we actually received, and we should update the bounding count with it. + // If we're here because ToList threw an exception, then itemCount will be 0, + // and we still need to update the bounding count with this in order to counteract + // the increased bounding count for the corresponding input. + if (isBounded) UpdateBoundingCountWithOutputCount(count: itemCount); + } + } + // else if the item isn't valid, the finally block will see itemCopy as null and output invalid + } + finally + { + // Tell the base reordering buffer that we're done. If we already output + // all of the data, itemCopy will be null, and we just pass down the invalid item. + // If we haven't, pass down the real thing. We do this even in the case of an exception, + // in which case this will be a dummy element. + _reorderingBuffer.AddItem(id, itemCopy, itemIsValid: itemCopy != null); + } + } + + /// + /// Stores the trusted enumerable en mass into the source core. + /// This method does not go through the reordering buffer. + /// + /// + private void StoreOutputItemsNonReorderedAtomic(IEnumerable outputItems) + { + Contract.Requires(_reorderingBuffer == null, "Expected not to have a reordering buffer"); + Contract.Requires(outputItems is TOutput[] || outputItems is List, "outputItems must be a list we've already vetted as trusted"); + if (_target.IsBounded) UpdateBoundingCountWithOutputCount(count: ((ICollection)outputItems).Count); + _source.AddMessages(outputItems); + } + + /// + /// Stores the untrusted enumerable into the source core. + /// This method does not go through the reordering buffer. + /// + /// The untrusted enumerable. + private void StoreOutputItemsNonReorderedWithIteration(IEnumerable outputItems) + { + // If we're bounding, we need to increment the bounded count + // for each individual item as we enumerate it. + if (_target.IsBounded) + { + // When the input item that generated this + // output was loaded, we incremented the bounding count. If it only + // output a single a item, then we don't need to touch the bounding count. + // Otherwise, we need to adjust the bounding count accordingly. + bool outputFirstItem = false; + try + { + foreach (TOutput item in outputItems) + { + if (outputFirstItem) _target.ChangeBoundingCount(count: 1); + else outputFirstItem = true; + _source.AddMessage(item); + } + } + finally + { + if (!outputFirstItem) _target.ChangeBoundingCount(count: -1); + } + } + // If we're not bounding, just output each individual item. + else + { + foreach (TOutput item in outputItems) _source.AddMessage(item); + } + } + + /// + /// Updates the bounding count based on the number of output items + /// generated for a single input. + /// + /// The number of output items. + private void UpdateBoundingCountWithOutputCount(int count) + { + // We already incremented the count for a single input item, and + // that input spawned 0 or more outputs. Take the input tracking + // into account when figuring out how much to increment or decrement + // the bounding count. + + Contract.Requires(_target.IsBounded, "Expected to be in bounding mode."); + if (count > 1) _target.ChangeBoundingCount(count - 1); + else if (count == 0) _target.ChangeBoundingCount(-1); + else Debug.Assert(count == 1, "Count shouldn't be negative."); + } + + /// + public void Complete() { _target.Complete(exception: null, dropPendingMessages: false); } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + _target.Complete(exception, dropPendingMessages: true); + } + + /// + public IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) { return _source.LinkTo(target, linkOptions); } + + /// + public Boolean TryReceive(Predicate filter, out TOutput item) { return _source.TryReceive(filter, out item); } + + /// + public bool TryReceiveAll(out IList items) { return _source.TryReceiveAll(out items); } + + /// + public Task Completion { get { return _source.Completion; } } + + /// + public int InputCount { get { return _target.InputCount; } } + + /// + public int OutputCount { get { return _source.OutputCount; } } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock source, Boolean consumeToAccept) + { + return _target.OfferMessage(messageHeader, messageValue, source, consumeToAccept); + } + + /// + TOutput ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + return _source.ConsumeMessage(messageHeader, target, out messageConsumed); + } + + /// + bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + return _source.ReserveMessage(messageHeader, target); + } + + /// + void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + _source.ReleaseReservation(messageHeader, target); + } + + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int InputCountForDebugger { get { return _target.GetDebuggingInformation().InputCount; } } + /// Gets the number of messages waiting to be processed. This must only be used from the debugger as it avoids taking necessary locks. + private int OutputCountForDebugger { get { return _source.GetDebuggingInformation().OutputCount; } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _source.DataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, InputCount={1}, OutputCount={2}", + Common.GetNameForDebugger(this, _source.DataflowBlockOptions), + InputCountForDebugger, + OutputCountForDebugger); + } + } + /// 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 the TransformManyBlock. + private sealed class DebugView + { + /// The transform many block being viewed. + private readonly TransformManyBlock _transformManyBlock; + /// The target half of the block being viewed. + private readonly TargetCore.DebuggingInformation _targetDebuggingInformation; + /// The source half of the block being viewed. + private readonly SourceCore.DebuggingInformation _sourceDebuggingInformation; + + /// Initializes the debug view. + /// The transform being viewed. + public DebugView(TransformManyBlock transformManyBlock) + { + Contract.Requires(transformManyBlock != null, "Need a block with which to construct the debug view."); + _transformManyBlock = transformManyBlock; + _targetDebuggingInformation = transformManyBlock._target.GetDebuggingInformation(); + _sourceDebuggingInformation = transformManyBlock._source.GetDebuggingInformation(); + } + + /// Gets the messages waiting to be processed. + public IEnumerable InputQueue { get { return _targetDebuggingInformation.InputQueue; } } + /// Gets any postponed messages. + public QueuedMap, DataflowMessageHeader> PostponedMessages { get { return _targetDebuggingInformation.PostponedMessages; } } + /// Gets the messages waiting to be received. + public IEnumerable OutputQueue { get { return _sourceDebuggingInformation.OutputQueue; } } + + /// Gets the number of input operations currently in flight. + public Int32 CurrentDegreeOfParallelism { get { return _targetDebuggingInformation.CurrentDegreeOfParallelism; } } + /// Gets the task being used for output processing. + public Task TaskForOutputProcessing { get { return _sourceDebuggingInformation.TaskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _targetDebuggingInformation.DataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + public bool IsDecliningPermanently { get { return _targetDebuggingInformation.IsDecliningPermanently; } } + /// Gets whether the block is completed. + public bool IsCompleted { get { return _sourceDebuggingInformation.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_transformManyBlock); } } + + /// Gets the set of all targets linked from this block. + public TargetRegistry LinkedTargets { get { return _sourceDebuggingInformation.LinkedTargets; } } + /// Gets the set of all targets linked from this block. + public ITargetBlock NextMessageReservedFor { get { return _sourceDebuggingInformation.NextMessageReservedFor; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/WriteOnceBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/WriteOnceBlock.cs new file mode 100644 index 00000000000..7b6fa7f9e5f --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Blocks/WriteOnceBlock.cs @@ -0,0 +1,568 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// WriteOnceBlock.cs +// +// +// A propagator block capable of receiving and storing only one message, ever. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Security; +using System.Threading.Tasks.Dataflow.Internal; + +namespace System.Threading.Tasks.Dataflow +{ + /// Provides a buffer for receiving and storing at most one element in a network of dataflow blocks. + /// Specifies the type of the data buffered by this dataflow block. + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + [DebuggerTypeProxy(typeof(WriteOnceBlock<>.DebugView))] + public sealed class WriteOnceBlock : IPropagatorBlock, IReceivableSourceBlock, IDebuggerDisplay + { + /// A registry used to store all linked targets and information about them. + private readonly TargetRegistry _targetRegistry; + /// The cloning function. + private readonly Func _cloningFunction; + /// The options used to configure this block's execution. + private readonly DataflowBlockOptions _dataflowBlockOptions; + /// Lazily initialized task completion source that produces the actual completion task when needed. + private TaskCompletionSource _lazyCompletionTaskSource; + /// Whether all future messages should be declined. + private bool _decliningPermanently; + /// Whether block completion is disallowed. + private bool _completionReserved; + /// The header of the singly-assigned value. + private DataflowMessageHeader _header; + /// The singly-assigned value. + private T _value; + + /// Gets the object used as the value lock. + private object ValueLock { get { return _targetRegistry; } } + + /// Initializes the . + /// + /// The function to use to clone the data when offered to other blocks. + /// This may be null to indicate that no cloning need be performed. + /// + public WriteOnceBlock(Func cloningFunction) : + this(cloningFunction, DataflowBlockOptions.Default) + { } + + /// Initializes the with the specified . + /// + /// The function to use to clone the data when offered to other blocks. + /// This may be null to indicate that no cloning need be performed. + /// + /// The options with which to configure this . + /// The is null (Nothing in Visual Basic). + public WriteOnceBlock(Func cloningFunction, DataflowBlockOptions dataflowBlockOptions) + { + // Validate arguments + if (dataflowBlockOptions == null) throw new ArgumentNullException("dataflowBlockOptions"); + Contract.EndContractBlock(); + + // Store the option + _cloningFunction = cloningFunction; + _dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); + + // The target registry also serves as our ValueLock, + // and thus must always be initialized, even if the block is pre-canceled, as + // subsequent usage of the block may run through code paths that try to take this lock. + _targetRegistry = new TargetRegistry(this); + + // If a cancelable CancellationToken has been passed in, + // we need to initialize the completion task's TCS now. + if (dataflowBlockOptions.CancellationToken.CanBeCanceled) + { + _lazyCompletionTaskSource = new TaskCompletionSource(); + + // If we've already had cancellation requested, do as little work as we have to + // in order to be done. + if (dataflowBlockOptions.CancellationToken.IsCancellationRequested) + { + _completionReserved = _decliningPermanently = true; + + // Cancel the completion task's TCS + _lazyCompletionTaskSource.SetCanceled(); + } + else + { + // Handle async cancellation requests by declining on the target + Common.WireCancellationToComplete( + dataflowBlockOptions.CancellationToken, _lazyCompletionTaskSource.Task, state => ((WriteOnceBlock)state).Complete(), this); + } + } +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCreated(this, dataflowBlockOptions); + } +#endif + } + + /// Asynchronously completes the block on another task. + /// + /// This must only be called once all of the completion conditions are met. + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + private void CompleteBlockAsync(IList exceptions) + { + Contract.Requires(_decliningPermanently, "We may get here only after we have started to decline permanently."); + Contract.Requires(_completionReserved, "We may get here only after we have reserved completion."); + Common.ContractAssertMonitorStatus(ValueLock, held: false); + + // If there is no exceptions list, we offer the message around, and then complete. + // If there is an exception list, we complete without offering the message. + if (exceptions == null) + { + // Offer the message to any linked targets and complete the block asynchronously to avoid blocking the caller + var taskForOutputProcessing = new Task(state => ((WriteOnceBlock)state).OfferToTargetsAndCompleteBlock(), this, + Common.GetCreationOptionsForTask()); + +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TaskLaunchedForMessageHandling( + this, taskForOutputProcessing, DataflowEtwProvider.TaskLaunchedReason.OfferingOutputMessages, _header.IsValid ? 1 : 0); + } +#endif + + // Start the task handling scheduling exceptions + Exception exception = Common.StartTaskSafe(taskForOutputProcessing, _dataflowBlockOptions.TaskScheduler); + if (exception != null) CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: true); + } + else + { + // Complete the block asynchronously to avoid blocking the caller + Task.Factory.StartNew(state => + { + Tuple, IList> blockAndList = (Tuple, IList>)state; + blockAndList.Item1.CompleteBlock(blockAndList.Item2); + }, + Tuple.Create(this, exceptions), CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + + /// Offers the message and completes the block. + /// + /// This is called only once. + /// + private void OfferToTargetsAndCompleteBlock() + { + // OfferToTargets calls to potentially multiple targets, each of which + // could be faulty and throw an exception. OfferToTargets creates a + // list of all such exceptions and returns it. + // If _value is null, OfferToTargets does nothing. + List exceptions = OfferToTargets(); + CompleteBlock(exceptions); + } + + /// Completes the block. + /// + /// This is called only once. + /// + private void CompleteBlock(IList exceptions) + { + // Do not invoke the CompletionTaskSource property if there is a chance that _lazyCompletionTaskSource + // has not been initialized yet and we may have to complete normally, because that would defeat the + // sole purpose of the TCS being lazily initialized. + + Contract.Requires(_lazyCompletionTaskSource == null || !_lazyCompletionTaskSource.Task.IsCompleted, "The task completion source must not be completed. This must be the only thread that ever completes the block."); + + // Save the linked list of targets so that it could be traversed later to propagate completion + TargetRegistry.LinkedTargetInfo linkedTargets = _targetRegistry.ClearEntryPoints(); + + // Complete the block's completion task + if (exceptions != null && exceptions.Count > 0) + { + CompletionTaskSource.TrySetException(exceptions); + } + else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested) + { + CompletionTaskSource.TrySetCanceled(); + } + else + { + // Safely try to initialize the completion task's TCS with a cached completed TCS. + // If our attempt succeeds (CompareExchange returns null), we have nothing more to do. + // If the completion task's TCS was already initialized (CompareExchange returns non-null), + // we have to complete that TCS instance. + if (Interlocked.CompareExchange(ref _lazyCompletionTaskSource, Common.CompletedVoidResultTaskCompletionSource, null) != null) + { + _lazyCompletionTaskSource.TrySetResult(default(VoidResult)); + } + } + + // Now that the completion task is completed, we may propagate completion to the linked targets + _targetRegistry.PropagateCompletion(linkedTargets); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCompleted(this); + } +#endif + } + + /// + void IDataflowBlock.Fault(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + Contract.EndContractBlock(); + + CompleteCore(exception, storeExceptionEvenIfAlreadyCompleting: false); + } + + /// + public void Complete() + { + CompleteCore(exception: null, storeExceptionEvenIfAlreadyCompleting: false); + } + + private void CompleteCore(Exception exception, bool storeExceptionEvenIfAlreadyCompleting) + { + Contract.Requires(exception != null || !storeExceptionEvenIfAlreadyCompleting, + "When storeExceptionEvenIfAlreadyCompleting is set to true, an exception must be provided."); + Contract.EndContractBlock(); + + bool thisThreadReservedCompletion = false; + lock (ValueLock) + { + // Faulting from outside is allowed until we start declining permanently + if (_decliningPermanently && !storeExceptionEvenIfAlreadyCompleting) return; + + // Decline further messages + _decliningPermanently = true; + + // Reserve Completion. + // If storeExceptionEvenIfAlreadyCompleting is true, we are here to fault the block, + // because we couldn't launch the offer-and-complete task. + // We have to retry to just complete. We do that by pretending completion wasn't reserved. + if (!_completionReserved || storeExceptionEvenIfAlreadyCompleting) thisThreadReservedCompletion = _completionReserved = true; + } + + // This call caused us to start declining further messages, + // there's nothing more this block needs to do... complete it if we just reserved completion. + if (thisThreadReservedCompletion) + { + List exceptions = null; + if (exception != null) + { + exceptions = new List(); + exceptions.Add(exception); + } + + CompleteBlockAsync(exceptions); + } + } + + /// + public Boolean TryReceive(Predicate filter, out T item) + { + // No need to take the outgoing lock, as we don't need to synchronize with other + // targets, and _value only ever goes from null to non-null, not the other way around. + + // If we have a value, give it up. All receives on a successfully + // completed WriteOnceBlock will return true, as long as the message + // passes the filter (all messages pass a null filter). + if (_header.IsValid && (filter == null || filter(_value))) + { + item = CloneItem(_value); + return true; + } + // Otherwise, nothing to receive + else + { + item = default(T); + return false; + } + } + + /// + Boolean IReceivableSourceBlock.TryReceiveAll(out IList items) + { + // Try to receive the one item this block may have. + // If we can, give back an array of one item. Otherwise, + // give back null. + T item; + if (TryReceive(null, out item)) + { + items = new T[] { item }; + return true; + } + else + { + items = null; + return false; + } + } + + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + public IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) + { + // Validate arguments + if (target == null) throw new ArgumentNullException("target"); + if (linkOptions == null) throw new ArgumentNullException("linkOptions"); + Contract.EndContractBlock(); + + bool hasValue; + bool isCompleted; + lock (ValueLock) + { + hasValue = HasValue; + isCompleted = _completionReserved; + + // If we haven't gotten a value yet and the block is not complete, add the target and bail + if (!hasValue && !isCompleted) + { + _targetRegistry.Add(ref target, linkOptions); + return Common.CreateUnlinker(ValueLock, _targetRegistry, target); + } + } + + // If we already have a value, send it along to the linking target + if (hasValue) + { + bool useCloning = _cloningFunction != null; + target.OfferMessage(_header, _value, this, consumeToAccept: useCloning); + } + + // If completion propagation has been requested, do it safely. + // The Completion property will ensure the lazy TCS is initialized. + if (linkOptions.PropagateCompletion) Common.PropagateCompletionOnceCompleted(Completion, target); + + return Disposables.Nop; + } + + /// + public Task Completion { get { return CompletionTaskSource.Task; } } + + /// + DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + bool thisThreadReservedCompletion = false; + lock (ValueLock) + { + // If we are declining messages, bail + if (_decliningPermanently) return DataflowMessageStatus.DecliningPermanently; + + // Consume the message from the source if necessary. We do this while holding ValueLock to prevent multiple concurrent + // offers from all succeeding. + if (consumeToAccept) + { + bool consumed; + messageValue = source.ConsumeMessage(messageHeader, this, out consumed); + if (!consumed) return DataflowMessageStatus.NotAvailable; + } + + // Update the header and the value + _header = Common.SingleMessageHeader; + _value = messageValue; + + // We got what we needed. Start declining permanently. + _decliningPermanently = true; + + // Reserve Completion + if (!_completionReserved) thisThreadReservedCompletion = _completionReserved = true; + } + + // Since this call to OfferMessage succeeded (and only one can ever), complete the block + // (but asynchronously so as not to block the Post call while offering to + // targets, running synchronous continuations off of the completion task, etc.) + if (thisThreadReservedCompletion) CompleteBlockAsync(exceptions: null); + return DataflowMessageStatus.Accepted; + } + + /// + T ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + // As long as the message being requested is the one we have, allow it to be consumed, + // but make a copy using the provided cloning function. + if (_header.Id == messageHeader.Id) + { + messageConsumed = true; + return CloneItem(_value); + } + else + { + messageConsumed = false; + return default(T); + } + } + + /// + Boolean ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + // As long as the message is the one we have, it can be "reserved." + // Reservations on a WriteOnceBlock are not exclusive, because + // everyone who wants a copy can get one. + return _header.Id == messageHeader.Id; + } + + /// + void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + // As long as the message is the one we have, everything's fine. + if (_header.Id != messageHeader.Id) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget); + + // In other blocks, upon release we typically re-offer the message to all linked targets. + // We need to do the same thing for WriteOnceBlock, in order to account for cases where the block + // may be linked to a join or similar block, such that the join could never again be satisfied + // if it didn't receive another offer from this source. However, since the message is broadcast + // and all targets can get a copy, we don't need to broadcast to all targets, only to + // the target that released the message. Note that we don't care whether it's accepted + // or not, nor do we care about any exceptions which may emerge (they should just propagate). + Debug.Assert(_header.IsValid, "A valid header is required."); + bool useCloning = _cloningFunction != null; + target.OfferMessage(_header, _value, this, consumeToAccept: useCloning); + } + + /// Clones the item. + /// The item to clone. + /// The cloned item. + private T CloneItem(T item) + { + return _cloningFunction != null ? + _cloningFunction(item) : + item; + } + + /// Offers the WriteOnceBlock's message to all targets. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private List OfferToTargets() + { + Common.ContractAssertMonitorStatus(ValueLock, held: false); + + // If there is a message, offer it to everyone. Return values + // don't matter, because we only get one message and then complete, + // and everyone who wants a copy can get a copy. + List exceptions = null; + if (HasValue) + { + TargetRegistry.LinkedTargetInfo cur = _targetRegistry.FirstTargetNode; + while (cur != null) + { + TargetRegistry.LinkedTargetInfo next = cur.Next; + ITargetBlock target = cur.Target; + try + { + // Offer the message. If there's a cloning function, we force the target to + // come back to us to consume the message, allowing us the opportunity to run + // the cloning function once we know they want the data. If there is no cloning + // function, there's no reason for them to call back here. + bool useCloning = _cloningFunction != null; + target.OfferMessage(_header, _value, this, consumeToAccept: useCloning); + } + catch (Exception exc) + { + // Track any erroneous exceptions that may occur + // and return them to the caller so that they may + // be logged in the completion task. + Common.StoreDataflowMessageValueIntoExceptionData(exc, _value); + Common.AddException(ref exceptions, exc); + } + cur = next; + } + } + return exceptions; + } + + /// Ensures the completion task's TCS is initialized. + /// The completion task's TCS. + private TaskCompletionSource CompletionTaskSource + { + get + { + // If the completion task's TCS has not been initialized by now, safely try to initialize it. + // It is very important that once a completion task/source instance has been handed out, + // it remains the block's completion task. + if (_lazyCompletionTaskSource == null) + { + Interlocked.CompareExchange(ref _lazyCompletionTaskSource, new TaskCompletionSource(), null); + } + + return _lazyCompletionTaskSource; + } + } + + /// Gets whether the block is storing a value. + private bool HasValue { get { return _header.IsValid; } } + /// Gets the value being stored by the block. + private T Value { get { return _header.IsValid ? _value : default(T); } } + + /// + public override string ToString() { return Common.GetNameForDebugger(this, _dataflowBlockOptions); } + + /// The data to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + return string.Format("{0}, HasValue={1}, Value={2}", + Common.GetNameForDebugger(this, _dataflowBlockOptions), HasValue, Value); + } + } + /// 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 WriteOnceBlock. + private sealed class DebugView + { + /// The WriteOnceBlock being viewed. + private readonly WriteOnceBlock _writeOnceBlock; + + /// Initializes the debug view. + /// The WriteOnceBlock to view. + public DebugView(WriteOnceBlock writeOnceBlock) + { + Contract.Requires(writeOnceBlock != null, "Need a block with which to construct the debug view."); + _writeOnceBlock = writeOnceBlock; + } + + /// Gets whether the WriteOnceBlock has completed. + public bool IsCompleted { get { return _writeOnceBlock.Completion.IsCompleted; } } + /// Gets the block's Id. + public int Id { get { return Common.GetBlockId(_writeOnceBlock); } } + + /// Gets whether the WriteOnceBlock has a value. + public bool HasValue { get { return _writeOnceBlock.HasValue; } } + /// Gets the WriteOnceBlock's value if it has one, or default(T) if it doesn't. + public T Value { get { return _writeOnceBlock.Value; } } + + /// Gets the DataflowBlockOptions used to configure this block. + public DataflowBlockOptions DataflowBlockOptions { get { return _writeOnceBlock._dataflowBlockOptions; } } + /// Gets the set of all targets linked from this block. + public TargetRegistry LinkedTargets { get { return _writeOnceBlock._targetRegistry; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ActionOnDispose.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ActionOnDispose.cs new file mode 100644 index 00000000000..5d8a905e4c8 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ActionOnDispose.cs @@ -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 +{ + /// Provider of disposables that run actions. + internal sealed class Disposables + { + /// An IDisposable that does nothing. + internal readonly static IDisposable Nop = new NopDisposable(); + + /// Creates an IDisposable that runs an action when disposed. + /// Specifies the type of the first argument. + /// Specifies the type of the second argument. + /// The action to invoke. + /// The first argument. + /// The second argument. + /// The created disposable. + internal static IDisposable Create(Action action, T1 arg1, T2 arg2) + { + Contract.Requires(action != null, "Non-null disposer action required."); + return new Disposable(action, arg1, arg2); + } + + /// Creates an IDisposable that runs an action when disposed. + /// Specifies the type of the first argument. + /// Specifies the type of the second argument. + /// Specifies the type of the third argument. + /// The action to invoke. + /// The first argument. + /// The second argument. + /// The third argument. + /// The created disposable. + internal static IDisposable Create(Action action, T1 arg1, T2 arg2, T3 arg3) + { + Contract.Requires(action != null, "Non-null disposer action required."); + return new Disposable(action, arg1, arg2, arg3); + } + + /// A disposable that's a nop. + [DebuggerDisplay("Disposed = true")] + private sealed class NopDisposable : IDisposable + { + void IDisposable.Dispose() { } + } + + /// An IDisposable that will run a delegate when disposed. + [DebuggerDisplay("Disposed = {Disposed}")] + private sealed class Disposable : IDisposable + { + /// First state argument. + private readonly T1 _arg1; + /// Second state argument. + private readonly T2 _arg2; + /// The action to run when disposed. Null if disposed. + private Action _action; + + /// Initializes the ActionOnDispose. + /// The action to run when disposed. + /// The first argument. + /// The second argument. + internal Disposable(Action action, T1 arg1, T2 arg2) + { + Contract.Requires(action != null, "Non-null action needed for disposable"); + _action = action; + _arg1 = arg1; + _arg2 = arg2; + } + + /// Gets whether the IDisposable has been disposed. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private bool Disposed { get { return _action == null; } } + + /// Invoke the action. + void IDisposable.Dispose() + { + Action toRun = _action; + if (toRun != null && + Interlocked.CompareExchange(ref _action, null, toRun) == toRun) + { + toRun(_arg1, _arg2); + } + } + } + + /// An IDisposable that will run a delegate when disposed. + [DebuggerDisplay("Disposed = {Disposed}")] + private sealed class Disposable : IDisposable + { + /// First state argument. + private readonly T1 _arg1; + /// Second state argument. + private readonly T2 _arg2; + /// Third state argument. + private readonly T3 _arg3; + /// The action to run when disposed. Null if disposed. + private Action _action; + + /// Initializes the ActionOnDispose. + /// The action to run when disposed. + /// The first argument. + /// The second argument. + /// The third argument. + internal Disposable(Action 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; + } + + /// Gets whether the IDisposable has been disposed. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private bool Disposed { get { return _action == null; } } + + /// Invoke the action. + void IDisposable.Dispose() + { + Action toRun = _action; + if (toRun != null && + Interlocked.CompareExchange(ref _action, null, toRun) == toRun) + { + toRun(_arg1, _arg2, _arg3); + } + } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Common.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Common.cs new file mode 100644 index 00000000000..274220631fc --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Common.cs @@ -0,0 +1,694 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// Common.cs +// +// +// Helper routines for the rest of the TPL Dataflow implementation. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Security; +using System.Collections; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks.Dataflow.Internal.Threading; + +namespace System.Threading.Tasks.Dataflow.Internal +{ + /// Internal helper utilities. + internal static class Common + { + /// + /// An invalid ID to assign for reordering purposes. This value is chosen to be the last of the 64-bit integers that + /// could ever be assigned as a reordering ID. + /// + internal const long INVALID_REORDERING_ID = -1; + /// A well-known message ID for code that will send exactly one message or + /// where the exact message ID is not important. + internal const int SINGLE_MESSAGE_ID = 1; + /// A perf optimization for caching a well-known message header instead of + /// constructing one every time it is needed. + internal static readonly DataflowMessageHeader SingleMessageHeader = new DataflowMessageHeader(SINGLE_MESSAGE_ID); + /// The cached completed Task{bool} with a result of true. + internal static readonly Task CompletedTaskWithTrueResult = CreateCachedBooleanTask(true); + /// The cached completed Task{bool} with a result of false. + internal static readonly Task CompletedTaskWithFalseResult = CreateCachedBooleanTask(false); + /// The cached completed TaskCompletionSource{VoidResult}. + internal static readonly TaskCompletionSource CompletedVoidResultTaskCompletionSource = CreateCachedTaskCompletionSource(); + + /// Asserts that a given synchronization object is either held or not held. + /// The monitor to check. + /// Whether we want to assert that it's currently held or not held. + [Conditional("DEBUG")] + internal static void ContractAssertMonitorStatus(object syncObj, bool held) + { + Contract.Requires(syncObj != null, "The monitor object to check must be provided."); + Debug.Assert(Monitor.IsEntered(syncObj) == held, "The locking scheme was not correctly followed."); + } + + /// Keeping alive processing tasks: maximum number of processed messages. + internal const int KEEP_ALIVE_NUMBER_OF_MESSAGES_THRESHOLD = 1; + /// Keeping alive processing tasks: do not attempt this many times. + internal const int KEEP_ALIVE_BAN_COUNT = 1000; + + /// A predicate type for TryKeepAliveUntil. + /// Input state for the predicate in order to avoid closure allocations. + /// Output state for the predicate in order to avoid closure allocations. + /// The state of the predicate. + internal delegate bool KeepAlivePredicate(TStateIn stateIn, out TStateOut stateOut); + + /// Actively waits for a predicate to become true. + /// The predicate to become true. + /// Input state for the predicate in order to avoid closure allocations. + /// Output state for the predicate in order to avoid closure allocations. + /// True if the predicate was evaluated and it returned true. False otherwise. + internal static bool TryKeepAliveUntil(KeepAlivePredicate predicate, + TStateIn stateIn, out TStateOut stateOut) + { + Contract.Requires(predicate != null, "Non-null predicate to execute is required."); + const int ITERATION_LIMIT = 16; + + for (int c = ITERATION_LIMIT; c > 0; c--) + { + if (!Thread.Yield()) + { + // There was no other thread waiting. + // We may spend some more cycles to evaluate the predicate. + if (predicate(stateIn, out stateOut)) return true; + } + } + + stateOut = default(TStateOut); + return false; + } + + /// Unwraps an instance T from object state that is a WeakReference to that instance. + /// The type of the data to be unwrapped. + /// The weak reference. + /// The T instance. + internal static T UnwrapWeakReference(object state) where T : class + { + var wr = state as WeakReference; + Debug.Assert(wr != null, "Expected a WeakReference as the state argument"); + T item; + return wr.TryGetTarget(out item) ? item : null; + } + + /// Gets an ID for the dataflow block. + /// The dataflow block. + /// An ID for the dataflow block. + internal static int GetBlockId(IDataflowBlock block) + { + Contract.Requires(block != null, "Block required to extract an Id."); + const int NOTASKID = 0; // tasks don't have 0 as ids + Task t = Common.GetPotentiallyNotSupportedCompletionTask(block); + return t != null ? t.Id : NOTASKID; + } + + /// Gets the name for the specified block, suitable to be rendered in a debugger window. + /// The block for which a name is needed. + /// + /// The options to use when rendering the name. If no options are provided, the block's name is used directly. + /// + /// The name of the object. + /// This is used from DebuggerDisplay attributes. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + internal static string GetNameForDebugger( + IDataflowBlock block, DataflowBlockOptions options = null) + { + Contract.Requires(block != null, "Should only be used with valid objects being displayed in the debugger."); + Contract.Requires(options == null || options.NameFormat != null, "If options are provided, NameFormat must be valid."); + + if (block == null) return string.Empty; + + string blockName = block.GetType().Name; + if (options == null) return blockName; + + // {0} == block name + // {1} == block id + int blockId = GetBlockId(block); + + // Since NameFormat is public, formatting may throw if the user has set + // a string that contains a reference to an argument higher than {1}. + // In the case of an exception, show the exception message. + try + { + return string.Format(options.NameFormat, blockName, blockId); + } + catch (Exception exception) + { + return exception.Message; + } + } + + /// + /// Gets whether the exception represents a cooperative cancellation acknowledgment. + /// + /// The exception to check. + /// true if this exception represents a cooperative cancellation acknowledgment; otherwise, false. + internal static bool IsCooperativeCancellation(Exception exception) + { + Contract.Requires(exception != null, "An exception to check for cancellation must be provided."); + return exception is OperationCanceledException; + // Note that the behavior of this method does not exactly match that of Parallel.*, PLINQ, and Task.Factory.StartNew, + // in that it's more liberal and treats any OCE as acknowledgment of cancellation; in contrast, the other + // libraries only treat OCEs as such if they contain the same token that was provided by the user + // and if that token has cancellation requested. Such logic could be achieved here with: + // var oce = exception as OperationCanceledException; + // return oce != null && + // oce.CancellationToken == dataflowBlockOptions.CancellationToken && + // oce.CancellationToken.IsCancellationRequested; + // However, that leads to a discrepancy with the async processing case of dataflow blocks, + // where tasks are returned to represent the message processing, potentially in the Canceled state, + // and we simply ignore such tasks. Further, for blocks like TransformBlock, it's useful to be able + // to cancel an individual operation which must return a TOutput value, simply by throwing an OperationCanceledException. + // In such cases, you wouldn't want cancellation tied to the token, because you would only be able to + // cancel an individual message processing if the whole block was canceled. + } + + /// Registers a block for cancellation by completing when cancellation is requested. + /// The block's cancellation token. + /// The task that will complete when the block is completely done processing. + /// An action that will decline permanently on the state passed to it. + /// The block on which to decline permanently. + internal static void WireCancellationToComplete( + CancellationToken cancellationToken, Task completionTask, Action completeAction, object completeState) + { + Contract.Requires(completionTask != null, "A task to wire up for completion is needed."); + Contract.Requires(completeAction != null, "An action to invoke upon cancellation is required."); + + // If a cancellation request has already occurred, just invoke the declining action synchronously. + // CancellationToken would do this anyway but we can short-circuit it further and avoid a bunch of unnecessary checks. + if (cancellationToken.IsCancellationRequested) + { + completeAction(completeState); + } + // Otherwise, if a cancellation request occurs, we want to prevent the block from accepting additional + // data, and we also want to dispose of that registration when we complete so that we don't + // leak into a long-living cancellation token. + else if (cancellationToken.CanBeCanceled) + { + CancellationTokenRegistration reg = cancellationToken.Register(completeAction, completeState); + completionTask.ContinueWith((completed, state) => ((CancellationTokenRegistration)state).Dispose(), + reg, cancellationToken, Common.GetContinuationOptions(), TaskScheduler.Default); + } + } + + /// Initializes the stack trace and watson bucket of an inactive exception. + /// The exception to initialize. + /// The initialized exception. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal static Exception InitializeStackTrace(Exception exception) + { + Contract.Requires(exception != null && exception.StackTrace == null, + "A valid but uninitialized exception should be provided."); + try { throw exception; } + catch { return exception; } + } + + /// The name of the key in an Exception's Data collection used to store information on a dataflow message. + internal const string EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE = "DataflowMessageValue"; // should not be localized + + /// Stores details on a dataflow message into an Exception's Data collection. + /// Specifies the type of data stored in the message. + /// The Exception whose Data collection should store message information. + /// The message information to be stored. + /// Whether to store the data into the exception's inner exception(s) in addition to the exception itself. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal static void StoreDataflowMessageValueIntoExceptionData(Exception exc, T messageValue, bool targetInnerExceptions = false) + { + Contract.Requires(exc != null, "The exception into which data should be stored must be provided."); + + // Get the string value to store + string strValue = messageValue as string; + if (strValue == null && messageValue != null) + { + try + { + strValue = messageValue.ToString(); + } + catch { /* It's ok to eat all exceptions here. If ToString throws, we'll just ignore it. */ } + } + if (strValue == null) return; + + // Store the data into the exception itself + StoreStringIntoExceptionData(exc, Common.EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE, strValue); + + // If we also want to target inner exceptions... + if (targetInnerExceptions) + { + // If this is an aggregate, store into all inner exceptions. + var aggregate = exc as AggregateException; + if (aggregate != null) + { + foreach (Exception innerException in aggregate.InnerExceptions) + { + StoreStringIntoExceptionData(innerException, Common.EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE, strValue); + } + } + // Otherwise, if there's an Exception.InnerException, store into that. + else if (exc.InnerException != null) + { + StoreStringIntoExceptionData(exc.InnerException, Common.EXCEPTIONDATAKEY_DATAFLOWMESSAGEVALUE, strValue); + } + } + } + + /// Stores the specified string value into the specified key slot of the specified exception's data dictionary. + /// The exception into which the key/value should be stored. + /// The key. + /// The value to be serialized as a string and stored. + /// If the key is already present in the exception's data dictionary, the value is not overwritten. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private static void StoreStringIntoExceptionData(Exception exception, string key, string value) + { + Contract.Requires(exception != null, "An exception is needed to store the data into."); + Contract.Requires(key != null, "A key into the exception's data collection is needed."); + Contract.Requires(value != null, "The value to store must be provided."); + try + { + IDictionary data = exception.Data; + if (data != null && !data.IsFixedSize && !data.IsReadOnly && data[key] == null) + { + data[key] = value; + } + } + catch + { + // It's ok to eat all exceptions here. This could throw if an Exception type + // has overridden Data to behave differently than we expect. + } + } + + /// Throws an exception asynchronously on the thread pool. + /// The exception to throw. + /// + /// This function is used when an exception needs to be propagated from a thread + /// other than the current context. This could happen, for example, if the exception + /// should cause the standard CLR exception escalation behavior, but we're inside + /// of a task that will squirrel the exception away. + /// + internal static void ThrowAsync(Exception error) + { + ExceptionDispatchInfo edi = ExceptionDispatchInfo.Capture(error); + ThreadPool.QueueUserWorkItem(state => { ((ExceptionDispatchInfo)state).Throw(); }, edi); + } + + /// Adds the exception to the list, first initializing the list if the list is null. + /// The list to add the exception to, and initialize if null. + /// The exception to add or whose inner exception(s) should be added. + /// Unwrap and add the inner exception(s) rather than the specified exception directly. + /// This method is not thread-safe, in that it manipulates without any synchronization. + internal static void AddException(ref List list, Exception exception, bool unwrapInnerExceptions = false) + { + Contract.Requires(exception != null, "An exception to add is required."); + Contract.Requires(!unwrapInnerExceptions || exception.InnerException != null, + "If unwrapping is requested, an inner exception is required."); + + // Make sure the list of exceptions is initialized (lazily). + if (list == null) list = new List(); + + if (unwrapInnerExceptions) + { + AggregateException aggregate = exception as AggregateException; + if (aggregate != null) + { + list.AddRange(aggregate.InnerExceptions); + } + else + { + list.Add(exception.InnerException); + } + } + else list.Add(exception); + } + + /// Creates a task we can cache for the desired Boolean result. + /// The value of the Boolean. + /// A task that may be cached. + private static Task CreateCachedBooleanTask(bool value) + { + // AsyncTaskMethodBuilder caches tasks that are non-disposable. + // By using these same tasks, we're a bit more robust against disposals, + // in that such a disposed task's ((IAsyncResult)task).AsyncWaitHandle + // is still valid. + var atmb = System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create(); + atmb.SetResult(value); + return atmb.Task; // must be accessed after SetResult to get the cached task + } + + /// Creates a TaskCompletionSource{T} completed with a value of default(T) that we can cache. + /// Completed TaskCompletionSource{T} that may be cached. + private static TaskCompletionSource CreateCachedTaskCompletionSource() + { + var tcs = new TaskCompletionSource(); + tcs.SetResult(default(T)); + return tcs; + } + + /// Creates a task faulted with the specified exception. + /// Specifies the type of the result for this task. + /// The exception with which to complete the task. + /// The faulted task. + internal static Task CreateTaskFromException(Exception exception) + { + var atmb = System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create(); + atmb.SetException(exception); + return atmb.Task; + } + + /// Creates a task canceled with the specified cancellation token. + /// Specifies the type of the result for this task. + /// The canceled task. + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + internal static Task CreateTaskFromCancellation(CancellationToken cancellationToken) + { + Contract.Requires(cancellationToken.IsCancellationRequested, + "The task will only be immediately canceled if the token has cancellation requested already."); + var t = new Task(CachedGenericDelegates.DefaultTResultFunc, cancellationToken); + Debug.Assert(t.IsCanceled, "Task's constructor should cancel the task synchronously in the ctor."); + return t; + } + + /// Gets the completion task of a block, and protects against common cases of the completion task not being implemented or supported. + /// The block. + /// The completion task, or null if the block's completion task is not implemented or supported. + internal static Task GetPotentiallyNotSupportedCompletionTask(IDataflowBlock block) + { + Contract.Requires(block != null, "We need a block from which to retrieve a cancellation task."); + try + { + return block.Completion; + } + catch (NotImplementedException) { } + catch (NotSupportedException) { } + return null; + } + + /// + /// Creates an IDisposable that, when disposed, will acquire the outgoing lock while removing + /// the target block from the target registry. + /// + /// Specifies the type of data in the block. + /// The outgoing lock used to protect the target registry. + /// The target registry from which the target should be removed. + /// The target to remove from the registry. + /// An IDisposable that will unregister the target block from the registry while holding the outgoing lock. + internal static IDisposable CreateUnlinker(object outgoingLock, TargetRegistry targetRegistry, ITargetBlock targetBlock) + { + Contract.Requires(outgoingLock != null, "Monitor object needed to protect the operation."); + Contract.Requires(targetRegistry != null, "Registry from which to remove is required."); + Contract.Requires(targetBlock != null, "Target block to unlink is required."); + return Disposables.Create(CachedGenericDelegates.CreateUnlinkerShimAction, + outgoingLock, targetRegistry, targetBlock); + } + + /// An infinite TimeSpan. + internal static readonly TimeSpan InfiniteTimeSpan = Timeout.InfiniteTimeSpan; + + /// Validates that a timeout either is -1 or is non-negative and within the range of an Int32. + /// The timeout to validate. + /// true if the timeout is valid; otherwise, false. + internal static bool IsValidTimeout(TimeSpan timeout) + { + long millisecondsTimeout = (long)timeout.TotalMilliseconds; + return millisecondsTimeout >= Timeout.Infinite && millisecondsTimeout <= Int32.MaxValue; + } + + /// Gets the options to use for continuation tasks. + /// Any options to include in the result. + /// The options to use. + internal static TaskContinuationOptions GetContinuationOptions(TaskContinuationOptions toInclude = TaskContinuationOptions.None) + { + return toInclude | TaskContinuationOptions.DenyChildAttach; + } + + /// Gets the options to use for tasks. + /// If this task is being created to replace another. + /// + /// These options should be used for all tasks that have the potential to run user code or + /// that are repeatedly spawned and thus need a modicum of fair treatment. + /// + /// The options to use. + internal static TaskCreationOptions GetCreationOptionsForTask(bool isReplacementReplica = false) + { + TaskCreationOptions options = TaskCreationOptions.DenyChildAttach; + if (isReplacementReplica) options |= TaskCreationOptions.PreferFairness; + return options; + } + + /// Starts an already constructed task with handling and observing exceptions that may come from the scheduling process. + /// Task to be started. + /// TaskScheduler to schedule the task on. + /// null on success, an exception reference on scheduling error. In the latter case, the task reference is nulled out. + internal static Exception StartTaskSafe(Task task, TaskScheduler scheduler) + { + Contract.Requires(task != null, "Task to start is required."); + Contract.Requires(scheduler != null, "Scheduler on which to start the task is required."); + + if (scheduler == TaskScheduler.Default) + { + task.Start(scheduler); + return null; // We don't need to worry about scheduler exceptions from the default scheduler. + } + // Slow path with try/catch separated out so that StartTaskSafe may be inlined in the common case. + else return StartTaskSafeCore(task, scheduler); + } + + /// Starts an already constructed task with handling and observing exceptions that may come from the scheduling process. + /// Task to be started. + /// TaskScheduler to schedule the task on. + /// null on success, an exception reference on scheduling error. In the latter case, the task reference is nulled out. + [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals")] + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private static Exception StartTaskSafeCore(Task task, TaskScheduler scheduler) + { + Contract.Requires(task != null, "Task to start is needed."); + Contract.Requires(scheduler != null, "Scheduler on which to start the task is required."); + + Exception schedulingException = null; + + try + { + task.Start(scheduler); + } + catch (Exception caughtException) + { + // Verify TPL has faulted the task + Debug.Assert(task.IsFaulted, "The task should have been faulted if it failed to start."); + + // Observe the task's exception + AggregateException ignoredTaskException = task.Exception; + + schedulingException = caughtException; + } + + return schedulingException; + } + + /// Pops and explicitly releases postponed messages after the block is done with processing. + /// No locks should be held at this time. Unfortunately we cannot assert that. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal static void ReleaseAllPostponedMessages(ITargetBlock target, + QueuedMap, DataflowMessageHeader> postponedMessages, + ref List exceptions) + { + Contract.Requires(target != null, "There must be a subject target."); + Contract.Requires(postponedMessages != null, "The stacked map of postponed messages must exist."); + + // Note that we don't synchronize on lockObject for postponedMessages here, + // because no one should be adding to it at this time. We do a bit of + // checking just for sanity's sake. + int initialCount = postponedMessages.Count; + int processedCount = 0; + + KeyValuePair, DataflowMessageHeader> sourceAndMessage; + while (postponedMessages.TryPop(out sourceAndMessage)) + { + // Loop through all postponed messages declining each messages. + // The only way we have to do this is by reserving and then immediately releasing each message. + // This is important for sources like SendAsyncSource, which keep state around until + // they get a response to a postponed message. + try + { + Debug.Assert(sourceAndMessage.Key != null, "Postponed messages must have an associated source."); + if (sourceAndMessage.Key.ReserveMessage(sourceAndMessage.Value, target)) + { + sourceAndMessage.Key.ReleaseReservation(sourceAndMessage.Value, target); + } + } + catch (Exception exc) + { + Common.AddException(ref exceptions, exc); + } + + processedCount++; + } + + Debug.Assert(processedCount == initialCount, + "We should have processed the exact number of elements that were initially there."); + } + + /// Cache ThrowAsync to avoid allocations when it is passed into PropagateCompletionXxx. + internal static readonly Action AsyncExceptionHandler = ThrowAsync; + + /// + /// Propagates completion of sourceCompletionTask to target synchronously. + /// + /// The task whose completion is to be propagated. It must be completed. + /// The block where completion is propagated. + /// Handler for exceptions from the target. May be null which would propagate the exception to the caller. + internal static void PropagateCompletion(Task sourceCompletionTask, IDataflowBlock target, Action exceptionHandler) + { + Contract.Requires(sourceCompletionTask != null, "sourceCompletionTask may not be null."); + Contract.Requires(target != null, "The target where completion is to be propagated may not be null."); + Debug.Assert(sourceCompletionTask.IsCompleted, "sourceCompletionTask must be completed in order to propagate its completion."); + + AggregateException exception = sourceCompletionTask.IsFaulted ? sourceCompletionTask.Exception : null; + + try + { + if (exception != null) target.Fault(exception); + else target.Complete(); + } + catch (Exception exc) + { + if (exceptionHandler != null) exceptionHandler(exc); + else throw; + } + } + + /// + /// Creates a continuation off sourceCompletionTask to complete target. See PropagateCompletion. + /// + private static void PropagateCompletionAsContinuation(Task sourceCompletionTask, IDataflowBlock target) + { + Contract.Requires(sourceCompletionTask != null, "sourceCompletionTask may not be null."); + Contract.Requires(target != null, "The target where completion is to be propagated may not be null."); + sourceCompletionTask.ContinueWith((task, state) => Common.PropagateCompletion(task, (IDataflowBlock)state, AsyncExceptionHandler), + target, CancellationToken.None, Common.GetContinuationOptions(), TaskScheduler.Default); + } + + /// + /// Propagates completion of sourceCompletionTask to target based on sourceCompletionTask's current state. See PropagateCompletion. + /// + internal static void PropagateCompletionOnceCompleted(Task sourceCompletionTask, IDataflowBlock target) + { + Contract.Requires(sourceCompletionTask != null, "sourceCompletionTask may not be null."); + Contract.Requires(target != null, "The target where completion is to be propagated may not be null."); + + // If sourceCompletionTask is completed, propagate completion synchronously. + // Otherwise hook up a continuation. + if (sourceCompletionTask.IsCompleted) PropagateCompletion(sourceCompletionTask, target, exceptionHandler: null); + else PropagateCompletionAsContinuation(sourceCompletionTask, target); + } + + /// Static class used to cache generic delegates the C# compiler doesn't cache by default. + /// Without this, we end up allocating the generic delegate each time the operation is used. + static class CachedGenericDelegates + { + /// A function that returns the default value of T. + internal readonly static Func DefaultTResultFunc = () => default(T); + /// + /// A function to use as the body of ActionOnDispose in CreateUnlinkerShim. + /// Passed a tuple of the sync obj, the target registry, and the target block as the state parameter. + /// + internal readonly static Action, ITargetBlock> CreateUnlinkerShimAction = + (syncObj, registry, target) => + { + lock (syncObj) registry.Remove(target); + }; + } + } + + /// State used only when bounding. + [DebuggerDisplay("BoundedCapacity={BoundedCapacity}}")] + internal class BoundingState + { + /// The maximum number of messages allowed to be buffered. + internal readonly int BoundedCapacity; + /// The number of messages currently stored. + /// + /// This value may temporarily be higher than the actual number stored. + /// That's ok, we just can't accept any new messages if CurrentCount >= BoundedCapacity. + /// Worst case is that we may temporarily have fewer items in the block than our maximum allows, + /// but we'll never have more. + /// + internal int CurrentCount; + + /// Initializes the BoundingState. + /// The positive bounded capacity. + internal BoundingState(int boundedCapacity) + { + Contract.Requires(boundedCapacity > 0, "Bounded is only supported with positive values."); + BoundedCapacity = boundedCapacity; + } + + /// Gets whether there's room available to add another message. + internal bool CountIsLessThanBound { get { return CurrentCount < BoundedCapacity; } } + } + + /// Stated used only when bounding and when postponed messages are stored. + /// Specifies the type of input messages. + [DebuggerDisplay("BoundedCapacity={BoundedCapacity}, PostponedMessages={PostponedMessagesCountForDebugger}")] + internal class BoundingStateWithPostponed : BoundingState + { + /// Queue of postponed messages. + internal readonly QueuedMap, DataflowMessageHeader> PostponedMessages = + new QueuedMap, DataflowMessageHeader>(); + /// + /// The number of transfers from the postponement queue to the input queue currently being processed. + /// + /// + /// Blocks that use TargetCore need to transfer messages from the postponed queue to the input messages + /// queue. While doing that, new incoming messages may arrive, and if they view the postponed queue + /// as being empty (after the block has removed the last postponed message and is consuming it, before + /// storing it into the input queue), they might go directly into the input queue... that will then mess + /// up the ordering between those postponed messages and the newly incoming messages. To address that, + /// OutstandingTransfers is used to track the number of transfers currently in progress. Incoming + /// messages must be postponed not only if there are already any postponed messages, but also if + /// there are any transfers in progress (i.e. this value is > 0). It's an integer because the DOP could + /// be greater than 1, and thus we need to ref count multiple transfers that might be in progress. + /// + internal int OutstandingTransfers; + + /// Initializes the BoundingState. + /// The positive bounded capacity. + internal BoundingStateWithPostponed(int boundedCapacity) : base(boundedCapacity) + { + } + + /// Gets the number of postponed messages for the debugger. + private int PostponedMessagesCountForDebugger { get { return PostponedMessages.Count; } } + } + + /// Stated used only when bounding and when postponed messages and a task are stored. + /// Specifies the type of input messages. + internal class BoundingStateWithPostponedAndTask : BoundingStateWithPostponed + { + /// The task used to process messages. + internal Task TaskForInputProcessing; + + /// Initializes the BoundingState. + /// The positive bounded capacity. + internal BoundingStateWithPostponedAndTask(int boundedCapacity) : base(boundedCapacity) + { + } + } + + /// + /// Type used with TaskCompletionSource(Of TResult) as the TResult + /// to ensure that the resulting task can't be upcast to something + /// that in the future could lead to compat problems. + /// + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] + [DebuggerNonUserCode] + internal struct VoidResult { } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ConcurrentQueue.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ConcurrentQueue.cs new file mode 100644 index 00000000000..33b137292f2 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ConcurrentQueue.cs @@ -0,0 +1,947 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +#pragma warning disable 0420 + + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// ConcurrentQueue.cs +// +// +// A lock-free, concurrent queue primitive, and its associated debugger view type. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security; +using System.Threading; + +namespace System.Threading.Tasks.Dataflow.Internal.Collections +{ + /// + /// Represents a thread-safe first-in, first-out collection of objects. + /// + /// Specifies the type of elements in the queue. + /// + /// All public and protected members of are thread-safe and may be used + /// concurrently from multiple threads. + /// + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView<>))] + internal class ConcurrentQueue : IProducerConsumerCollection + { + //fields of ConcurrentQueue + private volatile Segment _head; + + private volatile Segment _tail; + + private T[] _serializationArray; // Used for custom serialization. + + private const int SEGMENT_SIZE = 32; + + //number of snapshot takers, GetEnumerator(), ToList() and ToArray() operations take snapshot. + internal volatile int _numSnapshotTakers = 0; + + /// + /// Initializes a new instance of the class. + /// + public ConcurrentQueue() + { + _head = _tail = new Segment(0, this); + } + + /// + /// Initializes the contents of the queue from an existing collection. + /// + /// A collection from which to copy elements. + private void InitializeFromCollection(IEnumerable collection) + { + Segment localTail = new Segment(0, this);//use this local variable to avoid the extra volatile read/write. this is safe because it is only called from ctor + _head = localTail; + + int index = 0; + foreach (T element in collection) + { + Debug.Assert(index >= 0 && index < SEGMENT_SIZE); + localTail.UnsafeAdd(element); + index++; + + if (index >= SEGMENT_SIZE) + { + localTail = localTail.UnsafeGrow(); + index = 0; + } + } + + _tail = localTail; + } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified collection + /// + /// The collection whose elements are copied to the new . + /// The argument is + /// null. + public ConcurrentQueue(IEnumerable collection) + { + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + + InitializeFromCollection(collection); + } + + /// + /// Get the data array to be serialized + /// + [OnSerializing] + private void OnSerializing(StreamingContext context) + { + // save the data into the serialization array to be saved + _serializationArray = ToArray(); + } + + /// + /// Construct the queue from a previously serialized one + /// + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + Debug.Assert(_serializationArray != null); + InitializeFromCollection(_serializationArray); + _serializationArray = null; + } + + /// + /// Copies the elements of the to an , starting at a particular + /// index. + /// + /// The one-dimensional Array that is the + /// destination of the elements copied from the + /// . The Array must have zero-based indexing. + /// The zero-based index in at which copying + /// begins. + /// is a null reference (Nothing in + /// Visual Basic). + /// is less than + /// zero. + /// + /// is multidimensional. -or- + /// does not have zero-based indexing. -or- + /// is equal to or greater than the length of the + /// -or- The number of elements in the source is + /// greater than the available space from to the end of the destination + /// . -or- The type of the source cannot be cast automatically to the type of the + /// destination . + /// + void ICollection.CopyTo(Array array, int index) + { + // Validate arguments. + if (array == null) + { + throw new ArgumentNullException("array"); + } + + // We must be careful not to corrupt the array, so we will first accumulate an + // internal list of elements that we will then copy to the array. This requires + // some extra allocation, but is necessary since we don't know up front whether + // the array is sufficiently large to hold the stack's contents. + ((ICollection)ToList()).CopyTo(array, index); + } + + /// + /// Gets a value indicating whether access to the is + /// synchronized with the SyncRoot. + /// + /// true if access to the is synchronized + /// with the SyncRoot; otherwise, false. For , this property always + /// returns false. + bool ICollection.IsSynchronized + { + // Gets a value indicating whether access to this collection is synchronized. Always returns + // false. The reason is subtle. While access is in face thread safe, it's not the case that + // locking on the SyncRoot would have prevented concurrent pushes and pops, as this property + // would typically indicate; that's because we internally use CAS operations vs. true locks. + get { return false; } + } + + + /// + /// Gets an object that can be used to synchronize access to the . This property is not supported. + /// + /// The SyncRoot property is not supported. + object ICollection.SyncRoot + { + get + { + throw new NotSupportedException(SR.ConcurrentCollection_SyncRoot_NotSupported); + } + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// An that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this).GetEnumerator(); + } + + /// + /// Attempts to add an object to the . + /// + /// The object to add to the . The value can be a null + /// reference (Nothing in Visual Basic) for reference types. + /// + /// true if the object was added successfully; otherwise, false. + /// For , this operation will always add the object to the + /// end of the + /// and return true. + bool IProducerConsumerCollection.TryAdd(T item) + { + Enqueue(item); + return true; + } + + /// + /// Attempts to remove and return an object from the . + /// + /// + /// When this method returns, if the operation was successful, contains the + /// object removed. If no object was available to be removed, the value is unspecified. + /// + /// true if an element was removed and returned successfully; otherwise, false. + /// For , this operation will attempt to remove the object + /// from the beginning of the . + /// + bool IProducerConsumerCollection.TryTake(out T item) + { + return TryDequeue(out item); + } + + /// + /// Gets a value that indicates whether the is empty. + /// + /// true if the is empty; otherwise, false. + /// + /// For determining whether the collection contains any items, use of this property is recommended + /// rather than retrieving the number of items from the property and comparing it + /// to 0. However, as this collection is intended to be accessed concurrently, it may be the case + /// that another thread will modify the collection after returns, thus invalidating + /// the result. + /// + public bool IsEmpty + { + get + { + Segment head = _head; + if (!head.IsEmpty) + //fast route 1: + //if current head is not empty, then queue is not empty + return false; + else if (head.Next == null) + //fast route 2: + //if current head is empty and it's the last segment + //then queue is empty + return true; + else + //slow route: + //current head is empty and it is NOT the last segment, + //it means another thread is growing new segment + { + SpinWait spin = new SpinWait(); + while (head.IsEmpty) + { + if (head.Next == null) + return true; + + spin.SpinOnce(); + head = _head; + } + return false; + } + } + } + + /// + /// Copies the elements stored in the to a new array. + /// + /// A new array containing a snapshot of elements copied from the . + public T[] ToArray() + { + return ToList().ToArray(); + } + + /// + /// Copies the elements to a new . + /// + /// A new containing a snapshot of + /// elements copied from the . + private List ToList() + { + // Increments the number of active snapshot takers. This increment must happen before the snapshot is + // taken. At the same time, Decrement must happen after list copying is over. Only in this way, can it + // eliminate race condition when Segment.TryRemove() checks whether _numSnapshotTakers == 0. + Interlocked.Increment(ref _numSnapshotTakers); + + List list = new List(); + try + { + //store head and tail positions in buffer, + Segment head, tail; + int headLow, tailHigh; + GetHeadTailPositions(out head, out tail, out headLow, out tailHigh); + + if (head == tail) + { + head.AddToList(list, headLow, tailHigh); + } + else + { + head.AddToList(list, headLow, SEGMENT_SIZE - 1); + Segment curr = head.Next; + while (curr != tail) + { + curr.AddToList(list, 0, SEGMENT_SIZE - 1); + curr = curr.Next; + } + //Add tail segment + tail.AddToList(list, 0, tailHigh); + } + } + finally + { + // This Decrement must happen after copying is over. + Interlocked.Decrement(ref _numSnapshotTakers); + } + return list; + } + + /// + /// Store the position of the current head and tail positions. + /// + /// return the head segment + /// return the tail segment + /// return the head offset, value range [0, SEGMENT_SIZE] + /// return the tail offset, value range [-1, SEGMENT_SIZE-1] + private void GetHeadTailPositions(out Segment head, out Segment tail, + out int headLow, out int tailHigh) + { + head = _head; + tail = _tail; + headLow = head.Low; + tailHigh = tail.High; + SpinWait spin = new SpinWait(); + + //we loop until the observed values are stable and sensible. + //This ensures that any update order by other methods can be tolerated. + while ( + //if head and tail changed, retry + head != _head || tail != _tail + //if low and high pointers, retry + || headLow != head.Low || tailHigh != tail.High + //if head jumps ahead of tail because of concurrent grow and dequeue, retry + || head._index > tail._index) + { + spin.SpinOnce(); + head = _head; + tail = _tail; + headLow = head.Low; + tailHigh = tail.High; + } + } + + + /// + /// Gets the number of elements contained in the . + /// + /// The number of elements contained in the . + /// + /// For determining whether the collection contains any items, use of the + /// property is recommended rather than retrieving the number of items from the + /// property and comparing it to 0. + /// + public int Count + { + get + { + //store head and tail positions in buffer, + Segment head, tail; + int headLow, tailHigh; + GetHeadTailPositions(out head, out tail, out headLow, out tailHigh); + + if (head == tail) + { + return tailHigh - headLow + 1; + } + + //head segment + int count = SEGMENT_SIZE - headLow; + + //middle segment(s), if any, are full. + //We don't deal with overflow to be consistent with the behavior of generic types in CLR. + count += SEGMENT_SIZE * ((int)(tail._index - head._index - 1)); + + //tail segment + count += tailHigh + 1; + + return count; + } + } + + + /// + /// Copies the elements to an existing one-dimensional Array, starting at the specified array index. + /// + /// The one-dimensional Array that is the + /// destination of the elements copied from the + /// . The Array must have zero-based + /// indexing. + /// The zero-based index in at which copying + /// begins. + /// is a null reference (Nothing in + /// Visual Basic). + /// is less than + /// zero. + /// is equal to or greater than the + /// length of the + /// -or- The number of elements in the source is greater than the + /// available space from to the end of the destination . + /// + public void CopyTo(T[] array, int index) + { + if (array == null) + { + throw new ArgumentNullException("array"); + } + + // We must be careful not to corrupt the array, so we will first accumulate an + // internal list of elements that we will then copy to the array. This requires + // some extra allocation, but is necessary since we don't know up front whether + // the array is sufficiently large to hold the stack's contents. + ToList().CopyTo(array, index); + } + + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator for the contents of the . + /// + /// The enumeration represents a moment-in-time snapshot of the contents + /// of the queue. It does not reflect any updates to the collection after + /// was called. The enumerator is safe to use + /// concurrently with reads from and writes to the queue. + /// + public IEnumerator GetEnumerator() + { + // Increments the number of active snapshot takers. This increment must happen before the snapshot is + // taken. At the same time, Decrement must happen after the enumeration is over. Only in this way, can it + // eliminate race condition when Segment.TryRemove() checks whether _numSnapshotTakers == 0. + Interlocked.Increment(ref _numSnapshotTakers); + + // Takes a snapshot of the queue. + // A design flaw here: if a Thread.Abort() happens, we cannot decrement _numSnapshotTakers. But we cannot + // wrap the following with a try/finally block, otherwise the decrement will happen before the yield return + // statements in the GetEnumerator (head, tail, headLow, tailHigh) method. + Segment head, tail; + int headLow, tailHigh; + GetHeadTailPositions(out head, out tail, out headLow, out tailHigh); + + //If we put yield-return here, the iterator will be lazily evaluated. As a result a snapshot of + // the queue is not taken when GetEnumerator is initialized but when MoveNext() is first called. + // This is inconsistent with existing generic collections. In order to prevent it, we capture the + // value of _head in a buffer and call out to a helper method. + //The old way of doing this was to return the ToList().GetEnumerator(), but ToList() was an + // unnecessary performance hit. + return GetEnumerator(head, tail, headLow, tailHigh); + } + + /// + /// Helper method of GetEnumerator to separate out yield return statement, and prevent lazy evaluation. + /// + private IEnumerator GetEnumerator(Segment head, Segment tail, int headLow, int tailHigh) + { + try + { + SpinWait spin = new SpinWait(); + + if (head == tail) + { + for (int i = headLow; i <= tailHigh; i++) + { + // If the position is reserved by an Enqueue operation, but the value is not written into, + // spin until the value is available. + spin.Reset(); + while (!head._state[i]._value) + { + spin.SpinOnce(); + } + yield return head._array[i]; + } + } + else + { + //iterate on head segment + for (int i = headLow; i < SEGMENT_SIZE; i++) + { + // If the position is reserved by an Enqueue operation, but the value is not written into, + // spin until the value is available. + spin.Reset(); + while (!head._state[i]._value) + { + spin.SpinOnce(); + } + yield return head._array[i]; + } + //iterate on middle segments + Segment curr = head.Next; + while (curr != tail) + { + for (int i = 0; i < SEGMENT_SIZE; i++) + { + // If the position is reserved by an Enqueue operation, but the value is not written into, + // spin until the value is available. + spin.Reset(); + while (!curr._state[i]._value) + { + spin.SpinOnce(); + } + yield return curr._array[i]; + } + curr = curr.Next; + } + + //iterate on tail segment + for (int i = 0; i <= tailHigh; i++) + { + // If the position is reserved by an Enqueue operation, but the value is not written into, + // spin until the value is available. + spin.Reset(); + while (!tail._state[i]._value) + { + spin.SpinOnce(); + } + yield return tail._array[i]; + } + } + } + finally + { + // This Decrement must happen after the enumeration is over. + Interlocked.Decrement(ref _numSnapshotTakers); + } + } + + /// + /// Adds an object to the end of the . + /// + /// The object to add to the end of the . The value can be a null reference + /// (Nothing in Visual Basic) for reference types. + /// + public void Enqueue(T item) + { + SpinWait spin = new SpinWait(); + while (true) + { + Segment tail = _tail; + if (tail.TryAppend(item)) + return; + spin.SpinOnce(); + } + } + + + /// + /// Attempts to remove and return the object at the beginning of the . + /// + /// + /// When this method returns, if the operation was successful, contains the + /// object removed. If no object was available to be removed, the value is unspecified. + /// + /// true if an element was removed and returned from the beginning of the + /// successfully; otherwise, false. + public bool TryDequeue(out T result) + { + while (!IsEmpty) + { + Segment head = _head; + if (head.TryRemove(out result)) + return true; + //since method IsEmpty spins, we don't need to spin in the while loop + } + result = default(T); + return false; + } + + /// + /// Attempts to return an object from the beginning of the + /// without removing it. + /// + /// When this method returns, contains an object from + /// the beginning of the or an + /// unspecified value if the operation failed. + /// true if and object was returned successfully; otherwise, false. + public bool TryPeek(out T result) + { + Interlocked.Increment(ref _numSnapshotTakers); + + while (!IsEmpty) + { + Segment head = _head; + if (head.TryPeek(out result)) + { + Interlocked.Decrement(ref _numSnapshotTakers); + return true; + } + //since method IsEmpty spins, we don't need to spin in the while loop + } + result = default(T); + Interlocked.Decrement(ref _numSnapshotTakers); + return false; + } + + + /// + /// private class for ConcurrentQueue. + /// a queue is a linked list of small arrays, each node is called a segment. + /// A segment contains an array, a pointer to the next segment, and _low, _high indices recording + /// the first and last valid elements of the array. + /// + private class Segment + { + //we define two volatile arrays: _array and _state. Note that the accesses to the array items + //do not get volatile treatment. But we don't need to worry about loading adjacent elements or + //store/load on adjacent elements would suffer reordering. + // - Two stores: these are at risk, but CLRv2 memory model guarantees store-release hence we are safe. + // - Two loads: because one item from two volatile arrays are accessed, the loads of the array references + // are sufficient to prevent reordering of the loads of the elements. + internal volatile T[] _array; + + // For each entry in _array, the corresponding entry in _state indicates whether this position contains + // a valid value. _state is initially all false. + internal volatile VolatileBool[] _state; + + //pointer to the next segment. null if the current segment is the last segment + private volatile Segment _next; + + //We use this zero based index to track how many segments have been created for the queue, and + //to compute how many active segments are there currently. + // * The number of currently active segments is : _tail._index - _head._index + 1; + // * _index is incremented with every Segment.Grow operation. We use Int64 type, and we can safely + // assume that it never overflows. To overflow, we need to do 2^63 increments, even at a rate of 4 + // billion (2^32) increments per second, it takes 2^31 seconds, which is about 64 years. + internal readonly long _index; + + //indices of where the first and last valid values + // - _low points to the position of the next element to pop from this segment, range [0, infinity) + // _low >= SEGMENT_SIZE implies the segment is disposable + // - _high points to the position of the latest pushed element, range [-1, infinity) + // _high == -1 implies the segment is new and empty + // _high >= SEGMENT_SIZE-1 means this segment is ready to grow. + // and the thread who sets _high to SEGMENT_SIZE-1 is responsible to grow the segment + // - Math.Min(_low, SEGMENT_SIZE) > Math.Min(_high, SEGMENT_SIZE-1) implies segment is empty + // - initially _low =0 and _high=-1; + private volatile int _low; + private volatile int _high; + + private volatile ConcurrentQueue _source; + + /// + /// Create and initialize a segment with the specified index. + /// + internal Segment(long index, ConcurrentQueue source) + { + _array = new T[SEGMENT_SIZE]; + _state = new VolatileBool[SEGMENT_SIZE]; //all initialized to false + _high = -1; + Debug.Assert(index >= 0); + _index = index; + _source = source; + } + + /// + /// return the next segment + /// + internal Segment Next + { + get { return _next; } + } + + + /// + /// return true if the current segment is empty (doesn't have any element available to dequeue, + /// false otherwise + /// + internal bool IsEmpty + { + get { return (Low > High); } + } + + /// + /// Add an element to the tail of the current segment + /// exclusively called by ConcurrentQueue.InitializedFromCollection + /// InitializeFromCollection is responsible to guarantee that there is no index overflow, + /// and there is no contention + /// + /// + internal void UnsafeAdd(T value) + { + Debug.Assert(_high < SEGMENT_SIZE - 1); + _high++; + _array[_high] = value; + _state[_high]._value = true; + } + + /// + /// Create a new segment and append to the current one + /// Does not update the _tail pointer + /// exclusively called by ConcurrentQueue.InitializedFromCollection + /// InitializeFromCollection is responsible to guarantee that there is no index overflow, + /// and there is no contention + /// + /// the reference to the new Segment + internal Segment UnsafeGrow() + { + Debug.Assert(_high >= SEGMENT_SIZE - 1); + Segment newSegment = new Segment(_index + 1, _source); //_index is Int64, we don't need to worry about overflow + _next = newSegment; + return newSegment; + } + + /// + /// Create a new segment and append to the current one + /// Update the _tail pointer + /// This method is called when there is no contention + /// + internal void Grow() + { + //no CAS is needed, since there is no contention (other threads are blocked, busy waiting) + Segment newSegment = new Segment(_index + 1, _source); //_index is Int64, we don't need to worry about overflow + _next = newSegment; + Debug.Assert(_source._tail == this); + _source._tail = _next; + } + + + /// + /// Try to append an element at the end of this segment. + /// + /// the element to append + /// true if the element is appended, false if the current segment is full + /// if appending the specified element succeeds, and after which the segment is full, + /// then grow the segment + internal bool TryAppend(T value) + { + //quickly check if _high is already over the boundary, if so, bail out + if (_high >= SEGMENT_SIZE - 1) + { + return false; + } + + //Now we will use a CAS to increment _high, and store the result in newhigh. + //Depending on how many free spots left in this segment and how many threads are doing this Increment + //at this time, the returning "newhigh" can be + // 1) < SEGMENT_SIZE - 1 : we took a spot in this segment, and not the last one, just insert the value + // 2) == SEGMENT_SIZE - 1 : we took the last spot, insert the value AND grow the segment + // 3) > SEGMENT_SIZE - 1 : we failed to reserve a spot in this segment, we return false to + // Queue.Enqueue method, telling it to try again in the next segment. + + int newhigh = SEGMENT_SIZE; //initial value set to be over the boundary + + //We need do Interlocked.Increment and value/state update in a finally block to ensure that they run + //without interuption. This is to prevent anything from happening between them, and another dequeue + //thread maybe spinning forever to wait for _state[] to be true; + try + { } + finally + { + newhigh = Interlocked.Increment(ref _high); + if (newhigh <= SEGMENT_SIZE - 1) + { + _array[newhigh] = value; + _state[newhigh]._value = true; + } + + //if this thread takes up the last slot in the segment, then this thread is responsible + //to grow a new segment. Calling Grow must be in the finally block too for reliability reason: + //if thread abort during Grow, other threads will be left busy spinning forever. + if (newhigh == SEGMENT_SIZE - 1) + { + Grow(); + } + } + + //if newhigh <= SEGMENT_SIZE-1, it means the current thread successfully takes up a spot + return newhigh <= SEGMENT_SIZE - 1; + } + + + /// + /// try to remove an element from the head of current segment + /// + /// The result. + /// return false only if the current segment is empty + internal bool TryRemove(out T result) + { + SpinWait spin = new SpinWait(); + int lowLocal = Low, highLocal = High; + while (lowLocal <= highLocal) + { + //try to update _low + if (Interlocked.CompareExchange(ref _low, lowLocal + 1, lowLocal) == lowLocal) + { + //if the specified value is not available (this spot is taken by a push operation, + // but the value is not written into yet), then spin + SpinWait spinLocal = new SpinWait(); + while (!_state[lowLocal]._value) + { + spinLocal.SpinOnce(); + } + result = _array[lowLocal]; + + // If there is no other thread taking snapshot (GetEnumerator(), ToList(), etc), reset the deleted entry to null. + // It is ok if after this conditional check _numSnapshotTakers becomes > 0, because new snapshots won't include + // the deleted entry at _array[lowLocal]. + if (_source._numSnapshotTakers <= 0) + { + _array[lowLocal] = default(T); //release the reference to the object. + } + + //if the current thread sets _low to SEGMENT_SIZE, which means the current segment becomes + //disposable, then this thread is responsible to dispose this segment, and reset _head + if (lowLocal + 1 >= SEGMENT_SIZE) + { + // Invariant: we only dispose the current _head, not any other segment + // In usual situation, disposing a segment is simply setting _head to _head._next + // But there is one special case, where _head and _tail points to the same and ONLY + //segment of the queue: Another thread A is doing Enqueue and finds that it needs to grow, + //while the *current* thread is doing *this* Dequeue operation, and finds that it needs to + //dispose the current (and ONLY) segment. Then we need to wait till thread A finishes its + //Grow operation, this is the reason of having the following while loop + spinLocal = new SpinWait(); + while (_next == null) + { + spinLocal.SpinOnce(); + } + Debug.Assert(_source._head == this); + _source._head = _next; + } + return true; + } + else + { + //CAS failed due to contention: spin briefly and retry + spin.SpinOnce(); + lowLocal = Low; highLocal = High; + } + }//end of while + result = default(T); + return false; + } + + /// + /// try to peek the current segment + /// + /// holds the return value of the element at the head position, + /// value set to default(T) if there is no such an element + /// true if there are elements in the current segment, false otherwise + internal bool TryPeek(out T result) + { + result = default(T); + int lowLocal = Low; + if (lowLocal > High) + return false; + SpinWait spin = new SpinWait(); + while (!_state[lowLocal]._value) + { + spin.SpinOnce(); + } + result = _array[lowLocal]; + return true; + } + + /// + /// Adds part or all of the current segment into a List. + /// + /// the list to which to add + /// the start position + /// the end position + internal void AddToList(List list, int start, int end) + { + for (int i = start; i <= end; i++) + { + SpinWait spin = new SpinWait(); + while (!_state[i]._value) + { + spin.SpinOnce(); + } + list.Add(_array[i]); + } + } + + /// + /// return the position of the head of the current segment + /// Value range [0, SEGMENT_SIZE], if it's SEGMENT_SIZE, it means this segment is exhausted and thus empty + /// + internal int Low + { + get + { + return Math.Min(_low, SEGMENT_SIZE); + } + } + + /// + /// return the logical position of the tail of the current segment + /// Value range [-1, SEGMENT_SIZE-1]. When it's -1, it means this is a new segment and has no elemnet yet + /// + internal int High + { + get + { + //if _high > SEGMENT_SIZE, it means it's out of range, we should return + //SEGMENT_SIZE-1 as the logical position + return Math.Min(_high, SEGMENT_SIZE - 1); + } + } + } + }//end of class Segment + + /// + /// A wrapper struct for volatile bool, please note the copy of the struct it self will not be volatile + /// for example this statement will not include in volatile operation volatileBool1 = volatileBool2 the jit will copy the struct and will ignore the volatile + /// + struct VolatileBool + { + public VolatileBool(bool value) + { + _value = value; + } + public volatile bool _value; + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/DataflowEtwProvider.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/DataflowEtwProvider.cs new file mode 100644 index 00000000000..1befecd0877 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/DataflowEtwProvider.cs @@ -0,0 +1,235 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// DataflowEtwProvider.cs +// +// +// EventSource for Dataflow. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Security; +#if FEATURE_TRACING +using System.Diagnostics.Tracing; +#endif + +namespace System.Threading.Tasks.Dataflow.Internal +{ +#if FEATURE_TRACING + /// Provides an event source for tracing Dataflow information. + [EventSource( + Name = "System.Threading.Tasks.Dataflow.DataflowEventSource", + Guid = "16F53577-E41D-43D4-B47E-C17025BF4025", + LocalizationResources = "FxResources.System.Threading.Tasks.Dataflow.SR")] + internal sealed class DataflowEtwProvider : EventSource + { + /// + /// Defines the singleton instance for the dataflow ETW provider. + /// The dataflow provider GUID is {16F53577-E41D-43D4-B47E-C17025BF4025}. + /// + internal readonly static DataflowEtwProvider Log = new DataflowEtwProvider(); + /// Prevent external instantiation. All logging should go through the Log instance. + private DataflowEtwProvider() { } + + /// Enabled for all keywords. + private const EventKeywords ALL_KEYWORDS = (EventKeywords)(-1); + + //----------------------------------------------------------------------------------- + // + // Dataflow Event IDs (must be unique) + // + + /// The event ID for when we encounter a new dataflow block object that hasn't had its name traced to the trace file. + private const int DATAFLOWBLOCKCREATED_EVENTID = 1; + /// The event ID for the task launched event. + private const int TASKLAUNCHED_EVENTID = 2; + /// The event ID for the block completed event. + private const int BLOCKCOMPLETED_EVENTID = 3; + /// The event ID for the block linked event. + private const int BLOCKLINKED_EVENTID = 4; + /// The event ID for the block unlinked event. + private const int BLOCKUNLINKED_EVENTID = 5; + + //----------------------------------------------------------------------------------- + // + // Dataflow Events + // + + #region Block Creation + /// Trace an event for when a new block is instantiated. + /// The dataflow block that was created. + /// The options with which the block was created. + [NonEvent] + internal void DataflowBlockCreated(IDataflowBlock block, DataflowBlockOptions dataflowBlockOptions) + { + Contract.Requires(block != null, "Block needed for the ETW event."); + Contract.Requires(dataflowBlockOptions != null, "Options needed for the ETW event."); + + if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) + { + DataflowBlockCreated( + Common.GetNameForDebugger(block, dataflowBlockOptions), + Common.GetBlockId(block)); + } + } + + [Event(DATAFLOWBLOCKCREATED_EVENTID, Level = EventLevel.Informational)] + private void DataflowBlockCreated(string blockName, int blockId) + { + WriteEvent(DATAFLOWBLOCKCREATED_EVENTID, blockName, blockId); + } + #endregion + + #region Task Launching + /// Trace an event for a block launching a task to handle messages. + /// The owner block launching a task. + /// The task being launched for processing. + /// The reason the task is being launched. + /// The number of messages available to be handled by the task. + [NonEvent] + internal void TaskLaunchedForMessageHandling( + IDataflowBlock block, Task task, TaskLaunchedReason reason, int availableMessages) + { + Contract.Requires(block != null, "Block needed for the ETW event."); + Contract.Requires(task != null, "Task needed for the ETW event."); + Contract.Requires(reason == TaskLaunchedReason.ProcessingInputMessages || reason == TaskLaunchedReason.OfferingOutputMessages, + "The reason should be a supported value from the TaskLaunchedReason enumeration."); + if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) + { + TaskLaunchedForMessageHandling(Common.GetBlockId(block), reason, availableMessages, task.Id); + } + } + + [ThreadStatic] + private static object[] t_sharedArray; + + [Event(TASKLAUNCHED_EVENTID, Level = EventLevel.Informational)] + private void TaskLaunchedForMessageHandling(int blockId, TaskLaunchedReason reason, int availableMessages, int taskId) + { + // There is no explicit WriteEvent() overload matching this event's fields: + // WriteEvent(TASKLAUNCHED_EVENTID, blockId, (int)reason, availableMessages, taskId); + // Therefore this call would hit the "params" overload, which leads to multiple object + // allocations every time this event is fired. + + if (t_sharedArray == null) + { + t_sharedArray = new object[4]; + } + t_sharedArray[0] = blockId; + t_sharedArray[1] = (int)reason; + t_sharedArray[2] = availableMessages; + t_sharedArray[3] = taskId; + + WriteEvent(TASKLAUNCHED_EVENTID, t_sharedArray); + } + + /// Describes the reason a task is being launched. + internal enum TaskLaunchedReason + { + /// A task is being launched to process incoming messages. + ProcessingInputMessages = 1, + /// A task is being launched to offer outgoing messages to linked targets. + OfferingOutputMessages = 2, + } + #endregion + + #region Block Completion + /// Trace an event for a block completing. + /// The block that's completing. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [NonEvent] + internal void DataflowBlockCompleted(IDataflowBlock block) + { + Contract.Requires(block != null, "Block needed for the ETW event."); + if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) + { + Task completionTask = Common.GetPotentiallyNotSupportedCompletionTask(block); + bool blockIsCompleted = completionTask != null && completionTask.IsCompleted; + Debug.Assert(blockIsCompleted, "Block must be completed for this event to be valid."); + if (blockIsCompleted) + { + var reason = (BlockCompletionReason)completionTask.Status; + string exceptionData = string.Empty; + + if (completionTask.IsFaulted) + { + try { exceptionData = string.Join(Environment.NewLine, completionTask.Exception.InnerExceptions.Select(e => e.ToString())); } + catch { } + } + + DataflowBlockCompleted(Common.GetBlockId(block), reason, exceptionData); + } + } + } + + /// Describes the reason a block completed. + internal enum BlockCompletionReason + { + /// The block completed successfully. + RanToCompletion = (int)TaskStatus.RanToCompletion, + /// The block completed due to an error. + Faulted = (int)TaskStatus.Faulted, + /// The block completed due to cancellation. + Canceled = (int)TaskStatus.Canceled + } + + [Event(BLOCKCOMPLETED_EVENTID, Level = EventLevel.Informational)] + private void DataflowBlockCompleted(int blockId, BlockCompletionReason reason, string exceptionData) + { + WriteEvent(BLOCKCOMPLETED_EVENTID, blockId, (int)reason, exceptionData); + } + #endregion + + #region Linking + /// Trace an event for a block linking. + /// The source block linking to a target. + /// The target block being linked from a source. + [NonEvent] + internal void DataflowBlockLinking(ISourceBlock source, ITargetBlock target) + { + Contract.Requires(source != null, "Source needed for the ETW event."); + Contract.Requires(target != null, "Target needed for the ETW event."); + if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) + { + DataflowBlockLinking(Common.GetBlockId(source), Common.GetBlockId(target)); + } + } + + [Event(BLOCKLINKED_EVENTID, Level = EventLevel.Informational)] + private void DataflowBlockLinking(int sourceId, int targetId) + { + WriteEvent(BLOCKLINKED_EVENTID, sourceId, targetId); + } + #endregion + + #region Unlinking + /// Trace an event for a block unlinking. + /// The source block unlinking from a target. + /// The target block being unlinked from a source. + [NonEvent] + internal void DataflowBlockUnlinking(ISourceBlock source, ITargetBlock target) + { + Contract.Requires(source != null, "Source needed for the ETW event."); + Contract.Requires(target != null, "Target needed for the ETW event."); + if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) + { + // Try catch exists to prevent against faulty blocks or blocks that only partially implement the interface + DataflowBlockUnlinking(Common.GetBlockId(source), Common.GetBlockId(target)); + } + } + + [Event(BLOCKUNLINKED_EVENTID, Level = EventLevel.Informational)] + private void DataflowBlockUnlinking(int sourceId, int targetId) + { + WriteEvent(BLOCKUNLINKED_EVENTID, sourceId, targetId); + } + #endregion + } +#endif +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/EnumerableDebugView.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/EnumerableDebugView.cs new file mode 100644 index 00000000000..4bcabb3ee41 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/EnumerableDebugView.cs @@ -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 +{ + /// Debugger type proxy for an enumerable of T. + internal sealed class EnumerableDebugView + { + /// The enumerable being visualized. + private readonly IEnumerable> _enumerable; + + /// Initializes the debug view. + /// The enumerable being debugged. + public EnumerableDebugView(IEnumerable> enumerable) + { + Contract.Requires(enumerable != null, "Expected a non-null enumerable."); + _enumerable = enumerable; + } + + /// Gets the contents of the list. + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair[] Items { get { return _enumerable.ToArray(); } } + } + + /// Debugger type proxy for an enumerable of T. + internal sealed class EnumerableDebugView + { + /// The enumerable being visualized. + private readonly IEnumerable _enumerable; + + /// Initializes the debug view. + /// The enumerable being debugged. + public EnumerableDebugView(IEnumerable enumerable) + { + Contract.Requires(enumerable != null, "Expected a non-null enumerable."); + _enumerable = enumerable; + } + + /// Gets the contents of the list. + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items { get { return _enumerable.ToArray(); } } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/IDebuggerDisplay.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/IDebuggerDisplay.cs new file mode 100644 index 00000000000..4b7118af719 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/IDebuggerDisplay.cs @@ -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 +{ + /// Implemented to provide customizable data for debugger displays. + internal interface IDebuggerDisplay + { + /// The object to be displayed as the content of a DebuggerDisplayAttribute. + /// + /// 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. + /// + object Content { get; } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/IProducerConsumerCollection.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/IProducerConsumerCollection.cs new file mode 100644 index 00000000000..2ca643a5ab4 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/IProducerConsumerCollection.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// IProducerConsumerCollection.cs +// +// +// A common interface for all concurrent collections. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Dataflow.Internal.Collections +{ + /// + /// Defines methods to manipulate thread-safe collections intended for producer/consumer usage. + /// + /// Specifies the type of elements in the collection. + /// + /// All implementations of this interface must enable all members of this interface + /// to be used concurrently from multiple threads. + /// + internal interface IProducerConsumerCollection : IEnumerable, ICollection + { + /// + /// Copies the elements of the to + /// an + /// , starting at a specified index. + /// + /// The one-dimensional that is the destination of + /// the elements copied from the . + /// The array must have zero-based indexing. + /// The zero-based index in at which copying + /// begins. + /// is a null reference (Nothing in + /// Visual Basic). + /// is less than + /// zero. + /// is equal to or greater than the + /// length of the + /// -or- The number of elements in the source is greater than the + /// available space from to the end of the destination . + /// + void CopyTo(T[] array, int index); + + /// + /// Attempts to add an object to the . + /// + /// The object to add to the . + /// true if the object was added successfully; otherwise, false. + /// The was invalid for this collection. + bool TryAdd(T item); + + /// + /// Attempts to remove and return an object from the . + /// + /// + /// When this method returns, if the object was removed and returned successfully, contains the removed object. If no object was available to be removed, the value is + /// unspecified. + /// + /// true if an object was removed and returned successfully; otherwise, false. + bool TryTake(out T item); + + /// + /// Copies the elements contained in the to a new array. + /// + /// A new array containing the elements copied from the . + T[] ToArray(); + } + + + /// + /// A debugger view of the IProducerConsumerCollection that makes it simple to browse the + /// collection's contents at a point in time. + /// + /// The type of elements stored within. + internal sealed class SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView + { + private IProducerConsumerCollection _collection; // The collection being viewed. + + /// + /// Constructs a new debugger view object for the provided collection object. + /// + /// A collection to browse in the debugger. + public SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView(IProducerConsumerCollection collection) + { + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + + _collection = collection; + } + + /// + /// Returns a snapshot of the underlying collection's elements. + /// + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get { return _collection.ToArray(); } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ImmutableList.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ImmutableList.cs new file mode 100644 index 00000000000..93943f72430 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ImmutableList.cs @@ -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 +{ + /// Provides a simple, immutable list. + /// Specifies the type of the data stored in the list. + [DebuggerDisplay("Count={Count}")] + [DebuggerTypeProxy(typeof(EnumerableDebugView<>))] + internal sealed class ImmutableList : IEnumerable + { + /// An empty list. + private readonly static ImmutableList _empty = new ImmutableList(); + /// The immutable data in this list instance. + private readonly T[] _array; + + /// Gets the empty list. + public static ImmutableList Empty { get { return _empty; } } + + /// Initializes the immutable list to be empty. + private ImmutableList() : this(new T[0]) { } + + /// Initializes the immutable list with the specified elements. + /// The element array to use for this list's data. + private ImmutableList(T[] elements) + { + Contract.Requires(elements != null, "List requires an array to wrap."); + _array = elements; + } + + /// Creates a new immutable list from this list and the additional element. + /// The item to add. + /// The new list. + public ImmutableList 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(newArray); + } + + /// Creates a new immutable list from this list and without the specified element. + /// The item to remove. + /// The new list. + public ImmutableList 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(newArray); + } + + /// Gets the number of elements in this list. + public int Count { get { return _array.Length; } } + + /// Gets whether the list contains the specified item. + /// The item to lookup. + /// true if the list contains the item; otherwise, false. + public bool Contains(T item) { return Array.IndexOf(_array, item) >= 0; } + + /// Returns an enumerator that iterates through the collection. + public IEnumerator GetEnumerator() { return ((IEnumerable)_array).GetEnumerator(); } + /// Returns an enumerator that iterates through the collection. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Padding.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Padding.cs new file mode 100644 index 00000000000..9786e14bd0c --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Padding.cs @@ -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 +{ + /// A placeholder class for common padding constants and eventually routines. + internal static class Padding + { + /// A size greater than or equal to the size of the most common CPU cache lines. + internal const int CACHE_LINE_SIZE = 128; + } + + /// Padding structure used to minimize false sharing in SingleProducerSingleConsumerQueue{T}. + [StructLayout(LayoutKind.Explicit, Size = Padding.CACHE_LINE_SIZE - sizeof(Int32))] // Based on common case of 64-byte cache lines + internal struct PaddingForInt32 + { + } + + /// Value type that contains single Int64 value padded on both sides. + [StructLayout(LayoutKind.Explicit, Size = 2 * Padding.CACHE_LINE_SIZE)] + internal struct PaddedInt64 + { + [FieldOffset(Padding.CACHE_LINE_SIZE)] + internal Int64 Value; + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ProducerConsumerQueues.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ProducerConsumerQueues.cs new file mode 100644 index 00000000000..4f2fbf5186d --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ProducerConsumerQueues.cs @@ -0,0 +1,558 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// ProducerConsumerQueues.cs +// +// +// Specialized producer/consumer queues. +// +// +// ************************* +// +// There are two exact copies of this file: +// src\ndp\clr\src\bcl\system\threading\tasks\producerConsumerQueue.cs +// src\ndp\fx\src\dataflow\system\threading\tasks\dataflow\internal\producerConsumerQueue.cs +// Keep both of them consistent by changing the other file when you change this one, also avoid: +// 1- To reference internal types in mscorlib +// 2- To reference any dataflow specific types +// This should be fixed post Dev11 when this class becomes public. +// +// ************************* +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections; +#if CONCURRENT_COLLECTIONS +using System.Collections.Concurrent; +#else +using System.Threading.Tasks.Dataflow.Internal.Collections; +#endif +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; + +namespace System.Threading.Tasks +{ + /// Represents a producer/consumer queue used internally by dataflow blocks. + /// Specifies the type of data contained in the queue. + internal interface IProducerConsumerQueue : IEnumerable + { + /// Enqueues an item into the queue. + /// The item to enqueue. + /// This method is meant to be thread-safe subject to the particular nature of the implementation. + void Enqueue(T item); + + /// Attempts to dequeue an item from the queue. + /// The dequeued item. + /// true if an item could be dequeued; otherwise, false. + /// This method is meant to be thread-safe subject to the particular nature of the implementation. + bool TryDequeue(out T result); + + /// Gets whether the collection is currently empty. + /// This method may or may not be thread-safe. + bool IsEmpty { get; } + + /// Gets the number of items in the collection. + /// In many implementations, this method will not be thread-safe. + int Count { get; } + + /// A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object. + /// The sync object used to lock + /// The collection count + int GetCountSafe(object syncObj); + } + + /// + /// Provides a producer/consumer queue safe to be used by any number of producers and consumers concurrently. + /// + /// Specifies the type of data contained in the queue. + [DebuggerDisplay("Count = {Count}")] + internal sealed class MultiProducerMultiConsumerQueue : ConcurrentQueue, IProducerConsumerQueue + { + /// Enqueues an item into the queue. + /// The item to enqueue. + void IProducerConsumerQueue.Enqueue(T item) { base.Enqueue(item); } + + /// Attempts to dequeue an item from the queue. + /// The dequeued item. + /// true if an item could be dequeued; otherwise, false. + bool IProducerConsumerQueue.TryDequeue(out T result) { return base.TryDequeue(out result); } + + /// Gets whether the collection is currently empty. + bool IProducerConsumerQueue.IsEmpty { get { return base.IsEmpty; } } + + /// Gets the number of items in the collection. + int IProducerConsumerQueue.Count { get { return base.Count; } } + + /// A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object. + /// ConcurrentQueue.Count is thread safe, no need to acquire the lock. + int IProducerConsumerQueue.GetCountSafe(object syncObj) { return base.Count; } + } + + /// + /// Provides a producer/consumer queue safe to be used by only one producer and one consumer concurrently. + /// + /// Specifies the type of data contained in the queue. + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(SingleProducerSingleConsumerQueue<>.SingleProducerSingleConsumerQueue_DebugView))] + internal sealed class SingleProducerSingleConsumerQueue : IProducerConsumerQueue + { + // Design: + // + // SingleProducerSingleConsumerQueue (SPSCQueue) is a concurrent queue designed to be used + // by one producer thread and one consumer thread. SPSCQueue does not work correctly when used by + // multiple producer threads concurrently or multiple consumer threads concurrently. + // + // SPSCQueue is based on segments that behave like circular buffers. Each circular buffer is represented + // as an array with two indexes: _first and _last. _first is the index of the array slot for the consumer + // to read next, and _last is the slot for the producer to write next. The circular buffer is empty when + // (_first == _last), and full when ((_last+1) % _array.Length == _first). + // + // Since _first is only ever modified by the consumer thread and _last by the producer, the two indices can + // be updated without interlocked operations. As long as the queue size fits inside a single circular buffer, + // enqueues and dequeues simply advance the corresponding indices around the circular buffer. If an enqueue finds + // that there is no room in the existing buffer, however, a new circular buffer is allocated that is twice as big + // as the old buffer. From then on, the producer will insert values into the new buffer. The consumer will first + // empty out the old buffer and only then follow the producer into the new (larger) buffer. + // + // As described above, the enqueue operation on the fast path only modifies the _first field of the current segment. + // However, it also needs to read _last in order to verify that there is room in the current segment. Similarly, the + // dequeue operation on the fast path only needs to modify _last, but also needs to read _first to verify that the + // queue is non-empty. This results in true cache line sharing between the producer and the consumer. + // + // The cache line sharing issue can be mitigating by having a possibly stale copy of _first that is owned by the producer, + // and a possibly stale copy of _last that is owned by the consumer. So, the consumer state is described using + // (_first, _lastCopy) and the producer state using (_firstCopy, _last). The consumer state is separated from + // the producer state by padding, which allows fast-path enqueues and dequeues from hitting shared cache lines. + // _lastCopy is the consumer's copy of _last. Whenever the consumer can tell that there is room in the buffer + // simply by observing _lastCopy, the consumer thread does not need to read _last and thus encounter a cache miss. Only + // when the buffer appears to be empty will the consumer refresh _lastCopy from _last. _firstCopy is used by the producer + // in the same way to avoid reading _first on the hot path. + + /// The initial size to use for segments (in number of elements). + private const int INIT_SEGMENT_SIZE = 32; // must be a power of 2 + /// The maximum size to use for segments (in number of elements). + private const int MAX_SEGMENT_SIZE = 0x1000000; // this could be made as large as Int32.MaxValue / 2 + + /// The head of the linked list of segments. + private volatile Segment _head; + /// The tail of the linked list of segments. + private volatile Segment _tail; + + /// Initializes the queue. + internal SingleProducerSingleConsumerQueue() + { + // Validate constants in ctor rather than in an explicit cctor that would cause perf degradation + Debug.Assert(INIT_SEGMENT_SIZE > 0, "Initial segment size must be > 0."); + Debug.Assert((INIT_SEGMENT_SIZE & (INIT_SEGMENT_SIZE - 1)) == 0, "Initial segment size must be a power of 2"); + Debug.Assert(INIT_SEGMENT_SIZE <= MAX_SEGMENT_SIZE, "Initial segment size should be <= maximum."); + Debug.Assert(MAX_SEGMENT_SIZE < Int32.MaxValue / 2, "Max segment size * 2 must be < Int32.MaxValue, or else overflow could occur."); + + // Initialize the queue + _head = _tail = new Segment(INIT_SEGMENT_SIZE); + } + + /// Enqueues an item into the queue. + /// The item to enqueue. + public void Enqueue(T item) + { + Segment segment = _tail; + T[] array = segment._array; + int last = segment._state._last; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously room in the current segment + int tail2 = (last + 1) & (array.Length - 1); + if (tail2 != segment._state._firstCopy) + { + array[last] = item; + segment._state._last = tail2; + } + // Slow path: there may not be room in the current segment. + else EnqueueSlow(item, ref segment); + } + + /// Enqueues an item into the queue. + /// The item to enqueue. + /// The segment in which to first attempt to store the item. + private void EnqueueSlow(T item, ref Segment segment) + { + Contract.Requires(segment != null, "Expected a non-null segment."); + + if (segment._state._firstCopy != segment._state._first) + { + segment._state._firstCopy = segment._state._first; + Enqueue(item); // will only recur once for this enqueue operation + return; + } + + int newSegmentSize = _tail._array.Length << 1; // double size + Debug.Assert(newSegmentSize > 0, "The max size should always be small enough that we don't overflow."); + if (newSegmentSize > MAX_SEGMENT_SIZE) newSegmentSize = MAX_SEGMENT_SIZE; + + var newSegment = new Segment(newSegmentSize); + newSegment._array[0] = item; + newSegment._state._last = 1; + newSegment._state._lastCopy = 1; + + try { } + finally + { + // Finally block to protect against corruption due to a thread abort + // between setting _next and setting _tail. + Volatile.Write(ref _tail._next, newSegment); // ensure segment not published until item is fully stored + _tail = newSegment; + } + } + + /// Attempts to dequeue an item from the queue. + /// The dequeued item. + /// true if an item could be dequeued; otherwise, false. + public bool TryDequeue(out T result) + { + Segment segment = _head; + T[] array = segment._array; + int first = segment._state._first; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously data available in the current segment + if (first != segment._state._lastCopy) + { + result = array[first]; + array[first] = default(T); // Clear the slot to release the element + segment._state._first = (first + 1) & (array.Length - 1); + return true; + } + // Slow path: there may not be data available in the current segment + else return TryDequeueSlow(ref segment, ref array, out result); + } + + /// Attempts to dequeue an item from the queue. + /// The array from which the item was dequeued. + /// The segment from which the item was dequeued. + /// The dequeued item. + /// true if an item could be dequeued; otherwise, false. + private bool TryDequeueSlow(ref Segment segment, ref T[] array, out T result) + { + Contract.Requires(segment != null, "Expected a non-null segment."); + Contract.Requires(array != null, "Expected a non-null item array."); + + if (segment._state._last != segment._state._lastCopy) + { + segment._state._lastCopy = segment._state._last; + return TryDequeue(out result); // will only recur once for this dequeue operation + } + + if (segment._next != null && segment._state._first == segment._state._last) + { + segment = segment._next; + array = segment._array; + _head = segment; + } + + int first = segment._state._first; // local copy to avoid extraneous volatile reads + + if (first == segment._state._last) + { + result = default(T); + return false; + } + + result = array[first]; + array[first] = default(T); // Clear the slot to release the element + segment._state._first = (first + 1) & (segment._array.Length - 1); + segment._state._lastCopy = segment._state._last; // Refresh _lastCopy to ensure that _first has not passed _lastCopy + + return true; + } + + /// Attempts to peek at an item in the queue. + /// The peeked item. + /// true if an item could be peeked; otherwise, false. + public bool TryPeek(out T result) + { + Segment segment = _head; + T[] array = segment._array; + int first = segment._state._first; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously data available in the current segment + if (first != segment._state._lastCopy) + { + result = array[first]; + return true; + } + // Slow path: there may not be data available in the current segment + else return TryPeekSlow(ref segment, ref array, out result); + } + + /// Attempts to peek at an item in the queue. + /// The array from which the item is peeked. + /// The segment from which the item is peeked. + /// The peeked item. + /// true if an item could be peeked; otherwise, false. + private bool TryPeekSlow(ref Segment segment, ref T[] array, out T result) + { + Contract.Requires(segment != null, "Expected a non-null segment."); + Contract.Requires(array != null, "Expected a non-null item array."); + + if (segment._state._last != segment._state._lastCopy) + { + segment._state._lastCopy = segment._state._last; + return TryPeek(out result); // will only recur once for this peek operation + } + + if (segment._next != null && segment._state._first == segment._state._last) + { + segment = segment._next; + array = segment._array; + _head = segment; + } + + int first = segment._state._first; // local copy to avoid extraneous volatile reads + + if (first == segment._state._last) + { + result = default(T); + return false; + } + + result = array[first]; + return true; + } + + /// Attempts to dequeue an item from the queue. + /// The predicate that must return true for the item to be dequeued. If null, all items implicitly return true. + /// The dequeued item. + /// true if an item could be dequeued; otherwise, false. + public bool TryDequeueIf(Predicate predicate, out T result) + { + Segment segment = _head; + T[] array = segment._array; + int first = segment._state._first; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously data available in the current segment + if (first != segment._state._lastCopy) + { + result = array[first]; + if (predicate == null || predicate(result)) + { + array[first] = default(T); // Clear the slot to release the element + segment._state._first = (first + 1) & (array.Length - 1); + return true; + } + else + { + result = default(T); + return false; + } + } + // Slow path: there may not be data available in the current segment + else return TryDequeueIfSlow(predicate, ref segment, ref array, out result); + } + + /// Attempts to dequeue an item from the queue. + /// The predicate that must return true for the item to be dequeued. If null, all items implicitly return true. + /// The array from which the item was dequeued. + /// The segment from which the item was dequeued. + /// The dequeued item. + /// true if an item could be dequeued; otherwise, false. + private bool TryDequeueIfSlow(Predicate predicate, ref Segment segment, ref T[] array, out T result) + { + Contract.Requires(segment != null, "Expected a non-null segment."); + Contract.Requires(array != null, "Expected a non-null item array."); + + if (segment._state._last != segment._state._lastCopy) + { + segment._state._lastCopy = segment._state._last; + return TryDequeueIf(predicate, out result); // will only recur once for this dequeue operation + } + + if (segment._next != null && segment._state._first == segment._state._last) + { + segment = segment._next; + array = segment._array; + _head = segment; + } + + int first = segment._state._first; // local copy to avoid extraneous volatile reads + + if (first == segment._state._last) + { + result = default(T); + return false; + } + + result = array[first]; + if (predicate == null || predicate(result)) + { + array[first] = default(T); // Clear the slot to release the element + segment._state._first = (first + 1) & (segment._array.Length - 1); + segment._state._lastCopy = segment._state._last; // Refresh _lastCopy to ensure that _first has not passed _lastCopy + return true; + } + else + { + result = default(T); + return false; + } + } + + public void Clear() + { + T ignored; + while (TryDequeue(out ignored)) ; + } + + /// Gets whether the collection is currently empty. + /// WARNING: This should not be used concurrently without further vetting. + public bool IsEmpty + { + // This implementation is optimized for calls from the consumer. + get + { + Segment head = _head; + if (head._state._first != head._state._lastCopy) return false; // _first is volatile, so the read of _lastCopy cannot get reordered + if (head._state._first != head._state._last) return false; + return head._next == null; + } + } + + /// Gets an enumerable for the collection. + /// WARNING: This should only be used for debugging purposes. It is not safe to be used concurrently. + public IEnumerator GetEnumerator() + { + for (Segment segment = _head; segment != null; segment = segment._next) + { + for (int pt = segment._state._first; + pt != segment._state._last; + pt = (pt + 1) & (segment._array.Length - 1)) + { + yield return segment._array[pt]; + } + } + } + /// Gets an enumerable for the collection. + /// WARNING: This should only be used for debugging purposes. It is not safe to be used concurrently. + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + /// Gets the number of items in the collection. + /// WARNING: This should only be used for debugging purposes. It is not meant to be used concurrently. + public int Count + { + get + { + int count = 0; + for (Segment segment = _head; segment != null; segment = segment._next) + { + int arraySize = segment._array.Length; + int first, last; + while (true) // Count is not meant to be used concurrently, but this helps to avoid issues if it is + { + first = segment._state._first; + last = segment._state._last; + if (first == segment._state._first) break; + } + count += (last - first) & (arraySize - 1); + } + return count; + } + } + + /// A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object. + /// The Count is not thread safe, so we need to acquire the lock. + int IProducerConsumerQueue.GetCountSafe(object syncObj) + { + Debug.Assert(syncObj != null, "The syncObj parameter is null."); + lock (syncObj) + { + return Count; + } + } + + /// A segment in the queue containing one or more items. + [StructLayout(LayoutKind.Sequential)] + private sealed class Segment + { + /// The next segment in the linked list of segments. + internal Segment _next; + /// The data stored in this segment. + internal readonly T[] _array; + /// Details about the segment. + internal SegmentState _state; // separated out to enable StructLayout attribute to take effect + + /// Initializes the segment. + /// The size to use for this segment. + internal Segment(int size) + { + Contract.Requires((size & (size - 1)) == 0, "Size must be a power of 2"); + _array = new T[size]; + } + } + + /// Stores information about a segment. + [StructLayout(LayoutKind.Sequential)] // enforce layout so that padding reduces false sharing + private struct SegmentState + { + /// Padding to reduce false sharing between the segment's array and _first. + internal PaddingFor32 _pad0; + + /// The index of the current head in the segment. + internal volatile int _first; + /// A copy of the current tail index. + internal int _lastCopy; // not volatile as read and written by the producer, except for IsEmpty, and there _lastCopy is only read after reading the volatile _first + + /// Padding to reduce false sharing between the first and last. + internal PaddingFor32 _pad1; + + /// A copy of the current head index. + internal int _firstCopy; // not volatile as only read and written by the consumer thread + /// The index of the current tail in the segment. + internal volatile int _last; + + /// Padding to reduce false sharing with the last and what's after the segment. + internal PaddingFor32 _pad2; + } + + /// Debugger type proxy for a SingleProducerSingleConsumerQueue of T. + private sealed class SingleProducerSingleConsumerQueue_DebugView + { + /// The queue being visualized. + private readonly SingleProducerSingleConsumerQueue _queue; + + /// Initializes the debug view. + /// The queue being debugged. + public SingleProducerSingleConsumerQueue_DebugView(SingleProducerSingleConsumerQueue queue) + { + Contract.Requires(queue != null, "Expected a non-null queue."); + _queue = queue; + } + + /// Gets the contents of the list. + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get + { + List list = new List(); + foreach (T item in _queue) + list.Add(item); + return list.ToArray(); + } + } + } + } + + + /// A placeholder class for common padding constants and eventually routines. + static class PaddingHelpers + { + /// A size greater than or equal to the size of the most common CPU cache lines. + internal const int CACHE_LINE_SIZE = 128; + } + + /// Padding structure used to minimize false sharing in SingleProducerSingleConsumerQueue{T}. + [StructLayout(LayoutKind.Explicit, Size = PaddingHelpers.CACHE_LINE_SIZE - sizeof(Int32))] // Based on common case of 64-byte cache lines + struct PaddingFor32 + { + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/QueuedMap.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/QueuedMap.cs new file mode 100644 index 00000000000..f70de5ba71d --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/QueuedMap.cs @@ -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 +{ + /// + /// 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. + /// + /// Specifies the type of keys in the map. + /// Specifies the type of values in the map. + /// This type is not thread-safe. + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(EnumerableDebugView<,>))] + internal sealed class QueuedMap + { + /// + /// A queue structure that uses an array-based list to store its items + /// and that supports overwriting elements at specific indices. + /// + /// The type of the items storedin the queue + /// This type is not thread-safe. + private sealed class ArrayBasedLinkedQueue + { + /// Terminator index. + private const int TERMINATOR_INDEX = -1; + /// + /// The queue where the items will be stored. + /// The key of each entry is the index of the next entry in the queue. + /// + private readonly List> _storage; + /// Index of the first queue item. + private int _headIndex = TERMINATOR_INDEX; + /// Index of the last queue item. + private int _tailIndex = TERMINATOR_INDEX; + /// Index of the first free slot. + private int _freeIndex = TERMINATOR_INDEX; + + /// Initializes the Queue instance. + internal ArrayBasedLinkedQueue() + { + _storage = new List>(); + } + + /// Initializes the Queue instance. + /// The capacity of the internal storage. + internal ArrayBasedLinkedQueue(int capacity) + { + _storage = new List>(capacity); + } + + /// Enqueues an item. + /// The item to be enqueued. + /// The index of the slot where item was stored. + 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(TERMINATOR_INDEX, item); + } + // If there is no free slot, add one + else + { + newIndex = _storage.Count; + _storage.Add(new KeyValuePair(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(newIndex, _storage[_tailIndex].Value); + } + + // Point the tail slot newIndex + _tailIndex = newIndex; + + return newIndex; + } + + /// Tries to dequeue an item. + /// The item that is dequeued. + 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(_freeIndex, default(T)); + _freeIndex = _headIndex; + _headIndex = newHeadIndex; + if (_headIndex == TERMINATOR_INDEX) _tailIndex = TERMINATOR_INDEX; + + return true; + } + + /// Replaces the item of a given slot. + /// The index of the slot where the value should be replaced. + /// The item to be places. + 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(_storage[index].Key, item); + } + + internal bool IsEmpty { get { return _headIndex == TERMINATOR_INDEX; } } + } + + /// The queue of elements. + private readonly ArrayBasedLinkedQueue> _queue; + /// A map from key to index into the list. + /// The correctness of this map relies on the list only having elements removed from its end. + private readonly Dictionary _mapKeyToIndex; + + /// Initializes the QueuedMap. + internal QueuedMap() + { + _queue = new ArrayBasedLinkedQueue>(); + _mapKeyToIndex = new Dictionary(); + } + + /// Initializes the QueuedMap. + /// The initial capacity of the data structure. + internal QueuedMap(int capacity) + { + _queue = new ArrayBasedLinkedQueue>(capacity); + _mapKeyToIndex = new Dictionary(capacity); + } + + /// Pushes a key/value pair into the data structure. + /// The key for the pair. + /// The value for the pair. + 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(key, value)); + } + // If it's not there, add it to the queue and then add the mapping. + else + { + indexOfKeyInQueue = _queue.Enqueue(new KeyValuePair(key, value)); + _mapKeyToIndex.Add(key, indexOfKeyInQueue); + } + } + + /// Try to pop the next element from the data structure. + /// The popped pair. + /// true if an item could be popped; otherwise, false. + internal bool TryPop(out KeyValuePair item) + { + bool popped = _queue.TryDequeue(out item); + if (popped) _mapKeyToIndex.Remove(item.Key); + return popped; + } + + /// Tries to pop one or more elements from the data structure. + /// The items array into which the popped elements should be stored. + /// The offset into the array at which to start storing popped items. + /// The number of items to be popped. + /// The number of items popped, which may be less than the requested number if fewer existed in the data structure. + internal int PopRange(KeyValuePair[] 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 item; + if (TryPop(out item)) items[i] = item; + else break; + } + + return actualCount; + } + + /// Gets the number of items in the data structure. + internal int Count { get { return _mapKeyToIndex.Count; } } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ReorderingBuffer.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ReorderingBuffer.cs new file mode 100644 index 00000000000..472f691f0fb --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/ReorderingBuffer.cs @@ -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 +{ + /// Base interface for reordering buffers. + internal interface IReorderingBuffer + { + /// Informs the reordering buffer not to expect the message with the specified id. + /// The id of the message to be ignored. + void IgnoreItem(long id); + } + + /// Provides a buffer that reorders items according to their incoming IDs. + /// Specifies the type of data stored in the items being reordered. + /// + /// This type expects the first item to be ID==0 and for all subsequent items + /// to increase IDs sequentially. + /// + [DebuggerDisplay("Count={CountForDebugging}")] + [DebuggerTypeProxy(typeof(ReorderingBuffer<>.DebugView))] + internal sealed class ReorderingBuffer : IReorderingBuffer + { + /// The source that owns this reordering buffer. + private readonly object _owningSource; + /// A reordering buffer used when parallelism is employed and items may be completed out-of-order. + /// Also serves as the sync object to protect the contents of this class. + private readonly Dictionary> _reorderingBuffer = new Dictionary>(); + /// Action used to output items in order. + private readonly Action _outputAction; + /// The ID of the next item that should be released from the reordering buffer. + private long _nextReorderedIdToOutput = 0; + + /// Gets the object used to synchronize all access to the reordering buffer's internals. + private object ValueLock { get { return _reorderingBuffer; } } + + /// Initializes the reordering buffer. + /// The source that owns this reordering buffer. + /// The action to invoke when the next in-order item is available to be output. + internal ReorderingBuffer(object owningSource, Action 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; + } + + /// Stores the next item as it completes processing. + /// The ID of the item. + /// The completed item. + /// Specifies whether the item is valid (true) or just a placeholder (false). + 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(itemIsValid, item)); + } + } + } + + /// + /// 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. + /// + /// The id of the item. + /// The item. + /// + /// Whether to allow the item to be output directly if it is the next item. + /// + /// + /// 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. + /// + 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; + } + } + + /// Informs the reordering buffer not to expect the message with the specified id. + /// The id of the message to be ignored. + public void IgnoreItem(long id) + { + AddItem(id, default(TOutput), itemIsValid: false); + } + + /// Outputs the item. The item must have already been confirmed to have the next ID. + /// The item to output. + /// Whether the item is valid. + 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 nextOutputItemWithValidity; + while (_reorderingBuffer.TryGetValue(_nextReorderedIdToOutput, out nextOutputItemWithValidity)) + { + _reorderingBuffer.Remove(_nextReorderedIdToOutput); + _nextReorderedIdToOutput++; + if (nextOutputItemWithValidity.Key) _outputAction(_owningSource, nextOutputItemWithValidity.Value); + } + } + + /// Gets a item count for debugging purposes. + private int CountForDebugging { get { return _reorderingBuffer.Count; } } + + /// Provides a debugger type proxy for the buffer. + private sealed class DebugView + { + /// The buffer being debugged. + private readonly ReorderingBuffer _buffer; + + /// Initializes the debug view. + /// The buffer being debugged. + public DebugView(ReorderingBuffer buffer) + { + Contract.Requires(buffer != null, "Need a buffer with which to construct the debug view."); + _buffer = buffer; + } + + /// Gets a dictionary of buffered items and their reordering IDs. + public Dictionary> ItemsBuffered { get { return _buffer._reorderingBuffer; } } + /// Gets the next ID required for outputting. + public long NextIdRequired { get { return _buffer._nextReorderedIdToOutput; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/SourceCore.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/SourceCore.cs new file mode 100644 index 00000000000..8aa140d23ce --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/SourceCore.cs @@ -0,0 +1,1035 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// SourceCore.cs +// +// +// The core implementation of a standard ISourceBlock. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Security; + +namespace System.Threading.Tasks.Dataflow.Internal +{ + // LOCK-LEVELING SCHEME + // -------------------- + // SourceCore employs two locks: OutgoingLock and ValueLock. Additionally, targets we call out to + // likely utilize their own IncomingLock. We can hold OutgoingLock while acquiring ValueLock or IncomingLock. + // However, we cannot hold ValueLock while calling out to external code or while acquiring OutgoingLock, and + // we cannot hold IncomingLock when acquiring OutgoingLock. Additionally, the locks employed must be reentrant. + + /// Provides a core implementation for blocks that implement . + /// Specifies the type of data supplied by the . + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + internal sealed class SourceCore + { + // *** These fields are readonly and are initialized to new instances at construction. + + /// A TaskCompletionSource that represents the completion of this block. + private readonly TaskCompletionSource _completionTask = new TaskCompletionSource(); + /// A registry used to store all linked targets and information about them. + private readonly TargetRegistry _targetRegistry; + /// The output messages queued up to be received by consumers/targets. + /// + /// The queue is only ever accessed by a single producer and single consumer at a time. On the producer side, + /// we require that AddMessage/AddMessages are the only places the queue is added to, and we require that those + /// methods not be used concurrently with anything else. All of our target halves today follow that restriction; + /// for example, TransformBlock with DOP==1 will have at most a single task processing the user provided delegate, + /// and thus at most one task calling AddMessage. If it has a DOP > 1, it'll go through the ReorderingBuffer, + /// which will use a lock to synchronize the output of all of the processing tasks such that only one is using + /// AddMessage at a time. On the consumer side of SourceCore, all consumption is protected by ValueLock, and thus + /// all consumption is serialized. + /// + private readonly SingleProducerSingleConsumerQueue _messages = new SingleProducerSingleConsumerQueue(); // protected by AddMessage/ValueLock + + /// Gets the object to use as the outgoing lock. + private object OutgoingLock { get { return _completionTask; } } + /// Gets the object to use as the value lock. + private object ValueLock { get { return _targetRegistry; } } + + // *** These fields are readonly and are initialized by arguments to the constructor. + + /// The source utilizing this helper. + private readonly ISourceBlock _owningSource; + /// The options used to configure this block's execution. + private readonly DataflowBlockOptions _dataflowBlockOptions; + /// + /// An action to be invoked on the owner block to stop accepting messages. + /// This action is invoked when SourceCore encounters an exception. + /// + private readonly Action> _completeAction; + /// + /// An action to be invoked on the owner block when an item is removed. + /// This may be null if the owner block doesn't need to be notified. + /// + private readonly Action, int> _itemsRemovedAction; + /// Item counting function + private readonly Func, TOutput, IList, int> _itemCountingFunc; + + // *** These fields are mutated during execution. + + /// The task used to process the output and offer it to targets. + private Task _taskForOutputProcessing; // protected by ValueLock + /// Counter for message IDs unique within this source block. + private PaddedInt64 _nextMessageId = new PaddedInt64 { Value = 1 }; // We are going to use this value before incrementing. Protected by ValueLock. + /// The target that the next message is reserved for, or null if nothing is reserved. + private ITargetBlock _nextMessageReservedFor; // protected by OutgoingLock + /// Whether all future messages should be declined. + private bool _decliningPermanently; // Protected by ValueLock + /// Whether this block should again attempt to offer messages to targets. + private bool _enableOffering = true; // Protected by ValueLock, sometimes read with volatile reads + /// Whether someone has reserved the right to call CompleteBlockOncePossible. + private bool _completionReserved; // Protected by OutgoingLock + /// Exceptions that may have occurred and gone unhandled during processing. + private List _exceptions; // Protected by ValueLock, sometimes read with volatile reads + + /// Initializes the source core. + /// The source utilizing this core. + /// The options to use to configure the block. + /// Action to invoke in order to decline the associated target half, which will in turn decline this source core. + /// Action to invoke when one or more items is removed. This may be null. + /// + /// Action to invoke when the owner needs to be able to count the number of individual + /// items in an output or set of outputs. + /// + internal SourceCore( + ISourceBlock owningSource, DataflowBlockOptions dataflowBlockOptions, + Action> completeAction, + Action, int> itemsRemovedAction = null, + Func, TOutput, IList, int> itemCountingFunc = null) + { + Contract.Requires(owningSource != null, "Core must be associated with a source."); + Contract.Requires(dataflowBlockOptions != null, "Options must be provided to configure the core."); + Contract.Requires(completeAction != null, "Action to invoke on completion is required."); + + // Store the args + _owningSource = owningSource; + _dataflowBlockOptions = dataflowBlockOptions; + _itemsRemovedAction = itemsRemovedAction; + _itemCountingFunc = itemCountingFunc; + _completeAction = completeAction; + + // Construct members that depend on the args + _targetRegistry = new TargetRegistry(_owningSource); + } + + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + internal IDisposable LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions) + { + // Validate arguments + if (target == null) throw new ArgumentNullException("target"); + if (linkOptions == null) throw new ArgumentNullException("linkOptions"); + Contract.EndContractBlock(); + + // If the block is already completed, there is not much to do - + // we have to propagate completion if that was requested, and + // then bail without taking the lock. + if (_completionTask.Task.IsCompleted) + { + if (linkOptions.PropagateCompletion) Common.PropagateCompletion(_completionTask.Task, target, exceptionHandler: null); + return Disposables.Nop; + } + + lock (OutgoingLock) + { + // If completion has been reserved, the target registry has either been cleared already + // or is about to be cleared. So we can link and offer only if completion is not reserved. + if (!_completionReserved) + { + _targetRegistry.Add(ref target, linkOptions); + OfferToTargets(linkToTarget: target); + return Common.CreateUnlinker(OutgoingLock, _targetRegistry, target); + } + } + + // The block should not offer any messages when it is in this state, but + // it should still propagate completion if that has been requested. + if (linkOptions.PropagateCompletion) Common.PropagateCompletionOnceCompleted(_completionTask.Task, target); + return Disposables.Nop; + } + + /// + internal TOutput ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out Boolean messageConsumed) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + TOutput consumedMessageValue = default(TOutput); + + lock (OutgoingLock) + { + // If this target doesn't hold the reservation, then for this ConsumeMessage + // to be valid, there must not be any reservation (since otherwise we can't + // consume a message destined for someone else). + if (_nextMessageReservedFor != target && + _nextMessageReservedFor != null) + { + messageConsumed = false; + return default(TOutput); + } + + lock (ValueLock) + { + // If the requested message isn't the next message to be served up, bail. + // Otherwise, we're good to go: dequeue the message as it will now be owned by the target, + // signal that we can resume enabling offering as there's potentially a new "next message", + // complete if necessary, and offer asynchronously all messages as is appropriate. + + if (messageHeader.Id != _nextMessageId.Value || + !_messages.TryDequeue(out consumedMessageValue)) + { + messageConsumed = false; + return default(TOutput); + } + + _nextMessageReservedFor = null; + _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true); + _enableOffering = true; // reenable offering if it was disabled + _nextMessageId.Value++; + CompleteBlockIfPossible(); + OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: true); + } + } + + // Notify the owner block that our count has decreased + if (_itemsRemovedAction != null) + { + int count = _itemCountingFunc != null ? _itemCountingFunc(_owningSource, consumedMessageValue, null) : 1; + _itemsRemovedAction(_owningSource, count); + } + + // Return the consumed message value + messageConsumed = true; + return consumedMessageValue; + } + + /// + internal Boolean ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + lock (OutgoingLock) + { + // If no one currently holds a reservation... + if (_nextMessageReservedFor == null) + { + lock (ValueLock) + { + // ...and if the requested message is next in the queue, allow it + if (messageHeader.Id == _nextMessageId.Value && !_messages.IsEmpty) + { + _nextMessageReservedFor = target; + _enableOffering = false; + return true; + } + } + } + } + return false; + } + + /// + internal void ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (target == null) throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + lock (OutgoingLock) + { + // If someone else holds the reservation, bail. + if (_nextMessageReservedFor != target) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget); + + lock (ValueLock) + { + // If this is not the message at the head of the queue, bail + if (messageHeader.Id != _nextMessageId.Value || _messages.IsEmpty) throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget); + + // Otherwise, release the reservation + _nextMessageReservedFor = null; + Debug.Assert(!_enableOffering, "Offering should have been disabled if there was a valid reservation"); + _enableOffering = true; + + // Now there is at least one message ready for offering. So offer it. + // If a cancellation is pending, this method will bail out. + OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: true); + + // This reservation may be holding the block's completion. So try to complete. + CompleteBlockIfPossible(); + } + } + } + + /// + internal Task Completion { get { return _completionTask.Task; } } + + /// + internal Boolean TryReceive(Predicate filter, out TOutput item) + { + item = default(TOutput); + bool itemReceived = false; + + lock (OutgoingLock) + { + // If the next message is reserved for someone, we can't receive right now. Otherwise... + if (_nextMessageReservedFor == null) + { + lock (ValueLock) + { + // If there's at least one message, and there's no filter or the next item + // passes the filter, dequeue it to be returned. + if (_messages.TryDequeueIf(filter, out item)) + { + _nextMessageId.Value++; + + // Now that the next message has changed, reenable offering if it was disabled + _enableOffering = true; + + // If removing this item was the last thing this block will ever do, complete it, + CompleteBlockIfPossible(); + + // Now, try to offer up messages asynchronously, since we've + // changed what's at the head of the queue + OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: true); + + itemReceived = true; + } + } + } + } + + if (itemReceived) + { + // Notify the owner block that our count has decreased + if (_itemsRemovedAction != null) + { + int count = _itemCountingFunc != null ? _itemCountingFunc(_owningSource, item, null) : 1; + _itemsRemovedAction(_owningSource, count); + } + } + return itemReceived; + } + + /// + internal bool TryReceiveAll(out IList items) + { + items = null; + int countReceived = 0; + + lock (OutgoingLock) + { + // If the next message is reserved for someone, we can't receive right now. Otherwise... + if (_nextMessageReservedFor == null) + { + lock (ValueLock) + { + if (!_messages.IsEmpty) + { + // Receive all of the data, clearing it out in the process. + var tmpList = new List(); + TOutput item; + while (_messages.TryDequeue(out item)) tmpList.Add(item); + countReceived = tmpList.Count; + items = tmpList; + + // Increment the next ID. Any new value is good. + _nextMessageId.Value++; + + // Now that the next message has changed, reenable offering if it was disabled + _enableOffering = true; + + // Now that the block is empty, check to see whether we should complete. + CompleteBlockIfPossible(); + } + } + } + } + + if (countReceived > 0) + { + // Notify the owner block that our count has decreased + if (_itemsRemovedAction != null) + { + int count = _itemCountingFunc != null ? _itemCountingFunc(_owningSource, default(TOutput), items) : countReceived; + _itemsRemovedAction(_owningSource, count); + } + return true; + } + else return false; + } + + /// Gets the number of items available to be received from this block. + internal int OutputCount { get { lock (OutgoingLock) lock (ValueLock) return _messages.Count; } } + + /// + /// Adds a message to the source block for propagation. + /// This method must only be used by one thread at a time, and must not be used concurrently + /// with any other producer side methods, e.g. AddMessages, Complete. + /// + /// The item to be wrapped in a message to be added. + internal void AddMessage(TOutput item) + { + // This method must not take the OutgoingLock, as it will likely be called in situations + // where an IncomingLock is held. + + if (_decliningPermanently) return; + _messages.Enqueue(item); + + Interlocked.MemoryBarrier(); // ensure the read of _taskForOutputProcessing doesn't move up before the writes in Enqueue + + if (_taskForOutputProcessing == null) + { + // Separated out to enable inlining of AddMessage + OfferAsyncIfNecessaryWithValueLock(); + } + } + + /// + /// Adds messages to the source block for propagation. + /// This method must only be used by one thread at a time, and must not be used concurrently + /// with any other producer side methods, e.g. AddMessage, Complete. + /// + /// The list of items to be wrapped in messages to be added. + internal void AddMessages(IEnumerable items) + { + Contract.Requires(items != null, "Items list must be valid."); + + // This method must not take the OutgoingLock, as it will likely be called in situations + // where an IncomingLock is held. + + if (_decliningPermanently) return; + + // Special case arrays and lists, for which we can avoid the + // enumerator allocation that'll result from using a foreach. + // This also avoids virtual method calls that we'd get if we + // didn't special case. + var itemsAsList = items as List; + if (itemsAsList != null) + { + for (int i = 0; i < itemsAsList.Count; i++) + { + _messages.Enqueue(itemsAsList[i]); + } + } + else + { + TOutput[] itemsAsArray = items as TOutput[]; + if (itemsAsArray != null) + { + for (int i = 0; i < itemsAsArray.Length; i++) + { + _messages.Enqueue(itemsAsArray[i]); + } + } + else + { + foreach (TOutput item in items) + { + _messages.Enqueue(item); + } + } + } + + Interlocked.MemoryBarrier(); // ensure the read of _taskForOutputProcessing doesn't move up before the writes in Enqueue + + if (_taskForOutputProcessing == null) + { + OfferAsyncIfNecessaryWithValueLock(); + } + } + + /// Adds an individual exceptionto this source. + /// The exception to add + internal void AddException(Exception exception) + { + Contract.Requires(exception != null, "Valid exception must be provided to be added."); + Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions."); + lock (ValueLock) + { + Common.AddException(ref _exceptions, exception); + } + } + + /// Adds exceptions to this source. + /// The exceptions to add + internal void AddExceptions(List exceptions) + { + Contract.Requires(exceptions != null, "Valid exceptions must be provided to be added."); + Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions."); + lock (ValueLock) + { + foreach (Exception exception in exceptions) + { + Common.AddException(ref _exceptions, exception); + } + } + } + + /// Adds the exceptions contained in an AggregateException to this source. + /// The exception to add + internal void AddAndUnwrapAggregateException(AggregateException aggregateException) + { + Contract.Requires(aggregateException != null && aggregateException.InnerExceptions.Count > 0, "Aggregate must be valid and contain inner exceptions to unwrap."); + Contract.Requires(!Completion.IsCompleted || Completion.IsFaulted, "The block must either not be completed or be faulted if we're still storing exceptions."); + lock (ValueLock) + { + Common.AddException(ref _exceptions, aggregateException, unwrapInnerExceptions: true); + } + } + + /// Gets whether the _exceptions list is non-null. + internal bool HasExceptions + { + get + { + // We may check whether _exceptions is null without taking a lock because it is volatile + return Volatile.Read(ref _exceptions) != null; + } + } + + /// Informs the block that it will not be receiving additional messages. + internal void Complete() + { + lock (ValueLock) + { + _decliningPermanently = true; + + // CompleteAdding may be called in a context where an incoming lock is held. We need to + // call CompleteBlockIfPossible, but we can't do so if the incoming lock is held. + // However, we know that _decliningPermanently has been set, and thus the timing of + // CompleteBlockIfPossible doesn't matter, so we schedule it to run asynchronously + // and take the necessary locks in a situation where we're sure it won't cause a problem. + Task.Factory.StartNew(state => + { + var thisSourceCore = (SourceCore)state; + lock (thisSourceCore.OutgoingLock) + { + lock (thisSourceCore.ValueLock) + { + thisSourceCore.CompleteBlockIfPossible(); + } + } + }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + + /// Gets the DataflowBlockOptions used to configure this block. + internal DataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } } + + /// Offers messages to all targets. + /// + /// The newly linked target, if OfferToTargets is being called to synchronously + /// propagate to a target during a LinkTo operation. + /// + private bool OfferToTargets(ITargetBlock linkToTarget = null) + { + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + Common.ContractAssertMonitorStatus(ValueLock, held: false); + + // If the next message is reserved, we can't offer anything + if (_nextMessageReservedFor != null) + return false; + + // Peek at the next message if there is one, so we can offer it. + DataflowMessageHeader header = default(DataflowMessageHeader); + TOutput message = default(TOutput); + bool offerJustToLinkToTarget = false; + + // If offering isn't enabled and if we're not doing this as + // a result of LinkTo, bail. Otherwise, with offering disabled, we must have + // already offered this message to all existing targets, so we can just offer + // it to the newly linked target. + if (!Volatile.Read(ref _enableOffering)) + { + if (linkToTarget == null) return false; + else offerJustToLinkToTarget = true; + } + + // Otherwise, peek at message to offer + if (_messages.TryPeek(out message)) + { + header = new DataflowMessageHeader(_nextMessageId.Value); + } + + // If there is a message, offer it. + bool messageWasAccepted = false; + if (header.IsValid) + { + if (offerJustToLinkToTarget) + { + // If we've already offered the message to everyone else, + // we can just offer it to the newly linked target + Debug.Assert(linkToTarget != null, "Must have a valid target to offer to."); + OfferMessageToTarget(header, message, linkToTarget, out messageWasAccepted); + } + else + { + // Otherwise, we've not yet offered this message to anyone, so even + // if linkToTarget is non-null, we need to propagate the message in order + // through all of the registered targets, the last of which will be the linkToTarget + // if it's non-null (no need to special-case it, though). + + // Note that during OfferMessageToTarget, a target may call ConsumeMessage (taking advantage of the + // reentrancy of OutgoingLock), which may unlink the target if the target is registered as "unlinkAfterOne". + // Doing so will remove the target from the targets list. As such, we maintain the next node + // separately from cur.Next, in case cur.Next changes by cur being removed from the list. + // No other node in the list should change, as we're protected by OutgoingLock. + + TargetRegistry.LinkedTargetInfo cur = _targetRegistry.FirstTargetNode; + while (cur != null) + { + TargetRegistry.LinkedTargetInfo next = cur.Next; + if (OfferMessageToTarget(header, message, cur.Target, out messageWasAccepted)) break; + cur = next; + } + + // If none of the targets accepted the message, disable offering. + if (!messageWasAccepted) + { + lock (ValueLock) + { + _enableOffering = false; + } + } + } + } + + // If a message got accepted, consume it and reenable offering. + if (messageWasAccepted) + { + lock (ValueLock) + { + // SourceCore set consumeToAccept to false. However, it's possible + // that an incorrectly written target may ignore that parameter and synchronously consume + // even though they weren't supposed to. To recover from that, + // we'll only dequeue if the correct message is still at the head of the queue. + // However, we'll assert so that we can at least catch this in our own debug builds. + TOutput dropped; + if (_nextMessageId.Value != header.Id || + !_messages.TryDequeue(out dropped)) // remove the next message + { + Debug.Assert(false, "The target did not follow the protocol."); + } + _nextMessageId.Value++; + + // The message was accepted, so there's now going to be a new next message. + // If offering had been disabled, reenable it. + _enableOffering = true; + + // Now that a message has been removed, we need to complete if possible or + // or asynchronously offer if necessary. However, if we're calling this as part of our + // offering loop, we won't be able to do either, since by definition there's already + // a processing task spun up (us) that would prevent these things. So we only + // do the checks if we're being called to link a new target rather than as part + // of normal processing. + if (linkToTarget != null) + { + CompleteBlockIfPossible(); + OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: true); + } + } + + // Notify the owner block that our count has decreased + if (_itemsRemovedAction != null) + { + int count = _itemCountingFunc != null ? _itemCountingFunc(_owningSource, message, null) : 1; + _itemsRemovedAction(_owningSource, count); + } + } + + return messageWasAccepted; + } + + /// Offers the message to the target. + /// The header of the message to offer. + /// The message being offered. + /// The single target to which the message should be offered. + /// true if the message was accepted by the target; otherwise, false. + /// + /// true if the message should not be offered to additional targets; + /// false if propagation should be allowed to continue. + /// + private bool OfferMessageToTarget( + DataflowMessageHeader header, TOutput message, ITargetBlock target, + out bool messageWasAccepted) + { + Contract.Requires(target != null, "Valid target to offer to is required."); + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + Common.ContractAssertMonitorStatus(ValueLock, held: false); + + DataflowMessageStatus result = target.OfferMessage(header, message, _owningSource, consumeToAccept: false); + Debug.Assert(result != DataflowMessageStatus.NotAvailable, "Messages are not being offered concurrently, so nothing should be missed."); + messageWasAccepted = false; + + // If accepted, note it, and if the target was linked as "once", remove it + if (result == DataflowMessageStatus.Accepted) + { + _targetRegistry.Remove(target, onlyIfReachedMaxMessages: true); + messageWasAccepted = true; + return true; // the message should not be offered to anyone else + } + // If declined permanently, remove the target + else if (result == DataflowMessageStatus.DecliningPermanently) + { + _targetRegistry.Remove(target); + } + // If the message was reserved by the target, stop propagating + else if (_nextMessageReservedFor != null) + { + Debug.Assert(result == DataflowMessageStatus.Postponed, + "If the message was reserved, it should also have been postponed."); + return true; // the message should not be offered to anyone else + } + // If the result was Declined, there's nothing more to be done. + // This message will sit at the front of the queue until someone claims it. + + return false; // allow the message to be offered to someone else + } + + /// + /// Called when we want to enable asynchronously offering message to targets. + /// Takes the ValueLock before delegating to OfferAsyncIfNecessary. + /// + private void OfferAsyncIfNecessaryWithValueLock() + { + lock (ValueLock) + { + OfferAsyncIfNecessary(isReplacementReplica: false, outgoingLockKnownAcquired: false); + } + } + + /// Called when we want to enable asynchronously offering message to targets. + /// Whether this call is the continuation of a previous message loop. + /// Whether the caller is sure that the outgoing lock is currently held by this thread. + private void OfferAsyncIfNecessary(bool isReplacementReplica, bool outgoingLockKnownAcquired) + { + Common.ContractAssertMonitorStatus(ValueLock, held: true); + + // Fast path to enable OfferAsyncIfNecessary to be inlined. We only need + // to proceed if there's no task processing, offering is enabled, and + // there are no messages to be processed. + if (_taskForOutputProcessing == null && _enableOffering && !_messages.IsEmpty) + { + // Slow path: do additional checks and potentially launch new task + OfferAsyncIfNecessary_Slow(isReplacementReplica, outgoingLockKnownAcquired); + } + } + + /// Called when we want to enable asynchronously offering message to targets. + /// Whether this call is the continuation of a previous message loop. + /// Whether the caller is sure that the outgoing lock is currently held by this thread. + private void OfferAsyncIfNecessary_Slow(bool isReplacementReplica, bool outgoingLockKnownAcquired) + { + Common.ContractAssertMonitorStatus(ValueLock, held: true); + Debug.Assert(_taskForOutputProcessing == null && _enableOffering && !_messages.IsEmpty, + "The block must be enabled for offering, not currently be processing, and have messages available to process."); + + // This method must not take the outgoing lock, as it will likely be called in situations + // where a derived type's incoming lock is held. + + bool targetsAvailable = true; + if (outgoingLockKnownAcquired || Monitor.IsEntered(OutgoingLock)) + { + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + targetsAvailable = _targetRegistry.FirstTargetNode != null; + } + + // If there's any work to be done... + if (targetsAvailable && !CanceledOrFaulted) + { + // Create task and store into _taskForOutputProcessing prior to scheduling the task + // so that _taskForOutputProcessing will be visibly set in the task loop. + _taskForOutputProcessing = new Task(thisSourceCore => ((SourceCore)thisSourceCore).OfferMessagesLoopCore(), this, + Common.GetCreationOptionsForTask(isReplacementReplica)); + +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TaskLaunchedForMessageHandling( + _owningSource, _taskForOutputProcessing, DataflowEtwProvider.TaskLaunchedReason.OfferingOutputMessages, _messages.Count); + } +#endif + + // Start the task handling scheduling exceptions +#pragma warning disable 0420 + Exception exception = Common.StartTaskSafe(_taskForOutputProcessing, _dataflowBlockOptions.TaskScheduler); +#pragma warning restore 0420 + if (exception != null) + { + // First, log the exception while the processing state is dirty which is preventing the block from completing. + // Then revert the proactive processing state changes. + // And last, try to complete the block. + AddException(exception); + _taskForOutputProcessing = null; + _decliningPermanently = true; + + // Get out from under currently held locks - ValueLock is taken, but OutgoingLock may not be. + // Re-take the locks on a separate thread. + Task.Factory.StartNew(state => + { + var thisSourceCore = (SourceCore)state; + lock (thisSourceCore.OutgoingLock) + { + lock (thisSourceCore.ValueLock) + { + thisSourceCore.CompleteBlockIfPossible(); + } + } + }, this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + if (exception != null) AddException(exception); + } + } + + /// Task body used to process messages. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void OfferMessagesLoopCore() + { + Debug.Assert(_taskForOutputProcessing != null && _taskForOutputProcessing.Id == Task.CurrentId, + "Must be part of the current processing task."); + try + { + int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask; + + // We need to hold the outgoing lock while offering messages. We can either + // lock and unlock for each individual offering, or we can lock around multiple or all + // possible offerings. The former ensures that other operations don't get starved, + // while the latter is much more efficient (not continually acquiring and releasing + // the lock). For blocks that aren't linked to any targets, this won't matter + // (no offering is done), and for blocks that are only linked to targets, this shouldn't + // matter (no one is contending for the lock), thus + // the only case it would matter is when a block both has targets and is being + // explicitly received from, which is an uncommon scenario. Thus, we want to lock + // around the whole thing to improve performance, but just in case we do hit + // an uncommon scenario, in the default case we release the lock every now and again. + // If a developer wants to control this, they can limit the duration of the + // lock by using MaxMessagesPerTask. + + const int DEFAULT_RELEASE_LOCK_ITERATIONS = 10; // Dialable + int releaseLockIterations = + _dataflowBlockOptions.MaxMessagesPerTask == DataflowBlockOptions.Unbounded ? + DEFAULT_RELEASE_LOCK_ITERATIONS : maxMessagesPerTask; + + for (int messageCounter = 0; + messageCounter < maxMessagesPerTask && !CanceledOrFaulted;) + { + lock (OutgoingLock) + { + // While there are more messages to process, offer each in turn + // to the targets. If we're unable to propagate a particular message, + // stop trying until something changes in the future. + for ( + int lockReleaseCounter = 0; + messageCounter < maxMessagesPerTask && lockReleaseCounter < releaseLockIterations && !CanceledOrFaulted; + ++messageCounter, ++lockReleaseCounter) + { + if (!OfferToTargets()) return; + } + } + } + } + catch (Exception exc) + { + // Record the exception + AddException(exc); + + // Notify the owning block it should stop accepting new messages + _completeAction(_owningSource); + } + finally + { + lock (OutgoingLock) + { + lock (ValueLock) + { + // We're no longer processing, so null out the processing task + Debug.Assert(_taskForOutputProcessing != null && _taskForOutputProcessing.Id == Task.CurrentId, + "Must be part of the current processing task."); + _taskForOutputProcessing = null; + Interlocked.MemoryBarrier(); // synchronize with AddMessage(s) and its read of _taskForOutputProcessing + + // However, we may have given up early because we hit our own configured + // processing limits rather than because we ran out of work to do. If that's + // the case, make sure we spin up another task to keep going. + OfferAsyncIfNecessary(isReplacementReplica: true, outgoingLockKnownAcquired: true); + + // If, however, we stopped because we ran out of work to do and we + // know we'll never get more, then complete. + CompleteBlockIfPossible(); + } + } + } + } + + /// Gets whether the source has had cancellation requested or an exception has occurred. + private bool CanceledOrFaulted + { + get + { + // Cancellation is honored as soon as the CancellationToken has been signaled. + // Faulting is honored after an exception has been encountered and the owning block + // has invoked Complete on us. + return _dataflowBlockOptions.CancellationToken.IsCancellationRequested || + (HasExceptions && _decliningPermanently); + } + } + + /// Completes the block's processing if there's nothing left to do and never will be. + private void CompleteBlockIfPossible() + { + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + Common.ContractAssertMonitorStatus(ValueLock, held: true); + + if (!_completionReserved) + { + if (_decliningPermanently && // declining permanently, so no more messages will arrive + _taskForOutputProcessing == null && // no current processing + _nextMessageReservedFor == null) // no pending reservation + { + CompleteBlockIfPossible_Slow(); + } + } + } + + /// + /// Slow path for CompleteBlockIfPossible. + /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined. + /// + private void CompleteBlockIfPossible_Slow() + { + Contract.Requires( + _decliningPermanently && _taskForOutputProcessing == null && _nextMessageReservedFor == null, + "The block must be declining permanently, there must be no reservations, and there must be no processing tasks"); + Common.ContractAssertMonitorStatus(OutgoingLock, held: true); + Common.ContractAssertMonitorStatus(ValueLock, held: true); + + if (_messages.IsEmpty || CanceledOrFaulted) + { + _completionReserved = true; + + // Get out from under currently held locks. This is to avoid + // invoking synchronous continuations off of _completionTask.Task + // while holding a lock. + Task.Factory.StartNew(state => ((SourceCore)state).CompleteBlockOncePossible(), + this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + + /// + /// Completes the block. This must only be called once, and only once all of the completion conditions are met. + /// As such, it must only be called from CompleteBlockIfPossible. + /// + private void CompleteBlockOncePossible() + { + TargetRegistry.LinkedTargetInfo linkedTargets; + List exceptions; + + // Avoid completing while the code that caused this completion to occur is still holding a lock. + // Clear out the target registry and buffers to help avoid memory leaks. + lock (OutgoingLock) + { + // Save the linked list of targets so that it could be traversed later to propagate completion + linkedTargets = _targetRegistry.ClearEntryPoints(); + lock (ValueLock) + { + _messages.Clear(); + + // Save a local reference to the exceptions list and null out the field, + // so that if the target side tries to add an exception this late, + // it will go to a separate list (that will be ignored.) + exceptions = _exceptions; + _exceptions = null; + } + } + + // If it's due to an unhandled exception, finish in an error state + if (exceptions != null) + { + _completionTask.TrySetException(exceptions); + } + // If it's due to cancellation, finish in a canceled state + else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested) + { + _completionTask.TrySetCanceled(); + } + // Otherwise, finish in a successful state. + else + { + _completionTask.TrySetResult(default(VoidResult)); + } + + // Now that the completion task is completed, we may propagate completion to the linked targets + _targetRegistry.PropagateCompletion(linkedTargets); +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.DataflowBlockCompleted(_owningSource); + } +#endif + } + + /// Gets the object to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displaySource = _owningSource as IDebuggerDisplay; + return string.Format("Block=\"{0}\"", + displaySource != null ? displaySource.Content : _owningSource); + } + } + + /// Gets information about this helper to be used for display in a debugger. + /// Debugging information about this source core. + internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); } + + /// Provides debugging information about the source core. + internal sealed class DebuggingInformation + { + /// The source being viewed. + private SourceCore _source; + + /// Initializes the type proxy. + /// The source being viewed. + internal DebuggingInformation(SourceCore source) { _source = source; } + + /// Gets the number of messages available for receiving. + internal int OutputCount { get { return _source._messages.Count; } } + /// Gets the messages available for receiving. + internal IEnumerable OutputQueue { get { return _source._messages.ToList(); } } + /// Gets the task being used for output processing. + internal Task TaskForOutputProcessing { get { return _source._taskForOutputProcessing; } } + + /// Gets the DataflowBlockOptions used to configure this block. + internal DataflowBlockOptions DataflowBlockOptions { get { return _source._dataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + internal bool IsDecliningPermanently { get { return _source._decliningPermanently; } } + /// Gets whether the block is completed. + internal bool IsCompleted { get { return _source.Completion.IsCompleted; } } + + /// Gets the set of all targets linked from this block. + internal TargetRegistry LinkedTargets { get { return _source._targetRegistry; } } + /// Gets the target that holds a reservation on the next message, if any. + internal ITargetBlock NextMessageReservedFor { get { return _source._nextMessageReservedFor; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/SpscTargetCore.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/SpscTargetCore.cs new file mode 100644 index 00000000000..0e036e80e92 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/SpscTargetCore.cs @@ -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. + + /// + /// Provides a core implementation of for use when there's only a single producer posting data. + /// + /// Specifies the type of data accepted by the . + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + internal sealed class SpscTargetCore + { + /// The target block using this helper. + private readonly ITargetBlock _owningTarget; + /// The messages in this target. + private readonly SingleProducerSingleConsumerQueue _messages = new SingleProducerSingleConsumerQueue(); + /// The options to use to configure this block. The target core assumes these options are immutable. + private readonly ExecutionDataflowBlockOptions _dataflowBlockOptions; + /// An action to invoke for every accepted message. + private readonly Action _action; + + /// Exceptions that may have occurred and gone unhandled during processing. This field is lazily initialized. + private volatile List _exceptions; + /// Whether to stop accepting new messages. + private volatile bool _decliningPermanently; + /// A task has reserved the right to run the completion routine. + private volatile bool _completionReserved; + /// + /// 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. + /// + private volatile Task _activeConsumer; + /// A task representing the completion of the block. This field is lazily initialized. + private TaskCompletionSource _completionTask; + + /// Initialize the SPSC target core. + /// The owning target block. + /// The action to be invoked for every message. + /// The options to use to configure this block. The target core assumes these options are immutable. + internal SpscTargetCore( + ITargetBlock owningTarget, Action 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; + } + + /// + internal DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock 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); + } + + /// Implements the slow path for OfferMessage. + /// The message header for the offered value. + /// The offered value. + /// The source offering the message. This may be null. + /// true if we need to call back to the source to consume the message; otherwise, false if we can simply accept it directly. + /// The status of the message. + private DataflowMessageStatus OfferMessage_Slow(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock 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; + } + + /// Schedules a consumer task if there's none currently running. + /// Whether the new consumer is being scheduled to replace a currently running consumer. + [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)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); + } + } + } + + /// Task body used to process messages. + [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(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); + } + } + } + } + } + } + + /// Gets the number of messages waiting to be processed. + internal int InputCount { get { return _messages.Count; } } + + /// + /// 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. + /// + /// The exception to be stored. + 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); + } + } + + /// + /// Ensures the exceptions list is initialized and stores the exception into the list using a lock. + /// + /// The exception to store. + 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())) + { + _exceptions.Add(exception); + } + } + + /// + /// Completes the block. This must only be called once, and only once all of the completion conditions are met. + /// + 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 + } + + /// + internal Task Completion { get { return CompletionSource.Task; } } + + /// Gets the lazily-initialized completion source. + private TaskCompletionSource CompletionSource + { + get { return LazyInitializer.EnsureInitialized(ref _completionTask, () => new TaskCompletionSource()); } + } + + /// Gets the DataflowBlockOptions used to configure this block. + internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } } + + /// Gets information about this helper to be used for display in a debugger. + /// Debugging information about this target. + internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); } + + /// Gets the object to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displayTarget = _owningTarget as IDebuggerDisplay; + return string.Format("Block=\"{0}\"", + displayTarget != null ? displayTarget.Content : _owningTarget); + } + } + + /// Provides a wrapper for commonly needed debugging information. + internal sealed class DebuggingInformation + { + /// The target being viewed. + private readonly SpscTargetCore _target; + + /// Initializes the debugging helper. + /// The target being viewed. + internal DebuggingInformation(SpscTargetCore target) { _target = target; } + + /// Gets the number of messages waiting to be processed. + internal int InputCount { get { return _target.InputCount; } } + /// Gets the messages waiting to be processed. + internal IEnumerable InputQueue { get { return _target._messages.ToList(); } } + + /// Gets the current number of outstanding input processing operations. + internal Int32 CurrentDegreeOfParallelism { get { return _target._activeConsumer != null && !_target.Completion.IsCompleted ? 1 : 0; } } + /// Gets the DataflowBlockOptions used to configure this block. + internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _target._dataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + internal bool IsDecliningPermanently { get { return _target._decliningPermanently; } } + /// Gets whether the block is completed. + internal bool IsCompleted { get { return _target.Completion.IsCompleted; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/TargetCore.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/TargetCore.cs new file mode 100644 index 00000000000..ed1c8069f47 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/TargetCore.cs @@ -0,0 +1,881 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// TargetCore.cs +// +// +// The core implementation of a standard ITargetBlock. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Security; + +namespace System.Threading.Tasks.Dataflow.Internal +{ + // LOCK-LEVELING SCHEME + // -------------------- + // TargetCore employs a single lock: IncomingLock. This lock must not be used when calling out to any targets, + // which TargetCore should not have, anyway. It also must not be held when calling back to any sources, except + // during calls to OfferMessage from that same source. + + /// Options used to configure a target core. + [Flags] + internal enum TargetCoreOptions : byte + { + /// Synchronous completion, both a target and a source, etc. + None = 0x0, + /// Whether the block relies on the delegate to signal when an async operation has completed. + UsesAsyncCompletion = 0x1, + /// + /// Whether the block containing this target core is just a target or also has a source side. + /// If it's just a target, then this target core's completion represents the entire block's completion. + /// + RepresentsBlockCompletion = 0x2 + } + + /// + /// Provides a core implementation of . + /// Specifies the type of data accepted by the . + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + [DebuggerDisplay("{DebuggerDisplayContent,nq}")] + internal sealed class TargetCore + { + // *** These fields are readonly and are initialized at AppDomain startup. + + /// Caching the keep alive predicate. + private static readonly Common.KeepAlivePredicate, KeyValuePair> _keepAlivePredicate = + (TargetCore thisTargetCore, out KeyValuePair messageWithId) => + thisTargetCore.TryGetNextAvailableOrPostponedMessage(out messageWithId); + + // *** These fields are readonly and are initialized to new instances at construction. + + /// A task representing the completion of the block. + private readonly TaskCompletionSource _completionSource = new TaskCompletionSource(); + + // *** These fields are readonly and are initialized by arguments to the constructor. + + /// The target block using this helper. + private readonly ITargetBlock _owningTarget; + /// The messages in this target. + /// This field doubles as the IncomingLock. + private readonly IProducerConsumerQueue> _messages; + /// The options associated with this block. + private readonly ExecutionDataflowBlockOptions _dataflowBlockOptions; + /// An action to invoke for every accepted message. + private readonly Action> _callAction; + /// Whether the block relies on the delegate to signal when an async operation has completed. + private readonly TargetCoreOptions _targetCoreOptions; + /// Bounding state for when the block is executing in bounded mode. + private readonly BoundingStateWithPostponed _boundingState; + /// The reordering buffer used by the owner. May be null. + private readonly IReorderingBuffer _reorderingBuffer; + + /// Gets the object used as the incoming lock. + private object IncomingLock { get { return _messages; } } + + // *** These fields are mutated during execution. + + /// Exceptions that may have occurred and gone unhandled during processing. + private List _exceptions; + /// Whether to stop accepting new messages. + private bool _decliningPermanently; + /// The number of operations (including service tasks) currently running asynchronously. + /// Must always be accessed from inside a lock. + private int _numberOfOutstandingOperations; + /// The number of service tasks in async mode currently running. + /// Must always be accessed from inside a lock. + private int _numberOfOutstandingServiceTasks; + /// The next available ID we can assign to a message about to be processed. + private PaddedInt64 _nextAvailableInputMessageId; // initialized to 0... very important for a reordering buffer + /// A task has reserved the right to run the completion routine. + private bool _completionReserved; + /// This counter is set by the processing loop to prevent itself from trying to keep alive. + private int _keepAliveBanCounter; + + /// Initializes the target core. + /// The target using this helper. + /// An action to invoke for all accepted items. + /// The reordering buffer used by the owner; may be null. + /// The options to use to configure this block. The target core assumes these options are immutable. + /// Options for how the target core should behave. + internal TargetCore( + ITargetBlock owningTarget, + Action> callAction, + IReorderingBuffer reorderingBuffer, + ExecutionDataflowBlockOptions dataflowBlockOptions, + TargetCoreOptions targetCoreOptions) + { + // Validate internal arguments + Contract.Requires(owningTarget != null, "Core must be associated with a target block."); + Contract.Requires(dataflowBlockOptions != null, "Options must be provided to configure the core."); + Contract.Requires(callAction != null, "Action to invoke for each item is required."); + + // Store arguments and do additional initialization + _owningTarget = owningTarget; + _callAction = callAction; + _reorderingBuffer = reorderingBuffer; + _dataflowBlockOptions = dataflowBlockOptions; + _targetCoreOptions = targetCoreOptions; + _messages = (dataflowBlockOptions.MaxDegreeOfParallelism == 1) ? + (IProducerConsumerQueue>)new SingleProducerSingleConsumerQueue>() : + (IProducerConsumerQueue>)new MultiProducerMultiConsumerQueue>(); + if (_dataflowBlockOptions.BoundedCapacity != System.Threading.Tasks.Dataflow.DataflowBlockOptions.Unbounded) + { + Debug.Assert(_dataflowBlockOptions.BoundedCapacity > 0, "Positive bounding count expected; should have been verified by options ctor"); + _boundingState = new BoundingStateWithPostponed(_dataflowBlockOptions.BoundedCapacity); + } + } + + /// Internal Complete entry point with extra parameters for different contexts. + /// If not null, the block will be faulted. + /// If true, any unprocessed input messages will be dropped. + /// If true, an exception will be stored after _decliningPermanently has been set to true. + /// If true, exception will be treated as an AggregateException. + /// Indicates whether the processing state is dirty and has to be reverted. + internal void Complete(Exception exception, bool dropPendingMessages, bool storeExceptionEvenIfAlreadyCompleting = false, + bool unwrapInnerExceptions = false, bool revertProcessingState = false) + { + Contract.Requires(storeExceptionEvenIfAlreadyCompleting || !revertProcessingState, + "Indicating dirty processing state may only come with storeExceptionEvenIfAlreadyCompleting==true."); + Contract.EndContractBlock(); + + // Ensure that no new messages may be added + lock (IncomingLock) + { + // Faulting from outside is allowed until we start declining permanently. + // Faulting from inside is allowed at any time. + if (exception != null && (!_decliningPermanently || storeExceptionEvenIfAlreadyCompleting)) + { + Debug.Assert(_numberOfOutstandingOperations > 0 || !storeExceptionEvenIfAlreadyCompleting, + "Calls with storeExceptionEvenIfAlreadyCompleting==true may only be coming from processing task."); + +#pragma warning disable 0420 + Common.AddException(ref _exceptions, exception, unwrapInnerExceptions); + } + + // Clear the messages queue if requested + if (dropPendingMessages) + { + KeyValuePair dummy; + while (_messages.TryDequeue(out dummy)) ; + } + + // Revert the dirty processing state if requested + if (revertProcessingState) + { + Debug.Assert(_numberOfOutstandingOperations > 0 && (!UsesAsyncCompletion || _numberOfOutstandingServiceTasks > 0), + "The processing state must be dirty when revertProcessingState==true."); + _numberOfOutstandingOperations--; + if (UsesAsyncCompletion) _numberOfOutstandingServiceTasks--; + } + + // Trigger completion + _decliningPermanently = true; + CompleteBlockIfPossible(); + } + } + + /// + internal DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock source, Boolean consumeToAccept) + { + // Validate arguments + if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, "messageHeader"); + if (source == null && consumeToAccept) throw new ArgumentException(SR.Argument_CantConsumeFromANullSource, "consumeToAccept"); + Contract.EndContractBlock(); + + lock (IncomingLock) + { + // If we shouldn't be accepting more messages, don't. + if (_decliningPermanently) + { + CompleteBlockIfPossible(); + return DataflowMessageStatus.DecliningPermanently; + } + + // We can directly accept the message if: + // 1) we are not bounding, OR + // 2) we are bounding AND there is room available AND there are no postponed messages AND no messages are currently being transfered to the input queue. + // (If there were any postponed messages, we would need to postpone so that ordering would be maintained.) + // (Unlike all other blocks, TargetCore can accept messages while processing, because + // input message IDs are properly assigned and the correct order is preserved.) + if (_boundingState == null || + (_boundingState.OutstandingTransfers == 0 && _boundingState.CountIsLessThanBound && _boundingState.PostponedMessages.Count == 0)) + { + // Consume the message from the source if necessary + if (consumeToAccept) + { + Debug.Assert(source != null, "We must have thrown if source == null && consumeToAccept == true."); + + bool consumed; + messageValue = source.ConsumeMessage(messageHeader, _owningTarget, out consumed); + if (!consumed) return DataflowMessageStatus.NotAvailable; + } + + // Assign a message ID - strictly sequential, no gaps. + // Once consumed, enqueue the message with its ID and kick off asynchronous processing. + long messageId = _nextAvailableInputMessageId.Value++; + Debug.Assert(messageId != Common.INVALID_REORDERING_ID, "The assigned message ID is invalid."); + if (_boundingState != null) _boundingState.CurrentCount += 1; // track this new item against our bound + _messages.Enqueue(new KeyValuePair(messageValue, messageId)); + ProcessAsyncIfNecessary(); + return DataflowMessageStatus.Accepted; + } + // Otherwise, we try to postpone if a source was provided + else if (source != null) + { + Debug.Assert(_boundingState != null && _boundingState.PostponedMessages != null, + "PostponedMessages must have been initialized during construction in non-greedy mode."); + + // Store the message's info and kick off asynchronous processing + _boundingState.PostponedMessages.Push(source, messageHeader); + ProcessAsyncIfNecessary(); + return DataflowMessageStatus.Postponed; + } + // We can't do anything else about this message + return DataflowMessageStatus.Declined; + } + } + + /// + internal Task Completion { get { return _completionSource.Task; } } + + /// Gets the number of items waiting to be processed by this target. + internal int InputCount { get { return _messages.GetCountSafe(IncomingLock); } } + + /// Signals to the target core that a previously launched asynchronous operation has now completed. + internal void SignalOneAsyncMessageCompleted() + { + SignalOneAsyncMessageCompleted(boundingCountChange: 0); + } + + /// Signals to the target core that a previously launched asynchronous operation has now completed. + /// The number of elements by which to change the bounding count, if bounding is occurring. + internal void SignalOneAsyncMessageCompleted(int boundingCountChange) + { + lock (IncomingLock) + { + // We're no longer processing, so decrement the DOP counter + Debug.Assert(_numberOfOutstandingOperations > 0, "Operations may only be completed if any are outstanding."); + if (_numberOfOutstandingOperations > 0) _numberOfOutstandingOperations--; + + // Fix up the bounding count if necessary + if (_boundingState != null && boundingCountChange != 0) + { + Debug.Assert(boundingCountChange <= 0 && _boundingState.CurrentCount + boundingCountChange >= 0, + "Expected a negative bounding change and not to drop below zero."); + _boundingState.CurrentCount += boundingCountChange; + } + + // However, we may have given up early because we hit our own configured + // processing limits rather than because we ran out of work to do. If that's + // the case, make sure we spin up another task to keep going. + ProcessAsyncIfNecessary(repeat: true); + + // If, however, we stopped because we ran out of work to do and we + // know we'll never get more, then complete. + CompleteBlockIfPossible(); + } + } + + /// Gets whether this instance has been constructed for async processing. + private bool UsesAsyncCompletion + { + get + { + return (_targetCoreOptions & TargetCoreOptions.UsesAsyncCompletion) != 0; + } + } + + /// Gets whether there's room to launch more processing operations. + private bool HasRoomForMoreOperations + { + get + { + Contract.Requires(_numberOfOutstandingOperations >= 0, "Number of outstanding operations should never be negative."); + Contract.Requires(_numberOfOutstandingServiceTasks >= 0, "Number of outstanding service tasks should never be negative."); + Contract.Requires(_numberOfOutstandingOperations >= _numberOfOutstandingServiceTasks, "Number of outstanding service tasks should never exceed the number of outstanding operations."); + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + // In async mode, we increment _numberOfOutstandingOperations before we start + // our own processing loop which should not count towards the MaxDOP. + return (_numberOfOutstandingOperations - _numberOfOutstandingServiceTasks) < _dataflowBlockOptions.ActualMaxDegreeOfParallelism; + } + } + + /// Gets whether there's room to launch more service tasks for doing/launching processing operations. + private bool HasRoomForMoreServiceTasks + { + get + { + Contract.Requires(_numberOfOutstandingOperations >= 0, "Number of outstanding operations should never be negative."); + Contract.Requires(_numberOfOutstandingServiceTasks >= 0, "Number of outstanding service tasks should never be negative."); + Contract.Requires(_numberOfOutstandingOperations >= _numberOfOutstandingServiceTasks, "Number of outstanding service tasks should never exceed the number of outstanding operations."); + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + if (!UsesAsyncCompletion) + { + // Sync mode: + // We don't count service tasks, because our tasks are counted as operations. + // Therefore, return HasRoomForMoreOperations. + return HasRoomForMoreOperations; + } + else + { + // Async mode: + // We allow up to MaxDOP true service tasks. + // Checking whether there is room for more processing operations is not necessary, + // but doing so will help us avoid spinning up a task that will go away without + // launching any processing operation. + return HasRoomForMoreOperations && + _numberOfOutstandingServiceTasks < _dataflowBlockOptions.ActualMaxDegreeOfParallelism; + } + } + } + + /// Called when new messages are available to be processed. + /// Whether this call is the continuation of a previous message loop. + private void ProcessAsyncIfNecessary(bool repeat = false) + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + if (HasRoomForMoreServiceTasks) + { + ProcessAsyncIfNecessary_Slow(repeat); + } + } + + /// + /// Slow path for ProcessAsyncIfNecessary. + /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined. + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + private void ProcessAsyncIfNecessary_Slow(bool repeat) + { + Contract.Requires(HasRoomForMoreServiceTasks, "There must be room to process asynchronously."); + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + // Determine preconditions to launching a processing task + bool messagesAvailableOrPostponed = + !_messages.IsEmpty || + (!_decliningPermanently && _boundingState != null && _boundingState.CountIsLessThanBound && _boundingState.PostponedMessages.Count > 0); + + // If all conditions are met, launch away + if (messagesAvailableOrPostponed && !CanceledOrFaulted) + { + // Any book keeping related to the processing task like incrementing the + // DOP counter or eventually recording the tasks reference must be done + // before the task starts. That is because the task itself will do the + // reverse operation upon its completion. + _numberOfOutstandingOperations++; + if (UsesAsyncCompletion) _numberOfOutstandingServiceTasks++; + + var taskForInputProcessing = new Task(thisTargetCore => ((TargetCore)thisTargetCore).ProcessMessagesLoopCore(), this, + Common.GetCreationOptionsForTask(repeat)); + +#if FEATURE_TRACING + DataflowEtwProvider etwLog = DataflowEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TaskLaunchedForMessageHandling( + _owningTarget, taskForInputProcessing, DataflowEtwProvider.TaskLaunchedReason.ProcessingInputMessages, + _messages.Count + (_boundingState != null ? _boundingState.PostponedMessages.Count : 0)); + } +#endif + + // Start the task handling scheduling exceptions + Exception exception = Common.StartTaskSafe(taskForInputProcessing, _dataflowBlockOptions.TaskScheduler); + if (exception != null) + { + // Get out from under currently held locks. Complete re-acquires the locks it needs. + Task.Factory.StartNew(exc => Complete(exception: (Exception)exc, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, + unwrapInnerExceptions: false, revertProcessingState: true), + exception, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + } + + /// Task body used to process messages. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ProcessMessagesLoopCore() + { + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + KeyValuePair messageWithId = default(KeyValuePair); + try + { + bool useAsyncCompletion = UsesAsyncCompletion; + bool shouldAttemptPostponedTransfer = _boundingState != null && _boundingState.BoundedCapacity > 1; + int numberOfMessagesProcessedByThisTask = 0; + int numberOfMessagesProcessedSinceTheLastKeepAlive = 0; + int maxMessagesPerTask = _dataflowBlockOptions.ActualMaxMessagesPerTask; + + while (numberOfMessagesProcessedByThisTask < maxMessagesPerTask && !CanceledOrFaulted) + { + // If we're bounding, try to transfer a message from the postponed queue + // to the input queue. This enables us to more quickly unblock sources + // sending data to the block (otherwise, no postponed messages will be consumed + // until the input queue is entirely empty). If the bounded size is 1, + // there's no need to transfer, as attempting to get the next message will + // just go and consume the postponed message anyway, and we'll save + // the extra trip through the _messages queue. + KeyValuePair transferMessageWithId; + if (shouldAttemptPostponedTransfer && + TryConsumePostponedMessage(forPostponementTransfer: true, result: out transferMessageWithId)) + { + lock (IncomingLock) + { + Debug.Assert( + _boundingState.OutstandingTransfers > 0 + && _boundingState.OutstandingTransfers <= _dataflowBlockOptions.ActualMaxDegreeOfParallelism, + "Expected TryConsumePostponedMessage to have incremented the count and for the count to not exceed the DOP."); + _boundingState.OutstandingTransfers--; // was incremented in TryConsumePostponedMessage + _messages.Enqueue(transferMessageWithId); + ProcessAsyncIfNecessary(); + } + } + + if (useAsyncCompletion) + { + // Get the next message if DOP is available. + // If we can't get a message or DOP is not available, bail out. + if (!TryGetNextMessageForNewAsyncOperation(out messageWithId)) break; + } + else + { + // Try to get a message for sequential execution, i.e. without checking DOP availability + if (!TryGetNextAvailableOrPostponedMessage(out messageWithId)) + { + // Try to keep the task alive only if MaxDOP=1 + if (_dataflowBlockOptions.MaxDegreeOfParallelism != 1) break; + + // If this task has processed enough messages without being kept alive, + // it has served its purpose. Don't keep it alive. + if (numberOfMessagesProcessedSinceTheLastKeepAlive > Common.KEEP_ALIVE_NUMBER_OF_MESSAGES_THRESHOLD) break; + + // If keep alive is banned, don't attempt it + if (_keepAliveBanCounter > 0) + { + _keepAliveBanCounter--; + break; + } + + // Reset the keep alive counter. (Keep this line together with TryKeepAliveUntil.) + numberOfMessagesProcessedSinceTheLastKeepAlive = 0; + + // Try to keep the task alive briefly until a new message arrives + if (!Common.TryKeepAliveUntil(_keepAlivePredicate, this, out messageWithId)) + { + // Keep alive was unsuccessful. + // Therefore ban further attempts temporarily. + _keepAliveBanCounter = Common.KEEP_ALIVE_BAN_COUNT; + break; + } + } + } + + // We have popped a message from the queue. + // So increment the counter of processed messages. + numberOfMessagesProcessedByThisTask++; + numberOfMessagesProcessedSinceTheLastKeepAlive++; + + // Invoke the user action + _callAction(messageWithId); + } + } + catch (Exception exc) + { + Common.StoreDataflowMessageValueIntoExceptionData(exc, messageWithId.Key); + Complete(exc, dropPendingMessages: true, storeExceptionEvenIfAlreadyCompleting: true, unwrapInnerExceptions: false); + } + finally + { + lock (IncomingLock) + { + // We incremented _numberOfOutstandingOperations before we launched this task. + // So we must decremented it before exiting. + // Note that each async task additionally incremented it before starting and + // is responsible for decrementing it prior to exiting. + Debug.Assert(_numberOfOutstandingOperations > 0, "Expected a positive number of outstanding operations, since we're completing one here."); + _numberOfOutstandingOperations--; + + // If we are in async mode, we've also incremented _numberOfOutstandingServiceTasks. + // Now it's time to decrement it. + if (UsesAsyncCompletion) + { + Debug.Assert(_numberOfOutstandingServiceTasks > 0, "Expected a positive number of outstanding service tasks, since we're completing one here."); + _numberOfOutstandingServiceTasks--; + } + + // However, we may have given up early because we hit our own configured + // processing limits rather than because we ran out of work to do. If that's + // the case, make sure we spin up another task to keep going. + ProcessAsyncIfNecessary(repeat: true); + + // If, however, we stopped because we ran out of work to do and we + // know we'll never get more, then complete. + CompleteBlockIfPossible(); + } + } + } + + /// Retrieves the next message from the input queue for the useAsyncCompletion mode. + /// The next message retrieved. + /// true if a message was found and removed; otherwise, false. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private bool TryGetNextMessageForNewAsyncOperation(out KeyValuePair messageWithId) + { + Contract.Requires(UsesAsyncCompletion, "Only valid to use when in async mode."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + bool parallelismAvailable; + + lock (IncomingLock) + { + // If we have room for another asynchronous operation, reserve it. + // If later it turns out that we had no work to fill the slot, we'll undo the addition. + parallelismAvailable = HasRoomForMoreOperations; + if (parallelismAvailable) ++_numberOfOutstandingOperations; + } + + messageWithId = default(KeyValuePair); + if (parallelismAvailable) + { + // If a parallelism slot was available, try to get an item. + // Be careful, because an exception may be thrown from ConsumeMessage + // and we have already incremented _numberOfOutstandingOperations. + bool gotMessage = false; + try + { + gotMessage = TryGetNextAvailableOrPostponedMessage(out messageWithId); + } + catch + { + // We have incremented the counter, but we didn't get a message. + // So we must undo the increment and eventually complete the block. + SignalOneAsyncMessageCompleted(); + + // Re-throw the exception. The processing loop will catch it. + throw; + } + + // There may not be an error, but may have still failed to get a message. + // So we must undo the increment and eventually complete the block. + if (!gotMessage) SignalOneAsyncMessageCompleted(); + + return gotMessage; + } + + // If there was no parallelism available, we didn't increment _numberOfOutstandingOperations. + // So there is nothing to do except to return false. + return false; + } + + /// + /// Either takes the next available message from the input queue or retrieves a postponed + /// message from a source, based on whether we're in greedy or non-greedy mode. + /// + /// The retrieved item with its Id. + /// true if a message could be removed and returned; otherwise, false. + private bool TryGetNextAvailableOrPostponedMessage(out KeyValuePair messageWithId) + { + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // First try to get a message from our input buffer. + if (_messages.TryDequeue(out messageWithId)) + { + return true; + } + // If we can't, but if we have any postponed messages due to bounding, then + // try to consume one of these postponed messages. + // Since we are not currently holding the lock, it is possible that new messages get queued up + // by the time we take the lock to manipulate _boundingState. So we have to double-check the + // input queue once we take the lock before we consider postponed messages. + else if (_boundingState != null && TryConsumePostponedMessage(forPostponementTransfer: false, result: out messageWithId)) + { + return true; + } + // Otherwise, there's no message available. + else + { + messageWithId = default(KeyValuePair); + return false; + } + } + + /// Consumes a single postponed message. + /// + /// true if the method is being called to consume a message that'll then be stored into the input queue; + /// false if the method is being called to consume a message that'll be processed immediately. + /// If true, the bounding state's ForcePostponement will be updated. + /// If false, the method will first try (while holding the lock) to consume from the input queue before + /// consuming a postponed message. + /// + /// The consumed message. + /// true if a message was consumed; otherwise, false. + private bool TryConsumePostponedMessage( + bool forPostponementTransfer, + out KeyValuePair result) + { + Contract.Requires( + _dataflowBlockOptions.BoundedCapacity != + System.Threading.Tasks.Dataflow.DataflowBlockOptions.Unbounded, "Only valid to use when in bounded mode."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + + // Iterate until we either consume a message successfully or there are no more postponed messages. + bool countIncrementedExpectingToGetItem = false; + long messageId = Common.INVALID_REORDERING_ID; + while (true) + { + KeyValuePair, DataflowMessageHeader> element; + lock (IncomingLock) + { + // If we are declining permanently, don't consume postponed messages. + if (_decliningPermanently) break; + + // New messages may have been queued up while we weren't holding the lock. + // In particular, the input queue may have been filled up and messages may have + // gotten postponed. If we process such a postponed message, we would mess up the + // order. Therefore, we have to double-check the input queue first. + if (!forPostponementTransfer && _messages.TryDequeue(out result)) return true; + + // We can consume a message to process if there's one to process and also if + // if we have logical room within our bound for the message. + if (!_boundingState.CountIsLessThanBound || !_boundingState.PostponedMessages.TryPop(out element)) + { + if (countIncrementedExpectingToGetItem) + { + countIncrementedExpectingToGetItem = false; + _boundingState.CurrentCount -= 1; + } + break; + } + if (!countIncrementedExpectingToGetItem) + { + countIncrementedExpectingToGetItem = true; + messageId = _nextAvailableInputMessageId.Value++; // optimistically assign an ID + Debug.Assert(messageId != Common.INVALID_REORDERING_ID, "The assigned message ID is invalid."); + _boundingState.CurrentCount += 1; // optimistically take bounding space + if (forPostponementTransfer) + { + Debug.Assert(_boundingState.OutstandingTransfers >= 0, "Expected TryConsumePostponedMessage to not be negative."); + _boundingState.OutstandingTransfers++; // temporarily force postponement until we've successfully consumed the element + } + } + } // Must not call to source while holding lock + + bool consumed; + TInput consumedValue = element.Key.ConsumeMessage(element.Value, _owningTarget, out consumed); + if (consumed) + { + result = new KeyValuePair(consumedValue, messageId); + return true; + } + else + { + if (forPostponementTransfer) + { + // We didn't consume message so we need to decrement because we havent consumed the element. + _boundingState.OutstandingTransfers--; + } + } + } + + // We optimistically acquired a message ID for a message that, in the end, we never got. + // So, we need to let the reordering buffer (if one exists) know that it should not + // expect an item with this ID. Otherwise, it would stall forever. + if (_reorderingBuffer != null && messageId != Common.INVALID_REORDERING_ID) _reorderingBuffer.IgnoreItem(messageId); + + // Similarly, we optimistically increased the bounding count, expecting to get another message in. + // Since we didn't, we need to fix the bounding count back to what it should have been. + if (countIncrementedExpectingToGetItem) ChangeBoundingCount(-1); + + // Inform the caller that no message could be consumed. + result = default(KeyValuePair); + return false; + } + + /// Gets whether the target has had cancellation requested or an exception has occurred. + private bool CanceledOrFaulted + { + get + { + return _dataflowBlockOptions.CancellationToken.IsCancellationRequested || Volatile.Read(ref _exceptions) != null; + } + } + + /// Completes the block once all completion conditions are met. + private void CompleteBlockIfPossible() + { + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + bool noMoreMessages = _decliningPermanently && _messages.IsEmpty; + if (noMoreMessages || CanceledOrFaulted) + { + CompleteBlockIfPossible_Slow(); + } + } + + /// + /// Slow path for CompleteBlockIfPossible. + /// Separating out the slow path into its own method makes it more likely that the fast path method will get inlined. + /// + private void CompleteBlockIfPossible_Slow() + { + Contract.Requires((_decliningPermanently && _messages.IsEmpty) || CanceledOrFaulted, "There must be no more messages."); + Common.ContractAssertMonitorStatus(IncomingLock, held: true); + + bool notCurrentlyProcessing = _numberOfOutstandingOperations == 0; + if (notCurrentlyProcessing && !_completionReserved) + { + // Make sure no one else tries to call CompleteBlockOncePossible + _completionReserved = true; + + // Make sure the target is declining + _decliningPermanently = true; + + // Get out from under currently held locks. This is to avoid + // invoking synchronous continuations off of _completionSource.Task + // while holding a lock. + Task.Factory.StartNew(state => ((TargetCore)state).CompleteBlockOncePossible(), + this, CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default); + } + } + + /// + /// Completes the block. This must only be called once, and only once all of the completion conditions are met. + /// As such, it must only be called from CompleteBlockIfPossible. + /// + private void CompleteBlockOncePossible() + { + // Since the lock is needed only for the Assert, we do this only in DEBUG mode +#if DEBUG + lock (IncomingLock) Debug.Assert(_numberOfOutstandingOperations == 0, "Everything must be done by now."); +#endif + + // Release any postponed messages + if (_boundingState != null) + { + // Note: No locks should be held at this point. + Common.ReleaseAllPostponedMessages(_owningTarget, _boundingState.PostponedMessages, ref _exceptions); + } + + // For good measure and help in preventing leaks, clear out the incoming message queue, + // which may still contain orphaned data if we were canceled or faulted. However, + // we don't reset the bounding count here, as the block as a whole may still be active. + KeyValuePair ignored; + IProducerConsumerQueue> messages = _messages; + while (messages.TryDequeue(out ignored)) ; + + // If we completed with any unhandled exception, finish in an error state + if (Volatile.Read(ref _exceptions) != null) + { + // It's ok to read _exceptions' content here, because + // at this point no more exceptions can be generated and thus no one will + // be writing to it. + _completionSource.TrySetException(Volatile.Read(ref _exceptions)); + } + // If we completed with cancellation, finish in a canceled state + else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested) + { + _completionSource.TrySetCanceled(); + } + // Otherwise, finish in a successful state. + else + { + _completionSource.TrySetResult(default(VoidResult)); + } +#if FEATURE_TRACING + // We only want to do tracing for block completion if this target core represents the whole block. + // If it only represents a part of the block (i.e. there's a source associated with it as well), + // then we shouldn't log just for the first half of the block; the source half will handle logging. + DataflowEtwProvider etwLog; + if ((_targetCoreOptions & TargetCoreOptions.RepresentsBlockCompletion) != 0 && + (etwLog = DataflowEtwProvider.Log).IsEnabled()) + { + etwLog.DataflowBlockCompleted(_owningTarget); + } +#endif + } + + /// Gets whether the target core is operating in a bounded mode. + internal bool IsBounded { get { return _boundingState != null; } } + + /// Increases or decreases the bounding count. + /// The incremental addition (positive to increase, negative to decrease). + internal void ChangeBoundingCount(int count) + { + Contract.Requires(count != 0, "Should only be called when the count is actually changing."); + Common.ContractAssertMonitorStatus(IncomingLock, held: false); + if (_boundingState != null) + { + lock (IncomingLock) + { + Debug.Assert(count > 0 || (count < 0 && _boundingState.CurrentCount + count >= 0), + "If count is negative, it must not take the total count negative."); + _boundingState.CurrentCount += count; + ProcessAsyncIfNecessary(); + CompleteBlockIfPossible(); + } + } + } + + /// Gets the object to display in the debugger display attribute. + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")] + private object DebuggerDisplayContent + { + get + { + var displayTarget = _owningTarget as IDebuggerDisplay; + return string.Format("Block=\"{0}\"", + displayTarget != null ? displayTarget.Content : _owningTarget); + } + } + + /// Gets the DataflowBlockOptions used to configure this block. + internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _dataflowBlockOptions; } } + + /// Gets information about this helper to be used for display in a debugger. + /// Debugging information about this target. + internal DebuggingInformation GetDebuggingInformation() { return new DebuggingInformation(this); } + + /// Provides a wrapper for commonly needed debugging information. + internal sealed class DebuggingInformation + { + /// The target being viewed. + private readonly TargetCore _target; + + /// Initializes the debugging helper. + /// The target being viewed. + internal DebuggingInformation(TargetCore target) { _target = target; } + + /// Gets the number of messages waiting to be processed. + internal int InputCount { get { return _target._messages.Count; } } + /// Gets the messages waiting to be processed. + internal IEnumerable InputQueue { get { return _target._messages.Select(kvp => kvp.Key).ToList(); } } + + /// Gets any postponed messages. + internal QueuedMap, DataflowMessageHeader> PostponedMessages + { + get { return _target._boundingState != null ? _target._boundingState.PostponedMessages : null; } + } + + /// Gets the current number of outstanding input processing operations. + internal Int32 CurrentDegreeOfParallelism { get { return _target._numberOfOutstandingOperations - _target._numberOfOutstandingServiceTasks; } } + + /// Gets the DataflowBlockOptions used to configure this block. + internal ExecutionDataflowBlockOptions DataflowBlockOptions { get { return _target._dataflowBlockOptions; } } + /// Gets whether the block is declining further messages. + internal bool IsDecliningPermanently { get { return _target._decliningPermanently; } } + /// Gets whether the block is completed. + internal bool IsCompleted { get { return _target.Completion.IsCompleted; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/TargetRegistry.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/TargetRegistry.cs new file mode 100644 index 00000000000..ef3035314ca --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/TargetRegistry.cs @@ -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 +{ + /// 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; } } + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Threading.cs b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Threading.cs new file mode 100644 index 00000000000..aa65a5824f2 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Internal/Threading.cs @@ -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)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)s; + tuple.Item1(tuple.Item2); + }, Tuple.Create(callback, state)); + } + } +} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Resources/Strings.resx b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Resources/Strings.resx new file mode 100644 index 00000000000..4e0b7be8d30 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/CoreFxSources/Resources/Strings.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Number must be no greater than the value specified in BoundedCapacity. + + + Number must be positive. + + + Number must be either non-negative and less than or equal to Int32.MaxValue or -1 + + + BoundedCapacity must be Unbounded or -1 for this dataflow block. + + + The argument must be false if no source from which to consume is specified. + + + The DataflowMessageHeader instance does not represent a valid message header. + + + To construct a DataflowMessageHeader instance, either pass a non-zero value or use the parameterless constructor. + + + This block must only be used with the source from which it was created. + + + Greedy must be true for this dataflow block. + + + Block {0} completed as {1}. {2} + + + Block of type {0} instantiated with Id {1}. + + + Source {0} linked to target {1}. + + + Source {0} unlinked from target {1}. + + + {1} task launched from block {0} with {2} message(s) pending. + + + The source completed without providing data to receive. + + + The target block failed to consume a message it had successfully reserved. + + + The target does not have the message reserved. + + + This member is not supported on this dataflow block. The block is intended for a specific purpose that does not utilize this member. + + + The SyncRoot property may not be used for the synchronization of concurrent collections. + + \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/Makefile b/mcs/class/System.Threading.Tasks.Dataflow/Makefile index 83bdafab3cc..349e5c75754 100644 --- a/mcs/class/System.Threading.Tasks.Dataflow/Makefile +++ b/mcs/class/System.Threading.Tasks.Dataflow/Makefile @@ -7,7 +7,8 @@ LIBRARY = System.Threading.Tasks.Dataflow.dll include ../../build/library.make LIB_REFS += System.Core System -LIB_MCS_FLAGS += -r:$(corlib) +LIB_MCS_FLAGS += -r:$(corlib) -d:CONCURRENT_COLLECTIONS TEST_MCS_FLAGS = -r:System.Core.dll -r:System.dll +EXTRA_DISTFILES=README.md diff --git a/mcs/class/System.Threading.Tasks.Dataflow/README.md b/mcs/class/System.Threading.Tasks.Dataflow/README.md new file mode 100644 index 00000000000..aa7e41f9a03 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/README.md @@ -0,0 +1,6 @@ +The CoreFxSources folder contains the implementation taken from MS CoreFx +repository at 905a1940bcda0afdca2f14ceb2b0161ebc4d1d02. + +While we'd ideally not ship this assembly at all with Mono (it doesn't ship +with .NET Framework, there's only as a NuGet package), we shipped it in +the past and as such people might rely on it so we can't remove it. diff --git a/mcs/class/System.Threading.Tasks.Dataflow/SR.cs b/mcs/class/System.Threading.Tasks.Dataflow/SR.cs new file mode 100644 index 00000000000..7113138fbf6 --- /dev/null +++ b/mcs/class/System.Threading.Tasks.Dataflow/SR.cs @@ -0,0 +1,27 @@ +// +// Resource strings referenced by the code. +// +// Copyright 2015 Xamarin Inc +// +// Use the following script to extract strings from CoreFxSources/Resources/Strings.resx +// +// var d = XDocument.Load ("Strings.resx"); +// foreach (var j in d.XPathSelectElements ("/root/data")){ var v = j.XPathSelectElement ("value"); Console.WriteLine ("\tpublic const string {0}=\"{1}\";", j.Attribute ("name").Value, v.Value); } +// +partial class SR +{ + public const string ArgumentOutOfRange_BatchSizeMustBeNoGreaterThanBoundedCapacity="Number must be no greater than the value specified in BoundedCapacity."; + public const string ArgumentOutOfRange_GenericPositive="Number must be positive."; + public const string ArgumentOutOfRange_NeedNonNegOrNegative1="Number must be either non-negative and less than or equal to Int32.MaxValue or -1"; + public const string Argument_BoundedCapacityNotSupported="BoundedCapacity must be Unbounded or -1 for this dataflow block."; + public const string Argument_CantConsumeFromANullSource="The argument must be false if no source from which to consume is specified."; + public const string Argument_InvalidMessageHeader="The DataflowMessageHeader instance does not represent a valid message header."; + public const string Argument_InvalidMessageId="To construct a DataflowMessageHeader instance, either pass a non-zero value or use the parameterless constructor."; + public const string Argument_InvalidSourceForFilteredLink="This block must only be used with the source from which it was created."; + public const string Argument_NonGreedyNotSupported="Greedy must be true for this dataflow block."; + public const string InvalidOperation_DataNotAvailableForReceive="The source completed without providing data to receive."; + public const string InvalidOperation_FailedToConsumeReservedMessage="The target block failed to consume a message it had successfully reserved."; + public const string InvalidOperation_MessageNotReservedByTarget="The target does not have the message reserved."; + public const string NotSupported_MemberNotNeeded="This member is not supported on this dataflow block. The block is intended for a specific purpose that does not utilize this member."; + public const string ConcurrentCollection_SyncRoot_NotSupported="The SyncRoot property may not be used for the synchronization of concurrent collections."; +} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow.dll.sources b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow.dll.sources index 3c479dc8737..c2437d0f8fe 100644 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow.dll.sources +++ b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow.dll.sources @@ -1,49 +1,39 @@ ../../build/common/Consts.cs ../../build/common/Locale.cs ../../build/common/MonoTODOAttribute.cs +../../build/common/SR.cs +SR.cs Assembly/AssemblyInfo.cs -System.Threading.Tasks.Dataflow/ExecutingMessageBox.cs -System.Threading.Tasks.Dataflow/DataflowBlockOptions.cs -System.Threading.Tasks.Dataflow/DataflowMessageHeader.cs -System.Threading.Tasks.Dataflow/DataflowMessageStatus.cs -System.Threading.Tasks.Dataflow/ExecutionDataflowBlockOptions.cs -System.Threading.Tasks.Dataflow/GroupingDataflowBlockOptions.cs -System.Threading.Tasks.Dataflow/DataflowLinkOptions.cs -System.Threading.Tasks.Dataflow/IDataflowBlock.cs -System.Threading.Tasks.Dataflow/IPropagatorBlock.cs -System.Threading.Tasks.Dataflow/IReceivableSourceBlock.cs -System.Threading.Tasks.Dataflow/ISourceBlock.cs -System.Threading.Tasks.Dataflow/ITargetBlock.cs -System.Threading.Tasks.Dataflow/CompletionHelper.cs -System.Threading.Tasks.Dataflow/MessageBox.cs -System.Threading.Tasks.Dataflow/OutgoingQueueBase.cs -System.Threading.Tasks.Dataflow/OutgoingQueue.cs -System.Threading.Tasks.Dataflow/BroadcastOutgoingQueue.cs -System.Threading.Tasks.Dataflow/PassingMessageBox.cs -System.Threading.Tasks.Dataflow/NameHelper.cs -System.Threading.Tasks.Dataflow/TargetCollection.cs -System.Threading.Tasks.Dataflow/JoinTarget.cs -../Mono.Parallel/Mono.Threading/AtomicBoolean.cs -System.Threading.Tasks.Dataflow/ActionBlock.cs -System.Threading.Tasks.Dataflow/BatchBlock.cs -System.Threading.Tasks.Dataflow/BatchedJoinBlock.cs -System.Threading.Tasks.Dataflow/BatchedJoinBlock`3.cs -System.Threading.Tasks.Dataflow/BroadcastBlock.cs -System.Threading.Tasks.Dataflow/BufferBlock.cs -System.Threading.Tasks.Dataflow/ChooserBlock.cs -System.Threading.Tasks.Dataflow/DataflowBlock.cs -System.Threading.Tasks.Dataflow/JoinBlock.cs -System.Threading.Tasks.Dataflow/JoinBlock`3.cs -System.Threading.Tasks.Dataflow/ObservableDataflowBlock.cs -System.Threading.Tasks.Dataflow/ObserverDataflowBlock.cs -System.Threading.Tasks.Dataflow/PropagatorWrapperBlock.cs -System.Threading.Tasks.Dataflow/ReceiveBlock.cs -System.Threading.Tasks.Dataflow/TransformBlock.cs -System.Threading.Tasks.Dataflow/TransformManyBlock.cs -System.Threading.Tasks.Dataflow/WriteOnceBlock.cs -System.Threading.Tasks.Dataflow/SendBlock.cs -System.Threading.Tasks.Dataflow/PredicateBlock.cs -System.Threading.Tasks.Dataflow/OutputAvailableBlock.cs -System.Threading.Tasks.Dataflow/NullTargetBlock.cs -System.Threading.Tasks.Dataflow/AsyncExecutingMessageBox.cs -System.Threading.Tasks.Dataflow/ExecutingMessageBoxBase.cs +CoreFxSources/Base/DataflowBlock.cs +CoreFxSources/Base/DataflowBlockOptions.cs +CoreFxSources/Base/DataflowLinkOptions.cs +CoreFxSources/Base/DataflowMessageHeader.cs +CoreFxSources/Base/DataflowMessageStatus.cs +CoreFxSources/Base/IDataflowBlock.cs +CoreFxSources/Base/IPropagatorBlock.cs +CoreFxSources/Base/IReceivableSourceBlock.cs +CoreFxSources/Base/ISourceBlock.cs +CoreFxSources/Base/ITargetBlock.cs +CoreFxSources/Blocks/ActionBlock.cs +CoreFxSources/Blocks/BatchBlock.cs +CoreFxSources/Blocks/BatchedJoinBlock.cs +CoreFxSources/Blocks/BroadcastBlock.cs +CoreFxSources/Blocks/BufferBlock.cs +CoreFxSources/Blocks/JoinBlock.cs +CoreFxSources/Blocks/TransformBlock.cs +CoreFxSources/Blocks/TransformManyBlock.cs +CoreFxSources/Blocks/WriteOnceBlock.cs +CoreFxSources/Internal/ActionOnDispose.cs +CoreFxSources/Internal/Common.cs +CoreFxSources/Internal/EnumerableDebugView.cs +CoreFxSources/Internal/IDebuggerDisplay.cs +CoreFxSources/Internal/ImmutableList.cs +CoreFxSources/Internal/Padding.cs +CoreFxSources/Internal/ProducerConsumerQueues.cs +CoreFxSources/Internal/QueuedMap.cs +CoreFxSources/Internal/ReorderingBuffer.cs +CoreFxSources/Internal/SourceCore.cs +CoreFxSources/Internal/SpscTargetCore.cs +CoreFxSources/Internal/TargetCore.cs +CoreFxSources/Internal/TargetRegistry.cs +CoreFxSources/Internal/Threading.cs \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ActionBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ActionBlock.cs deleted file mode 100644 index 1b8455c659b..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ActionBlock.cs +++ /dev/null @@ -1,150 +0,0 @@ -// ActionBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - public sealed class ActionBlock : ITargetBlock { - readonly CompletionHelper compHelper; - readonly BlockingCollection messageQueue = new BlockingCollection (); - readonly ExecutingMessageBoxBase messageBox; - readonly Action action; - readonly Func asyncAction; - readonly ExecutionDataflowBlockOptions dataflowBlockOptions; - - ActionBlock (ExecutionDataflowBlockOptions dataflowBlockOptions) - { - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - this.dataflowBlockOptions = dataflowBlockOptions; - this.compHelper = new CompletionHelper (dataflowBlockOptions); - } - - public ActionBlock (Action action) - : this (action, ExecutionDataflowBlockOptions.Default) - { - } - - public ActionBlock (Action action, - ExecutionDataflowBlockOptions dataflowBlockOptions) - : this (dataflowBlockOptions) - { - if (action == null) - throw new ArgumentNullException ("action"); - - this.action = action; - this.messageBox = new ExecutingMessageBox (this, messageQueue, compHelper, - () => true, ProcessItem, () => { }, dataflowBlockOptions); - } - - public ActionBlock (Func action) - : this (action, ExecutionDataflowBlockOptions.Default) - { - } - - public ActionBlock (Func action, - ExecutionDataflowBlockOptions dataflowBlockOptions) - : this (dataflowBlockOptions) - { - if (action == null) - throw new ArgumentNullException ("action"); - - this.asyncAction = action; - this.messageBox = new AsyncExecutingMessageBox ( - this, messageQueue, compHelper, () => true, AsyncProcessItem, null, - () => { }, dataflowBlockOptions); - } - - DataflowMessageStatus ITargetBlock.OfferMessage ( - DataflowMessageHeader messageHeader, TInput messageValue, - ISourceBlock source, bool consumeToAccept) - { - return messageBox.OfferMessage ( - messageHeader, messageValue, source, consumeToAccept); - } - - public bool Post (TInput item) - { - return messageBox.OfferMessage ( - new DataflowMessageHeader (1), item, null, false) - == DataflowMessageStatus.Accepted; - } - - /// - /// Processes one item from the queue if the action is synchronous. - /// - /// Returns whether an item was processed. Returns false if the queue is empty. - bool ProcessItem () - { - TInput data; - bool dequeued = messageQueue.TryTake (out data); - if (dequeued) - action (data); - return dequeued; - } - - /// - /// Processes one item from the queue if the action is asynchronous. - /// - /// The Task that was returned by the synchronous part of the action. - /// Returns whether an item was processed. Returns false if the queue was empty. - bool AsyncProcessItem(out Task task) - { - TInput data; - bool dequeued = messageQueue.TryTake (out data); - if (dequeued) - task = asyncAction (data); - else - task = null; - return dequeued; - } - - public void Complete () - { - messageBox.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { - return compHelper.Completion; - } - } - - public int InputCount { - get { - return messageQueue.Count; - } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/AsyncExecutingMessageBox.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/AsyncExecutingMessageBox.cs deleted file mode 100644 index 3cf41c3f293..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/AsyncExecutingMessageBox.cs +++ /dev/null @@ -1,121 +0,0 @@ -// AsyncExecutingMessageBox.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Message box for executing blocks with asynchrnous - /// (-returning) actions. - /// - /// Type of the item the block is processing. - /// Type of the Task the action is returning. - class AsyncExecutingMessageBox - : ExecutingMessageBoxBase - where TTask : Task { - /// - /// Represents executing synchrnous part of the action. - /// - /// The Task that was returned by the synchronous part of the action. - /// Returns whether an item was processed. Returns false if the queue was empty. - public delegate bool AsyncProcessItem (out TTask task); - - readonly AsyncProcessItem processItem; - readonly Action processFinishedTask; - - public AsyncExecutingMessageBox ( - ITargetBlock target, BlockingCollection messageQueue, - CompletionHelper compHelper, Func externalCompleteTester, - AsyncProcessItem processItem, Action processFinishedTask, - Action outgoingQueueComplete, ExecutionDataflowBlockOptions options) - : base ( - target, messageQueue, compHelper, externalCompleteTester, - outgoingQueueComplete, options) - { - this.processItem = processItem; - this.processFinishedTask = processFinishedTask; - } - - /// - /// Processes the input queue of the block. - /// - protected override void ProcessQueue () - { - StartProcessQueue (); - - ProcessQueueWithoutStart (); - } - - /// - /// The part of specific to asynchronous execution. - /// Handles scheduling continuation on the Task returned by the block's action - /// (or continuing synchrnously if possible). - /// - void ProcessQueueWithoutStart () - { - // catch is needed here, if the Task-returning delegate throws exception itself - try { - int i = 0; - while (CanRun (i)) { - TTask task; - if (!processItem (out task)) - break; - if (task == null || task.IsCanceled - || (task.IsCompleted && !task.IsFaulted)) { - if (processFinishedTask != null) - processFinishedTask (task); - } else if (task.IsFaulted) { - CompHelper.RequestFault (task.Exception, false); - break; - } else { - task.ContinueWith ( - t => TaskFinished ((TTask)t), Options.TaskScheduler); - return; - } - i++; - } - } catch (Exception e) { - CompHelper.RequestFault (e, false); - } - - FinishProcessQueue (); - } - - /// - /// Handles asynchronously finished Task, continues processing the queue. - /// - void TaskFinished (TTask task) - { - if (task.IsFaulted) { - CompHelper.RequestFault (task.Exception, false); - FinishProcessQueue (); - return; - } - - if (processFinishedTask != null) - processFinishedTask (task); - - ProcessQueueWithoutStart (); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchBlock.cs deleted file mode 100644 index 312e5f28038..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchBlock.cs +++ /dev/null @@ -1,376 +0,0 @@ -// BatchBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - public sealed class BatchBlock : IPropagatorBlock, IReceivableSourceBlock { - readonly CompletionHelper compHelper; - readonly BlockingCollection messageQueue = new BlockingCollection (); - readonly MessageBox messageBox; - readonly GroupingDataflowBlockOptions dataflowBlockOptions; - readonly int batchSize; - int batchCount; - long numberOfGroups; - SpinLock batchCountLock; - readonly OutgoingQueue outgoing; - SpinLock batchLock; - readonly AtomicBoolean nonGreedyProcessing = new AtomicBoolean (); - - public BatchBlock (int batchSize) : this (batchSize, GroupingDataflowBlockOptions.Default) - { - } - - public BatchBlock (int batchSize, GroupingDataflowBlockOptions dataflowBlockOptions) - { - if (batchSize <= 0) - throw new ArgumentOutOfRangeException ("batchSize", batchSize, - "The batchSize must be positive."); - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - if (dataflowBlockOptions.BoundedCapacity != -1 - && batchSize > dataflowBlockOptions.BoundedCapacity) - throw new ArgumentOutOfRangeException ("batchSize", - "The batchSize must be smaller than the value of BoundedCapacity."); - - this.batchSize = batchSize; - this.dataflowBlockOptions = dataflowBlockOptions; - this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions); - - Action processQueue; - Func canAccept; - if (dataflowBlockOptions.MaxNumberOfGroups == -1) { - processQueue = newItem => BatchProcess (newItem ? 1 : 0); - canAccept = null; - } else { - processQueue = _ => BatchProcess (); - canAccept = TryAdd; - } - - this.messageBox = new PassingMessageBox (this, messageQueue, compHelper, - () => outgoing.IsCompleted, processQueue, dataflowBlockOptions, - dataflowBlockOptions.Greedy, canAccept); - this.outgoing = new OutgoingQueue (this, compHelper, - () => messageQueue.IsCompleted, messageBox.DecreaseCount, - dataflowBlockOptions, batch => batch.Length); - } - - DataflowMessageStatus ITargetBlock.OfferMessage ( - DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, - bool consumeToAccept) - { - return messageBox.OfferMessage ( - messageHeader, messageValue, source, consumeToAccept); - } - - public IDisposable LinkTo (ITargetBlock target, DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget (target, linkOptions); - } - - T[] ISourceBlock.ConsumeMessage ( - DataflowMessageHeader messageHeader, ITargetBlock target, - out bool messageConsumed) - { - return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - void ISourceBlock.ReleaseReservation ( - DataflowMessageHeader messageHeader, ITargetBlock target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - bool ISourceBlock.ReserveMessage ( - DataflowMessageHeader messageHeader, ITargetBlock target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - public bool TryReceive (Predicate filter, out T[] item) - { - return outgoing.TryReceive (filter, out item); - } - - public bool TryReceiveAll (out IList items) - { - return outgoing.TryReceiveAll (out items); - } - - /// - /// Verifies whether - /// has been reached. If it did, s the block. - /// - void VerifyMaxNumberOfGroups () - { - if (dataflowBlockOptions.MaxNumberOfGroups == -1) - return; - - bool shouldComplete; - - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - shouldComplete = numberOfGroups >= dataflowBlockOptions.MaxNumberOfGroups; - } finally { - if (lockTaken) - batchCountLock.Exit (); - } - - if (shouldComplete) - Complete (); - } - - /// - /// Returns whether a new item can be accepted, and increments a counter if it can. - /// Only makes sense when - /// is not unbounded. - /// - bool TryAdd () - { - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - if (numberOfGroups + batchCount / batchSize - >= dataflowBlockOptions.MaxNumberOfGroups) - return false; - - batchCount++; - return true; - } finally { - if (lockTaken) - batchCountLock.Exit (); - } - } - - public void TriggerBatch () - { - if (dataflowBlockOptions.Greedy) { - int earlyBatchSize; - - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - if (batchCount == 0) - return; - - earlyBatchSize = batchCount; - batchCount = 0; - numberOfGroups++; - } finally { - if (lockTaken) - batchCountLock.Exit (); - } - - MakeBatch (earlyBatchSize); - } else { - if (dataflowBlockOptions.BoundedCapacity == -1 - || outgoing.Count <= dataflowBlockOptions.BoundedCapacity) - EnsureNonGreedyProcessing (true); - } - } - - /// - /// Decides whether to create a new batch or not. - /// - /// - /// Number of newly added items. Used only with greedy processing. - /// - void BatchProcess (int addedItems = 0) - { - if (dataflowBlockOptions.Greedy) { - bool makeBatch = false; - - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - batchCount += addedItems; - - if (batchCount >= batchSize) { - batchCount -= batchSize; - numberOfGroups++; - makeBatch = true; - } - } finally { - if (lockTaken) - batchCountLock.Exit (); - } - - if (makeBatch) - MakeBatch (batchSize); - } else { - if (ShouldProcessNonGreedy ()) - EnsureNonGreedyProcessing (false); - } - } - - /// - /// Returns whether non-greedy creation of a batch should be started. - /// - bool ShouldProcessNonGreedy () - { - // do we have enough items waiting and would the new batch fit? - return messageBox.PostponedMessagesCount >= batchSize - && (dataflowBlockOptions.BoundedCapacity == -1 - || outgoing.Count + batchSize <= dataflowBlockOptions.BoundedCapacity); - } - - /// - /// Creates a batch of the given size and adds the result to the output queue. - /// - void MakeBatch (int size) - { - T[] batch = new T[size]; - - // lock is necessary here to make sure items are in the correct order - bool taken = false; - try { - batchLock.Enter (ref taken); - - for (int i = 0; i < size; ++i) - messageQueue.TryTake (out batch [i]); - } finally { - if (taken) - batchLock.Exit (); - } - - outgoing.AddData (batch); - - VerifyMaxNumberOfGroups (); - } - - /// - /// Starts non-greedy creation of batches, if one doesn't already run. - /// - /// Whether the batch was triggered by . - void EnsureNonGreedyProcessing (bool manuallyTriggered) - { - if (nonGreedyProcessing.TrySet ()) - Task.Factory.StartNew (() => NonGreedyProcess (manuallyTriggered), - dataflowBlockOptions.CancellationToken, - TaskCreationOptions.PreferFairness, - dataflowBlockOptions.TaskScheduler); - } - - /// - /// Creates batches in non-greedy mode, - /// making sure the whole batch is available by using reservations. - /// - /// Whether the batch was triggered by . - void NonGreedyProcess (bool manuallyTriggered) - { - bool first = true; - - do { - var reservations = - new List, DataflowMessageHeader>> (); - - int expectedReservationsCount = messageBox.PostponedMessagesCount; - - if (expectedReservationsCount == 0) - break; - - bool gotReservation; - do { - var reservation = messageBox.ReserveMessage (); - gotReservation = reservation != null; - if (gotReservation) - reservations.Add (reservation); - } while (gotReservation && reservations.Count < batchSize); - - int expectedSize = manuallyTriggered && first - ? Math.Min (expectedReservationsCount, batchSize) - : batchSize; - - if (reservations.Count < expectedSize) { - foreach (var reservation in reservations) - messageBox.RelaseReservation (reservation); - - // some reservations failed, which most likely means the message - // was consumed by someone else and a new one will be offered soon; - // so postpone the batch, so that the other block has time to do that - // (MS .Net does something like this too) - if (manuallyTriggered && first) { - Task.Factory.StartNew (() => NonGreedyProcess (true), - dataflowBlockOptions.CancellationToken, - TaskCreationOptions.PreferFairness, - dataflowBlockOptions.TaskScheduler); - return; - } - } else { - T[] batch = new T[reservations.Count]; - - for (int i = 0; i < reservations.Count; i++) - batch [i] = messageBox.ConsumeReserved (reservations [i]); - - outgoing.AddData (batch); - - // non-greedy doesn't need lock - numberOfGroups++; - - VerifyMaxNumberOfGroups (); - } - - first = false; - } while (ShouldProcessNonGreedy ()); - - nonGreedyProcessing.Value = false; - if (ShouldProcessNonGreedy ()) - EnsureNonGreedyProcessing (false); - } - - public void Complete () - { - messageBox.Complete (); - TriggerBatch (); - outgoing.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { return compHelper.Completion; } - } - - public int OutputCount { - get { return outgoing.Count; } - } - - public int BatchSize { - get { return batchSize; } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchedJoinBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchedJoinBlock.cs deleted file mode 100644 index cfafa02c14f..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchedJoinBlock.cs +++ /dev/null @@ -1,270 +0,0 @@ -// BatchedJoinBlock.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; - -namespace System.Threading.Tasks.Dataflow { - public sealed class BatchedJoinBlock : - IReceivableSourceBlock, IList>> { - readonly GroupingDataflowBlockOptions options; - - readonly CompletionHelper completionHelper; - readonly OutgoingQueue, IList>> outgoing; - SpinLock batchLock; - - readonly JoinTarget target1; - readonly JoinTarget target2; - - int batchCount; - long numberOfGroups; - SpinLock batchCountLock; - - public BatchedJoinBlock (int batchSize) - : this (batchSize, GroupingDataflowBlockOptions.Default) - { - } - - public BatchedJoinBlock (int batchSize, - GroupingDataflowBlockOptions dataflowBlockOptions) - { - if (batchSize <= 0) - throw new ArgumentOutOfRangeException ( - "batchSize", batchSize, "The batchSize must be positive."); - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - if (!dataflowBlockOptions.Greedy) - throw new ArgumentException ( - "Greedy must be true for this dataflow block.", "dataflowBlockOptions"); - if (dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded) - throw new ArgumentException ( - "BoundedCapacity must be Unbounded or -1 for this dataflow block.", - "dataflowBlockOptions"); - - BatchSize = batchSize; - options = dataflowBlockOptions; - completionHelper = CompletionHelper.GetNew (dataflowBlockOptions); - - target1 = new JoinTarget ( - this, SignalTarget, completionHelper, () => outgoing.IsCompleted, - dataflowBlockOptions, true, TryAdd); - target2 = new JoinTarget ( - this, SignalTarget, completionHelper, () => outgoing.IsCompleted, - dataflowBlockOptions, true, TryAdd); - - outgoing = new OutgoingQueue, IList>> ( - this, completionHelper, - () => target1.Buffer.IsCompleted || target2.Buffer.IsCompleted, - _ => - { - target1.DecreaseCount (); - target2.DecreaseCount (); - }, options); - } - - public int BatchSize { get; private set; } - - public ITargetBlock Target1 { - get { return target1; } - } - - public ITargetBlock Target2 { - get { return target2; } - } - - /// - /// Returns whether a new item can be accepted, and increments a counter if it can. - /// - bool TryAdd () - { - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - if (options.MaxNumberOfGroups != -1 - && numberOfGroups + batchCount / BatchSize >= options.MaxNumberOfGroups) - return false; - - batchCount++; - return true; - } finally { - if (lockTaken) - batchCountLock.Exit(); - } - } - - /// - /// Decides whether to create a new batch or not. - /// - void SignalTarget () - { - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - if (batchCount < BatchSize) - return; - - batchCount -= BatchSize; - numberOfGroups++; - } finally { - if (lockTaken) - batchCountLock.Exit(); - } - - MakeBatch (BatchSize); - } - - /// - /// Creates a batch of the given size and adds the resulting batch to the output queue. - /// - void MakeBatch (int batchSize) - { - if (batchSize == 0) - return; - - var list1 = new List (); - var list2 = new List (); - - // lock is necessary here to make sure items are in the correct order - bool taken = false; - try { - batchLock.Enter (ref taken); - - int i = 0; - - T1 item1; - while (i < batchSize && target1.Buffer.TryTake (out item1)) { - list1.Add (item1); - i++; - } - - T2 item2; - while (i < batchSize && target2.Buffer.TryTake (out item2)) { - list2.Add (item2); - i++; - } - - if (i < batchSize) - throw new InvalidOperationException("Unexpected count of items."); - } finally { - if (taken) - batchLock.Exit (); - } - - var batch = Tuple.Create, IList> (list1, list2); - - outgoing.AddData (batch); - - VerifyMaxNumberOfGroups (); - } - - /// - /// Verifies whether - /// has been reached. If it did, s the block. - /// - void VerifyMaxNumberOfGroups () - { - if (options.MaxNumberOfGroups == -1) - return; - - bool shouldComplete; - - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - shouldComplete = numberOfGroups >= options.MaxNumberOfGroups; - } finally { - if (lockTaken) - batchCountLock.Exit (); - } - - if (shouldComplete) - Complete (); - } - - public Task Completion { - get { return completionHelper.Completion; } - } - - public void Complete () - { - target1.Complete (); - target2.Complete (); - MakeBatch (batchCount); - outgoing.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - completionHelper.RequestFault (exception); - } - - Tuple, IList> ISourceBlock, IList>>.ConsumeMessage ( - DataflowMessageHeader messageHeader, - ITargetBlock, IList>> target, - out bool messageConsumed) - { - return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - public IDisposable LinkTo (ITargetBlock, IList>> target, - DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget(target, linkOptions); - } - - void ISourceBlock, IList>>.ReleaseReservation ( - DataflowMessageHeader messageHeader, - ITargetBlock, IList>> target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - bool ISourceBlock, IList>>.ReserveMessage ( - DataflowMessageHeader messageHeader, - ITargetBlock, IList>> target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - public bool TryReceive (Predicate, IList>> filter, - out Tuple, IList> item) - { - return outgoing.TryReceive (filter, out item); - } - - public bool TryReceiveAll (out IList, IList>> items) - { - return outgoing.TryReceiveAll (out items); - } - - public int OutputCount { - get { return outgoing.Count; } - } - - public override string ToString () - { - return NameHelper.GetName (this, options); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchedJoinBlock`3.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchedJoinBlock`3.cs deleted file mode 100644 index 5e8c402688d..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BatchedJoinBlock`3.cs +++ /dev/null @@ -1,294 +0,0 @@ -// BatchedJoinBlock.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; - -namespace System.Threading.Tasks.Dataflow { - public sealed class BatchedJoinBlock : - IReceivableSourceBlock, IList, IList>> { - readonly GroupingDataflowBlockOptions options; - - readonly CompletionHelper completionHelper; - readonly OutgoingQueue, IList, IList>> outgoing; - SpinLock batchLock; - - readonly JoinTarget target1; - readonly JoinTarget target2; - readonly JoinTarget target3; - - int batchCount; - long numberOfGroups; - SpinLock batchCountLock; - - public BatchedJoinBlock (int batchSize) - : this (batchSize, GroupingDataflowBlockOptions.Default) - { - } - - public BatchedJoinBlock (int batchSize, - GroupingDataflowBlockOptions dataflowBlockOptions) - { - if (batchSize <= 0) - throw new ArgumentOutOfRangeException ( - "batchSize", batchSize, "The batchSize must be positive."); - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - if (!dataflowBlockOptions.Greedy) - throw new ArgumentException ( - "Greedy must be true for this dataflow block.", "dataflowBlockOptions"); - if (dataflowBlockOptions.BoundedCapacity != DataflowBlockOptions.Unbounded) - throw new ArgumentException ( - "BoundedCapacity must be Unbounded or -1 for this dataflow block.", - "dataflowBlockOptions"); - - BatchSize = batchSize; - options = dataflowBlockOptions; - completionHelper = CompletionHelper.GetNew (options); - - target1 = new JoinTarget ( - this, SignalTarget, completionHelper, () => outgoing.IsCompleted, - dataflowBlockOptions, true, TryAdd); - target2 = new JoinTarget ( - this, SignalTarget, completionHelper, () => outgoing.IsCompleted, - dataflowBlockOptions, true, TryAdd); - target3 = new JoinTarget ( - this, SignalTarget, completionHelper, () => outgoing.IsCompleted, - dataflowBlockOptions, true, TryAdd); - - outgoing = new OutgoingQueue, IList, IList>> ( - this, completionHelper, - () => target1.Buffer.IsCompleted || target2.Buffer.IsCompleted - || target3.Buffer.IsCompleted, - _ => - { - target1.DecreaseCount (); - target2.DecreaseCount (); - target3.DecreaseCount (); - }, options); - } - - public int BatchSize { get; private set; } - - public ITargetBlock Target1 { - get { return target1; } - } - - public ITargetBlock Target2 { - get { return target2; } - } - - public ITargetBlock Target3 { - get { return target3; } - } - - /// - /// Returns whether a new item can be accepted, and increments a counter if it can. - /// - bool TryAdd () - { - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - if (options.MaxNumberOfGroups != -1 - && numberOfGroups + batchCount / BatchSize >= options.MaxNumberOfGroups) - return false; - - batchCount++; - return true; - } finally { - if (lockTaken) - batchCountLock.Exit (); - } - } - - /// - /// Decides whether to create a new batch or not. - /// - void SignalTarget () - { - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - if (batchCount < BatchSize) - return; - - batchCount -= BatchSize; - numberOfGroups++; - } finally { - if (lockTaken) - batchCountLock.Exit (); - } - - MakeBatch (BatchSize); - } - - /// - /// Creates a batch of the given size and adds the resulting batch to the output queue. - /// - void MakeBatch (int batchSize) - { - if (batchSize == 0) - return; - - var list1 = new List (); - var list2 = new List (); - var list3 = new List (); - - // lock is necessary here to make sure items are in the correct order - bool taken = false; - try { - batchLock.Enter (ref taken); - - int i = 0; - - T1 item1; - while (i < batchSize && target1.Buffer.TryTake (out item1)) { - list1.Add (item1); - i++; - } - - T2 item2; - while (i < batchSize && target2.Buffer.TryTake (out item2)) { - list2.Add (item2); - i++; - } - - T3 item3; - while (i < batchSize && target3.Buffer.TryTake (out item3)) { - list3.Add (item3); - i++; - } - - if (i < batchSize) - throw new InvalidOperationException ("Unexpected count of items."); - } finally { - if (taken) - batchLock.Exit (); - } - - var batch = Tuple.Create, IList, IList> (list1, list2, - list3); - - outgoing.AddData (batch); - - VerifyMaxNumberOfGroups (); - } - - /// - /// Verifies whether - /// has been reached. If it did, s the block. - /// - void VerifyMaxNumberOfGroups () - { - if (options.MaxNumberOfGroups == -1) - return; - - bool shouldComplete; - - bool lockTaken = false; - try { - batchCountLock.Enter (ref lockTaken); - - shouldComplete = numberOfGroups >= options.MaxNumberOfGroups; - } finally { - if (lockTaken) - batchCountLock.Exit (); - } - - if (shouldComplete) - Complete (); - } - - public Task Completion - { - get { return completionHelper.Completion; } - } - - public void Complete () - { - target1.Complete (); - target2.Complete (); - target3.Complete (); - MakeBatch (batchCount); - outgoing.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - completionHelper.RequestFault (exception); - } - - Tuple, IList, IList> - ISourceBlock, IList, IList>>.ConsumeMessage ( - DataflowMessageHeader messageHeader, - ITargetBlock, IList, IList>> target, - out bool messageConsumed) - { - return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - public IDisposable LinkTo ( - ITargetBlock, IList, IList>> target, - DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget (target, linkOptions); - } - - void ISourceBlock, IList, IList>>.ReleaseReservation ( - DataflowMessageHeader messageHeader, - ITargetBlock, IList, IList>> target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - bool ISourceBlock, IList, IList>>.ReserveMessage ( - DataflowMessageHeader messageHeader, - ITargetBlock, IList, IList>> target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - public bool TryReceive ( - Predicate, IList, IList>> filter, - out Tuple, IList, IList> item) - { - return outgoing.TryReceive (filter, out item); - } - - public bool TryReceiveAll ( - out IList, IList, IList>> items) - { - return outgoing.TryReceiveAll (out items); - } - - public int OutputCount { - get { return outgoing.Count; } - } - - public override string ToString () - { - return NameHelper.GetName (this, options); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BroadcastBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BroadcastBlock.cs deleted file mode 100644 index b51d2b37c57..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BroadcastBlock.cs +++ /dev/null @@ -1,146 +0,0 @@ -// BroadcastBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - public sealed class BroadcastBlock : IPropagatorBlock, IReceivableSourceBlock { - readonly CompletionHelper compHelper; - readonly BlockingCollection messageQueue = new BlockingCollection (); - readonly MessageBox messageBox; - readonly DataflowBlockOptions dataflowBlockOptions; - readonly Func cloningFunction; - readonly BroadcastOutgoingQueue outgoing; - - public BroadcastBlock (Func cloningFunction) - : this (cloningFunction, DataflowBlockOptions.Default) - { - } - - public BroadcastBlock (Func cloningFunction, - DataflowBlockOptions dataflowBlockOptions) - { - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - this.cloningFunction = cloningFunction; - this.dataflowBlockOptions = dataflowBlockOptions; - this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions); - this.messageBox = new PassingMessageBox (this, messageQueue, compHelper, - () => outgoing.IsCompleted, _ => BroadcastProcess (), dataflowBlockOptions); - this.outgoing = new BroadcastOutgoingQueue (this, compHelper, - () => messageQueue.IsCompleted, messageBox.DecreaseCount, - dataflowBlockOptions, cloningFunction != null); - } - - DataflowMessageStatus ITargetBlock.OfferMessage ( - DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, - bool consumeToAccept) - { - return messageBox.OfferMessage (messageHeader, messageValue, source, - consumeToAccept); - } - - public IDisposable LinkTo (ITargetBlock target, DataflowLinkOptions linkOptions) - { - if (linkOptions == null) - throw new ArgumentNullException("linkOptions"); - - return outgoing.AddTarget (target, linkOptions); - } - - T ISourceBlock.ConsumeMessage (DataflowMessageHeader messageHeader, - ITargetBlock target, - out bool messageConsumed) - { - T message = outgoing.ConsumeMessage ( - messageHeader, target, out messageConsumed); - if (messageConsumed && cloningFunction != null) - message = cloningFunction (message); - return message; - } - - bool ISourceBlock.ReserveMessage (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - void ISourceBlock.ReleaseReservation (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - public bool TryReceive (Predicate filter, out T item) - { - var received = outgoing.TryReceive (filter, out item); - if (received && cloningFunction != null) - item = cloningFunction (item); - return received; - } - - bool IReceivableSourceBlock.TryReceiveAll (out IList items) - { - T item; - if (!TryReceive (null, out item)) { - items = null; - return false; - } - - items = new[] { item }; - return true; - } - - /// - /// Moves items from the input queue to the output queue. - /// - void BroadcastProcess () - { - T item; - while (messageQueue.TryTake (out item)) - outgoing.AddData (item); - } - - public void Complete () - { - messageBox.Complete (); - outgoing.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { return compHelper.Completion; } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BroadcastOutgoingQueue.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BroadcastOutgoingQueue.cs deleted file mode 100644 index b2e2884502b..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BroadcastOutgoingQueue.cs +++ /dev/null @@ -1,212 +0,0 @@ -// BroadcastOutgoingQueue.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Version of for broadcast blocks. - /// - class BroadcastOutgoingQueue : OutgoingQueueBase { - volatile bool hasCurrentItem; - // don't use directly, only through CurrentItem (and carefully) - T currentItem; - SpinLock currentItemLock = new SpinLock(); - - readonly BroadcastTargetCollection targets; - - protected override TargetCollectionBase Targets { - get { return targets; } - } - - readonly ConcurrentDictionary>, T> - reservedMessages = - new ConcurrentDictionary>, T>(); - - public BroadcastOutgoingQueue ( - ISourceBlock block, CompletionHelper compHelper, - Func externalCompleteTester, Action decreaseItemsCount, - DataflowBlockOptions options, bool hasCloner) - : base (compHelper, externalCompleteTester, decreaseItemsCount, options) - { - targets = new BroadcastTargetCollection (block, hasCloner); - } - - /// - /// The current item that is to be sent to taget blocks. - /// - T CurrentItem { - get { - T item; - bool lockTaken = false; - try { - currentItemLock.Enter (ref lockTaken); - item = currentItem; - } finally { - if (lockTaken) - currentItemLock.Exit (); - } - return item; - } - set { - hasCurrentItem = true; - - bool lockTaken = false; - try { - currentItemLock.Enter (ref lockTaken); - currentItem = value; - } finally { - if (lockTaken) - currentItemLock.Exit (); - } - } - } - - /// - /// Takes an item from the queue and sets it as . - /// - public void DequeueItem () - { - T item; - if (Outgoing.TryTake (out item)) { - DecreaseCounts (item); - targets.SetCurrentItem (item); - - CurrentItem = item; - } - } - - /// - /// Manages sending items to the target blocks. - /// - protected override void Process () - { - do { - ForceProcessing = false; - - DequeueItem (); - - targets.OfferItemToTargets (); - } while (!Store.IsEmpty || targets.NeedsProcessing); - - IsProcessing.Value = false; - - // to guard against race condition - if (ForceProcessing) - EnsureProcessing (); - - VerifyCompleteness (); - } - - public T ConsumeMessage (DataflowMessageHeader messageHeader, - ITargetBlock target, out bool messageConsumed) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (target == null) - throw new ArgumentNullException("target"); - - T item; - if (reservedMessages.TryRemove (Tuple.Create (messageHeader, target), out item)) { - messageConsumed = true; - return item; - } - - // if we first retrieve CurrentItem and then check the header, - // there will be no race condition - - item = CurrentItem; - - if (!targets.VerifyHeader (messageHeader)) { - targets.UnpostponeTargetNotConsumed (target); - - messageConsumed = false; - return default(T); - } - - targets.UnpostponeTargetConsumed (target, messageHeader); - EnsureProcessing (); - - messageConsumed = true; - return item; - } - - public bool ReserveMessage (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (target == null) - throw new ArgumentNullException("target"); - - T item = CurrentItem; - - if (!targets.VerifyHeader (messageHeader)) { - targets.UnpostponeTargetNotConsumed (target); - EnsureProcessing (); - return false; - } - - targets.ReserveTarget (target); - reservedMessages [Tuple.Create (messageHeader, target)] = item; - return true; - } - - public void ReleaseReservation (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (target == null) - throw new ArgumentNullException("target"); - - T item; - if (!reservedMessages.TryRemove (Tuple.Create (messageHeader, target), out item)) - throw new InvalidOperationException ( - "The target did not have the message reserved."); - - targets.UnpostponeTargetNotConsumed (target); - EnsureProcessing (); - } - - public bool TryReceive (Predicate filter, out T retrievedItem) - { - retrievedItem = default(T); - - if (!hasCurrentItem) { - return false; - } - - T item = CurrentItem; - - if (filter == null || filter(item)) { - retrievedItem = item; - return true; - } - - return false; - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BufferBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BufferBlock.cs deleted file mode 100644 index b6b60bf7259..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/BufferBlock.cs +++ /dev/null @@ -1,132 +0,0 @@ -// BufferBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - public sealed class BufferBlock : IPropagatorBlock, IReceivableSourceBlock { - readonly DataflowBlockOptions dataflowBlockOptions; - readonly CompletionHelper compHelper; - readonly MessageBox messageBox; - readonly OutgoingQueue outgoing; - readonly BlockingCollection messageQueue = new BlockingCollection (); - - public BufferBlock () : this (DataflowBlockOptions.Default) - { - } - - public BufferBlock (DataflowBlockOptions dataflowBlockOptions) - { - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - this.dataflowBlockOptions = dataflowBlockOptions; - this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions); - this.messageBox = new PassingMessageBox (this, messageQueue, compHelper, - () => outgoing.IsCompleted, _ => ProcessQueue (), dataflowBlockOptions); - this.outgoing = new OutgoingQueue (this, compHelper, - () => messageQueue.IsCompleted, messageBox.DecreaseCount, - dataflowBlockOptions); - } - - DataflowMessageStatus ITargetBlock.OfferMessage ( - DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, - bool consumeToAccept) - { - return messageBox.OfferMessage (messageHeader, messageValue, source, consumeToAccept); - } - - public IDisposable LinkTo (ITargetBlock target, DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget (target, linkOptions); - } - - T ISourceBlock.ConsumeMessage (DataflowMessageHeader messageHeader, - ITargetBlock target, - out bool messageConsumed) - { - return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - bool ISourceBlock.ReserveMessage (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - void ISourceBlock.ReleaseReservation (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - public bool TryReceive (Predicate filter, out T item) - { - return outgoing.TryReceive (filter, out item); - } - - public bool TryReceiveAll (out IList items) - { - return outgoing.TryReceiveAll (out items); - } - - /// - /// Moves items from the input queue to the output queue. - /// - void ProcessQueue () - { - T item; - while (messageQueue.TryTake (out item)) - outgoing.AddData (item); - } - - public void Complete () - { - messageBox.Complete (); - outgoing.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { - return compHelper.Completion; - } - } - - public int Count { - get { - return outgoing.Count; - } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} - diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ChooserBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ChooserBlock.cs deleted file mode 100644 index bf2d0b03dd6..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ChooserBlock.cs +++ /dev/null @@ -1,179 +0,0 @@ -// JoinBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// Block used in all versions of . - /// - class ChooserBlock { - /// - /// Target for one of the sources to choose from. - /// - class ChooseTarget : ITargetBlock { - readonly ChooserBlock chooserBlock; - readonly int index; - readonly Action action; - - public ChooseTarget (ChooserBlock chooserBlock, - int index, Action action) - { - this.chooserBlock = chooserBlock; - this.index = index; - this.action = action; - } - - public DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, TMessage messageValue, - ISourceBlock source, bool consumeToAccept) - { - if (!chooserBlock.canAccept) - return DataflowMessageStatus.DecliningPermanently; - - bool lockTaken = false; - try { - chooserBlock.messageLock.Enter (ref lockTaken); - if (!chooserBlock.canAccept) - return DataflowMessageStatus.DecliningPermanently; - - if (consumeToAccept) { - bool consummed; - messageValue = source.ConsumeMessage (messageHeader, this, out consummed); - if (!consummed) - return DataflowMessageStatus.NotAvailable; - } - - chooserBlock.canAccept = false; - } finally { - if (lockTaken) - chooserBlock.messageLock.Exit (); - } - - chooserBlock.MessageArrived (index, action, messageValue); - return DataflowMessageStatus.Accepted; - } - - public Task Completion { - get { return null; } - } - - public void Complete () - { - } - - public void Fault (Exception exception) - { - } - } - - readonly TaskCompletionSource completion = new TaskCompletionSource (); - - SpinLock messageLock; - bool canAccept = true; - - public ChooserBlock ( - Action action1, Action action2, Action action3, - DataflowBlockOptions dataflowBlockOptions) - { - Target1 = new ChooseTarget (this, 0, action1); - Target2 = new ChooseTarget (this, 1, action2); - if (action3 != null) - Target3 = new ChooseTarget (this, 2, action3); - - if (dataflowBlockOptions.CancellationToken != CancellationToken.None) - dataflowBlockOptions.CancellationToken.Register (Cancelled); - } - - /// - /// Causes cancellation of . - /// If a message is already being consumed (and the consumsing succeeds) - /// or if its action is being invoked, the Task is not cancelled. - /// - void Cancelled () - { - if (!canAccept) - return; - - bool lockTaken = false; - try { - messageLock.Enter (ref lockTaken); - if (!canAccept) - return; - - completion.SetCanceled (); - - canAccept = false; - } finally { - if (lockTaken) - messageLock.Exit (); - } - } - - /// - /// Called when all sources have completed, - /// causes cancellation of . - /// - public void AllSourcesCompleted () - { - Cancelled (); - } - - /// - /// Called when message has arrived (and was consumed, if necessary). - /// This method can be called only once in the lifetime of this object. - /// - void MessageArrived ( - int index, Action action, TMessage value) - { - try { - action (value); - completion.SetResult (index); - } catch (Exception e) { - completion.SetException (e); - } - } - - /// - /// Target block for the first source block. - /// - public ITargetBlock Target1 { get; private set; } - - /// - /// Target block for the second source block. - /// - public ITargetBlock Target2 { get; private set; } - - /// - /// Target block for the third source block. - /// Is null if there are only two actions. - /// - public ITargetBlock Target3 { get; private set; } - - /// - /// Task that signifies that an item was accepted and - /// its action has been called. - /// - public Task Completion { - get { return completion.Task; } - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/CompletionHelper.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/CompletionHelper.cs deleted file mode 100644 index cc6a0056d6d..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/CompletionHelper.cs +++ /dev/null @@ -1,199 +0,0 @@ -// CompletionHelper.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Used to implement Dataflow completion tracking, - /// that is the Completion property, Complete/Fault method combo - /// and the CancellationToken option. - /// - class CompletionHelper { - readonly TaskCompletionSource source = - new TaskCompletionSource (); - - readonly AtomicBoolean canFaultOrCancelImmediatelly = - new AtomicBoolean { Value = true }; - readonly AtomicBoolean requestedFaultOrCancel = - new AtomicBoolean { Value = false }; - - readonly ConcurrentQueue> requestedExceptions = - new ConcurrentQueue> (); - - public CompletionHelper (DataflowBlockOptions options) - { - if (options != null && options.CancellationToken != CancellationToken.None) - options.CancellationToken.Register (RequestCancel); - } - - [Obsolete ("Use ctor")] - public static CompletionHelper GetNew (DataflowBlockOptions options) - { - return new CompletionHelper (options); - } - - public Task Completion { - get { return source.Task; } - } - - /// - /// Whether can be faulted or cancelled immediatelly. - /// It can't for example when a block is currently executing user action. - /// In that case, the fault (or cancellation) is queued, - /// and is actually acted upon when this property is set back to true. - /// - public bool CanFaultOrCancelImmediatelly { - get { return canFaultOrCancelImmediatelly.Value; } - set { - if (value) { - if (canFaultOrCancelImmediatelly.TrySet () && requestedFaultOrCancel.Value) { - bool canAllBeIgnored = requestedExceptions.All (t => t.Item2); - if (canAllBeIgnored) { - Tuple tuple; - requestedExceptions.TryDequeue (out tuple); - var exception = tuple.Item1; - if (exception == null) - Cancel (); - else - Fault (exception); - } else { - Tuple tuple; - bool first = true; - var exceptions = new List (requestedExceptions.Count); - while (requestedExceptions.TryDequeue (out tuple)) { - var exception = tuple.Item1; - bool canBeIgnored = tuple.Item2; - if (first || !canBeIgnored) { - if (exception != null) - exceptions.Add (exception); - } - first = false; - } - Fault (exceptions); - } - } - } else - canFaultOrCancelImmediatelly.Value = false; - } - } - - /// - /// Whether the block can act as if it's not completed - /// (accept new items, start executing user action). - /// - public bool CanRun { - get { return !Completion.IsCompleted && !requestedFaultOrCancel.Value; } - } - - /// - /// Sets the block as completed. - /// Should be called only when the block is really completed - /// (e.g. the output queue is empty) and not right after - /// the user calls . - /// - public void Complete () - { - source.TrySetResult (null); - } - - /// - /// Requests faulting of the block using a given exception. - /// If the block can't be faulted immediatelly (see ), - /// the exception will be queued, and the block will fault as soon as it can. - /// - /// The exception that is the cause of the fault. - /// Can this exception be ignored, if there are more exceptions? - /// - /// When calling repeatedly, only the first exception counts, - /// even in the cases where the block can't be faulted immediatelly. - /// But exceptions from user actions in execution blocks count always, - /// which is the reason for the parameter. - /// - public void RequestFault (Exception exception, bool canBeIgnored = true) - { - if (exception == null) - throw new ArgumentNullException ("exception"); - - if (CanFaultOrCancelImmediatelly) - Fault (exception); - else { - // still need to store canBeIgnored, if we don't want to add locking here - if (!canBeIgnored || requestedExceptions.Count == 0) - requestedExceptions.Enqueue (Tuple.Create (exception, canBeIgnored)); - requestedFaultOrCancel.Value = true; - } - } - - /// - /// Actually faults the block with a single exception. - /// - /// - /// Should be only called when is true. - /// - void Fault (Exception exception) - { - source.TrySetException (exception); - } - - /// - /// Actually faults the block with a multiple exceptions. - /// - /// - /// Should be only called when is true. - /// - void Fault (IEnumerable exceptions) - { - source.TrySetException (exceptions); - } - - /// - /// Requests cancellation of the block. - /// If the block can't be cancelled immediatelly (see ), - /// the cancellation will be queued, and the block will cancel as soon as it can. - /// - void RequestCancel () - { - if (CanFaultOrCancelImmediatelly) - Cancel (); - else { - if (requestedExceptions.Count == 0) - requestedExceptions.Enqueue (Tuple.Create (null, true)); - requestedFaultOrCancel.Value = true; - } - } - - /// - /// Actually cancels the block. - /// - /// - /// Should be only called when is true. - /// - void Cancel () - { - source.TrySetCanceled (); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowBlock.cs deleted file mode 100644 index e9f23a85897..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowBlock.cs +++ /dev/null @@ -1,311 +0,0 @@ -// DataflowBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public static class DataflowBlock { - public static IObservable AsObservable (this ISourceBlock source) - { - if (source == null) - throw new ArgumentNullException ("source"); - - return new ObservableDataflowBlock (source); - } - - public static IObserver AsObserver (this ITargetBlock target) - { - if (target == null) - throw new ArgumentNullException ("target"); - - return new ObserverDataflowBlock (target); - } - - public static Task Choose ( - ISourceBlock source1, Action action1, - ISourceBlock source2, Action action2) - { - return Choose (source1, action1, source2, action2, - DataflowBlockOptions.Default); - } - - public static Task Choose ( - ISourceBlock source1, Action action1, - ISourceBlock source2, Action action2, - DataflowBlockOptions dataflowBlockOptions) - { - if (source1 == null) - throw new ArgumentNullException ("source1"); - if (source2 == null) - throw new ArgumentNullException ("source2"); - if (action1 == null) - throw new ArgumentNullException ("action1"); - if (action2 == null) - throw new ArgumentNullException ("action2"); - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - var chooser = new ChooserBlock (action1, action2, null, dataflowBlockOptions); - source1.LinkTo (chooser.Target1); - source2.LinkTo (chooser.Target2); - - Task.WhenAll (source1.Completion, source2.Completion) - .ContinueWith (_ => chooser.AllSourcesCompleted ()); - - return chooser.Completion; - } - - public static Task Choose ( - ISourceBlock source1, Action action1, - ISourceBlock source2, Action action2, - ISourceBlock source3, Action action3) - { - return Choose (source1, action1, source2, action2, source3, action3, - DataflowBlockOptions.Default); - } - - public static Task Choose ( - ISourceBlock source1, Action action1, - ISourceBlock source2, Action action2, - ISourceBlock source3, Action action3, - DataflowBlockOptions dataflowBlockOptions) - { - if (source1 == null) - throw new ArgumentNullException ("source1"); - if (source2 == null) - throw new ArgumentNullException ("source2"); - if (source3 == null) - throw new ArgumentNullException ("source3"); - if (action1 == null) - throw new ArgumentNullException ("action1"); - if (action2 == null) - throw new ArgumentNullException ("action2"); - if (action3 == null) - throw new ArgumentNullException ("action3"); - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - var chooser = new ChooserBlock (action1, action2, action3, dataflowBlockOptions); - source1.LinkTo (chooser.Target1); - source2.LinkTo (chooser.Target2); - source3.LinkTo (chooser.Target3); - - Task.WhenAll (source1.Completion, source2.Completion, source3.Completion) - .ContinueWith (_ => chooser.AllSourcesCompleted ()); - - return chooser.Completion; - } - - public static IPropagatorBlock Encapsulate ( - ITargetBlock target, ISourceBlock source) - { - return new PropagatorWrapperBlock (target, source); - } - - public static IDisposable LinkTo (this ISourceBlock source, ITargetBlock target) - { - if (source == null) - throw new ArgumentNullException ("source"); - - return source.LinkTo (target, DataflowLinkOptions.Default); - } - - public static IDisposable LinkTo ( - this ISourceBlock source, ITargetBlock target, - Predicate predicate) - { - if (source == null) - throw new ArgumentNullException ("source"); - - return source.LinkTo (target, DataflowLinkOptions.Default, predicate); - } - - public static IDisposable LinkTo ( - this ISourceBlock source, ITargetBlock target, - DataflowLinkOptions linkOptions, Predicate predicate) - { - if (source == null) - throw new ArgumentNullException ("source"); - if (predicate == null) - throw new ArgumentNullException ("predicate"); - if (target == null) - throw new ArgumentNullException ("target"); - - var predicateBlock = new PredicateBlock (source, target, predicate); - - return source.LinkTo (predicateBlock, linkOptions); - } - - public static Task OutputAvailableAsync ( - this ISourceBlock source) - { - return OutputAvailableAsync (source, CancellationToken.None); - } - - public static Task OutputAvailableAsync ( - this ISourceBlock source, CancellationToken cancellationToken) - { - if (source == null) - throw new ArgumentNullException ("source"); - - cancellationToken.ThrowIfCancellationRequested (); - - if (source.Completion.IsCompleted || source.Completion.IsCanceled - || source.Completion.IsFaulted) - return Task.FromResult (false); - - var block = new OutputAvailableBlock (); - var bridge = source.LinkTo (block, - new DataflowLinkOptions { PropagateCompletion = true }); - return block.AsyncGet (bridge, cancellationToken); - } - - public static bool Post (this ITargetBlock target, TInput item) - { - if (target == null) - throw new ArgumentNullException ("target"); - - return target.OfferMessage (new DataflowMessageHeader(1), item, null, false) - == DataflowMessageStatus.Accepted; - } - - public static TOutput Receive (this ISourceBlock source) - { - return Receive (source, TimeSpan.FromMilliseconds (-1), CancellationToken.None); - } - - public static TOutput Receive (this ISourceBlock source, CancellationToken cancellationToken) - { - return Receive (source, TimeSpan.FromMilliseconds (-1), cancellationToken); - } - - public static TOutput Receive (this ISourceBlock source, TimeSpan timeout) - { - return Receive (source, timeout, CancellationToken.None); - } - - public static TOutput Receive ( - this ISourceBlock source, TimeSpan timeout, - CancellationToken cancellationToken) - { - if (source == null) - throw new ArgumentNullException ("source"); - if (timeout.TotalMilliseconds < -1) - throw new ArgumentOutOfRangeException ("timeout"); - if (timeout.TotalMilliseconds > int.MaxValue) - throw new ArgumentOutOfRangeException ("timeout"); - - cancellationToken.ThrowIfCancellationRequested (); - - TOutput item; - var receivableSource = source as IReceivableSourceBlock; - if (receivableSource != null && receivableSource.TryReceive (null, out item)) - return item; - - if (source.Completion.IsCompleted || source.Completion.IsCanceled - || source.Completion.IsFaulted) - throw new InvalidOperationException ( - "No item could be received from the source."); - - int timeoutMilliseconds = (int)timeout.TotalMilliseconds; - var block = new ReceiveBlock (cancellationToken, timeoutMilliseconds); - var bridge = source.LinkTo (block, - new DataflowLinkOptions { PropagateCompletion = true }); - return block.WaitAndGet (bridge); - } - - public static Task ReceiveAsync (this ISourceBlock source) - { - return ReceiveAsync (source, TimeSpan.FromMilliseconds (-1), CancellationToken.None); - } - - public static Task ReceiveAsync (this ISourceBlock source, CancellationToken cancellationToken) - { - return ReceiveAsync (source, TimeSpan.FromMilliseconds (-1), cancellationToken); - } - - public static Task ReceiveAsync (this ISourceBlock source, TimeSpan timeout) - { - return ReceiveAsync (source, timeout, CancellationToken.None); - } - - public static Task ReceiveAsync ( - this ISourceBlock source, TimeSpan timeout, - CancellationToken cancellationToken) - { - if (source == null) - throw new ArgumentNullException ("source"); - if (timeout.TotalMilliseconds < -1) - throw new ArgumentOutOfRangeException ("timeout"); - if (timeout.TotalMilliseconds > int.MaxValue) - throw new ArgumentOutOfRangeException ("timeout"); - - cancellationToken.ThrowIfCancellationRequested (); - - int timeoutMilliseconds = (int)timeout.TotalMilliseconds; - var block = new ReceiveBlock (cancellationToken, timeoutMilliseconds); - var bridge = source.LinkTo (block); - return block.AsyncGet (bridge); - } - - public static bool TryReceive (this IReceivableSourceBlock source, out TOutput item) - { - item = default (TOutput); - if (source == null) - throw new ArgumentNullException ("source"); - - return source.TryReceive (null, out item); - } - - public static Task SendAsync ( - this ITargetBlock target, TInput item) - { - return SendAsync (target, item, CancellationToken.None); - } - - public static Task SendAsync ( - this ITargetBlock target, TInput item, - CancellationToken cancellationToken) - { - if (target == null) - throw new ArgumentNullException ("target"); - - cancellationToken.ThrowIfCancellationRequested (); - - var status = target.OfferMessage ( - new DataflowMessageHeader (1), item, null, false); - - if (status == DataflowMessageStatus.Accepted) - return Task.FromResult (true); - if (status != DataflowMessageStatus.Declined - && status != DataflowMessageStatus.Postponed) - return Task.FromResult (false); - - var block = new SendBlock (target, item, cancellationToken); - return block.Send (); - } - - public static ITargetBlock NullTarget() - { - return new NullTargetBlock (); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowBlockOptions.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowBlockOptions.cs deleted file mode 100644 index 608e22f1425..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowBlockOptions.cs +++ /dev/null @@ -1,94 +0,0 @@ -// DataflowBlockOptions.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public class DataflowBlockOptions { - static readonly DataflowBlockOptions DefaultOptions = - new DataflowBlockOptions (); - - /// - /// Cached default block options - /// - internal static DataflowBlockOptions Default { - get { return DefaultOptions; } - } - - public const int Unbounded = -1; - - int boundedCapacity; - int maxMessagesPerTask; - TaskScheduler taskScheduler; - string nameFormat; - - public DataflowBlockOptions () - { - BoundedCapacity = -1; - CancellationToken = CancellationToken.None; - MaxMessagesPerTask = -1; - TaskScheduler = TaskScheduler.Default; - NameFormat = "{0} Id={1}"; - } - - public int BoundedCapacity { - get { return boundedCapacity; } - set { - if (value < -1) - throw new ArgumentOutOfRangeException("value"); - - boundedCapacity = value; - } - } - - public CancellationToken CancellationToken { get; set; } - - public int MaxMessagesPerTask { - get { return maxMessagesPerTask; } - set { - if (value < -1) - throw new ArgumentOutOfRangeException("value"); - - maxMessagesPerTask = value; - } - } - - public TaskScheduler TaskScheduler { - get { return taskScheduler; } - set { - if (value == null) - throw new ArgumentNullException("value"); - - taskScheduler = value; - } - } - - public string NameFormat { - get { return nameFormat; } - set { - if (value == null) - throw new ArgumentNullException("value"); - - nameFormat = value; - } - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowLinkOptions.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowLinkOptions.cs deleted file mode 100644 index 6411b2c8031..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowLinkOptions.cs +++ /dev/null @@ -1,55 +0,0 @@ -// DataflowLinkOptions.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public class DataflowLinkOptions { - static readonly DataflowLinkOptions DefaultOptions = - new DataflowLinkOptions (); - - int maxMessages; - - internal static DataflowLinkOptions Default { - get { return DefaultOptions; } - } - - public DataflowLinkOptions() - { - PropagateCompletion = false; - MaxMessages = DataflowBlockOptions.Unbounded; - Append = true; - } - - public bool PropagateCompletion { get; set; } - - public int MaxMessages { - get { return maxMessages; } - set { - if (value < -1) - throw new ArgumentOutOfRangeException("value"); - - maxMessages = value; - } - } - - public bool Append { get; set; } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowMessageHeader.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowMessageHeader.cs deleted file mode 100644 index 38ceb022e61..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowMessageHeader.cs +++ /dev/null @@ -1,73 +0,0 @@ -// DataflowMessageHeader.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public struct DataflowMessageHeader : IEquatable { - readonly long id; - - public DataflowMessageHeader (long id) - { - if (id == 0) - throw new ArgumentException("The value of 0 can't be used as an id for a valid header."); - - this.id = id; - } - - public long Id { - get { - return id; - } - } - - public bool IsValid { - get { - return id != 0; - } - } - - public override bool Equals (object obj) - { - return obj is DataflowMessageHeader && Equals ((DataflowMessageHeader)obj); - } - - public bool Equals (DataflowMessageHeader other) - { - return other.id == id; - } - - public override int GetHashCode () - { - return id.GetHashCode (); - } - - public static bool operator== (DataflowMessageHeader left, DataflowMessageHeader right) - { - return left.Equals (right); - } - - public static bool operator!= (DataflowMessageHeader left, DataflowMessageHeader right) - { - return !left.Equals (right); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowMessageStatus.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowMessageStatus.cs deleted file mode 100644 index 63ec6102b3a..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/DataflowMessageStatus.cs +++ /dev/null @@ -1,41 +0,0 @@ -// DataflowMessageStatus.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// - - -using System; -using System.Threading.Tasks; -using System.Collections.Generic; - -namespace System.Threading.Tasks.Dataflow -{ - public enum DataflowMessageStatus - { - Accepted, - Declined, - Postponed, - NotAvailable, - DecliningPermanently - } -} - diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutingMessageBox.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutingMessageBox.cs deleted file mode 100644 index bbeccb560f9..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutingMessageBox.cs +++ /dev/null @@ -1,67 +0,0 @@ -// ExecutingMessageBox.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Message box for executing blocks with synchrnous actions. - /// - /// Type of the item the block is processing. - class ExecutingMessageBox : ExecutingMessageBoxBase { - readonly Func processItem; - - public ExecutingMessageBox ( - ITargetBlock target, BlockingCollection messageQueue, - CompletionHelper compHelper, Func externalCompleteTester, - Func processItem, Action outgoingQueueComplete, - ExecutionDataflowBlockOptions options) - : base ( - target, messageQueue, compHelper, externalCompleteTester, - outgoingQueueComplete, options) - { - this.processItem = processItem; - } - - /// - /// Processes the input queue of the block. - /// - protected override void ProcessQueue () - { - StartProcessQueue (); - - try { - int i = 0; - while (CanRun (i)) { - if (!processItem ()) - break; - i++; - } - } catch (Exception e) { - CompHelper.RequestFault (e, false); - } - - FinishProcessQueue (); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutingMessageBoxBase.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutingMessageBoxBase.cs deleted file mode 100644 index d6015665b28..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutingMessageBoxBase.cs +++ /dev/null @@ -1,163 +0,0 @@ -// ExecutingMessageBoxBase.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Base message box for execution blocks (synchronous and asynchrnous). - /// - /// Type of the item the block is processing. - abstract class ExecutingMessageBoxBase : MessageBox { - protected ExecutionDataflowBlockOptions Options { get; private set; } - readonly Action outgoingQueueComplete; - - // even number: Task is waiting to run - // odd number: Task is not waiting to run - // invariant: dop / 2 Tasks are running or waiting - int degreeOfParallelism = 1; - - protected ExecutingMessageBoxBase ( - ITargetBlock target, BlockingCollection messageQueue, - CompletionHelper compHelper, Func externalCompleteTester, - Action outgoingQueueComplete, ExecutionDataflowBlockOptions options) - : base ( - target, messageQueue, compHelper, externalCompleteTester, - options) - { - this.Options = options; - this.outgoingQueueComplete = outgoingQueueComplete; - } - - /// - /// Makes sure the input queue is processed the way it needs to. - /// - /// Was new item just added? - protected override void EnsureProcessing (bool newItem) - { - StartProcessing (); - } - - /// - /// Starts processing queue on a task, - /// assuming - /// was't reached yet. - /// - void StartProcessing () - { - // atomically increase degreeOfParallelism by 1 only if it's odd - // and low enough - int startDegreeOfParallelism; - int currentDegreeOfParallelism = degreeOfParallelism; - do { - startDegreeOfParallelism = currentDegreeOfParallelism; - if (startDegreeOfParallelism % 2 == 0 - || (Options.MaxDegreeOfParallelism != DataflowBlockOptions.Unbounded - && startDegreeOfParallelism / 2 >= Options.MaxDegreeOfParallelism)) - return; - currentDegreeOfParallelism = - Interlocked.CompareExchange (ref degreeOfParallelism, - startDegreeOfParallelism + 1, startDegreeOfParallelism); - } while (startDegreeOfParallelism != currentDegreeOfParallelism); - - Task.Factory.StartNew (ProcessQueue, CancellationToken.None, - TaskCreationOptions.PreferFairness, Options.TaskScheduler); - } - - /// - /// Processes the input queue of the block. - /// - /// - /// Should first call , - /// then process the queue and finally call . - /// - protected abstract void ProcessQueue (); - - /// - /// Notifies that another processing task was started. - /// Should be called right after is actually executed. - /// - protected void StartProcessQueue () - { - CompHelper.CanFaultOrCancelImmediatelly = false; - - int incrementedDegreeOfParallelism = - Interlocked.Increment (ref degreeOfParallelism); - if ((Options.MaxDegreeOfParallelism == DataflowBlockOptions.Unbounded - || incrementedDegreeOfParallelism / 2 < Options.MaxDegreeOfParallelism) - && MessageQueue.Count > 1 && CompHelper.CanRun) - StartProcessing (); - } - - /// - /// Notifies that a processing task was finished. - /// Should be called after actually finishes processing. - /// - protected void FinishProcessQueue () - { - int decrementedDegreeOfParallelism = - Interlocked.Add (ref degreeOfParallelism, -2); - - if (decrementedDegreeOfParallelism % 2 == 1) { - if (decrementedDegreeOfParallelism == 1) { - CompHelper.CanFaultOrCancelImmediatelly = true; - base.VerifyCompleteness (); - if (MessageQueue.IsCompleted) - outgoingQueueComplete (); - } - if (MessageQueue.Count > 0 && CompHelper.CanRun) - StartProcessing (); - } - } - - /// - /// Notifies that outgoing queue should be completed, if possible. - /// - protected override void OutgoingQueueComplete () - { - if (MessageQueue.IsCompleted - && Volatile.Read (ref degreeOfParallelism) == 1) - outgoingQueueComplete (); - } - - /// - /// Makes sure the block is completed if it should be. - /// - protected override void VerifyCompleteness () - { - if (Volatile.Read (ref degreeOfParallelism) == 1) - base.VerifyCompleteness (); - } - - /// - /// Indicates whether a processing task can continue executing. - /// - /// The number of the iteration of the task, starting from 0. - protected bool CanRun (int iteration) - { - return CompHelper.CanRun - && (Options.MaxMessagesPerTask == DataflowBlockOptions.Unbounded - || iteration < Options.MaxMessagesPerTask); - } - } -} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutionDataflowBlockOptions.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutionDataflowBlockOptions.cs deleted file mode 100644 index 2b5658208c7..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ExecutionDataflowBlockOptions.cs +++ /dev/null @@ -1,55 +0,0 @@ -// ExecutionDataflowBlockOptions.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public class ExecutionDataflowBlockOptions : DataflowBlockOptions { - static readonly ExecutionDataflowBlockOptions DefaultOptions = - new ExecutionDataflowBlockOptions (); - - int maxDegreeOfParallelism; - - /// - /// Cached default block options - /// - internal static new ExecutionDataflowBlockOptions Default { - get { return DefaultOptions; } - } - - public ExecutionDataflowBlockOptions () - { - MaxDegreeOfParallelism = 1; - } - - public int MaxDegreeOfParallelism { - get { return maxDegreeOfParallelism; } - set { - if (value < -1) - throw new ArgumentOutOfRangeException("value"); - - maxDegreeOfParallelism = value; - } - } - - public bool SingleProducerConstrained { get; set; } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/GroupingDataflowBlockOptions.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/GroupingDataflowBlockOptions.cs deleted file mode 100644 index f7a24d4b6fe..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/GroupingDataflowBlockOptions.cs +++ /dev/null @@ -1,57 +0,0 @@ -// GroupingDataflowBlockOptions.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow -{ - public class GroupingDataflowBlockOptions : DataflowBlockOptions { - static readonly GroupingDataflowBlockOptions DefaultOptions = - new GroupingDataflowBlockOptions (); - - long maxNumberOfGroups; - - /// - /// Cached default block options - /// - internal static new GroupingDataflowBlockOptions Default { - get { return DefaultOptions; } - } - - public GroupingDataflowBlockOptions () - { - Greedy = true; - MaxNumberOfGroups = -1; - } - - public bool Greedy { get; set; } - - public long MaxNumberOfGroups { - get { return maxNumberOfGroups; } - set { - if (value < -1) - throw new ArgumentOutOfRangeException("value"); - - maxNumberOfGroups = value; - } - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IDataflowBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IDataflowBlock.cs deleted file mode 100644 index a6e69b5630e..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IDataflowBlock.cs +++ /dev/null @@ -1,30 +0,0 @@ -// IDataflowBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public interface IDataflowBlock { - Task Completion { get; } - - void Complete (); - void Fault (Exception exception); - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IPropagatorBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IPropagatorBlock.cs deleted file mode 100644 index 0ac3f2be81b..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IPropagatorBlock.cs +++ /dev/null @@ -1,28 +0,0 @@ -// IPropagatorBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public interface IPropagatorBlock - : ITargetBlock, ISourceBlock { - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IReceivableSourceBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IReceivableSourceBlock.cs deleted file mode 100644 index dc35f58cfbb..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/IReceivableSourceBlock.cs +++ /dev/null @@ -1,30 +0,0 @@ -// DataflowBlockOptions.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; - -namespace System.Threading.Tasks.Dataflow { - public interface IReceivableSourceBlock : ISourceBlock { - bool TryReceive (Predicate filter, out TOutput item); - bool TryReceiveAll (out IList items); - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ISourceBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ISourceBlock.cs deleted file mode 100644 index 325a4faf191..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ISourceBlock.cs +++ /dev/null @@ -1,31 +0,0 @@ -// ISourceBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public interface ISourceBlock : IDataflowBlock { - TOutput ConsumeMessage (DataflowMessageHeader messageHeader, ITargetBlock target, out bool messageConsumed); - IDisposable LinkTo (ITargetBlock target, DataflowLinkOptions linkOptions); - void ReleaseReservation (DataflowMessageHeader messageHeader, ITargetBlock target); - bool ReserveMessage (DataflowMessageHeader messageHeader, ITargetBlock target); - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ITargetBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ITargetBlock.cs deleted file mode 100644 index a64569e1bbe..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ITargetBlock.cs +++ /dev/null @@ -1,30 +0,0 @@ -// ITargetBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - public interface ITargetBlock : IDataflowBlock { - DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, TInput messageValue, - ISourceBlock source, bool consumeToAccept); - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinBlock.cs deleted file mode 100644 index fb424fc0e8b..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinBlock.cs +++ /dev/null @@ -1,256 +0,0 @@ -// JoinBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; - -namespace System.Threading.Tasks.Dataflow -{ - public sealed class JoinBlock : IReceivableSourceBlock> - { - readonly CompletionHelper compHelper; - readonly GroupingDataflowBlockOptions dataflowBlockOptions; - readonly OutgoingQueue> outgoing; - - readonly JoinTarget target1; - readonly JoinTarget target2; - - SpinLock targetLock = new SpinLock(false); - readonly AtomicBoolean nonGreedyProcessing = new AtomicBoolean (); - - long target1Count; - long target2Count; - long numberOfGroups; - - public JoinBlock () : this (GroupingDataflowBlockOptions.Default) - { - } - - public JoinBlock (GroupingDataflowBlockOptions dataflowBlockOptions) - { - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - this.dataflowBlockOptions = dataflowBlockOptions; - compHelper = CompletionHelper.GetNew (dataflowBlockOptions); - target1 = new JoinTarget (this, SignalArrivalTarget, compHelper, - () => outgoing.IsCompleted, dataflowBlockOptions, - dataflowBlockOptions.Greedy, TryAdd1); - target2 = new JoinTarget (this, SignalArrivalTarget, compHelper, - () => outgoing.IsCompleted, dataflowBlockOptions, - dataflowBlockOptions.Greedy, TryAdd2); - outgoing = new OutgoingQueue> (this, compHelper, - () => target1.Buffer.IsCompleted || target2.Buffer.IsCompleted, - _ => - { - target1.DecreaseCount (); - target2.DecreaseCount (); - }, dataflowBlockOptions); - } - - public IDisposable LinkTo (ITargetBlock> target, DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget (target, linkOptions); - } - - public bool TryReceive (Predicate> filter, out Tuple item) - { - return outgoing.TryReceive (filter, out item); - } - - public bool TryReceiveAll (out IList> items) - { - return outgoing.TryReceiveAll (out items); - } - - Tuple ISourceBlock>.ConsumeMessage ( - DataflowMessageHeader messageHeader, ITargetBlock> target, - out bool messageConsumed) - { - return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - void ISourceBlock>.ReleaseReservation ( - DataflowMessageHeader messageHeader, ITargetBlock> target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - bool ISourceBlock>.ReserveMessage ( - DataflowMessageHeader messageHeader, ITargetBlock> target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - public void Complete () - { - target1.Complete (); - target2.Complete (); - outgoing.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { return compHelper.Completion; } - } - - /// - /// Returns whether a new item can be accepted by the first target, - /// and increments a counter if it can. - /// - bool TryAdd1 () - { - return dataflowBlockOptions.MaxNumberOfGroups == -1 - || Interlocked.Increment (ref target1Count) - <= dataflowBlockOptions.MaxNumberOfGroups; - } - - /// - /// Returns whether a new item can be accepted by the second target, - /// and increments a counter if it can. - /// - bool TryAdd2 () - { - return dataflowBlockOptions.MaxNumberOfGroups == -1 - || Interlocked.Increment (ref target2Count) - <= dataflowBlockOptions.MaxNumberOfGroups; - } - - /// - /// Decides whether to create a new tuple or not. - /// - void SignalArrivalTarget () - { - if (dataflowBlockOptions.Greedy) { - bool taken = false; - T1 value1; - T2 value2; - - try { - targetLock.Enter (ref taken); - - if (target1.Buffer.Count == 0 || target2.Buffer.Count == 0) - return; - - value1 = target1.Buffer.Take (); - value2 = target2.Buffer.Take (); - } finally { - if (taken) - targetLock.Exit (); - } - - TriggerMessage (value1, value2); - } else { - if (ShouldProcessNonGreedy ()) - EnsureNonGreedyProcessing (); - } - } - - /// - /// Returns whether non-greedy creation of a tuple should be started. - /// - bool ShouldProcessNonGreedy () - { - return target1.PostponedMessagesCount >= 1 - && target2.PostponedMessagesCount >= 1 - && (dataflowBlockOptions.BoundedCapacity == -1 - || outgoing.Count < dataflowBlockOptions.BoundedCapacity); - } - - /// - /// Starts non-greedy creation of tuples, if one doesn't already run. - /// - void EnsureNonGreedyProcessing () - { - if (nonGreedyProcessing.TrySet ()) - Task.Factory.StartNew (NonGreedyProcess, - dataflowBlockOptions.CancellationToken, - TaskCreationOptions.PreferFairness, - dataflowBlockOptions.TaskScheduler); - } - - /// - /// Creates tuples in non-greedy mode, - /// making sure the whole tuple is available by using reservations. - /// - void NonGreedyProcess() - { - while (ShouldProcessNonGreedy ()) { - var reservation1 = target1.ReserveMessage (); - - if (reservation1 == null) - break; - - var reservation2 = target2.ReserveMessage (); - if (reservation2 == null) { - target1.RelaseReservation (reservation1); - break; - } - - var value1 = target1.ConsumeReserved (reservation1); - var value2 = target2.ConsumeReserved (reservation2); - - TriggerMessage (value1, value2); - } - - nonGreedyProcessing.Value = false; - - if (ShouldProcessNonGreedy ()) - EnsureNonGreedyProcessing (); - } - - - /// - /// Creates a tuple from the given values and adds the result to the output queue. - /// - void TriggerMessage (T1 val1, T2 val2) - { - outgoing.AddData (Tuple.Create (val1, val2)); - - if (dataflowBlockOptions.MaxNumberOfGroups != -1 - && Interlocked.Increment (ref numberOfGroups) - >= dataflowBlockOptions.MaxNumberOfGroups) - Complete (); - } - - public ITargetBlock Target1 { - get { return target1; } - } - - public ITargetBlock Target2 { - get { return target2; } - } - - public int OutputCount { - get { return outgoing.Count; } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinBlock`3.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinBlock`3.cs deleted file mode 100644 index 419beb75c60..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinBlock`3.cs +++ /dev/null @@ -1,292 +0,0 @@ -// JoinBlock`3.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; - -namespace System.Threading.Tasks.Dataflow -{ - public sealed class JoinBlock : IReceivableSourceBlock> - { - readonly CompletionHelper compHelper; - readonly GroupingDataflowBlockOptions dataflowBlockOptions; - readonly OutgoingQueue> outgoing; - - readonly JoinTarget target1; - readonly JoinTarget target2; - readonly JoinTarget target3; - - SpinLock targetLock = new SpinLock (false); - readonly AtomicBoolean nonGreedyProcessing = new AtomicBoolean (); - - long target1Count; - long target2Count; - long target3Count; - long numberOfGroups; - - public JoinBlock () : this (GroupingDataflowBlockOptions.Default) - { - } - - public JoinBlock (GroupingDataflowBlockOptions dataflowBlockOptions) - { - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - this.dataflowBlockOptions = dataflowBlockOptions; - this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions); - - target1 = new JoinTarget (this, SignalArrivalTarget, compHelper, - () => outgoing.IsCompleted, dataflowBlockOptions, - dataflowBlockOptions.Greedy, TryAdd1); - target2 = new JoinTarget (this, SignalArrivalTarget, compHelper, - () => outgoing.IsCompleted, dataflowBlockOptions, - dataflowBlockOptions.Greedy, TryAdd2); - target3 = new JoinTarget (this, SignalArrivalTarget, compHelper, - () => outgoing.IsCompleted, dataflowBlockOptions, - dataflowBlockOptions.Greedy, TryAdd3); - outgoing = new OutgoingQueue> ( - this, compHelper, - () => target1.Buffer.IsCompleted || target2.Buffer.IsCompleted - || target3.Buffer.IsCompleted, - _ => - { - target1.DecreaseCount (); - target2.DecreaseCount (); - target3.DecreaseCount (); - }, dataflowBlockOptions); - } - - public IDisposable LinkTo (ITargetBlock> target, DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget (target, linkOptions); - } - - public bool TryReceive (Predicate> filter, out Tuple item) - { - return outgoing.TryReceive (filter, out item); - } - - public bool TryReceiveAll (out IList> items) - { - return outgoing.TryReceiveAll (out items); - } - - Tuple ISourceBlock>.ConsumeMessage ( - DataflowMessageHeader messageHeader, ITargetBlock> target, - out bool messageConsumed) - { - return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - void ISourceBlock>.ReleaseReservation ( - DataflowMessageHeader messageHeader, ITargetBlock> target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - bool ISourceBlock>.ReserveMessage ( - DataflowMessageHeader messageHeader, ITargetBlock> target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - public void Complete () - { - target1.Complete (); - target2.Complete (); - target3.Complete (); - outgoing.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { return compHelper.Completion; } - } - - /// - /// Returns whether a new item can be accepted by the first target, - /// and increments a counter if it can. - /// - bool TryAdd1 () - { - return dataflowBlockOptions.MaxNumberOfGroups == -1 - || Interlocked.Increment (ref target1Count) - <= dataflowBlockOptions.MaxNumberOfGroups; - } - - /// - /// Returns whether a new item can be accepted by the second target, - /// and increments a counter if it can. - /// - bool TryAdd2 () - { - return dataflowBlockOptions.MaxNumberOfGroups == -1 - || Interlocked.Increment (ref target2Count) - <= dataflowBlockOptions.MaxNumberOfGroups; - } - - /// - /// Returns whether a new item can be accepted by the third target, - /// and increments a counter if it can. - /// - bool TryAdd3 () - { - return dataflowBlockOptions.MaxNumberOfGroups == -1 - || Interlocked.Increment (ref target3Count) - <= dataflowBlockOptions.MaxNumberOfGroups; - } - - /// - /// Decides whether to create a new tuple or not. - /// - void SignalArrivalTarget () - { - if (dataflowBlockOptions.Greedy) { - bool taken = false; - T1 value1; - T2 value2; - T3 value3; - - try { - targetLock.Enter (ref taken); - - if (target1.Buffer.Count == 0 || target2.Buffer.Count == 0 - || target3.Buffer.Count == 0) - return; - - value1 = target1.Buffer.Take (); - value2 = target2.Buffer.Take (); - value3 = target3.Buffer.Take (); - } finally { - if (taken) - targetLock.Exit (); - } - - TriggerMessage (value1, value2, value3); - } else { - if (ShouldProcesNonGreedy ()) - EnsureNonGreedyProcessing (); - } - } - - /// - /// Returns whether non-greedy creation of a tuple should be started. - /// - bool ShouldProcesNonGreedy () - { - return target1.PostponedMessagesCount >= 1 - && target2.PostponedMessagesCount >= 1 - && target3.PostponedMessagesCount >= 1 - && (dataflowBlockOptions.BoundedCapacity == -1 - || outgoing.Count < dataflowBlockOptions.BoundedCapacity); - } - - /// - /// Starts non-greedy creation of tuples, if one doesn't already run. - /// - void EnsureNonGreedyProcessing () - { - if (nonGreedyProcessing.TrySet()) - Task.Factory.StartNew (NonGreedyProcess, - dataflowBlockOptions.CancellationToken, - TaskCreationOptions.PreferFairness, - dataflowBlockOptions.TaskScheduler); - } - - /// - /// Creates tuples in non-greedy mode, - /// making sure the whole tuple is available by using reservations. - /// - void NonGreedyProcess () - { - while (ShouldProcesNonGreedy ()) { - var reservation1 = target1.ReserveMessage (); - - if (reservation1 == null) - break; - - var reservation2 = target2.ReserveMessage (); - if (reservation2 == null) { - target1.RelaseReservation (reservation1); - break; - } - - var reservation3 = target3.ReserveMessage (); - if (reservation3 == null) { - target1.RelaseReservation (reservation1); - target2.RelaseReservation (reservation2); - break; - } - - var value1 = target1.ConsumeReserved (reservation1); - var value2 = target2.ConsumeReserved (reservation2); - var value3 = target3.ConsumeReserved (reservation3); - - TriggerMessage (value1, value2, value3); - } - - nonGreedyProcessing.Value = false; - - if (ShouldProcesNonGreedy ()) - EnsureNonGreedyProcessing (); - } - - /// - /// Creates a tuple from the given values and adds the result to the output queue. - /// - void TriggerMessage (T1 val1, T2 val2, T3 val3) - { - outgoing.AddData (Tuple.Create (val1, val2, val3)); - - if (dataflowBlockOptions.MaxNumberOfGroups != -1 - && Interlocked.Increment (ref numberOfGroups) - >= dataflowBlockOptions.MaxNumberOfGroups) - Complete (); - } - - public ITargetBlock Target1 { - get { return target1; } - } - - public ITargetBlock Target2 { - get { return target2; } - } - - public ITargetBlock Target3 { - get { return target3; } - } - - public int OutputCount { - get { return outgoing.Count; } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinTarget.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinTarget.cs deleted file mode 100644 index 48f293e1a47..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/JoinTarget.cs +++ /dev/null @@ -1,84 +0,0 @@ -// JoinBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Target block use by join blocks in their TargetN properties. - /// Also serves as its own . - /// - class JoinTarget : MessageBox, ITargetBlock { - readonly IDataflowBlock joinBlock; - readonly Action signal; - - public JoinTarget ( - IDataflowBlock joinBlock, Action signal, CompletionHelper helper, - Func externalCompleteTester, DataflowBlockOptions options, - bool greedy, Func canAccept) - : base (null, new BlockingCollection (), helper, externalCompleteTester, - options, greedy, canAccept) - { - this.joinBlock = joinBlock; - this.signal = signal; - Target = this; - } - - /// - /// Makes sure the input queue is processed the way it needs to, - /// by signaling the parent join block. - /// - protected override void EnsureProcessing (bool newItem) - { - signal (); - } - - /// - /// The input queue of this block. - /// - public BlockingCollection Buffer { - get { return MessageQueue; } - } - - DataflowMessageStatus ITargetBlock.OfferMessage ( - DataflowMessageHeader messageHeader, TTarget messageValue, - ISourceBlock source, bool consumeToAccept) - { - return OfferMessage (messageHeader, messageValue, source, consumeToAccept); - } - - void IDataflowBlock.Complete () - { - Complete (); - } - - Task IDataflowBlock.Completion { - get { throw new NotSupportedException (); } - } - - void IDataflowBlock.Fault (Exception exception) - { - joinBlock.Fault (exception); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/MessageBox.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/MessageBox.cs deleted file mode 100644 index 867f803a9aa..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/MessageBox.cs +++ /dev/null @@ -1,332 +0,0 @@ -// MessageBox.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; -using System.Linq; - -namespace System.Threading.Tasks.Dataflow { - /// - /// In MessageBox we store message that have been offered to us so that they can be - /// later processed - /// - internal abstract class MessageBox { - protected ITargetBlock Target { get; set; } - protected CompletionHelper CompHelper { get; private set; } - readonly Func externalCompleteTester; - readonly DataflowBlockOptions options; - readonly bool greedy; - readonly Func canAccept; - - readonly ConcurrentDictionary, DataflowMessageHeader> - postponedMessages = - new ConcurrentDictionary, DataflowMessageHeader> (); - int itemCount; - readonly AtomicBoolean postponedProcessing = new AtomicBoolean (); - - // these two fields are used only in one special case - SpinLock consumingLock; - // this is necessary, because canAccept is not pure - bool canAcceptFromBefore; - - protected BlockingCollection MessageQueue { get; private set; } - - protected MessageBox ( - ITargetBlock target, BlockingCollection messageQueue, - CompletionHelper compHelper, Func externalCompleteTester, - DataflowBlockOptions options, bool greedy = true, Func canAccept = null) - { - this.Target = target; - this.CompHelper = compHelper; - this.MessageQueue = messageQueue; - this.externalCompleteTester = externalCompleteTester; - this.options = options; - this.greedy = greedy; - this.canAccept = canAccept; - } - - public DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, TInput messageValue, - ISourceBlock source, bool consumeToAccept) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (consumeToAccept && source == null) - throw new ArgumentException ( - "consumeToAccept may only be true if provided with a non-null source.", - "consumeToAccept"); - - if (MessageQueue.IsAddingCompleted || !CompHelper.CanRun) - return DataflowMessageStatus.DecliningPermanently; - - var full = options.BoundedCapacity != -1 - && Volatile.Read (ref itemCount) >= options.BoundedCapacity; - if (!greedy || full) { - if (source == null) - return DataflowMessageStatus.Declined; - - postponedMessages [source] = messageHeader; - - // necessary to avoid race condition - DecreaseCount (0); - - if (!greedy && !full) - EnsureProcessing (true); - - return DataflowMessageStatus.Postponed; - } - - // in this case, we need to use locking to make sure - // we don't consume when we can't accept - if (consumeToAccept && canAccept != null) { - bool lockTaken = false; - try { - consumingLock.Enter (ref lockTaken); - if (!canAcceptFromBefore && !canAccept ()) - return DataflowMessageStatus.DecliningPermanently; - - bool consummed; - messageValue = source.ConsumeMessage (messageHeader, Target, out consummed); - if (!consummed) { - canAcceptFromBefore = true; - return DataflowMessageStatus.NotAvailable; - } - - canAcceptFromBefore = false; - } finally { - if (lockTaken) - consumingLock.Exit (); - } - } else { - if (consumeToAccept) { - bool consummed; - messageValue = source.ConsumeMessage (messageHeader, Target, out consummed); - if (!consummed) - return DataflowMessageStatus.NotAvailable; - } - - if (canAccept != null && !canAccept ()) - return DataflowMessageStatus.DecliningPermanently; - } - - try { - MessageQueue.Add (messageValue); - } catch (InvalidOperationException) { - // This is triggered either if the underlying collection didn't accept the item - // or if the messageQueue has been marked complete, either way it corresponds to a false - return DataflowMessageStatus.DecliningPermanently; - } - - IncreaseCount (); - - EnsureProcessing (true); - - VerifyCompleteness (); - - return DataflowMessageStatus.Accepted; - } - - /// - /// Increses the count of items in the block by 1. - /// - public void IncreaseCount () - { - Interlocked.Increment (ref itemCount); - } - - /// - /// Decreses the number of items in the block by the given count. - /// - /// - /// The parameter is used when one object - /// can represent many items, like a batch in . - /// - public void DecreaseCount (int count = 1) - { - int decreased = Interlocked.Add (ref itemCount, -count); - - // if BoundedCapacity is -1, there is no need to do this - if (decreased < options.BoundedCapacity && !postponedMessages.IsEmpty) { - if (greedy) - EnsurePostponedProcessing (); - else - EnsureProcessing (false); - } - } - - /// - /// The number of messages that were postponed - /// and can be attempted to be consumed. - /// - public int PostponedMessagesCount { - get { return postponedMessages.Count; } - } - - /// - /// Reserves a message from those that were postponed. - /// Does not guarantee any order of the messages being reserved. - /// - /// - /// An object representing the reservation on success, - /// null on failure. - /// - public Tuple, DataflowMessageHeader> ReserveMessage() - { - while (!postponedMessages.IsEmpty) { - // KeyValuePair is a struct, so default value is not null - var block = postponedMessages.FirstOrDefault () .Key; - - // collection is empty - if (block == null) - break; - - DataflowMessageHeader header; - bool removed = postponedMessages.TryRemove (block, out header); - - // another thread was faster, try again - if (!removed) - continue; - - bool reserved = block.ReserveMessage (header, Target); - if (reserved) - return Tuple.Create (block, header); - } - - return null; - } - - /// - /// Releases the given reservation. - /// - public void RelaseReservation(Tuple, DataflowMessageHeader> reservation) - { - reservation.Item1.ReleaseReservation (reservation.Item2, Target); - } - - /// - /// Consumes previously reserved item. - /// - public TInput ConsumeReserved(Tuple, DataflowMessageHeader> reservation) - { - bool consumed; - return reservation.Item1.ConsumeMessage ( - reservation.Item2, Target, out consumed); - } - - /// - /// Makes sure retrieving items that were postponed, - /// because they would exceed , - /// is currently running. - /// - void EnsurePostponedProcessing () - { - if (postponedProcessing.TrySet()) - Task.Factory.StartNew (RetrievePostponed, options.CancellationToken, - TaskCreationOptions.PreferFairness, options.TaskScheduler); - } - - /// - /// Retrieves items that were postponed, - /// because they would exceed . - /// - void RetrievePostponed () - { - // BoundedCapacity can't be -1 here, because in that case there would be no postponing - while (Volatile.Read (ref itemCount) < options.BoundedCapacity - && !postponedMessages.IsEmpty && !MessageQueue.IsAddingCompleted) { - var block = postponedMessages.First ().Key; - DataflowMessageHeader header; - postponedMessages.TryRemove (block, out header); - - bool consumed; - var item = block.ConsumeMessage (header, Target, out consumed); - if (consumed) { - try { - MessageQueue.Add (item); - IncreaseCount (); - EnsureProcessing (false); - } catch (InvalidOperationException) { - break; - } - } - } - - // release all postponed messages - if (MessageQueue.IsAddingCompleted) { - while (!postponedMessages.IsEmpty) { - var block = postponedMessages.First ().Key; - DataflowMessageHeader header; - postponedMessages.TryRemove (block, out header); - - if (block.ReserveMessage (header, Target)) - block.ReleaseReservation (header, Target); - } - } - - postponedProcessing.Value = false; - - // because of race - if ((Volatile.Read (ref itemCount) < options.BoundedCapacity - || MessageQueue.IsAddingCompleted) - && !postponedMessages.IsEmpty) - EnsurePostponedProcessing (); - } - - /// - /// Makes sure the input queue is processed the way it needs to. - /// - /// Was new item just added? - protected abstract void EnsureProcessing (bool newItem); - - /// - /// Completes the box, no new messages will be accepted. - /// Also starts the process of completing the output queue. - /// - public void Complete () - { - // Make message queue complete - MessageQueue.CompleteAdding (); - OutgoingQueueComplete (); - VerifyCompleteness (); - - if (!postponedMessages.IsEmpty) - EnsurePostponedProcessing (); - } - - /// - /// Notifies that outgoing queue should be completed, if possible. - /// - protected virtual void OutgoingQueueComplete () - { - } - - /// - /// Makes sure the block is completed if it should be. - /// - protected virtual void VerifyCompleteness () - { - if (MessageQueue.IsCompleted && externalCompleteTester ()) - CompHelper.Complete (); - } - } -} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/NameHelper.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/NameHelper.cs deleted file mode 100644 index 8c7022415b8..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/NameHelper.cs +++ /dev/null @@ -1,44 +0,0 @@ -// NameHelper.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// Helper class for figuring out the name of a block. - /// - static class NameHelper { - /// - /// Returns the name of the block, based on . - /// - /// - /// If the NameFormat is invalid, returns the exception message. - /// - public static string GetName(IDataflowBlock block, DataflowBlockOptions options) - { - try { - return string.Format ( - options.NameFormat, block.GetType ().Name, block.Completion.Id); - } catch (FormatException e) { - return e.Message; - } - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/NullTargetBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/NullTargetBlock.cs deleted file mode 100644 index 2b89b5b124c..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/NullTargetBlock.cs +++ /dev/null @@ -1,67 +0,0 @@ -// NullTargetBlock.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// Target block returned by . - /// - class NullTargetBlock : ITargetBlock { - public NullTargetBlock () - { - Completion = new TaskCompletionSource ().Task; - } - - public DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, TInput messageValue, - ISourceBlock source, bool consumeToAccept) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (consumeToAccept && source == null) - throw new ArgumentException ( - "consumeToAccept may only be true if provided with a non-null source.", - "consumeToAccept"); - - if (consumeToAccept) { - if (!source.ReserveMessage (messageHeader, this)) - return DataflowMessageStatus.NotAvailable; - bool consummed; - source.ConsumeMessage (messageHeader, this, out consummed); - if (!consummed) - return DataflowMessageStatus.NotAvailable; - } - - return DataflowMessageStatus.Accepted; - } - - public Task Completion { get; private set; } - - public void Complete () - { - } - - public void Fault (Exception exception) - { - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ObservableDataflowBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ObservableDataflowBlock.cs deleted file mode 100644 index e82d1306b4a..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ObservableDataflowBlock.cs +++ /dev/null @@ -1,83 +0,0 @@ -// ObservableDataflowBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// Rx Observable that represents a source block. - /// - class ObservableDataflowBlock : IObservable { - class ObserverWrapper : ITargetBlock { - readonly IObserver observer; - - public ObserverWrapper (IObserver observer) - { - this.observer = observer; - } - - public void Complete () - { - observer.OnCompleted (); - } - - public void Fault (Exception exception) - { - observer.OnError (exception); - } - - public Task Completion { - get { return null; } - } - - public DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, TSource messageValue, - ISourceBlock source, bool consumeToAccept) - { - if (consumeToAccept) { - if (!source.ReserveMessage (messageHeader, this)) - return DataflowMessageStatus.NotAvailable; - bool consumed; - messageValue = source.ConsumeMessage (messageHeader, this, out consumed); - if (!consumed) - return DataflowMessageStatus.NotAvailable; - } - - observer.OnNext (messageValue); - - return DataflowMessageStatus.Accepted; - } - } - - readonly ISourceBlock source; - - public ObservableDataflowBlock (ISourceBlock source) - { - this.source = source; - } - - public IDisposable Subscribe (IObserver observer) - { - var wrapper = new ObserverWrapper (observer); - return source.LinkTo (wrapper); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ObserverDataflowBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ObserverDataflowBlock.cs deleted file mode 100644 index 55f3b6e8e4f..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ObserverDataflowBlock.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ObserverDataflowBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// Rx Observer that represents a target block. - /// - class ObserverDataflowBlock : IObserver { - readonly ITargetBlock target; - - public ObserverDataflowBlock (ITargetBlock target) - { - this.target = target; - } - - public void OnCompleted () - { - target.Complete (); - } - - public void OnError (Exception ex) - { - target.Fault (ex); - } - - public void OnNext (TInput value) - { - target.Post (value); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutgoingQueue.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutgoingQueue.cs deleted file mode 100644 index aa273ab2f16..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutgoingQueue.cs +++ /dev/null @@ -1,281 +0,0 @@ -// OutgoingQueue.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Version of for - /// non-broadcast blocks. - /// - class OutgoingQueue : OutgoingQueueBase { - readonly Func countSelector; - SpinLock firstItemLock = new SpinLock(); - volatile ITargetBlock reservedForTargetBlock; - readonly TargetCollection targets; - - protected override TargetCollectionBase Targets { - get { return targets; } - } - - public OutgoingQueue ( - ISourceBlock block, CompletionHelper compHelper, - Func externalCompleteTester, Action decreaseItemsCount, - DataflowBlockOptions options, Func countSelector = null) - : base (compHelper, externalCompleteTester, - decreaseItemsCount, options) - { - targets = new TargetCollection (block); - this.countSelector = countSelector; - } - - /// - /// Calculates the count of items in the given object. - /// - protected override int GetModifiedCount(T data) - { - if (countSelector == null) - return 1; - - return countSelector (data); - } - - /// - /// Sends messages to targets. - /// - protected override void Process () - { - bool processed; - do { - ForceProcessing = false; - - bool lockTaken = false; - try { - firstItemLock.Enter (ref lockTaken); - - T item; - if (!Store.TryPeek (out item)) - break; - - if (!targets.HasCurrentItem) - targets.SetCurrentItem (item); - - if (reservedForTargetBlock != null) - break; - - processed = targets.OfferItemToTargets (); - if (processed) { - Outgoing.TryTake (out item); - DecreaseCounts (item); - FirstItemChanged (); - } - } finally { - if (lockTaken) - firstItemLock.Exit (); - } - } while (processed); - - IsProcessing.Value = false; - - // to guard against race condition - if (ForceProcessing && reservedForTargetBlock == null) - EnsureProcessing (); - - VerifyCompleteness (); - } - - public T ConsumeMessage (DataflowMessageHeader messageHeader, - ITargetBlock targetBlock, out bool messageConsumed) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (targetBlock == null) - throw new ArgumentNullException("target"); - - T result = default(T); - messageConsumed = false; - - bool lockTaken = false; - try { - firstItemLock.Enter (ref lockTaken); - - if (targets.VerifyHeader (messageHeader, targetBlock) - && (reservedForTargetBlock == null - || reservedForTargetBlock == targetBlock)) { - // cannot consume from faulted block, unless reserved - if (reservedForTargetBlock == null && IsFaultedOrCancelled) - return result; - - Outgoing.TryTake (out result); - messageConsumed = true; - DecreaseCounts (result); - reservedForTargetBlock = null; - FirstItemChanged (); - } - } finally { - if (lockTaken) - firstItemLock.Exit (); - } - - targets.UnpostponeTarget (targetBlock, messageConsumed); - EnsureProcessing (); - VerifyCompleteness (); - - return result; - } - - public bool ReserveMessage (DataflowMessageHeader messageHeader, ITargetBlock target) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (target == null) - throw new ArgumentNullException("target"); - - bool lockTaken = false; - try { - firstItemLock.Enter (ref lockTaken); - - if (targets.VerifyHeader(messageHeader, target)) { - reservedForTargetBlock = target; - return true; - } - - targets.UnpostponeTarget (target, false); - EnsureProcessing (); - - return false; - } finally { - if (lockTaken) - firstItemLock.Exit (); - } - } - - public void ReleaseReservation (DataflowMessageHeader messageHeader, ITargetBlock target) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (target == null) - throw new ArgumentNullException("target"); - - bool lockTaken = false; - try - { - firstItemLock.Enter(ref lockTaken); - - if (!targets.VerifyHeader(messageHeader, target) - || reservedForTargetBlock != target) - throw new InvalidOperationException( - "The target did not have the message reserved."); - - reservedForTargetBlock = null; - } finally { - if (lockTaken) - firstItemLock.Exit (); - } - - targets.UnpostponeTarget (target, false); - EnsureProcessing (); - } - - /// - /// Notifies that the first item in the queue changed. - /// - void FirstItemChanged () - { - T firstItem; - if (Store.TryPeek (out firstItem)) - targets.SetCurrentItem (firstItem); - else - targets.ResetCurrentItem (); - } - - public bool TryReceive (Predicate filter, out T item) - { - bool success = false; - item = default (T); - - bool lockTaken = false; - try { - firstItemLock.Enter (ref lockTaken); - - if (reservedForTargetBlock != null) - return false; - - T result; - if (Store.TryPeek (out result) && (filter == null || filter (result))) { - Outgoing.TryTake (out item); - success = true; - DecreaseCounts (item); - FirstItemChanged (); - } - } finally { - if (lockTaken) - firstItemLock.Exit (); - } - - EnsureProcessing (); - VerifyCompleteness (); - - return success; - } - - public bool TryReceiveAll (out IList items) - { - items = null; - - if (Store.IsEmpty) - return false; - - bool lockTaken = false; - try { - firstItemLock.Enter (ref lockTaken); - - if (reservedForTargetBlock != null) - return false; - - var list = new List (Outgoing.Count); - - T item; - while (Outgoing.TryTake (out item)) { - DecreaseCounts (item); - list.Add (item); - } - - items = list; - - FirstItemChanged (); - } finally { - if (lockTaken) - firstItemLock.Exit (); - } - - EnsureProcessing (); - VerifyCompleteness (); - - return items.Count > 0; - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutgoingQueueBase.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutgoingQueueBase.cs deleted file mode 100644 index b1c1c294870..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutgoingQueueBase.cs +++ /dev/null @@ -1,177 +0,0 @@ -// OutgoingQueueBase.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Handles outgoing messages that get queued when there is no - /// block on the other end to proces it. It also allows receive operations. - /// - abstract class OutgoingQueueBase { - protected ConcurrentQueue Store { get; private set; } - protected BlockingCollection Outgoing { get; private set; } - int outgoingCount; - readonly CompletionHelper compHelper; - readonly Func externalCompleteTester; - readonly DataflowBlockOptions options; - protected AtomicBoolean IsProcessing { get; private set; } - protected abstract TargetCollectionBase Targets { get; } - int totalModifiedCount; - readonly Action decreaseItemsCount; - volatile bool forceProcessing; - - protected OutgoingQueueBase ( - CompletionHelper compHelper, Func externalCompleteTester, - Action decreaseItemsCount, DataflowBlockOptions options) - { - IsProcessing = new AtomicBoolean (); - Store = new ConcurrentQueue (); - Outgoing = new BlockingCollection (Store); - this.compHelper = compHelper; - this.externalCompleteTester = externalCompleteTester; - this.options = options; - this.decreaseItemsCount = decreaseItemsCount; - } - - /// - /// Is the queue completed? - /// Queue is completed after is called - /// and all items are retrieved from it. - /// - public bool IsCompleted { - get { return Outgoing.IsCompleted; } - } - - /// - /// Current number of items in the queue. - /// Item are counted the way - /// counts them, e.g. each item in a batch counts, even if batch is a single object. - /// - public int Count { - get { return totalModifiedCount; } - } - - /// - /// Calculates the count of items in the given object. - /// - protected virtual int GetModifiedCount (T data) - { - return 1; - } - - /// - /// Adds an object to the queue. - /// - public void AddData (T data) - { - try { - Outgoing.Add (data); - Interlocked.Add (ref totalModifiedCount, GetModifiedCount (data)); - if (Interlocked.Increment (ref outgoingCount) == 1) - EnsureProcessing (); - } catch (InvalidOperationException) { - VerifyCompleteness (); - } - } - - /// - /// Makes sure sending messages to targets is running. - /// - protected void EnsureProcessing () - { - ForceProcessing = true; - if (IsProcessing.TrySet()) - Task.Factory.StartNew (Process, CancellationToken.None, - TaskCreationOptions.PreferFairness, options.TaskScheduler); - } - - /// - /// Indicates whether sending messages should be forced to start. - /// - protected bool ForceProcessing { - get { return forceProcessing; } - set { forceProcessing = value; } - } - - /// - /// Sends messages to targets. - /// - protected abstract void Process (); - - /// - /// Adds a target block to send messages to. - /// - /// - /// An object that can be used to destroy the link to the added target. - /// - public IDisposable AddTarget (ITargetBlock targetBlock, DataflowLinkOptions linkOptions) - { - if (targetBlock == null) - throw new ArgumentNullException ("targetBlock"); - if (linkOptions == null) - throw new ArgumentNullException ("linkOptions"); - - var result = Targets.AddTarget (targetBlock, linkOptions); - EnsureProcessing (); - return result; - } - - /// - /// Makes sure the block is completed if it should be. - /// - protected void VerifyCompleteness () - { - if (Outgoing.IsCompleted && externalCompleteTester ()) - compHelper.Complete (); - } - - /// - /// Is the block faulted or cancelled? - /// - protected bool IsFaultedOrCancelled { - get { return compHelper.Completion.IsFaulted || compHelper.Completion.IsCanceled; } - } - - /// - /// Used to notify that object was removed from the queue - /// and to update counts. - /// - protected void DecreaseCounts (T data) - { - var modifiedCount = GetModifiedCount (data); - Interlocked.Add (ref totalModifiedCount, -modifiedCount); - Interlocked.Decrement (ref outgoingCount); - decreaseItemsCount (modifiedCount); - } - - /// - /// Marks the queue for completion. - /// - public void Complete () - { - Outgoing.CompleteAdding (); - VerifyCompleteness (); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutputAvailableBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutputAvailableBlock.cs deleted file mode 100644 index b53be90dcc1..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/OutputAvailableBlock.cs +++ /dev/null @@ -1,97 +0,0 @@ -// OutputAvailableBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// This internal block is used by the methods - /// to check for available items in an asynchronous way. - /// - class OutputAvailableBlock : ITargetBlock { - readonly TaskCompletionSource completion = - new TaskCompletionSource (); - IDisposable linkBridge; - CancellationTokenRegistration cancellationRegistration; - - public DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, TOutput messageValue, - ISourceBlock source, bool consumeToAccept) - { - if (!messageHeader.IsValid) - return DataflowMessageStatus.Declined; - - if (completion.Task.Status != TaskStatus.WaitingForActivation) - return DataflowMessageStatus.DecliningPermanently; - - completion.TrySetResult (true); - CompletionSet (); - - return DataflowMessageStatus.DecliningPermanently; - } - - /// - /// Returns a Task that can be used to wait until output from a block is available. - /// - /// The disposable object returned by . - /// Cancellation token for this operation. - public Task AsyncGet (IDisposable bridge, CancellationToken token) - { - linkBridge = bridge; - cancellationRegistration = token.Register (() => - { - completion.TrySetCanceled (); - CompletionSet (); - }); - - return completion.Task; - } - - /// - /// Called after the result has been set, - /// cleans up after this block. - /// - void CompletionSet () - { - if (linkBridge != null) { - linkBridge.Dispose (); - linkBridge = null; - } - - cancellationRegistration.Dispose (); - } - - public Task Completion { - get { throw new NotSupportedException (); } - } - - public void Complete () - { - completion.TrySetResult (false); - CompletionSet (); - } - - public void Fault (Exception exception) - { - Complete (); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PassingMessageBox.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PassingMessageBox.cs deleted file mode 100644 index 962a0baf387..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PassingMessageBox.cs +++ /dev/null @@ -1,54 +0,0 @@ -// PassingMessageBox.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Message box for blocks that don't need any special processing of incoming items. - /// - class PassingMessageBox : MessageBox { - readonly Action processQueue; - - public PassingMessageBox ( - ITargetBlock target, BlockingCollection messageQueue, - CompletionHelper compHelper, Func externalCompleteTester, - Action processQueue, DataflowBlockOptions dataflowBlockOptions, - bool greedy = true, Func canAccept = null) - : base (target, messageQueue, compHelper, externalCompleteTester, - dataflowBlockOptions, greedy, canAccept) - { - this.processQueue = processQueue; - } - - /// - /// Makes sure the input queue is processed the way it needs to. - /// Executes synchronously, so shouldn't cause any long processing. - /// - /// Was new item just added? - protected override void EnsureProcessing (bool newItem) - { - processQueue (newItem); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PredicateBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PredicateBlock.cs deleted file mode 100644 index 60cfdb7ea17..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PredicateBlock.cs +++ /dev/null @@ -1,131 +0,0 @@ -// PredicateBlock.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// This block is used by the version of - /// that has a predicate to wrap the target block, - /// so that the predicate can be checked. - /// - class PredicateBlock : ITargetBlock { - /// - /// Wraps the source block of the link. - /// This is necessary so that the communication from target to source works correctly. - /// - class SourceBlock : ISourceBlock { - readonly ISourceBlock actualSource; - readonly PredicateBlock predicateBlock; - - public SourceBlock (ISourceBlock actualSource, - PredicateBlock predicateBlock) - { - this.actualSource = actualSource; - this.predicateBlock = predicateBlock; - } - - public Task Completion - { - get { return actualSource.Completion; } - } - - public void Complete () - { - actualSource.Complete (); - } - - public void Fault (Exception exception) - { - actualSource.Fault (exception); - } - - public T ConsumeMessage (DataflowMessageHeader messageHeader, - ITargetBlock target, out bool messageConsumed) - { - return actualSource.ConsumeMessage (messageHeader, predicateBlock, - out messageConsumed); - } - - public IDisposable LinkTo (ITargetBlock target, - DataflowLinkOptions linkOptions) - { - return actualSource.LinkTo (target, linkOptions); - } - - public void ReleaseReservation (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - actualSource.ReleaseReservation (messageHeader, predicateBlock); - } - - public bool ReserveMessage (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - return actualSource.ReserveMessage (messageHeader, predicateBlock); - } - } - - readonly ITargetBlock actualTarget; - readonly Predicate predicate; - readonly SourceBlock sourceBlock; - - public PredicateBlock (ISourceBlock actualSource, - ITargetBlock actualTarget, Predicate predicate) - { - this.actualTarget = actualTarget; - this.predicate = predicate; - sourceBlock = new SourceBlock (actualSource, this); - } - - public DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, T messageValue, ISourceBlock source, - bool consumeToAccept) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (consumeToAccept && source == null) - throw new ArgumentException ( - "consumeToAccept may only be true if provided with a non-null source.", - "consumeToAccept"); - - if (!predicate(messageValue)) - return DataflowMessageStatus.Declined; - - return actualTarget.OfferMessage (messageHeader, messageValue, sourceBlock, - consumeToAccept); - } - - public Task Completion { - get { return actualTarget.Completion; } - } - - public void Complete () - { - actualTarget.Complete (); - } - - public void Fault (Exception exception) - { - actualTarget.Fault (exception); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PropagatorWrapperBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PropagatorWrapperBlock.cs deleted file mode 100644 index 42b45d16b6c..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/PropagatorWrapperBlock.cs +++ /dev/null @@ -1,92 +0,0 @@ -// PropagatorWrapperBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// Block returned by . - /// - class PropagatorWrapperBlock : - IPropagatorBlock { - readonly ITargetBlock targetBlock; - readonly ISourceBlock sourceBlock; - - public PropagatorWrapperBlock ( - ITargetBlock target, ISourceBlock source) - { - if (target == null) - throw new ArgumentNullException ("target"); - if (source == null) - throw new ArgumentNullException ("source"); - - targetBlock = target; - sourceBlock = source; - } - - public DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, TInput messageValue, - ISourceBlock source, bool consumeToAccept) - { - return targetBlock.OfferMessage ( - messageHeader, messageValue, source, consumeToAccept); - } - - public TOutput ConsumeMessage ( - DataflowMessageHeader messageHeader, ITargetBlock target, - out bool messageConsumed) - { - return sourceBlock.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - public IDisposable LinkTo ( - ITargetBlock target, DataflowLinkOptions linkOptions) - { - return sourceBlock.LinkTo (target, linkOptions); - } - - public void ReleaseReservation ( - DataflowMessageHeader messageHeader, ITargetBlock target) - { - sourceBlock.ReleaseReservation (messageHeader, target); - } - - public bool ReserveMessage ( - DataflowMessageHeader messageHeader, ITargetBlock target) - { - return sourceBlock.ReserveMessage (messageHeader, target); - } - - public void Complete () - { - targetBlock.Complete (); - } - - public void Fault (Exception exception) - { - targetBlock.Fault (exception); - } - - public Task Completion { - get { return sourceBlock.Completion; } - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ReceiveBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ReceiveBlock.cs deleted file mode 100644 index 1e632e51464..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/ReceiveBlock.cs +++ /dev/null @@ -1,149 +0,0 @@ -// ReceiveBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// This internal block is used by the methods - /// to retrieve elements in either blocking or asynchronous way. - /// - class ReceiveBlock : ITargetBlock { - readonly TaskCompletionSource completion = - new TaskCompletionSource (); - - readonly CancellationToken token; - CancellationTokenRegistration cancellationRegistration; - readonly Timer timeoutTimer; - - IDisposable linkBridge; - - public ReceiveBlock (CancellationToken token, int timeout) - { - this.token = token; - cancellationRegistration = token.Register (() => - { - lock (completion) { - completion.TrySetCanceled (); - } - CompletionSet (); - }); - timeoutTimer = new Timer ( - _ => - { - lock (completion) { - completion.TrySetException (new TimeoutException ()); - } - CompletionSet (); - }, null, timeout, - Timeout.Infinite); - } - - public DataflowMessageStatus OfferMessage ( - DataflowMessageHeader messageHeader, TOutput messageValue, - ISourceBlock source, bool consumeToAccept) - { - if (!messageHeader.IsValid) - return DataflowMessageStatus.Declined; - - if (completion.Task.Status != TaskStatus.WaitingForActivation) - return DataflowMessageStatus.DecliningPermanently; - - lock (completion) { - if (completion.Task.Status != TaskStatus.WaitingForActivation) - return DataflowMessageStatus.DecliningPermanently; - - if (consumeToAccept) { - bool consummed; - if (!source.ReserveMessage (messageHeader, this)) - return DataflowMessageStatus.NotAvailable; - messageValue = source.ConsumeMessage (messageHeader, this, out consummed); - if (!consummed) - return DataflowMessageStatus.NotAvailable; - } - - completion.TrySetResult (messageValue); - } - CompletionSet (); - return DataflowMessageStatus.Accepted; - } - - /// - /// Synchronously waits until an item is available. - /// - /// The disposable object returned by . - public TOutput WaitAndGet (IDisposable bridge) - { - try { - return AsyncGet (bridge).Result; - } catch (AggregateException e) { - if (e.InnerException is TaskCanceledException) - throw new OperationCanceledException (token); - // resets the stack trace, but that shouldn't matter here - throw e.InnerException; - } - } - - /// - /// Asynchronously waits until an item is available. - /// - /// The disposable object returned by . - public Task AsyncGet (IDisposable bridge) - { - linkBridge = bridge; - - return completion.Task; - } - - /// - /// Called after the result has been set, - /// cleans up after this block. - /// - void CompletionSet () - { - if (linkBridge != null) { - linkBridge.Dispose (); - linkBridge = null; - } - - cancellationRegistration.Dispose (); - timeoutTimer.Dispose (); - } - - public Task Completion { - get { throw new NotSupportedException (); } - } - - public void Complete () - { - lock (completion) { - completion.TrySetException (new InvalidOperationException ( - "No item could be received from the source.")); - } - CompletionSet (); - } - - public void Fault (Exception exception) - { - Complete (); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/SendBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/SendBlock.cs deleted file mode 100644 index ec9b08745c4..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/SendBlock.cs +++ /dev/null @@ -1,188 +0,0 @@ -// SendBlock.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -namespace System.Threading.Tasks.Dataflow { - /// - /// This block is used in - /// to asynchronously wait until a single item is sent to a given target. - /// - class SendBlock : ISourceBlock { - readonly ITargetBlock sendTarget; - readonly T item; - CancellationToken cancellationToken; - readonly TaskCompletionSource taskCompletionSource = - new TaskCompletionSource (); - readonly DataflowMessageHeader sendHeader = new DataflowMessageHeader (1); - CancellationTokenRegistration cancellationTokenRegistration; - - bool isReserved; - - volatile bool cancelDisabled; - - public SendBlock (ITargetBlock sendTarget, T item, - CancellationToken cancellationToken) - { - this.sendTarget = sendTarget; - this.item = item; - this.cancellationToken = cancellationToken; - } - - /// - /// Sends the item given in the constructor to the target block. - /// - /// Task that completes when the sending is done, or can't be performed. - public Task Send () - { - cancellationTokenRegistration = cancellationToken.Register ( - () => - { - if (!cancelDisabled) - taskCompletionSource.SetCanceled (); - }); - - PerformSend (); - - return taskCompletionSource.Task; - } - - /// - /// Offers the item to the target and hadles its response. - /// - void PerformSend () - { - DisableCancel (); - - if (taskCompletionSource.Task.IsCanceled) - return; - - var status = sendTarget.OfferMessage (sendHeader, item, this, false); - - if (status == DataflowMessageStatus.Accepted) - SetResult (true); - else if (status != DataflowMessageStatus.Postponed) - SetResult (false); - else - EnableCancel (); - } - - public Task Completion { - get { throw new NotSupportedException (); } - } - - public void Complete () - { - throw new NotSupportedException (); - } - - public void Fault (Exception exception) - { - throw new NotSupportedException (); - } - - public T ConsumeMessage (DataflowMessageHeader messageHeader, - ITargetBlock target, out bool messageConsumed) - { - if (!messageHeader.IsValid) - throw new ArgumentException ("The messageHeader is not valid.", - "messageHeader"); - if (target == null) - throw new ArgumentNullException("target"); - - DisableCancel (); - - messageConsumed = false; - - if (taskCompletionSource.Task.IsCanceled) - return default(T); - - if (messageHeader != sendHeader || target != sendTarget) { - EnableCancel (); - return default(T); - } - - SetResult (true); - - messageConsumed = true; - return item; - } - - public IDisposable LinkTo (ITargetBlock target, DataflowLinkOptions linkOptions) - { - throw new NotSupportedException (); - } - - public void ReleaseReservation (DataflowMessageHeader messageHeader, ITargetBlock target) - { - if (messageHeader != sendHeader || target != sendTarget || !isReserved) - throw new InvalidOperationException ( - "The target did not have the message reserved."); - - isReserved = false; - EnableCancel (); - PerformSend (); - } - - public bool ReserveMessage (DataflowMessageHeader messageHeader, ITargetBlock target) - { - DisableCancel (); - - if (messageHeader == sendHeader && target == sendTarget) { - isReserved = true; - return true; - } - - EnableCancel (); - - return false; - } - - /// - /// Temporarily disables cancelling. - /// - void DisableCancel () - { - cancelDisabled = true; - } - - /// - /// Enables cancelling after it was disabled. - /// If cancellation was attempted in the meantime, - /// actually performs the cancelling. - /// - void EnableCancel () - { - cancelDisabled = false; - - if (cancellationToken.IsCancellationRequested) - taskCompletionSource.SetCanceled (); - } - - /// - /// Sets the result of the operation. - /// - void SetResult (bool result) - { - cancellationTokenRegistration.Dispose (); - taskCompletionSource.SetResult (result); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TargetCollection.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TargetCollection.cs deleted file mode 100644 index cdd9bcb5839..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TargetCollection.cs +++ /dev/null @@ -1,523 +0,0 @@ -// TargetCollection.cs -// -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace System.Threading.Tasks.Dataflow { - /// - /// Base class for collection of target blocks for a source block. - /// Also handles sending messages to the target blocks. - /// - abstract class TargetCollectionBase { - /// - /// Represents a target block with its options. - /// - protected class Target : IDisposable { - readonly TargetCollectionBase targetCollection; - volatile int remainingMessages; - readonly CancellationTokenSource cancellationTokenSource; - - public ITargetBlock TargetBlock { get; private set; } - - public Target (TargetCollectionBase targetCollection, - ITargetBlock targetBlock, int maxMessages, - CancellationTokenSource cancellationTokenSource) - { - TargetBlock = targetBlock; - this.targetCollection = targetCollection; - remainingMessages = maxMessages; - this.cancellationTokenSource = cancellationTokenSource; - - Postponed = new AtomicBoolean (); - Reserved = new AtomicBoolean (); - } - - /// - /// Is called after a message was sent, makes sure the linked is destroyed after - /// were sent. - /// - public void MessageSent() - { - if (remainingMessages != -1) - remainingMessages--; - if (remainingMessages == 0) - Dispose (); - } - - readonly AtomicBoolean disabled = new AtomicBoolean (); - /// - /// Is the link destroyed? - /// - public bool Disabled - { - get { return disabled.Value; } - } - - /// - /// Destroys the link to this target. - /// - public void Dispose () - { - disabled.Value = true; - - if (cancellationTokenSource != null) - cancellationTokenSource.Cancel (); - - Target ignored; - targetCollection.TargetDictionary.TryRemove (TargetBlock, out ignored); - - // to avoid memory leak; it could take a long time - // before this object is actually removed from the collection - TargetBlock = null; - } - - /// - /// Does this target have a postponed message? - /// - public AtomicBoolean Postponed { get; private set; } - - /// - /// Does this target have a reserved message? - /// - /// Used only by broadcast blocks. - public AtomicBoolean Reserved { get; private set; } - } - - readonly ISourceBlock block; - readonly bool broadcast; - readonly bool consumeToAccept; - - readonly ConcurrentQueue prependQueue = new ConcurrentQueue (); - readonly ConcurrentQueue appendQueue = new ConcurrentQueue (); - readonly LinkedList targets = new LinkedList (); - - protected readonly ConcurrentDictionary, Target> TargetDictionary = - new ConcurrentDictionary, Target> (); - - // lastMessageHeaderId will be always accessed only from one thread - long lastMessageHeaderId; - // currentMessageHeaderId can be read from multiple threads at the same time - long currentMessageHeaderId; - - bool firstOffering; - T currentItem; - - protected TargetCollectionBase (ISourceBlock block, bool broadcast, bool consumeToAccept) - { - this.block = block; - this.broadcast = broadcast; - this.consumeToAccept = consumeToAccept; - } - - /// - /// Adds a target block to send messages to. - /// - /// - /// An object that can be used to destroy the link to the added target. - /// - public IDisposable AddTarget (ITargetBlock targetBlock, DataflowLinkOptions options) - { - CancellationTokenSource cancellationTokenSource = null; - if (options.PropagateCompletion) { - cancellationTokenSource = new CancellationTokenSource(); - block.Completion.ContinueWith (t => - { - if (t.IsFaulted) - targetBlock.Fault (t.Exception); - else - targetBlock.Complete (); - }, cancellationTokenSource.Token); - } - - var target = new Target ( - this, targetBlock, options.MaxMessages, cancellationTokenSource); - TargetDictionary [targetBlock] = target; - if (options.Append) - appendQueue.Enqueue (target); - else - prependQueue.Enqueue (target); - - return target; - } - - /// - /// Sets the current item to be offered to targets - /// - public void SetCurrentItem (T item) - { - firstOffering = true; - currentItem = item; - Volatile.Write (ref currentMessageHeaderId, ++lastMessageHeaderId); - - ClearUnpostponed (); - } - - /// - /// Clears the collection of "unpostponed" targets. - /// - protected abstract void ClearUnpostponed (); - - /// - /// Resets the current item to be offered to targets. - /// This means there is currently nothing to offer. - /// - public void ResetCurrentItem () - { - currentItem = default(T); - Volatile.Write (ref currentMessageHeaderId, 0); - } - - /// - /// Is there an item to send right now? - /// - public bool HasCurrentItem { - get { return Volatile.Read (ref currentMessageHeaderId) != 0; } - } - - /// - /// Offers the current item to all eligible targets. - /// - /// Was the item accepted? (Always false for broadcast blocks.) - public bool OfferItemToTargets () - { - // is there an item to offer? - if (!HasCurrentItem) - return false; - - var old = Tuple.Create (targets.First, targets.Last); - - do { - // order is important here, we want to make sure that prepended target - // added before appended target is processed first - var appended = PrependOrAppend (false); - var prepended = PrependOrAppend (true); - - if (OfferItemToTargets (prepended)) - return true; - - if (firstOffering) { - if (OfferItemToTargets (old)) - return true; - firstOffering = false; - } else { - if (OfferItemToUnpostponed ()) - return true; - } - - if (OfferItemToTargets (appended)) - return true; - } while (NeedsProcessing); - - return false; - } - - /// - /// Are there any targets that currently require a message to be sent to them? - /// - public bool NeedsProcessing { - get { - return !appendQueue.IsEmpty || !prependQueue.IsEmpty - || !UnpostponedIsEmpty; - } - } - - /// - /// Is the collection of unpostponed targets empty? - /// - protected abstract bool UnpostponedIsEmpty { get; } - - /// - /// Prepends (appends) targets that should be prepended (appended) to the collection of targets. - /// - /// true to prepend, false to append. - /// - /// Nodes that contain first and last target added to the list, - /// or null if no nodes were added. - /// - Tuple, LinkedListNode> PrependOrAppend ( - bool prepend) - { - var queue = prepend ? prependQueue : appendQueue; - - if (queue.IsEmpty) - return null; - - LinkedListNode first = null; - LinkedListNode last = null; - - Target target; - while (queue.TryDequeue (out target)) { - var node = prepend - ? targets.AddFirst (target) - : targets.AddLast (target); - if (first == null) - first = node; - last = node; - } - - return prepend - ? Tuple.Create (last, first) - : Tuple.Create (first, last); - } - - /// - /// Offers the current item to the targets between the given nodes (inclusive). - /// - /// Was the item accepted? (Always false for broadcast blocks.) - bool OfferItemToTargets ( - Tuple, LinkedListNode> targetPair) - { - if (targetPair == null - || targetPair.Item1 == null || targetPair.Item2 == null) - return false; - - var node = targetPair.Item1; - while (node != targetPair.Item2.Next) { - if (node.Value.Disabled) { - var nodeToRemove = node; - node = node.Next; - targets.Remove (nodeToRemove); - continue; - } - - if (OfferItem (node.Value) && !broadcast) - return true; - - node = node.Next; - } - - return false; - } - - /// - /// Offers the current item to unpostponed targets. - /// - /// Was the item accepted? (Always false for broadcast blocks.) - protected abstract bool OfferItemToUnpostponed (); - - /// - /// Offers the current item to the given target. - /// - /// Was the item accepted? - protected bool OfferItem (Target target) - { - if (target.Reserved.Value) - return false; - if (!broadcast && target.Postponed.Value) - return false; - - var result = target.TargetBlock.OfferMessage ( - // volatile read is not necessary here, - // because currentMessageHeaderId is always written from this thread - new DataflowMessageHeader (currentMessageHeaderId), currentItem, block, - consumeToAccept); - - switch (result) { - case DataflowMessageStatus.Accepted: - target.MessageSent (); - return true; - case DataflowMessageStatus.Postponed: - target.Postponed.Value = true; - return false; - case DataflowMessageStatus.DecliningPermanently: - target.Dispose (); - return false; - default: - return false; - } - } - - /// - /// Returns whether the given header corresponds to the current item. - /// - public bool VerifyHeader (DataflowMessageHeader header) - { - return header.Id == Volatile.Read (ref currentMessageHeaderId); - } - } - - /// - /// Target collection for non-broadcast blocks. - /// - class TargetCollection : TargetCollectionBase { - readonly ConcurrentQueue unpostponedTargets = - new ConcurrentQueue (); - - public TargetCollection (ISourceBlock block) - : base (block, false, false) - { - } - - /// - /// Is the collection of unpostponed targets empty? - /// - protected override bool UnpostponedIsEmpty { - get { return unpostponedTargets.IsEmpty; } - } - - /// - /// Returns whether the given header corresponds to the current item - /// and that the given target block postponed this item. - /// - public bool VerifyHeader (DataflowMessageHeader header, ITargetBlock targetBlock) - { - return VerifyHeader (header) - && TargetDictionary[targetBlock].Postponed.Value; - } - - /// - /// Unpostpones the given target. - /// - /// Target to unpostpone. - /// Did the target consume an item? - public void UnpostponeTarget (ITargetBlock targetBlock, bool messageConsumed) - { - Target target; - if (!TargetDictionary.TryGetValue (targetBlock, out target)) - return; - - if (messageConsumed) - target.MessageSent (); - unpostponedTargets.Enqueue (target); - - target.Postponed.Value = false; - } - - /// - /// Clears the collection of "unpostponed" targets. - /// - protected override void ClearUnpostponed () - { - Target ignored; - while (unpostponedTargets.TryDequeue (out ignored)) { - } - } - - /// - /// Offers the current item to unpostponed targets. - /// - /// Was the item accepted? - protected override bool OfferItemToUnpostponed () - { - Target target; - while (unpostponedTargets.TryDequeue (out target)) { - if (!target.Disabled && OfferItem (target)) - return true; - } - - return false; - } - } - - /// - /// Target collection for broadcast blocks. - /// - class BroadcastTargetCollection : TargetCollectionBase { - // it's necessary to store the headers because of a race between - // UnpostponeTargetConsumed and SetCurrentItem - readonly ConcurrentQueue> - unpostponedTargets = - new ConcurrentQueue> (); - - public BroadcastTargetCollection (ISourceBlock block, bool consumeToAccept) - : base (block, true, consumeToAccept) - { - } - - /// - /// Is the collection of unpostponed targets empty? - /// - protected override bool UnpostponedIsEmpty { - get { return unpostponedTargets.IsEmpty; } - } - - /// - /// Marks the target as having a reserved message. - /// - public void ReserveTarget (ITargetBlock targetBlock) - { - TargetDictionary [targetBlock].Reserved.Value = true; - } - - /// - /// Unpostpone target after it consumed a message. - /// - /// The target to unpostpone. - /// Header of the message the target consumed. - public void UnpostponeTargetConsumed (ITargetBlock targetBlock, - DataflowMessageHeader header) - { - Target target = TargetDictionary [targetBlock]; - - target.MessageSent (); - unpostponedTargets.Enqueue (Tuple.Create (target, header)); - - target.Postponed.Value = false; - target.Reserved.Value = false; - } - - /// - /// Unpostpone target in the case when it didn't successfuly consume a message. - /// - public void UnpostponeTargetNotConsumed (ITargetBlock targetBlock) - { - Target target; - if (!TargetDictionary.TryGetValue (targetBlock, out target)) - return; - - unpostponedTargets.Enqueue (Tuple.Create (target, - new DataflowMessageHeader ())); - - target.Postponed.Value = false; - target.Reserved.Value = false; - } - - /// - /// Clears the collection of "unpostponed" targets. - /// - protected override void ClearUnpostponed () - { - Tuple ignored; - while (unpostponedTargets.TryDequeue (out ignored)) { - } - } - - /// - /// Offers the current item to unpostponed targets. - /// - /// Always false. - protected override bool OfferItemToUnpostponed () - { - Tuple tuple; - while (unpostponedTargets.TryDequeue (out tuple)) { - // offer to unconditionaly unpostponed - // and those that consumed some old value - if (!tuple.Item1.Disabled - && (!tuple.Item2.IsValid || !VerifyHeader (tuple.Item2))) - OfferItem (tuple.Item1); - } - - return false; - } - } -} diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TransformBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TransformBlock.cs deleted file mode 100644 index a1c37f4dce3..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TransformBlock.cs +++ /dev/null @@ -1,203 +0,0 @@ -// TransformBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - public sealed class TransformBlock : - IPropagatorBlock, IReceivableSourceBlock { - readonly ExecutionDataflowBlockOptions dataflowBlockOptions; - readonly CompletionHelper compHelper; - readonly BlockingCollection messageQueue = new BlockingCollection (); - readonly MessageBox messageBox; - readonly OutgoingQueue outgoing; - readonly Func transform; - readonly Func> asyncTransform; - - TransformBlock (ExecutionDataflowBlockOptions dataflowBlockOptions) - { - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - this.dataflowBlockOptions = dataflowBlockOptions; - this.compHelper = new CompletionHelper (dataflowBlockOptions); - } - - public TransformBlock (Func transform) - : this (transform, ExecutionDataflowBlockOptions.Default) - { - } - - public TransformBlock (Func transform, - ExecutionDataflowBlockOptions dataflowBlockOptions) - : this (dataflowBlockOptions) - { - if (transform == null) - throw new ArgumentNullException("transform"); - - this.transform = transform; - this.messageBox = new ExecutingMessageBox ( - this, messageQueue, compHelper, - () => outgoing.IsCompleted, TransformProcess, () => outgoing.Complete (), - dataflowBlockOptions); - this.outgoing = new OutgoingQueue (this, compHelper, - () => messageQueue.IsCompleted, messageBox.DecreaseCount, - dataflowBlockOptions); - } - - public TransformBlock(Func> transform) - : this(transform, ExecutionDataflowBlockOptions.Default) - { - } - - public TransformBlock (Func> transform, - ExecutionDataflowBlockOptions dataflowBlockOptions) - : this (dataflowBlockOptions) - { - if (transform == null) - throw new ArgumentNullException("transform"); - - this.asyncTransform = transform; - this.messageBox = new AsyncExecutingMessageBox> ( - this, messageQueue, compHelper, () => outgoing.IsCompleted, - AsyncTransformProcess, AsyncProcessFinishedTask, () => outgoing.Complete (), - dataflowBlockOptions); - this.outgoing = new OutgoingQueue (this, compHelper, - () => messageQueue.IsCompleted, messageBox.DecreaseCount, - dataflowBlockOptions); - } - - DataflowMessageStatus ITargetBlock.OfferMessage ( - DataflowMessageHeader messageHeader, TInput messageValue, - ISourceBlock source, bool consumeToAccept) - { - return messageBox.OfferMessage (messageHeader, messageValue, source, consumeToAccept); - } - - public IDisposable LinkTo (ITargetBlock target, DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget (target, linkOptions); - } - - TOutput ISourceBlock.ConsumeMessage ( - DataflowMessageHeader messageHeader, ITargetBlock target, - out bool messageConsumed) - { - return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - void ISourceBlock.ReleaseReservation ( - DataflowMessageHeader messageHeader, ITargetBlock target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - bool ISourceBlock.ReserveMessage ( - DataflowMessageHeader messageHeader, ITargetBlock target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - public bool TryReceive (Predicate filter, out TOutput item) - { - return outgoing.TryReceive (filter, out item); - } - - public bool TryReceiveAll (out IList items) - { - return outgoing.TryReceiveAll (out items); - } - - /// - /// Transforms one item from the queue if the transform delegate is synchronous. - /// - /// Returns whether an item was processed. Returns false if the queue is empty. - bool TransformProcess () - { - TInput input; - - var dequeued = messageQueue.TryTake (out input); - if (dequeued) - outgoing.AddData (transform (input)); - - return dequeued; - } - - /// - /// Processes one item from the queue if the transform delegate is asynchronous. - /// - /// The Task that was returned by the synchronous part of the delegate. - /// Returns whether an item was processed. Returns false if the queue was empty. - bool AsyncTransformProcess (out Task task) - { - TInput input; - - var dequeued = messageQueue.TryTake (out input); - if (dequeued) - task = asyncTransform (input); - else - task = null; - - return dequeued; - } - - /// - /// Process result of finished asynchronous transformation. - /// - void AsyncProcessFinishedTask (Task task) - { - if (task == null || task.IsCanceled) - messageBox.DecreaseCount (); - else - outgoing.AddData (task.Result); - } - - public void Complete () - { - messageBox.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { return compHelper.Completion; } - } - - public int OutputCount { - get { return outgoing.Count; } - } - - public int InputCount { - get { return messageQueue.Count; } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TransformManyBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TransformManyBlock.cs deleted file mode 100644 index 7bb465728a5..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/TransformManyBlock.cs +++ /dev/null @@ -1,223 +0,0 @@ -// TransformManyBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - public sealed class TransformManyBlock : - IPropagatorBlock, IReceivableSourceBlock { - readonly CompletionHelper compHelper; - readonly BlockingCollection messageQueue = new BlockingCollection (); - readonly MessageBox messageBox; - readonly ExecutionDataflowBlockOptions dataflowBlockOptions; - readonly Func> transform; - readonly Func>> asyncTransform; - readonly OutgoingQueue outgoing; - - TransformManyBlock (ExecutionDataflowBlockOptions dataflowBlockOptions) - { - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - this.dataflowBlockOptions = dataflowBlockOptions; - this.compHelper = new CompletionHelper (dataflowBlockOptions); - } - - public TransformManyBlock (Func> transform) - : this (transform, ExecutionDataflowBlockOptions.Default) - { - } - - public TransformManyBlock (Func> transform, - ExecutionDataflowBlockOptions dataflowBlockOptions) - : this (dataflowBlockOptions) - { - if (transform == null) - throw new ArgumentNullException ("transform"); - - this.transform = transform; - this.messageBox = new ExecutingMessageBox (this, messageQueue, compHelper, - () => outgoing.IsCompleted, TransformProcess, () => outgoing.Complete (), - dataflowBlockOptions); - this.outgoing = new OutgoingQueue (this, compHelper, - () => messageQueue.IsCompleted, messageBox.DecreaseCount, - dataflowBlockOptions); - } - - public TransformManyBlock (Func>> transform) - : this (transform, ExecutionDataflowBlockOptions.Default) - { - } - - public TransformManyBlock (Func>> transform, - ExecutionDataflowBlockOptions dataflowBlockOptions) - : this (dataflowBlockOptions) - { - if (transform == null) - throw new ArgumentNullException ("transform"); - - this.asyncTransform = transform; - this.messageBox = new AsyncExecutingMessageBox>> (this, messageQueue, compHelper, - () => outgoing.IsCompleted, AsyncTransformProcess, ProcessFinishedTask, () => outgoing.Complete (), - dataflowBlockOptions); - this.outgoing = new OutgoingQueue (this, compHelper, - () => messageQueue.IsCompleted, messageBox.DecreaseCount, - dataflowBlockOptions); - } - - DataflowMessageStatus ITargetBlock.OfferMessage ( - DataflowMessageHeader messageHeader, TInput messageValue, - ISourceBlock source, bool consumeToAccept) - { - return messageBox.OfferMessage (messageHeader, messageValue, source, consumeToAccept); - } - - public IDisposable LinkTo (ITargetBlock target, DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget (target, linkOptions); - } - - TOutput ISourceBlock.ConsumeMessage ( - DataflowMessageHeader messageHeader, ITargetBlock target, - out bool messageConsumed) - { - return outgoing.ConsumeMessage (messageHeader, target, out messageConsumed); - } - - void ISourceBlock.ReleaseReservation ( - DataflowMessageHeader messageHeader, ITargetBlock target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - bool ISourceBlock.ReserveMessage ( - DataflowMessageHeader messageHeader, ITargetBlock target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - public bool TryReceive (Predicate filter, out TOutput item) - { - return outgoing.TryReceive (filter, out item); - } - - public bool TryReceiveAll (out IList items) - { - return outgoing.TryReceiveAll (out items); - } - - /// - /// Transforms one item from the queue if the transform delegate is synchronous. - /// - /// Returns whether an item was processed. Returns false if the queue is empty. - bool TransformProcess () - { - TInput input; - - var dequeued = messageQueue.TryTake (out input); - if (dequeued) { - var result = transform (input); - - EnqueueTransformed (result); - } - - return dequeued; - } - - /// - /// Adds the transformed collection to the output queue. - /// - void EnqueueTransformed (IEnumerable transformed) - { - bool first = true; - if (transformed != null) { - foreach (var item in transformed) { - if (first) - first = false; - else - messageBox.IncreaseCount (); - outgoing.AddData (item); - } - } - if (first) - messageBox.DecreaseCount (); - } - - /// - /// Processes one item from the queue if the transform delegate is asynchronous. - /// - /// The Task that was returned by the synchronous part of the delegate. - /// Returns whether an item was processed. Returns false if the queue was empty. - bool AsyncTransformProcess (out Task> task) - { - TInput input; - - var dequeued = messageQueue.TryTake (out input); - if (dequeued) - task = asyncTransform (input); - else - task = null; - - return dequeued; - } - - /// - /// Process result of finished asynchronous transformation. - /// - void ProcessFinishedTask (Task> task) - { - if (task == null || task.IsCanceled) - messageBox.DecreaseCount (); - else - EnqueueTransformed (task.Result); - } - - public void Complete () - { - messageBox.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { return compHelper.Completion; } - } - - public int OutputCount { - get { return outgoing.Count; } - } - - public int InputCount { - get { return messageQueue.Count; } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/WriteOnceBlock.cs b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/WriteOnceBlock.cs deleted file mode 100644 index 6da70460598..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow/WriteOnceBlock.cs +++ /dev/null @@ -1,150 +0,0 @@ -// WriteOnceBlock.cs -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// Copyright (c) 2012 Petr Onderka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; -using System.Collections.Concurrent; - -namespace System.Threading.Tasks.Dataflow { - public sealed class WriteOnceBlock : IPropagatorBlock, IReceivableSourceBlock { - readonly CompletionHelper compHelper; - readonly BlockingCollection messageQueue = new BlockingCollection (); - readonly MessageBox messageBox; - readonly DataflowBlockOptions dataflowBlockOptions; - readonly Func cloningFunction; - readonly BroadcastOutgoingQueue outgoing; - AtomicBooleanValue written; - - public WriteOnceBlock (Func cloningFunction) - : this (cloningFunction, DataflowBlockOptions.Default) - { - } - - public WriteOnceBlock (Func cloningFunction, - DataflowBlockOptions dataflowBlockOptions) - { - if (dataflowBlockOptions == null) - throw new ArgumentNullException ("dataflowBlockOptions"); - - this.cloningFunction = cloningFunction; - this.dataflowBlockOptions = dataflowBlockOptions; - this.compHelper = CompletionHelper.GetNew (dataflowBlockOptions); - this.messageBox = new PassingMessageBox (this, messageQueue, compHelper, - () => true, _ => BroadcastProcess (), dataflowBlockOptions, - canAccept: () => written.TrySet ()); - this.outgoing = new BroadcastOutgoingQueue (this, compHelper, - () => messageQueue.IsCompleted, messageBox.DecreaseCount, - dataflowBlockOptions, cloningFunction != null); - } - - DataflowMessageStatus ITargetBlock.OfferMessage ( - DataflowMessageHeader messageHeader, T messageValue, - ISourceBlock source, bool consumeToAccept) - { - var result = messageBox.OfferMessage (messageHeader, messageValue, source, - consumeToAccept); - if (result == DataflowMessageStatus.Accepted) - messageQueue.CompleteAdding (); - return result; - } - - public IDisposable LinkTo (ITargetBlock target, - DataflowLinkOptions linkOptions) - { - return outgoing.AddTarget (target, linkOptions); - } - - T ISourceBlock.ConsumeMessage (DataflowMessageHeader messageHeader, - ITargetBlock target, - out bool messageConsumed) - { - T message = outgoing.ConsumeMessage ( - messageHeader, target, out messageConsumed); - if (messageConsumed && cloningFunction != null) - message = cloningFunction (message); - return message; - } - - void ISourceBlock.ReleaseReservation (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - outgoing.ReleaseReservation (messageHeader, target); - } - - bool ISourceBlock.ReserveMessage (DataflowMessageHeader messageHeader, - ITargetBlock target) - { - return outgoing.ReserveMessage (messageHeader, target); - } - - public bool TryReceive (Predicate filter, out T item) - { - var received = outgoing.TryReceive (filter, out item); - if (received && cloningFunction != null) - item = cloningFunction (item); - return received; - } - - bool IReceivableSourceBlock.TryReceiveAll (out IList items) - { - T item; - if (!TryReceive (null, out item)) { - items = null; - return false; - } - - items = new[] { item }; - return true; - } - - /// - /// Moves an item from the input queue to the output queue. - /// - void BroadcastProcess () - { - T item; - if (messageQueue.TryTake (out item)) - outgoing.AddData (item); - outgoing.DequeueItem (); - } - - public void Complete () - { - messageBox.Complete (); - outgoing.Complete (); - } - - void IDataflowBlock.Fault (Exception exception) - { - compHelper.RequestFault (exception); - } - - public Task Completion { - get { return compHelper.Completion; } - } - - public override string ToString () - { - return NameHelper.GetName (this, dataflowBlockOptions); - } - } -} \ No newline at end of file diff --git a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow_test.dll.sources b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow_test.dll.sources index a1d569fcb22..f23cf2edc1f 100644 --- a/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow_test.dll.sources +++ b/mcs/class/System.Threading.Tasks.Dataflow/System.Threading.Tasks.Dataflow_test.dll.sources @@ -2,7 +2,6 @@ TestScheduler.cs AssertEx.cs Blocks.cs System.Threading.Tasks.Dataflow/DataflowMessageHeaderTest.cs -System.Threading.Tasks.Dataflow/CompletionHelperTest.cs System.Threading.Tasks.Dataflow/CompletionTest.cs System.Threading.Tasks.Dataflow/ReceivingTest.cs System.Threading.Tasks.Dataflow/OptionsTest.cs @@ -26,5 +25,4 @@ System.Threading.Tasks.Dataflow/InvalidArgumentsTest.cs System.Threading.Tasks.Dataflow/OutputAvailableTest.cs System.Threading.Tasks.Dataflow/EncapsulateTest.cs System.Threading.Tasks.Dataflow/ChooseTest.cs -../System.Threading.Tasks.Dataflow/CompletionHelper.cs ../../Mono.Parallel/Mono.Threading/AtomicBoolean.cs diff --git a/mcs/class/System.Threading.Tasks.Dataflow/Test/System.Threading.Tasks.Dataflow/CompletionHelperTest.cs b/mcs/class/System.Threading.Tasks.Dataflow/Test/System.Threading.Tasks.Dataflow/CompletionHelperTest.cs deleted file mode 100644 index ca8309e21b2..00000000000 --- a/mcs/class/System.Threading.Tasks.Dataflow/Test/System.Threading.Tasks.Dataflow/CompletionHelperTest.cs +++ /dev/null @@ -1,81 +0,0 @@ -// -// CompletionHelperTest.cs -// -// Author: -// Jérémie "garuma" Laval -// -// Copyright (c) 2011 Jérémie "garuma" Laval -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; - -using NUnit.Framework; - -namespace MonoTests.System.Threading.Tasks.Dataflow -{ - [TestFixture] - public class CompletionHelperTest - { - CompletionHelper helper; - - [SetUp] - public void Setup () - { - helper = CompletionHelper.GetNew (null); - } - - [Test] - public void InitialStateTest () - { - Task completed = helper.Completion; - - Assert.IsNotNull (completed); - Assert.IsFalse (completed.IsCompleted); - } - - [Test] - public void FaultedTest () - { - Exception ex = new ApplicationException ("Foobar"); - helper.RequestFault (ex); - Task completed = helper.Completion; - - Assert.IsNotNull (completed); - Assert.IsTrue (completed.IsCompleted); - Assert.AreEqual (TaskStatus.Faulted, completed.Status); - Assert.AreEqual (ex, completed.Exception.InnerExceptions.First ()); - } - - [Test] - public void CompleteTest () - { - helper.Complete (); - Task completed = helper.Completion; - - Assert.IsNotNull (completed); - Assert.IsTrue (completed.IsCompleted); - Assert.IsFalse (completed.IsFaulted); - Assert.IsFalse (completed.IsCanceled); - } - } -} -- 2.25.1