// // Copyright (c) 2010-2023 Antmicro // // This file is licensed under the MIT License. // Full license text is available in 'licenses/MIT.txt'. // using System; using System.Threading; using Antmicro.Migrant; using Antmicro.Renode.Logging; using Antmicro.Renode.Debugging; namespace Antmicro.Renode.Time { /// /// Represents a main time source generating the time flow. /// /// /// This time source can be set to run for a specified time, specified number of sync points or indefinitely. /// public class MasterTimeSource : TimeSourceBase, IDisposable, ITimeDomain { /// /// Creates new master time source instance. /// public MasterTimeSource() { locker = new object(); } /// /// Disposes all slaves and stops underlying dispatcher thread. /// public override void Dispose() { this.Trace("Disposing..."); lock(locker) { if(isDisposed) { this.Trace("Already disposed"); return; } isDisposed = true; // `Dispose` must be called before `Stop` as the latter waits for all `slaves` to finish (naturally or as a result of `Dispose`) base.Dispose(); Stop(); } this.Trace("Disposed"); } /// /// Run the time source for a specified interval of virtual time. /// /// /// This method is blocking. It can be interrupted by disposing the time source. /// /// Amount of virtual time to pass. public void RunFor(TimeInterval period) { EnsureDispatcherExited(); using(ObtainStartedState()) using(this.ObtainSourceActiveState()) { while(!isDisposed && period.Ticks > 0) { WaitIfBlocked(); InnerExecute(out var timeElapsed, period); period -= timeElapsed; } } } /// /// Run the time source for a specified number of synchronization points. /// /// /// This method is blocking. It can be interrupted by disposing the time source. /// /// Number of synchronization points to pass (default 1). public void Run(uint numberOfSyncPoints = 1) { EnsureDispatcherExited(); using(ObtainStartedState()) using(this.ObtainSourceActiveState()) { for(var i = 0u; i < numberOfSyncPoints; i++) { bool syncPointReached; do { if(isDisposed) { break; } syncPointReached = InnerExecute(out var notused); } while(!syncPointReached); } } } /// /// Start the time-dispatching thread that provides new time grants in the background loop. /// /// /// This method is non-blocking. In order to stop the thread call method. /// public new void Start() { this.Trace("Starting..."); lock(locker) { if(!base.Start()) { this.Trace(); return; } // Make sure the previous instance of the dispatcher thread has finished. // Otherwise it could keep running after we started the new one and cause // a tricky crash down the line. dispatcherThread?.Join(); // Get a fresh cancellation token for the new thread. dispatcherThreadCanceller?.Dispose(); dispatcherThreadCanceller = new CancellationTokenSource(); dispatcherThread = new Thread(() => Dispatcher(dispatcherThreadCanceller.Token)) { Name = "MasterTimeSource Dispatcher", IsBackground = true }; dispatcherThread.Start(); this.Trace("Started"); } } /// /// Stop the time-dispatching thread. /// Note that if this is called on the dispatcher thread itself from a time callback, /// the thread will continue running for a moment after this function returns, but it /// will not begin a new iteration of InnerExecute. /// public new void Stop() { this.Trace("Stopping..."); lock(locker) { base.Stop(); // Cancel the currently-running dispatcher thread, if any. dispatcherThreadCanceller?.Cancel(); // If we're on the dispatcher thread, we are currently in the process of stopping // initiated by the dispatcher itself, for example as part of a hook callback. // In this case we can't join our own thread. if(dispatcherThread != null && dispatcherThread.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) { this.Trace("Waiting for dispatcher thread"); EnsureDispatcherExited(); } this.Trace("Stopped"); } } /// /// /// The object of type defines it's own time domain. /// public override ITimeDomain Domain => this; private void Dispatcher(CancellationToken token) { #if DEBUG using(this.TraceRegion("Dispatcher loop")) #endif using(ObtainSourceActiveState()) using(TimeDomainsManager.Instance.RegisterCurrentThread(() => new TimeStamp(NearestSyncPoint, Domain))) { try { // The token will be canceled when stopping, just after isStarted gets cleared, // with the crucial difference that the token is NOT shared between threads. while(!token.IsCancellationRequested) { WaitIfBlocked(); InnerExecute(out var notused); } } catch(Exception e) { this.Trace(LogLevel.Error, $"Got an exception: {e.Message} @ {e.StackTrace}"); throw; } } } private void EnsureDispatcherExited() { // We check isStarted to make sure the dispatcher thread is not supposed to be running // and then wait for it to exit if needed. When the time source is paused from within // the dispatcher, the thread object is left behind so that we can wait for it to exit // as needed before further time source operations. DebugHelper.Assert(!isStarted, "Dispatcher thread should not be set to run at this moment"); dispatcherThread?.Join(); dispatcherThread = null; } private bool isDisposed; [Transient] private Thread dispatcherThread; [Transient] private CancellationTokenSource dispatcherThreadCanceller; private readonly object locker; } }