Esempio n. 1
0
        /// <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);
        }
Esempio n. 2
0
        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);
            }
        }
Esempio n. 3
0
        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");
            }
        }
Esempio n. 4
0
        /// <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));
        }
Esempio n. 5
0
 /// <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]);
     }
 }
Esempio n. 6
0
        /// <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);
        }
Esempio n. 7
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));
        }
Esempio n. 8
0
        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");
            }
        }
Esempio n. 9
0
        /// <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");
            }
        }
Esempio n. 10
0
 /// <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);
 }
Esempio n. 11
0
        /// <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);
        }
Esempio n. 12
0
 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]);
 }
Esempio n. 13
0
        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();
            }
        }