/// <summary> /// Atomically transitions <see cref="State" /> to <paramref name="targetState" />. /// The current state must not change during the attempted transition (i.e., synchronization may be required). /// If provided, <paramref name="onCommit"/> is called upon committing to transition to the next state but /// before the state could possibly transition further. /// </summary> /// <remarks> /// This method is effectively not thread safe, since one must somehow know that a transition to /// <paramref name="targetState" /> is valid /// and that the current state will not change during this call (e.g. via some collaborating lock). /// </remarks> /// <returns>The prior pip state</returns> public PipState Transition(PipState targetState, PipType pipType, Action <PipState, PipState, PipType> onCommit = null) { // This Requires is Static (not at runtime) so we avoid loading State twice (it can change behind our backs). // Below we do a final (authoritative) load of State and then runtime-verify a valid transition. Contract.RequiresDebug(State.CanTransitionTo(targetState)); Contract.Requires(pipType < PipType.Max || onCommit == null); PipState presentState = State; if (!State.CanTransitionTo(targetState)) { Contract.Assume(false, I($"Transition failure (not a valid transition): {presentState:G} -> {targetState:G}")); } Contract.Assume(((int)presentState & PreCommitStateBit) == 0); bool transitioned = TryTransitionInternal(presentState, targetState, pipType, onCommit); if (!transitioned) { Contract.Assume( false, I($"Failed to transition a pip from {presentState:G} to {targetState:G} due to an unexpected intervening state change (current state is now {State:G})")); } return(presentState); }
private bool TryTransitionInternal(PipState assumedPresentState, PipState targetState, PipType pipType, Action <PipState, PipState, PipType> onCommit) { Contract.Requires(((int)assumedPresentState & PreCommitStateBit) == 0, "PipState values should have the high bit cleared"); // First we see if we can transition from assumedPresentState to a pre-commit version of it (note return value of State does // not change as a result of this). If we win, then we have rights to actually commit and visibly transition thereafter. // The end result is that {pre commit, onCommit(), actually transition} is atomic with respect to other concurrent calls. int preCommitStateValue = ((int)assumedPresentState) | PreCommitStateBit; int observedPresentStateValue = Interlocked.CompareExchange(ref m_state, preCommitStateValue, (int)assumedPresentState); if (observedPresentStateValue == (int)assumedPresentState) { if (onCommit != null) { onCommit(assumedPresentState, targetState, pipType); } Volatile.Write(ref m_state, (int)targetState); return(true); } else { return(false); } }
public static bool IndicatesFailure(this PipState current) { Contract.Ensures(ContractUtilities.Static(IsTerminal(current))); switch (current) { case PipState.Ignored: case PipState.Waiting: case PipState.Ready: case PipState.Running: return(false); // Non-terminal case PipState.Done: return(false); // Terminal but successful case PipState.Skipped: return(false); // Terminal but not considered a failure case PipState.Failed: case PipState.Canceled: return(true); // Oh no! default: throw Contract.AssertFailure("Unhandled Pip State"); } }
/// <summary> /// Atomically transitions <see cref="State" /> from <paramref name="assumedPresentState" /> to /// <paramref name="targetState" />. /// Returns a bool indicating if the transition succeeded (if false, the <paramref name="assumedPresentState" /> was /// mismatched with the current state). /// If provided, <paramref name="onCommit"/> is called upon committing to transition to the next state but /// before the state could possibly transition further. /// </summary> /// <remarks> /// This method is thread safe. /// </remarks> public bool TryTransition(PipState assumedPresentState, PipState targetState, PipType pipType = PipType.Max, Action <PipState, PipState, PipType> onCommit = null) { Contract.Requires(assumedPresentState.CanTransitionTo(targetState)); Contract.Requires(((int)assumedPresentState & PreCommitStateBit) == 0, "PipState values should have the high bit cleared"); Contract.Requires(pipType < PipType.Max || onCommit == null); return(TryTransitionInternal(assumedPresentState, targetState, pipType, onCommit)); }
/// <summary> /// Returns the number of pips in the given state (as of this snapshot). /// </summary> public long this[PipState state] { get { // TODO: not needed? Contract.Requires((int) state <= PipStateRange.MaxValue && (int) state >= PipStateRange.MinValue); return(m_counters[(int)state - PipStateRange.MinValue]); } }
/// <summary> /// Records that a pip is currently in <paramref name="state"/> and will have future transitions to count /// (but no prior transitions have been counted). /// </summary> public void AccumulateInitialStateBulk(PipState state, PipType pipType, int count) { long prior = Interlocked.CompareExchange( ref m_countersMatrix[(int)state - PipStateRange.MinValue, (int)state - PipStateRange.MinValue, (int)pipType], count, 0); Contract.Assume(prior == 0); }
/// <summary> /// See <see cref="PipRuntimeInfo.TryTransition" /> ; additionally accumulates counters for pips in the source and target /// states. /// </summary> public static bool TryTransition( this PipRuntimeInfo pipRuntimeInfo, PipStateCounters counters, PipType pipType, PipState assumedPresentState, PipState targetState) { Contract.Requires(pipRuntimeInfo != null); Contract.Requires(counters != null); return(pipRuntimeInfo.TryTransition(assumedPresentState, targetState, pipType, counters.OnPipStateTransitionCommit)); }
public static bool IsTerminal(this PipState current) { switch (current) { case PipState.Ignored: case PipState.Waiting: case PipState.Ready: case PipState.Running: return(false); case PipState.Skipped: case PipState.Failed: case PipState.Done: case PipState.Canceled: return(true); default: throw Contract.AssertFailure("Unhandled Pip State"); } }
/// <summary> /// Indicates if there is a valid state transition from <paramref name="current"/> to <paramref name="target"/>. /// </summary> public static bool CanTransitionTo(this PipState current, PipState target) { switch (target) { case PipState.Ignored: return(false); // Entry state. case PipState.Waiting: // Initial state on pip addition / explicit scheduling. return(current == PipState.Ignored || /* fail establish pip fingerprint */ current == PipState.Running); case PipState.Ready: // Pending pips are the staging area on the way to being queued - they may have unsatisfied dependencies return(current == PipState.Waiting); case PipState.Running: // Queued pips can be run when they reach the queue head. return(current == PipState.Ready); case PipState.Skipped: return // Skipping (due to failure of pre-requirements) can occur so long as the pip still has pre-requirements (i.e., Pending), // or if we tentatively skipped a filter-failing pip (that was later scheduled due to a filter change). (current == PipState.Waiting || // The pip may also transition from Running to Skipped if using /cacheonly mode and the pip was not a cache hit. current == PipState.Running); case PipState.Canceled: // Completion / failure is the consequence of actual execution. return(current == PipState.Running); case PipState.Failed: case PipState.Done: // Completion. return(current == PipState.Running); default: throw Contract.AssertFailure("Unhandled Pip State"); } }
/// <summary> /// Records that a pip is currently in <paramref name="state"/> and will have future transitions to count /// (but no prior transitions have been counted). /// </summary> public void AccumulateInitialState(PipState state, PipType pipType) { AccumulateTransitionInternal(state, state, pipType); }
/// <summary> /// Records that a pip transitioned between two states. /// </summary> /// <remarks> /// Note that for full consistency guarantees, this must be called as the <c>onCommit</c> callback of /// <see cref="PipRuntimeInfo.TryTransition" /> / <see cref="PipRuntimeInfo.Transition" />. /// </remarks> public void AccumulateTransition(PipState from, PipState to, PipType pipType) { Contract.Requires(from != to); AccumulateTransitionInternal(from, to, pipType); }
private void AccumulateTransitionInternal(PipState from, PipState to, PipType pipType) { // TODO: not needed? Contract.Requires((int) from <= PipStateRange.MaxValue && (int) from >= PipStateRange.MinValue); // TODO: not needed? Contract.Requires((int) to <= PipStateRange.MaxValue && (int) to >= PipStateRange.MinValue); Interlocked.Increment(ref m_countersMatrix[(int)from - PipStateRange.MinValue, (int)to - PipStateRange.MinValue, (int)pipType]); }
public void SnapshotStress() { var counters = new PipStateCounters(); bool[] exit = { false }; var transitionThreads = new Thread[4]; for (int i = 0; i < transitionThreads.Length; i++) { int threadId = i; transitionThreads[i] = new Thread( () => { while (!Volatile.Read(ref exit[0])) { PipState[] orderedStates = s_pipStates.ToArray(); Shuffle(new Random(threadId), orderedStates); // Create a new imaginary pip. counters.AccumulateInitialState(PipState.Ignored, PipType.Process); // Transition randomly until it is terminal. var currentState = PipState.Ignored; var priorStates = new HashSet <PipState> { currentState }; int nextCandidateStateIndex = 0; while (!currentState.IsTerminal()) { nextCandidateStateIndex = (nextCandidateStateIndex + 1) % orderedStates.Length; PipState possibleNextState = orderedStates[nextCandidateStateIndex]; if (priorStates.Contains(possibleNextState)) { // Cycle is possible. break; } if (possibleNextState != currentState && currentState.CanTransitionTo(possibleNextState)) { priorStates.Add(possibleNextState); counters.AccumulateTransition(currentState, possibleNextState, PipType.Process); currentState = possibleNextState; } } } }); } try { foreach (Thread t in transitionThreads) { t.Start(); } var snapshot = new PipStateCountersSnapshot(); long sum = 0; while (sum < 100 * 1000) { counters.CollectSnapshot(s_pipTypes, snapshot); foreach (PipState state in s_pipStates) { XAssert.IsTrue(snapshot[state] >= 0); } long newSum = s_pipStates.Sum(s => snapshot[s]); XAssert.IsTrue(newSum >= sum, "Counters must be (probably non-strictly) monotonic"); sum = newSum; } } finally { Volatile.Write(ref exit[0], true); } foreach (Thread t in transitionThreads) { t.Join(); } }