From e4e942f91e6e2e32988a610c0e7f754df7df56b1 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 9 Jul 2021 06:32:54 -0400 Subject: [PATCH] Add private Task.StateFlags property to help with debugging (#55297) It's a pain when you want to debug something with tasks, you look at its m_stateFlags, and you're met with an integer you have to decode by looking at consts defined in Task. This changes those consts to be an enum and adds a private property that returns the flags as that enum, to help with debugging. --- .../src/ILLink/ILLink.Descriptors.Shared.xml | 1 + .../src/System/Threading/Tasks/Future.cs | 8 +- .../src/System/Threading/Tasks/Task.cs | 234 +++++++++--------- .../Threading/Tasks/TaskContinuation.cs | 4 +- .../System/Threading/Tasks/TaskScheduler.cs | 4 +- 5 files changed, 126 insertions(+), 125 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml index 1989afaeda1bd..401fe001de280 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml @@ -13,6 +13,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs index f2dd5b2e37efa..ac42990b31d1a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @@ -378,8 +378,8 @@ internal bool TrySetResult(TResult? result) // been recorded, and (4) Cancellation has not been requested. // // If the reservation is successful, then set the result and finish completion processing. - if (AtomicStateUpdate(TASK_STATE_COMPLETION_RESERVED, - TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) + if (AtomicStateUpdate((int)TaskStateFlags.CompletionReserved, + (int)TaskStateFlags.CompletionReserved | (int)TaskStateFlags.RanToCompletion | (int)TaskStateFlags.Faulted | (int)TaskStateFlags.Canceled)) { m_result = result; @@ -390,7 +390,7 @@ internal bool TrySetResult(TResult? result) // However, that goes through a windy code path, involves many non-inlineable functions // and which can be summarized more concisely with the following snippet from // FinishStageTwo, omitting everything that doesn't pertain to TrySetResult. - Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_RAN_TO_COMPLETION); + Interlocked.Exchange(ref m_stateFlags, m_stateFlags | (int)TaskStateFlags.RanToCompletion); ContingentProperties? props = m_contingentProperties; if (props != null) { @@ -425,7 +425,7 @@ internal void DangerousSetResult(TResult result) else { m_result = result; - m_stateFlags |= TASK_STATE_RAN_TO_COMPLETION; + m_stateFlags |= (int)TaskStateFlags.RanToCompletion; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index 923c4cf71bcf4..a64dc00842991 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -138,36 +138,36 @@ public class Task : IAsyncResult, IDisposable private Task? ParentForDebugger => m_contingentProperties?.m_parent; // Private property used by a debugger to access this Task's parent private int StateFlagsForDebugger => m_stateFlags; // Private property used by a debugger to access this Task's state flags - - // State constants for m_stateFlags; - // The bits of m_stateFlags are allocated as follows: - // 0x40000000 - TaskBase state flag - // 0x3FFF0000 - Task state flags - // 0x0000FF00 - internal TaskCreationOptions flags - // 0x000000FF - publicly exposed TaskCreationOptions flags - // - // See TaskCreationOptions for bit values associated with TaskCreationOptions - // - private const int OptionsMask = 0xFFFF; // signifies the Options portion of m_stateFlags bin: 0000 0000 0000 0000 1111 1111 1111 1111 - internal const int TASK_STATE_STARTED = 0x10000; // bin: 0000 0000 0000 0001 0000 0000 0000 0000 - internal const int TASK_STATE_DELEGATE_INVOKED = 0x20000; // bin: 0000 0000 0000 0010 0000 0000 0000 0000 - internal const int TASK_STATE_DISPOSED = 0x40000; // bin: 0000 0000 0000 0100 0000 0000 0000 0000 - internal const int TASK_STATE_EXCEPTIONOBSERVEDBYPARENT = 0x80000; // bin: 0000 0000 0000 1000 0000 0000 0000 0000 - internal const int TASK_STATE_CANCELLATIONACKNOWLEDGED = 0x100000; // bin: 0000 0000 0001 0000 0000 0000 0000 0000 - internal const int TASK_STATE_FAULTED = 0x200000; // bin: 0000 0000 0010 0000 0000 0000 0000 0000 - internal const int TASK_STATE_CANCELED = 0x400000; // bin: 0000 0000 0100 0000 0000 0000 0000 0000 - internal const int TASK_STATE_WAITING_ON_CHILDREN = 0x800000; // bin: 0000 0000 1000 0000 0000 0000 0000 0000 - internal const int TASK_STATE_RAN_TO_COMPLETION = 0x1000000; // bin: 0000 0001 0000 0000 0000 0000 0000 0000 - internal const int TASK_STATE_WAITINGFORACTIVATION = 0x2000000; // bin: 0000 0010 0000 0000 0000 0000 0000 0000 - internal const int TASK_STATE_COMPLETION_RESERVED = 0x4000000; // bin: 0000 0100 0000 0000 0000 0000 0000 0000 - internal const int TASK_STATE_WAIT_COMPLETION_NOTIFICATION = 0x10000000; // bin: 0001 0000 0000 0000 0000 0000 0000 0000 - // This could be moved to InternalTaskOptions enum - internal const int TASK_STATE_EXECUTIONCONTEXT_IS_NULL = 0x20000000; // bin: 0010 0000 0000 0000 0000 0000 0000 0000 - internal const int TASK_STATE_TASKSCHEDULED_WAS_FIRED = 0x40000000; // bin: 0100 0000 0000 0000 0000 0000 0000 0000 - - // A mask for all of the final states a task may be in. - // SOS DumpAsync command depends on these values. - private const int TASK_STATE_COMPLETED_MASK = TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION; + private TaskStateFlags StateFlags => (TaskStateFlags)(m_stateFlags & ~(int)TaskStateFlags.OptionsMask); // Private property used to help with debugging + + [Flags] + internal enum TaskStateFlags + { + // State constants for m_stateFlags; + // The bits of m_stateFlags are allocated as follows: + // 0x7FFF0000 - Task state flags + // 0x0000FF00 - internal TaskCreationOptions flags + // 0x000000FF - publicly exposed TaskCreationOptions flags + // See TaskCreationOptions for bit values associated with TaskCreationOptions + + Started = 0x10000, // bin: 0000 0000 0000 0001 0000 0000 0000 0000 + DelegateInvoked = 0x20000, // bin: 0000 0000 0000 0010 0000 0000 0000 0000 + Disposed = 0x40000, // bin: 0000 0000 0000 0100 0000 0000 0000 0000 + ExceptionObservedByParent = 0x80000, // bin: 0000 0000 0000 1000 0000 0000 0000 0000 + CancellationAcknowledged = 0x100000, // bin: 0000 0000 0001 0000 0000 0000 0000 0000 + Faulted = 0x200000, // bin: 0000 0000 0010 0000 0000 0000 0000 0000 + Canceled = 0x400000, // bin: 0000 0000 0100 0000 0000 0000 0000 0000 + WaitingOnChildren = 0x800000, // bin: 0000 0000 1000 0000 0000 0000 0000 0000 + RanToCompletion = 0x1000000, // bin: 0000 0001 0000 0000 0000 0000 0000 0000 + WaitingForActivation = 0x2000000, // bin: 0000 0010 0000 0000 0000 0000 0000 0000 + CompletionReserved = 0x4000000, // bin: 0000 0100 0000 0000 0000 0000 0000 0000 + WaitCompletionNotification = 0x10000000, // bin: 0001 0000 0000 0000 0000 0000 0000 0000 + ExecutionContextIsNull = 0x20000000, // bin: 0010 0000 0000 0000 0000 0000 0000 0000 + TaskScheduledWasFired = 0x40000000, // bin: 0100 0000 0000 0000 0000 0000 0000 0000 + + CompletedMask = Canceled | Faulted | RanToCompletion, // A mask for all of the final states a task may be in. SOS DumpAsync command depends on these values. + OptionsMask = 0xFFFF, // signifies the Options portion of m_stateFlags bin: 0000 0000 0000 0000 1111 1111 1111 1111 + } // Values for ContingentProperties.m_internalCancellationRequested. private const int CANCELLATION_REQUESTED = 0x1; @@ -294,7 +294,7 @@ internal Task(bool canceled, TaskCreationOptions creationOptions, CancellationTo int optionFlags = (int)creationOptions; if (canceled) { - m_stateFlags = TASK_STATE_CANCELED | TASK_STATE_CANCELLATIONACKNOWLEDGED | optionFlags; + m_stateFlags = (int)TaskStateFlags.Canceled | (int)TaskStateFlags.CancellationAcknowledged | optionFlags; m_contingentProperties = new ContingentProperties() // can't have children, so just instantiate directly { m_cancellationToken = ct, @@ -303,14 +303,14 @@ internal Task(bool canceled, TaskCreationOptions creationOptions, CancellationTo } else { - m_stateFlags = TASK_STATE_RAN_TO_COMPLETION | optionFlags; + m_stateFlags = (int)TaskStateFlags.RanToCompletion | optionFlags; } } /// Constructor for use with promise-style tasks that aren't configurable. internal Task() { - m_stateFlags = TASK_STATE_WAITINGFORACTIVATION | (int)InternalTaskOptions.PromiseTask; + m_stateFlags = (int)TaskStateFlags.WaitingForActivation | (int)InternalTaskOptions.PromiseTask; } // Special constructor for use with promise-style tasks. @@ -559,10 +559,10 @@ internal void TaskConstructorCore(Delegate? action, object? state, CancellationT // Assign options to m_stateAndOptionsFlag. Debug.Assert(m_stateFlags == 0, "TaskConstructorCore: non-zero m_stateFlags"); - Debug.Assert((((int)creationOptions) | OptionsMask) == OptionsMask, "TaskConstructorCore: options take too many bits"); + Debug.Assert((((int)creationOptions) | (int)TaskStateFlags.OptionsMask) == (int)TaskStateFlags.OptionsMask, "TaskConstructorCore: options take too many bits"); int tmpFlags = (int)creationOptions | (int)internalOptions; // one write to the volatile m_stateFlags instead of two when setting the above options m_stateFlags = m_action == null || (internalOptions & InternalTaskOptions.ContinuationTask) != 0 ? - tmpFlags | TASK_STATE_WAITINGFORACTIVATION : + tmpFlags | (int)TaskStateFlags.WaitingForActivation : tmpFlags; // Now is the time to add the new task to the children list @@ -674,8 +674,8 @@ private void AssignCancellationToken(CancellationToken cancellationToken, Task? // a read of the volatile m_stateFlags field. internal static TaskCreationOptions OptionsMethod(int flags) { - Debug.Assert((OptionsMask & 1) == 1, "OptionsMask needs a shift in Options.get"); - return (TaskCreationOptions)(flags & OptionsMask); + Debug.Assert(((int)TaskStateFlags.OptionsMask & 1) == 1, "OptionsMask needs a shift in Options.get"); + return (TaskCreationOptions)(flags & (int)TaskStateFlags.OptionsMask); } // Atomically OR-in newBits to m_stateFlags, while making sure that @@ -720,7 +720,7 @@ internal bool AtomicStateUpdate(int newBits, int illegalBits, ref int oldFlags) } /// - /// Sets or clears the TASK_STATE_WAIT_COMPLETION_NOTIFICATION state bit. + /// Sets or clears the TaskStateFlags.WaitCompletionNotification state bit. /// The debugger sets this bit to aid it in "stepping out" of an async method body. /// If enabled is true, this must only be called on a task that has not yet been completed. /// If enabled is false, this may be called on completed tasks. @@ -734,15 +734,15 @@ internal void SetNotificationForWaitCompletion(bool enabled) if (enabled) { - // Atomically set the TASK_STATE_WAIT_COMPLETION_NOTIFICATION bit - bool success = AtomicStateUpdate(TASK_STATE_WAIT_COMPLETION_NOTIFICATION, - TASK_STATE_COMPLETED_MASK | TASK_STATE_COMPLETION_RESERVED); + // Atomically set the TaskStateFlags.WaitCompletionNotification bit + bool success = AtomicStateUpdate((int)TaskStateFlags.WaitCompletionNotification, + (int)TaskStateFlags.CompletedMask | (int)TaskStateFlags.CompletionReserved); Debug.Assert(success, "Tried to set enabled on completed Task"); } else { - // Atomically clear the TASK_STATE_WAIT_COMPLETION_NOTIFICATION bit - Interlocked.And(ref m_stateFlags, ~TASK_STATE_WAIT_COMPLETION_NOTIFICATION); + // Atomically clear the TaskStateFlags.WaitCompletionNotification bit + Interlocked.And(ref m_stateFlags, ~(int)TaskStateFlags.WaitCompletionNotification); } } @@ -785,8 +785,8 @@ internal static bool AnyTaskRequiresNotifyDebuggerOfWaitCompletion(Task?[] tasks internal bool IsWaitNotificationEnabledOrNotRanToCompletion { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (m_stateFlags & (Task.TASK_STATE_WAIT_COMPLETION_NOTIFICATION | Task.TASK_STATE_RAN_TO_COMPLETION)) - != Task.TASK_STATE_RAN_TO_COMPLETION; + get => (m_stateFlags & ((int)TaskStateFlags.WaitCompletionNotification | (int)TaskStateFlags.RanToCompletion)) + != (int)TaskStateFlags.RanToCompletion; } /// @@ -812,7 +812,7 @@ internal bool IsWaitNotificationEnabledOrNotRanToCompletion /// Gets whether the task's debugger notification for wait completion bit is set. /// true if the bit is set; false if it's not set. internal bool IsWaitNotificationEnabled => // internal only to enable unit tests; would otherwise be private - (m_stateFlags & TASK_STATE_WAIT_COMPLETION_NOTIFICATION) != 0; + (m_stateFlags & (int)TaskStateFlags.WaitCompletionNotification) != 0; /// Placeholder method used as a breakpoint target by the debugger. Must not be inlined or optimized. /// All joins with a task should end up calling this if their debugger notification bit is set. @@ -834,14 +834,14 @@ private void NotifyDebuggerOfWaitCompletion() // Atomically mark a Task as started while making sure that it is not canceled. internal bool MarkStarted() { - return AtomicStateUpdate(TASK_STATE_STARTED, TASK_STATE_CANCELED | TASK_STATE_STARTED); + return AtomicStateUpdate((int)TaskStateFlags.Started, (int)TaskStateFlags.Canceled | (int)TaskStateFlags.Started); } internal void FireTaskScheduledIfNeeded(TaskScheduler ts) { - if ((m_stateFlags & Task.TASK_STATE_TASKSCHEDULED_WAS_FIRED) == 0) + if ((m_stateFlags & (int)TaskStateFlags.TaskScheduledWasFired) == 0) { - m_stateFlags |= Task.TASK_STATE_TASKSCHEDULED_WAS_FIRED; + m_stateFlags |= (int)TaskStateFlags.TaskScheduledWasFired; if (TplEventSource.Log.IsEnabled()) { @@ -1119,7 +1119,7 @@ internal void InternalRunSynchronously(TaskScheduler scheduler, bool waitForComp } else { - Debug.Assert((m_stateFlags & TASK_STATE_CANCELED) != 0, "Task.RunSynchronously: expected TASK_STATE_CANCELED to be set"); + Debug.Assert((m_stateFlags & (int)TaskStateFlags.Canceled) != 0, "Task.RunSynchronously: expected TaskStateFlags.Canceled to be set"); // Can't call this method on canceled task. ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_TaskCompleted); } @@ -1269,13 +1269,13 @@ public TaskStatus Status // execution of this method. int sf = m_stateFlags; - if ((sf & TASK_STATE_FAULTED) != 0) rval = TaskStatus.Faulted; - else if ((sf & TASK_STATE_CANCELED) != 0) rval = TaskStatus.Canceled; - else if ((sf & TASK_STATE_RAN_TO_COMPLETION) != 0) rval = TaskStatus.RanToCompletion; - else if ((sf & TASK_STATE_WAITING_ON_CHILDREN) != 0) rval = TaskStatus.WaitingForChildrenToComplete; - else if ((sf & TASK_STATE_DELEGATE_INVOKED) != 0) rval = TaskStatus.Running; - else if ((sf & TASK_STATE_STARTED) != 0) rval = TaskStatus.WaitingToRun; - else if ((sf & TASK_STATE_WAITINGFORACTIVATION) != 0) rval = TaskStatus.WaitingForActivation; + if ((sf & (int)TaskStateFlags.Faulted) != 0) rval = TaskStatus.Faulted; + else if ((sf & (int)TaskStateFlags.Canceled) != 0) rval = TaskStatus.Canceled; + else if ((sf & (int)TaskStateFlags.RanToCompletion) != 0) rval = TaskStatus.RanToCompletion; + else if ((sf & (int)TaskStateFlags.WaitingOnChildren) != 0) rval = TaskStatus.WaitingForChildrenToComplete; + else if ((sf & (int)TaskStateFlags.DelegateInvoked) != 0) rval = TaskStatus.Running; + else if ((sf & (int)TaskStateFlags.Started) != 0) rval = TaskStatus.WaitingToRun; + else if ((sf & (int)TaskStateFlags.WaitingForActivation) != 0) rval = TaskStatus.WaitingForActivation; else rval = TaskStatus.Created; return rval; @@ -1295,7 +1295,7 @@ public TaskStatus Status /// public bool IsCanceled => // Return true if canceled bit is set and faulted bit is not set - (m_stateFlags & (TASK_STATE_CANCELED | TASK_STATE_FAULTED)) == TASK_STATE_CANCELED; + (m_stateFlags & ((int)TaskStateFlags.Canceled | (int)TaskStateFlags.Faulted)) == (int)TaskStateFlags.Canceled; /// /// Returns true if this task has a cancellation token and it was signaled. @@ -1353,7 +1353,7 @@ internal CancellationToken CancellationToken /// /// Gets whether this threw an OperationCanceledException while its CancellationToken was signaled. /// - internal bool IsCancellationAcknowledged => (m_stateFlags & TASK_STATE_CANCELLATIONACKNOWLEDGED) != 0; + internal bool IsCancellationAcknowledged => (m_stateFlags & (int)TaskStateFlags.CancellationAcknowledged) != 0; /// /// Gets whether this Task has completed. @@ -1377,10 +1377,10 @@ public bool IsCompleted // rather than reading the volatile m_stateFlags field. private static bool IsCompletedMethod(int flags) { - return (flags & TASK_STATE_COMPLETED_MASK) != 0; + return (flags & (int)TaskStateFlags.CompletedMask) != 0; } - public bool IsCompletedSuccessfully => (m_stateFlags & TASK_STATE_COMPLETED_MASK) == TASK_STATE_RAN_TO_COMPLETION; + public bool IsCompletedSuccessfully => (m_stateFlags & (int)TaskStateFlags.CompletedMask) == (int)TaskStateFlags.RanToCompletion; /// /// Gets the TaskCreationOptions used @@ -1418,7 +1418,7 @@ WaitHandle IAsyncResult.AsyncWaitHandle // forces allocation of a true WaitHandle when called. get { - bool isDisposed = (m_stateFlags & TASK_STATE_DISPOSED) != 0; + bool isDisposed = (m_stateFlags & (int)TaskStateFlags.Disposed) != 0; if (isDisposed) { ThrowHelper.ThrowObjectDisposedException(ExceptionResource.Task_ThrowIfDisposed); @@ -1513,18 +1513,18 @@ internal bool ExceptionRecorded /// public bool IsFaulted => // Faulted is "king" -- if that bit is present (regardless of other bits), we are faulted. - (m_stateFlags & TASK_STATE_FAULTED) != 0; + (m_stateFlags & (int)TaskStateFlags.Faulted) != 0; /// /// The captured execution context for the current task to run inside - /// If the TASK_STATE_EXECUTIONCONTEXT_IS_NULL flag is set, this means ExecutionContext.Capture returned null, otherwise + /// If the TaskStateFlags.ExecutionContextIsNull flag is set, this means ExecutionContext.Capture returned null, otherwise /// If the captured context is the default, nothing is saved, otherwise the m_contingentProperties inflates to save the context /// internal ExecutionContext? CapturedContext { get { - if ((m_stateFlags & TASK_STATE_EXECUTIONCONTEXT_IS_NULL) == TASK_STATE_EXECUTIONCONTEXT_IS_NULL) + if ((m_stateFlags & (int)TaskStateFlags.ExecutionContextIsNull) == (int)TaskStateFlags.ExecutionContextIsNull) { return null; } @@ -1538,7 +1538,7 @@ internal ExecutionContext? CapturedContext // There is no need to atomically set this bit because this set() method is only called during construction, and therefore there should be no contending accesses to m_stateFlags if (value == null) { - m_stateFlags |= TASK_STATE_EXECUTIONCONTEXT_IS_NULL; + m_stateFlags |= (int)TaskStateFlags.ExecutionContextIsNull; } else if (value != ExecutionContext.Default) // not the default context, then inflate the contingent properties and set it { @@ -1629,10 +1629,10 @@ protected virtual void Dispose(bool disposing) // We OR the flags to indicate the object has been disposed. The task // has already completed at this point, and the only conceivable race condition would - // be with the unsetting of the TASK_STATE_WAIT_COMPLETION_NOTIFICATION flag, which + // be with the unsetting of the TaskStateFlags.WaitCompletionNotification flag, which // is extremely unlikely and also benign. (Worst case: we hit a breakpoint // twice instead of once in the debugger. Weird, but not lethal.) - m_stateFlags |= TASK_STATE_DISPOSED; + m_stateFlags |= (int)TaskStateFlags.Disposed; } ///////////// @@ -1641,17 +1641,17 @@ protected virtual void Dispose(bool disposing) /// /// Schedules the task for execution. /// - /// If true, TASK_STATE_STARTED bit is turned on in - /// an atomic fashion, making sure that TASK_STATE_CANCELED does not get set - /// underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This + /// If true, TaskStateFlags.Started bit is turned on in + /// an atomic fashion, making sure that TaskStateFlags.Canceled does not get set + /// underneath us. If false, TaskStateFlags.Started bit is OR-ed right in. This /// allows us to streamline things a bit for StartNew(), where competing cancellations /// are not a problem. internal void ScheduleAndStart(bool needsProtection) { Debug.Assert(m_taskScheduler != null, "expected a task scheduler to have been selected"); - Debug.Assert((m_stateFlags & TASK_STATE_STARTED) == 0, "task has already started"); + Debug.Assert((m_stateFlags & (int)TaskStateFlags.Started) == 0, "task has already started"); - // Set the TASK_STATE_STARTED bit + // Set the TaskStateFlags.Started bit if (needsProtection) { if (!MarkStarted()) @@ -1662,7 +1662,7 @@ internal void ScheduleAndStart(bool needsProtection) } else { - m_stateFlags |= TASK_STATE_STARTED; + m_stateFlags |= (int)TaskStateFlags.Started; } if (s_asyncDebuggingEnabled) @@ -1915,7 +1915,7 @@ internal static void ThrowAsync(Exception exception, SynchronizationContext? tar /// /// Checks whether this is an attached task, and whether we are being called by the parent task. - /// And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that. + /// And sets the TaskStateFlags.ExceptionObservedByParent status flag based on that. /// /// This is meant to be used internally when throwing an exception, and when WaitAll is gathering /// exceptions for tasks it waited on. If this flag gets set, the implicit wait on children @@ -1932,21 +1932,21 @@ internal void UpdateExceptionObservedStatus() && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0) && Task.InternalCurrent == parent) { - m_stateFlags |= TASK_STATE_EXCEPTIONOBSERVEDBYPARENT; + m_stateFlags |= (int)TaskStateFlags.ExceptionObservedByParent; } } /// - /// Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set, + /// Checks whether the TaskStateFlags.ExceptionObservedByParent status flag is set, /// This will only be used by the implicit wait to prevent double throws /// /// - internal bool IsExceptionObservedByParent => (m_stateFlags & TASK_STATE_EXCEPTIONOBSERVEDBYPARENT) != 0; + internal bool IsExceptionObservedByParent => (m_stateFlags & (int)TaskStateFlags.ExceptionObservedByParent) != 0; /// /// Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task. /// - internal bool IsDelegateInvoked => (m_stateFlags & TASK_STATE_DELEGATE_INVOKED) != 0; + internal bool IsDelegateInvoked => (m_stateFlags & (int)TaskStateFlags.DelegateInvoked) != 0; /// /// Signals completion of this particular task. @@ -2000,11 +2000,11 @@ private void FinishSlow(bool userDelegateExecute) // We have to use an atomic update for this and make sure not to overwrite a final state, // because at this very moment the last child's thread may be concurrently completing us. - // Otherwise we risk overwriting the TASK_STATE_RAN_TO_COMPLETION, _CANCELED or _FAULTED bit which may have been set by that child task. - // Note that the concurrent update by the last child happening in FinishStageTwo could still wipe out the TASK_STATE_WAITING_ON_CHILDREN flag, + // Otherwise we risk overwriting the TaskStateFlags.RanToCompletion, _CANCELED or _FAULTED bit which may have been set by that child task. + // Note that the concurrent update by the last child happening in FinishStageTwo could still wipe out the TaskStateFlags.WaitingOnChildren flag, // but it is not critical to maintain, therefore we dont' need to intruduce a full atomic update into FinishStageTwo - AtomicStateUpdate(TASK_STATE_WAITING_ON_CHILDREN, TASK_STATE_FAULTED | TASK_STATE_CANCELED | TASK_STATE_RAN_TO_COMPLETION); + AtomicStateUpdate((int)TaskStateFlags.WaitingOnChildren, (int)TaskStateFlags.Faulted | (int)TaskStateFlags.Canceled | (int)TaskStateFlags.RanToCompletion); } // Now is the time to prune exceptional children. We'll walk the list and removes the ones whose exceptions we might have observed after they threw. @@ -2039,7 +2039,7 @@ private void FinishStageTwo() int completionState; if (ExceptionRecorded) { - completionState = TASK_STATE_FAULTED; + completionState = (int)TaskStateFlags.Faulted; if (TplEventSource.Log.IsEnabled()) TplEventSource.Log.TraceOperationEnd(this.Id, AsyncCausalityStatus.Error); @@ -2048,14 +2048,14 @@ private void FinishStageTwo() } else if (IsCancellationRequested && IsCancellationAcknowledged) { - // We transition into the TASK_STATE_CANCELED final state if the task's CT was signalled for cancellation, + // We transition into the TaskStateFlags.Canceled final state if the task's CT was signalled for cancellation, // and the user delegate acknowledged the cancellation request by throwing an OCE, - // and the task hasn't otherwise transitioned into faulted state. (TASK_STATE_FAULTED trumps TASK_STATE_CANCELED) + // and the task hasn't otherwise transitioned into faulted state. (TaskStateFlags.Faulted trumps TaskStateFlags.Canceled) // // If the task threw an OCE without cancellation being requestsed (while the CT not being in signaled state), // then we regard it as a regular exception - completionState = TASK_STATE_CANCELED; + completionState = (int)TaskStateFlags.Canceled; if (TplEventSource.Log.IsEnabled()) TplEventSource.Log.TraceOperationEnd(this.Id, AsyncCausalityStatus.Canceled); @@ -2064,7 +2064,7 @@ private void FinishStageTwo() } else { - completionState = TASK_STATE_RAN_TO_COMPLETION; + completionState = (int)TaskStateFlags.RanToCompletion; if (TplEventSource.Log.IsEnabled()) TplEventSource.Log.TraceOperationEnd(this.Id, AsyncCausalityStatus.Completed); @@ -2124,7 +2124,7 @@ internal void NotifyParentIfPotentiallyAttachedTask() Task? parent = m_contingentProperties?.m_parent; if (parent != null && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0) - && (((TaskCreationOptions)(m_stateFlags & OptionsMask)) & TaskCreationOptions.AttachedToParent) != 0) + && (((TaskCreationOptions)(m_stateFlags & (int)TaskStateFlags.OptionsMask)) & TaskCreationOptions.AttachedToParent) != 0) { parent.ProcessChildCompletion(this); } @@ -2227,9 +2227,9 @@ internal bool ExecuteEntry() // However we don't want this exception to be throw if the task was already canceled, because it's a // legitimate scenario for custom schedulers to dequeue a task and mark it as canceled (example: throttling scheduler) int previousState = 0; - if (!AtomicStateUpdate(TASK_STATE_DELEGATE_INVOKED, - TASK_STATE_DELEGATE_INVOKED | TASK_STATE_COMPLETED_MASK, - ref previousState) && (previousState & TASK_STATE_CANCELED) == 0) + if (!AtomicStateUpdate((int)TaskStateFlags.DelegateInvoked, + (int)TaskStateFlags.DelegateInvoked | (int)TaskStateFlags.CompletedMask, + ref previousState) && (previousState & (int)TaskStateFlags.Canceled) == 0) { // This task has already been invoked. Don't invoke it again. return false; @@ -2258,7 +2258,7 @@ internal bool ExecuteEntry() internal void ExecuteEntryUnsafe(Thread? threadPoolThread) // used instead of ExecuteEntry() when we don't have to worry about double-execution prevent { // Remember that we started running the task delegate. - m_stateFlags |= TASK_STATE_DELEGATE_INVOKED; + m_stateFlags |= (int)TaskStateFlags.DelegateInvoked; if (!IsCancellationRequested & !IsCanceled) { @@ -2274,8 +2274,8 @@ internal void ExecuteEntryCancellationRequestedOrCanceled() { if (!IsCanceled) { - int prevState = Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED); - if ((prevState & TASK_STATE_CANCELED) == 0) + int prevState = Interlocked.Exchange(ref m_stateFlags, m_stateFlags | (int)TaskStateFlags.Canceled); + if ((prevState & (int)TaskStateFlags.Canceled) == 0) { CancellationCleanupLogic(); } @@ -2727,7 +2727,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) ThrowIfExceptional(true); } - Debug.Assert((m_stateFlags & TASK_STATE_FAULTED) == 0, "Task.Wait() completing when in Faulted state."); + Debug.Assert((m_stateFlags & (int)TaskStateFlags.Faulted) == 0, "Task.Wait() completing when in Faulted state."); return true; } @@ -3060,7 +3060,7 @@ internal void InternalCancel() bool popped = false; // If started, and running in a task context, we can try to pop the chore. - if ((m_stateFlags & TASK_STATE_STARTED) != 0) + if ((m_stateFlags & (int)TaskStateFlags.Started) != 0) { TaskScheduler? ts = m_taskScheduler; try @@ -3081,24 +3081,24 @@ internal void InternalCancel() // Determine whether we need to clean up // This will be the case - // 1) if we were able to pop, and we are able to update task state to TASK_STATE_CANCELED + // 1) if we were able to pop, and we are able to update task state to TaskStateFlags.Canceled // 2) if the task seems to be yet unstarted, and we can transition to - // TASK_STATE_CANCELED before anyone else can transition into _STARTED or _CANCELED or + // TaskStateFlags.Canceled before anyone else can transition into _STARTED or _CANCELED or // _RAN_TO_COMPLETION or _FAULTED - // Note that we do not check for TASK_STATE_COMPLETION_RESERVED. That only applies to promise-style + // Note that we do not check for TaskStateFlags.CompletionReserved. That only applies to promise-style // tasks, and a promise-style task should not enter into this codepath. bool mustCleanup = false; if (popped) { - // Include TASK_STATE_DELEGATE_INVOKED in "illegal" bits to protect against the situation where + // Include TaskStateFlags.DelegateInvoked in "illegal" bits to protect against the situation where // TS.TryDequeue() returns true but the task is still left on the queue. - mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, TASK_STATE_CANCELED | TASK_STATE_DELEGATE_INVOKED); + mustCleanup = AtomicStateUpdate((int)TaskStateFlags.Canceled, (int)TaskStateFlags.Canceled | (int)TaskStateFlags.DelegateInvoked); } - else if ((m_stateFlags & TASK_STATE_STARTED) == 0) + else if ((m_stateFlags & (int)TaskStateFlags.Started) == 0) { - mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, - TASK_STATE_CANCELED | TASK_STATE_STARTED | TASK_STATE_RAN_TO_COMPLETION | - TASK_STATE_FAULTED | TASK_STATE_DELEGATE_INVOKED); + mustCleanup = AtomicStateUpdate((int)TaskStateFlags.Canceled, + (int)TaskStateFlags.Canceled | (int)TaskStateFlags.Started | (int)TaskStateFlags.RanToCompletion | + (int)TaskStateFlags.Faulted | (int)TaskStateFlags.DelegateInvoked); } // do the cleanup (i.e. set completion event and finish continuations) @@ -3125,12 +3125,12 @@ internal void InternalCancelContinueWithInitialState() // - it was canceled, which won't have happened without a token // - it was run as a continuation, which won't have happened because this method is only invoked once // As a result, we can take an optimized path that avoids inflating contingent properties. - const int IllegalFlags = TASK_STATE_STARTED | TASK_STATE_COMPLETED_MASK | TASK_STATE_DELEGATE_INVOKED; + const int IllegalFlags = (int)TaskStateFlags.Started | (int)TaskStateFlags.CompletedMask | (int)TaskStateFlags.DelegateInvoked; Debug.Assert((m_stateFlags & IllegalFlags) == 0, "The continuation was in an invalid state."); - Debug.Assert((m_stateFlags & TASK_STATE_WAITINGFORACTIVATION) != 0, "Expected continuation to be waiting for activation"); + Debug.Assert((m_stateFlags & (int)TaskStateFlags.WaitingForActivation) != 0, "Expected continuation to be waiting for activation"); Debug.Assert(m_contingentProperties is null || m_contingentProperties.m_cancellationToken == default); - m_stateFlags |= TASK_STATE_CANCELED; // no synchronization necessary, per above comment + m_stateFlags |= (int)TaskStateFlags.Canceled; // no synchronization necessary, per above comment CancellationCleanupLogic(); } @@ -3181,14 +3181,14 @@ internal void RecordInternalCancellationRequest(CancellationToken tokenToRecord, // And this method should be called at most once per task. internal void CancellationCleanupLogic() { - Debug.Assert((m_stateFlags & (TASK_STATE_CANCELED | TASK_STATE_COMPLETION_RESERVED)) != 0, "Task.CancellationCleanupLogic(): Task not canceled or reserved."); + Debug.Assert((m_stateFlags & ((int)TaskStateFlags.Canceled | (int)TaskStateFlags.CompletionReserved)) != 0, "Task.CancellationCleanupLogic(): Task not canceled or reserved."); // We'd like to be able to: // Debug.Assert((m_completionEvent == null) || !m_completionEvent.IsSet, "Task.CancellationCleanupLogic(): Completion event already set."); // However, there is a small window for a race condition. If someone calls Wait() between InternalCancel() and // here, that will set m_completionEvent, leading to a meaningless/harmless assertion. // This may have been set already, but we need to make sure. - Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED); + Interlocked.Exchange(ref m_stateFlags, m_stateFlags | (int)TaskStateFlags.Canceled); // Fire completion event if it has been lazily initialized ContingentProperties? cp = Volatile.Read(ref m_contingentProperties); @@ -3216,7 +3216,7 @@ private void SetCancellationAcknowledged() Debug.Assert(this == Task.InternalCurrent, "SetCancellationAcknowledged() should only be called while this is still the current task"); Debug.Assert(IsCancellationRequested, "SetCancellationAcknowledged() should not be called if the task's CT wasn't signaled"); - m_stateFlags |= TASK_STATE_CANCELLATIONACKNOWLEDGED; + m_stateFlags |= (int)TaskStateFlags.CancellationAcknowledged; } /// Completes a promise task as RanToCompletion. @@ -3227,8 +3227,8 @@ internal bool TrySetResult() Debug.Assert(m_action == null, "Task.TrySetResult(): non-null m_action"); if (AtomicStateUpdate( - TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION, - TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) + (int)TaskStateFlags.CompletionReserved | (int)TaskStateFlags.RanToCompletion, + (int)TaskStateFlags.CompletionReserved | (int)TaskStateFlags.RanToCompletion | (int)TaskStateFlags.Faulted | (int)TaskStateFlags.Canceled)) { ContingentProperties? props = m_contingentProperties; if (props != null) @@ -3275,8 +3275,8 @@ internal bool TrySetException(object exceptionObject) // anyway. Some downstream logic may depend upon an inflated m_contingentProperties. EnsureContingentPropertiesInitialized(); if (AtomicStateUpdate( - TASK_STATE_COMPLETION_RESERVED, - TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) + (int)TaskStateFlags.CompletionReserved, + (int)TaskStateFlags.CompletionReserved | (int)TaskStateFlags.RanToCompletion | (int)TaskStateFlags.Faulted | (int)TaskStateFlags.Canceled)) { AddException(exceptionObject); // handles singleton exception or exception collection Finish(false); @@ -3315,8 +3315,8 @@ cancellationException is OperationCanceledException || // // If the reservation is successful, then record the cancellation and finish completion processing. if (AtomicStateUpdate( - TASK_STATE_COMPLETION_RESERVED, - TASK_STATE_COMPLETION_RESERVED | TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION)) + (int)TaskStateFlags.CompletionReserved, + (int)TaskStateFlags.CompletionReserved | (int)TaskStateFlags.Canceled | (int)TaskStateFlags.Faulted | (int)TaskStateFlags.RanToCompletion)) { RecordInternalCancellationRequest(tokenToRecord, cancellationException); CancellationCleanupLogic(); // perform cancellation cleanup actions diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs index 18feb9a226698..05576dae666ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs @@ -216,7 +216,7 @@ protected static void InlineIfPossibleOrElseQueue(Task task, bool needsProtectio Debug.Assert(task != null); Debug.Assert(task.m_taskScheduler != null); - // Set the TASK_STATE_STARTED flag. This only needs to be done + // Set the TaskStateFlags.Started flag. This only needs to be done // if the task may be canceled or if someone else has a reference to it // that may try to execute it. if (needsProtection) @@ -226,7 +226,7 @@ protected static void InlineIfPossibleOrElseQueue(Task task, bool needsProtectio } else { - task.m_stateFlags |= Task.TASK_STATE_STARTED; + task.m_stateFlags |= (int)Task.TaskStateFlags.Started; } // Try to inline it but queue if we can't diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskScheduler.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskScheduler.cs index 2d901838707a4..e8d3ef133577f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskScheduler.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskScheduler.cs @@ -166,7 +166,7 @@ internal bool TryRunInline(Task task, bool taskWasPreviouslyQueued) { // Do not inline unstarted tasks (i.e., task.ExecutingTaskScheduler == null). // Do not inline TaskCompletionSource-style (a.k.a. "promise") tasks. - // No need to attempt inlining if the task body was already run (i.e. either TASK_STATE_DELEGATE_INVOKED or TASK_STATE_CANCELED bits set) + // No need to attempt inlining if the task body was already run (i.e. either TaskStateFlags.DelegateInvoked or TaskStateFlags.Canceled bits set) TaskScheduler? ets = task.ExecutingTaskScheduler; // Delegate cross-scheduler inlining requests to target scheduler @@ -189,7 +189,7 @@ internal bool TryRunInline(Task task, bool taskWasPreviouslyQueued) bool inlined = TryExecuteTaskInline(task, taskWasPreviouslyQueued); - // If the custom scheduler returned true, we should either have the TASK_STATE_DELEGATE_INVOKED or TASK_STATE_CANCELED bit set + // If the custom scheduler returned true, we should either have the TaskStateFlags.DelegateInvoked or TaskStateFlags.Canceled bit set // Otherwise the scheduler is buggy if (inlined && !(task.IsDelegateInvoked || task.IsCanceled)) {