Esempio n. 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");

            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 && quantum != TimeInterval.Empty)
                    {
                        executor.RegisterPhase(s => ExecuteGrantPhase(s, quantum));
                    }

                    if (!(shouldGrantTime && quantum == TimeInterval.Empty))
                    {
                        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);
        }
Esempio n. 2
0
 /// <summary>
 /// Grants time interval to a single handle.
 /// </summary>
 private void ExecuteGrantPhase(LinkedListNode <TimeHandle> handle, TimeInterval quantum)
 {
     State = TimeSourceState.ReportingElapsedTime;
     handle.Value.GrantTimeInterval(quantum);
 }
        /// <summary>
        /// Used by the slave to requests a new time interval from the source.
        /// This method blocks current thread until the time interval is granted.
        /// </summary>
        /// <remarks>
        /// This method will return immediately when the handle is disabled or detached.
        /// It is illegal to call this method twice in a row if the first call was successful (returned true). It must always be followed by calling <see cref="ReportBackAndContinue"> or <see cref="ReportBackAndBreak">.
        /// </remarks>
        /// <returns>
        /// True if the interval was granted or False when this call was interrupted as a result of detaching or disabling.
        /// If it returned true, <paramref name="interval"> contains the amount of virtual time to be used by the sink. It is the sum of time interval granted by the source (using <see cref="GrantInterval">) and a time left reported previously by <see cref="ReportBackAndContinue"> or <see cref="ReportBackAndBreak">.
        /// If it returned false, the time interval is not granted and it is illegal to report anything back using <see cref="ReportBackAndContinue"> or <see cref="ReportBackAndBreak">.
        /// </returns>
        public bool RequestTimeInterval(out TimeInterval interval)
        {
            this.Trace();
            lock (innerLock)
            {
                DebugHelper.Assert(!sinkSideInProgress, "Requested a new time interval, but the previous one is still processed.");

                var result = true;
                if (!Enabled)
                {
                    result = false;
                }
                else if (isBlocking && SourceSideActive)
                {
                    if (changingEnabled)
                    {
                        // we test `changingEnabled` here to avoid starvation:
                        // in order to change state of `Enabled` property the handle must not be latched,
                        // so the operation blocks until `latchLevel` drops down to 0;
                        // calling this method (`RequestTimeInterval`) when the handle is in a blocking state results
                        // in latching it temporarily until `WaitUntilDone` is called;
                        // this temporary latching/unlatching together with normal latching/unlatching in a short loop
                        // can cause `latchLevel` to fluctuate from 1 to 2 never allowing the operation modifying `Enabled` to finish
                        result = false;
                    }
                    else
                    {
                        // we check SourceSideActive here as otherwise unblocking will not succeed anyway
                        DebugHelper.Assert(!grantPending, "New grant not expected when blocked.");

                        this.Trace("Asking time source to unblock the time handle");
                        // latching here is to protect against disabling Enabled that would lead to making IsBlocking false while waiting for unblocking this handle
                        Latch();
                        Monitor.Exit(innerLock);
                        // it is necessary to leave `innerLock` since calling `UnblockHandle` could lead to a deadlock on `handles` collection
                        var isHandleUnblocked = TimeSource.UnblockHandle(this);
                        Monitor.Enter(innerLock);
                        if (!isHandleUnblocked)
                        {
                            Unlatch();
                            this.Trace("Unblocking handle is not allowed, quitting");
                            result = false;
                        }
                        else
                        {
                            this.Trace("Handle unblocked");
                            // since we are latched here, latchLevel 1 means that the handle is not currently latched by anybody else
                            // why? this is needed as we change the value of isBlocking - this should not happen when the handle is latched by another thread
                            this.Trace("About to wait until the latch reduces to 1");
                            innerLock.WaitWhile(() => latchLevel > 1, "Waiting for reducing the latch to 1");
                            isBlocking = false;

                            DebugHelper.Assert(!deferredUnlatch, "Unexpected value of deferredUnlatch");
                            deferredUnlatch   = true;
                            recentlyUnblocked = true;
                        }
                    }
                }
                else if (!grantPending)
                {
                    // wait until a new time interval is granted or this handle is disabled/deactivated
                    innerLock.WaitWhile(() => !grantPending && Enabled && SourceSideActive, "Waiting for a time grant");
                    result = grantPending;
                }

                if (!result)
                {
                    interval = TimeInterval.Empty;
                }
                else
                {
                    interval     = intervalGranted + timeResiduum;
                    timeResiduum = TimeInterval.Empty;

                    sinkSideInProgress = true;
                    grantPending       = false;
                }

                this.Trace($"{result}, {interval.Ticks}");
                return(result);
            }
        }