1 // 2 // Copyright (c) 2010-2023 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Threading; 9 using Antmicro.Migrant; 10 using Antmicro.Renode.Logging; 11 using Antmicro.Renode.Debugging; 12 13 namespace Antmicro.Renode.Time 14 { 15 /// <summary> 16 /// Represents a main time source generating the time flow. 17 /// </summary> 18 /// <remarks> 19 /// This time source can be set to run for a specified time, specified number of sync points or indefinitely. 20 /// </remarks> 21 public class MasterTimeSource : TimeSourceBase, IDisposable, ITimeDomain 22 { 23 /// <summary> 24 /// Creates new master time source instance. 25 /// </summary> MasterTimeSource()26 public MasterTimeSource() 27 { 28 locker = new object(); 29 } 30 31 /// <summary> 32 /// Disposes all slaves and stops underlying dispatcher thread. 33 /// </summary> Dispose()34 public override void Dispose() 35 { 36 this.Trace("Disposing..."); 37 lock(locker) 38 { 39 if(isDisposed) 40 { 41 this.Trace("Already disposed"); 42 return; 43 } 44 isDisposed = true; 45 // `Dispose` must be called before `Stop` as the latter waits for all `slaves` to finish (naturally or as a result of `Dispose`) 46 base.Dispose(); 47 Stop(); 48 } 49 this.Trace("Disposed"); 50 } 51 52 /// <summary> 53 /// Run the time source for a specified interval of virtual time. 54 /// </summary> 55 /// <remarks> 56 /// This method is blocking. It can be interrupted by disposing the time source. 57 /// </remarks> 58 /// <param name="period">Amount of virtual time to pass.</param> RunFor(TimeInterval period)59 public void RunFor(TimeInterval period) 60 { 61 EnsureDispatcherExited(); 62 63 using(ObtainStartedState()) 64 using(this.ObtainSourceActiveState()) 65 { 66 while(!isDisposed && period.Ticks > 0) 67 { 68 WaitIfBlocked(); 69 InnerExecute(out var timeElapsed, period); 70 period -= timeElapsed; 71 } 72 } 73 } 74 75 /// <summary> 76 /// Run the time source for a specified number of synchronization points. 77 /// </summary> 78 /// <remarks> 79 /// This method is blocking. It can be interrupted by disposing the time source. 80 /// </remarks> 81 /// <param name="numberOfSyncPoints">Number of synchronization points to pass (default 1).</param> Run(uint numberOfSyncPoints = 1)82 public void Run(uint numberOfSyncPoints = 1) 83 { 84 EnsureDispatcherExited(); 85 86 using(ObtainStartedState()) 87 using(this.ObtainSourceActiveState()) 88 { 89 for(var i = 0u; i < numberOfSyncPoints; i++) 90 { 91 bool syncPointReached; 92 do 93 { 94 if(isDisposed) 95 { 96 break; 97 } 98 syncPointReached = InnerExecute(out var notused); 99 } 100 while(!syncPointReached); 101 } 102 } 103 } 104 105 /// <summary> 106 /// Start the time-dispatching thread that provides new time grants in the background loop. 107 /// </summary> 108 /// <remarks> 109 /// This method is non-blocking. In order to stop the thread call <see cref="Stop"> method. 110 /// </remarks> Start()111 public new void Start() 112 { 113 this.Trace("Starting..."); 114 lock(locker) 115 { 116 if(!base.Start()) 117 { 118 this.Trace(); 119 return; 120 } 121 // Make sure the previous instance of the dispatcher thread has finished. 122 // Otherwise it could keep running after we started the new one and cause 123 // a tricky crash down the line. 124 dispatcherThread?.Join(); 125 // Get a fresh cancellation token for the new thread. 126 dispatcherThreadCanceller?.Dispose(); 127 dispatcherThreadCanceller = new CancellationTokenSource(); 128 dispatcherThread = new Thread(() => Dispatcher(dispatcherThreadCanceller.Token)) { Name = "MasterTimeSource Dispatcher", IsBackground = true }; 129 dispatcherThread.Start(); 130 this.Trace("Started"); 131 } 132 } 133 134 /// <summary> 135 /// Stop the time-dispatching thread. 136 /// Note that if this is called on the dispatcher thread itself from a time callback, 137 /// the thread will continue running for a moment after this function returns, but it 138 /// will not begin a new iteration of InnerExecute. 139 /// </summary> Stop()140 public new void Stop() 141 { 142 this.Trace("Stopping..."); 143 lock(locker) 144 { 145 base.Stop(); 146 // Cancel the currently-running dispatcher thread, if any. 147 dispatcherThreadCanceller?.Cancel(); 148 // If we're on the dispatcher thread, we are currently in the process of stopping 149 // initiated by the dispatcher itself, for example as part of a hook callback. 150 // In this case we can't join our own thread. 151 if(dispatcherThread != null && dispatcherThread.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) 152 { 153 this.Trace("Waiting for dispatcher thread"); 154 EnsureDispatcherExited(); 155 } 156 this.Trace("Stopped"); 157 } 158 } 159 160 /// <see cref="ITimeSource.Domain"> 161 /// <remarks> 162 /// The object of type <see cref="MasterTimeSource"> defines it's own time domain. 163 /// </remarks> 164 public override ITimeDomain Domain => this; 165 Dispatcher(CancellationToken token)166 private void Dispatcher(CancellationToken token) 167 { 168 #if DEBUG 169 using(this.TraceRegion("Dispatcher loop")) 170 #endif 171 using(ObtainSourceActiveState()) 172 using(TimeDomainsManager.Instance.RegisterCurrentThread(() => new TimeStamp(NearestSyncPoint, Domain))) 173 { 174 try 175 { 176 // The token will be canceled when stopping, just after isStarted gets cleared, 177 // with the crucial difference that the token is NOT shared between threads. 178 while(!token.IsCancellationRequested) 179 { 180 WaitIfBlocked(); 181 InnerExecute(out var notused); 182 } 183 } 184 catch(Exception e) 185 { 186 this.Trace(LogLevel.Error, $"Got an exception: {e.Message} @ {e.StackTrace}"); 187 throw; 188 } 189 } 190 } 191 EnsureDispatcherExited()192 private void EnsureDispatcherExited() 193 { 194 // We check isStarted to make sure the dispatcher thread is not supposed to be running 195 // and then wait for it to exit if needed. When the time source is paused from within 196 // the dispatcher, the thread object is left behind so that we can wait for it to exit 197 // as needed before further time source operations. 198 DebugHelper.Assert(!isStarted, "Dispatcher thread should not be set to run at this moment"); 199 dispatcherThread?.Join(); 200 dispatcherThread = null; 201 } 202 203 private bool isDisposed; 204 [Transient] 205 private Thread dispatcherThread; 206 [Transient] 207 private CancellationTokenSource dispatcherThreadCanceller; 208 private readonly object locker; 209 } 210 } 211