예제 #1
0
        /// <summary>
        /// Execute one iteration of time-granting loop.
        /// </summary>
        /// <remarks>
        /// The steps are as follows:
        /// (1) remove and forget all slave handles that requested detaching
        /// (2) check if there are any blocked slaves; if so DO NOT grant a time interval
        /// (2.1) if there are no blocked slaves grant a new time interval to every slave
        /// (3) wait for all slaves that are relevant in this execution (it can be either all slaves or just blocked ones) until they report back
        /// (4) (optional) sleep if the virtual time passed faster than a real one; this step is executed if <see cref="AdvanceImmediately"> is not set and <see cref="Performance"> is low enough
        /// (5) update elapsed virtual time
        /// (6) execute sync hook and delayed actions if any
        /// </remarks>
        /// <param name="virtualTimeElapsed">Contains the amount of virtual time that passed during execution of this method. It is the minimal value reported by a slave (i.e, some slaves can report higher/lower values).</param>
        /// <param name="timeLimit">Maximum amount of virtual time that can pass during the execution of this method. If not set, current <see cref="Quantum"> is used.</param>
        /// <returns>
        /// True if sync point has just been reached or False if the execution has been blocked.
        /// </returns>
        protected bool InnerExecute(out TimeInterval virtualTimeElapsed, TimeInterval?timeLimit = null)
        {
            if (updateNearestSyncPoint)
            {
                NearestSyncPoint      += timeLimit.HasValue ? TimeInterval.Min(timeLimit.Value, Quantum) : Quantum;
                updateNearestSyncPoint = false;
                this.Trace($"Updated NearestSyncPoint to: {NearestSyncPoint}");
            }
            DebugHelper.Assert(NearestSyncPoint.Ticks >= ElapsedVirtualTime.Ticks, $"Nearest sync point set in the past: EVT={ElapsedVirtualTime} NSP={NearestSyncPoint}");

            isBlocked = false;
            var quantum = NearestSyncPoint - ElapsedVirtualTime;

            this.Trace($"Starting a loop with #{quantum.Ticks} ticks");

            SynchronizeVirtualTime();
            var elapsedVirtualTimeAtStart = ElapsedVirtualTime;

            using (sync.LowPriority)
            {
                handles.LatchAllAndCollectGarbage();
                var shouldGrantTime = handles.AreAllReadyForNewGrant;

                this.Trace($"Iteration start: slaves left {handles.ActiveCount}; will we try to grant time? {shouldGrantTime}");

                if (handles.ActiveCount > 0)
                {
                    var executor = new PhaseExecutor <LinkedListNode <TimeHandle> >();

                    if (!shouldGrantTime)
                    {
                        executor.RegisterPhase(ExecuteUnblockPhase);
                        executor.RegisterPhase(ExecuteWaitPhase);
                    }
                    else if (quantum != TimeInterval.Empty)
                    {
                        executor.RegisterPhase(s => ExecuteGrantPhase(s, quantum));
                        executor.RegisterPhase(ExecuteWaitPhase);
                    }

                    if (ExecuteInSerial)
                    {
                        executor.ExecuteInSerial(handles.WithLinkedListNode);
                    }
                    else
                    {
                        executor.ExecuteInParallel(handles.WithLinkedListNode);
                    }

                    SynchronizeVirtualTime();
                    virtualTimeElapsed = ElapsedVirtualTime - elapsedVirtualTimeAtStart;
                }
                else
                {
                    this.Trace($"There are no slaves, updating VTE by {quantum.Ticks}");
                    // if there are no slaves just make the time pass
                    virtualTimeElapsed = quantum;

                    UpdateTime(quantum);
                    // here we must trigger `TimePassed` manually as no handles has been updated so they won't reflect the passed time
                    TimePassed?.Invoke(quantum);
                }

                handles.UnlatchAll();
            }

            SinksReportedkHook?.Invoke();
            if (!isBlocked)
            {
                ExecuteSyncPhase();
                updateNearestSyncPoint = true;
            }
            else
            {
                BlockHook?.Invoke();
            }

            State = TimeSourceState.Idle;

            this.Trace($"The end of {nameof(InnerExecute)} with result={!isBlocked}");
            return(!isBlocked);
        }
        /// <summary>
        /// Execute one iteration of time-granting loop.
        /// </summary>
        /// <remarks>
        /// The steps are as follows:
        /// (1) remove and forget all slave handles that requested detaching
        /// (2) check if there are any blocked slaves; if so DO NOT grant a time interval
        /// (2.1) if there are no blocked slaves grant a new time interval to every slave
        /// (3) wait for all slaves that are relevant in this execution (it can be either all slaves or just blocked ones) until they report back
        /// (4) (optional) sleep if the virtual time passed faster than a real one; this step is executed if <see cref="AdvanceImmediately"> is not set and <see cref="Performance"> is low enough
        /// (5) update elapsed virtual time
        /// (6) execute sync hook and delayed actions if any
        /// </remarks>
        /// <param name="virtualTimeElapsed">Contains the amount of virtual time that passed during execution of this method. It is the minimal value reported by a slave (i.e, some slaves can report higher/lower values).</param>
        /// <param name="timeLimit">Maximum amount of virtual time that can pass during the execution of this method. If not set, current <see cref="Quantum"> is used.</param>
        /// <returns>
        /// True if sync point has just been reached or False if the execution has been blocked.
        /// </returns>
        protected bool InnerExecute(out TimeInterval virtualTimeElapsed, TimeInterval?timeLimit = null)
        {
            if (updateNearestSyncPoint)
            {
                NearestSyncPoint      += timeLimit.HasValue ? TimeInterval.Min(timeLimit.Value, Quantum) : Quantum;
                updateNearestSyncPoint = false;
                this.Trace($"Updated NearestSyncPoint to: {NearestSyncPoint}");
            }
            DebugHelper.Assert(NearestSyncPoint.Ticks >= ElapsedVirtualTime.Ticks, $"Nearest sync point set in the past: EVT={ElapsedVirtualTime} NSP={NearestSyncPoint}");

            isBlocked = false;
            var quantum = NearestSyncPoint - ElapsedVirtualTime;

            this.Trace($"Starting a loop with #{quantum.Ticks} ticks");

            virtualTimeElapsed = TimeInterval.Empty;
            using (sync.LowPriority)
            {
                handles.LatchAllAndCollectGarbage();
                var shouldGrantTime = handles.AreAllReadyForNewGrant;

                this.Trace($"Iteration start: slaves left {handles.ActiveCount}; will we try to grant time? {shouldGrantTime}");
                elapsedAtLastGrant = stopwatch.Elapsed;

                if (handles.ActiveCount > 0)
                {
                    var executor = new PhaseExecutor <LinkedListNode <TimeHandle> >();

                    if (!shouldGrantTime)
                    {
                        executor.RegisterPhase(ExecuteUnblockPhase);
                        executor.RegisterPhase(ExecuteWaitPhase);
                    }
                    else if (quantum != TimeInterval.Empty)
                    {
                        executor.RegisterPhase(s => ExecuteGrantPhase(s, quantum));
                        executor.RegisterPhase(ExecuteWaitPhase);
                    }

                    if (ExecuteInSerial)
                    {
                        executor.ExecuteInSerial(handles.WithLinkedListNode);
                    }
                    else
                    {
                        executor.ExecuteInParallel(handles.WithLinkedListNode);
                    }

                    var commonElapsedTime = handles.CommonElapsedTime;
                    DebugHelper.Assert(commonElapsedTime >= ElapsedVirtualTime, $"A slave reports time from the past! The current virtual time is {ElapsedVirtualTime}, but {commonElapsedTime} has been reported");
                    virtualTimeElapsed = commonElapsedTime - ElapsedVirtualTime;
                }
                else
                {
                    this.Trace($"There are no slaves, updating VTE by {quantum.Ticks}");
                    // if there are no slaves just make the time pass
                    virtualTimeElapsed = quantum;

                    // here we must trigger `TimePassed` manually as no handles has been updated so they won't reflect the passed time
                    TimePassed?.Invoke(quantum);
                }

                handles.UnlatchAll();

                State = TimeSourceState.Sleeping;
                var elapsedThisTime = stopwatch.Elapsed - elapsedAtLastGrant;
                if (!AdvanceImmediately)
                {
                    var scaledVirtualTicksElapsed = virtualTimeElapsed.WithScaledTicks(1 / Performance).ToTimeSpan() - elapsedThisTime;
                    sleeper.Sleep(scaledVirtualTicksElapsed);
                }

                lock (hostTicksElapsed)
                {
                    this.Trace($"Updating virtual time by {virtualTimeElapsed.InMicroseconds} us");
                    this.virtualTicksElapsed.Update(virtualTimeElapsed.Ticks);
                    this.hostTicksElapsed.Update(TimeInterval.FromTimeSpan(elapsedThisTime).Ticks);
                }
            }

            if (!isBlocked)
            {
                ExecuteSyncPhase();
                updateNearestSyncPoint = true;
            }
            else
            {
                BlockHook?.Invoke();
            }

            State = TimeSourceState.Idle;

            this.Trace($"The end of {nameof(InnerExecute)} with result={!isBlocked}");
            return(!isBlocked);
        }