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