1 //
2 // Copyright (c) 2010-2022 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.Core;
11 using Antmicro.Renode.Logging;
12 using Antmicro.Renode.Utilities;
13 using Antmicro.Renode.Debugging;
14 
15 namespace Antmicro.Renode.Time
16 {
17     /// <summary>
18     /// Represents an intermediate entity that distribute interval granted from master time sources to other sinks.
19     /// </summary>
20     public class SlaveTimeSource : TimeSourceBase, ITimeSource, ITimeSink, IDisposable
21     {
22         /// <summary>
23         /// Creates a new instance of <see cref="SlaveTimeSource">.
24         /// </summary>
SlaveTimeSource()25         public SlaveTimeSource()
26         {
27             locker = new object();
28             TimePassed += HandleTimePassed;
29         }
30 
31         /// <summary>
32         /// Disposes this instance.
33         /// </summary>
Dispose()34         public override void Dispose()
35         {
36             this.Trace("Disposing...");
37             base.Dispose();
38             base.Stop();
39             lock(locker)
40             {
41                 TimeHandle?.Dispose();
42             }
43             StopDispatcher();
44             this.Trace("Disposed");
45         }
46 
47         /// <summary>
48         /// Pauses the execution of the time source. It can be resumed using <see cref="Resume"> method.
49         /// </summary>
Pause()50         public void Pause()
51         {
52             lock(locker)
53             {
54                 this.Trace("Pausing...");
55                 if(!isStarted)
56                 {
57                     this.Trace();
58                     return;
59                 }
60                 if(isPaused)
61                 {
62                     this.Trace();
63                     return;
64                 }
65                 RequestStop();
66                 using(sync.HighPriority)
67                 {
68                     stopwatch.Stop();
69                     isPaused = true;
70                     DeactivateSlavesSourceSide();
71                 }
72                 this.Trace("Paused");
73             }
74         }
75 
76         /// <summary>
77         /// Resumes execution of the time source.
78         /// </summary>
Resume()79         public void Resume()
80         {
81             this.Trace("Resuming...");
82             lock(locker)
83             {
84                 using(sync.HighPriority)
85                 {
86                     ActivateSlavesSourceSide();
87                     isPaused = false;
88                     stopwatch.Start();
89                 }
90             }
91             this.Trace("Resumed");
92         }
93 
94         /// <see cref="ITimeSource.Domain">
95         /// <remarks>
96         /// If this slave is not attached to any master time source, the domain is null.
97         /// </remarks>
98         public override ITimeDomain Domain { get { return timeHandle?.TimeSource.Domain; } }
99 
100         /// <see cref="ITimeSink.TimeHandle">
101         /// <remarks>
102         /// If this time source is already connected to a master, old handle is disposed before accepting the new one.
103         /// </remarks>
104         public TimeHandle TimeHandle
105         {
106             get
107             {
108                 return timeHandle;
109             }
110             set
111             {
112                 lock(locker)
113                 {
114                     StopDispatcher();
115                     TimeHandle?.Dispose();
116                     this.Trace("About to attach to the new master");
117                     timeHandle = value;
118                     timeHandle.PauseRequested += RequestStop;
119                     timeHandle.StartRequested += HandleStartRequest;
120                     ResetVirtualTime(timeHandle.TotalElapsedTime);
121                     StartDispatcher();
122                 }
123             }
124         }
125 
HandleStartRequest()126         private void HandleStartRequest()
127         {
128             this.Trace();
129             lock(locker)
130             {
131                 this.Trace();
132                 if(dispatcherThread == null)
133                 {
134                     this.Trace();
135                     // if the dispatcher is not started yet - start it
136                     StartDispatcher();
137                 }
138                 else
139                 {
140                     this.Trace();
141                     // if the dispatcher is already running - set the restart flag
142                     dispatcherStartRequested = true;
143                 }
144             }
145         }
146 
147         /// <summary>
148         /// Provides the implementation of time-distribution among slaves.
149         /// </summary>
Dispatch()150         private void Dispatch()
151         {
152             var isLocked = false;
153             try
154             {
155 #if DEBUG
156                 using(this.TraceRegion("Dispatcher loop"))
157 #endif
158                 using(this.ObtainSourceActiveState())
159                 using(this.ObtainSinkActiveState())
160                 using(TimeDomainsManager.Instance.RegisterCurrentThread(() => new TimeStamp(TimeHandle.TimeSource.NearestSyncPoint, TimeHandle.TimeSource.Domain)))
161                 {
162                     while(true)
163                     {
164                         try
165                         {
166                             while(isStarted)
167                             {
168                                 WaitIfBlocked();
169                                 if(!DispatchInner())
170                                 {
171                                     break;
172                                 }
173                             }
174                         }
175                         catch(Exception e)
176                         {
177                             this.Trace(LogLevel.Error, $"Got an exception: {e.Message} {e.StackTrace}");
178                             throw;
179                         }
180 
181                         lock(locker)
182                         {
183                             if(!dispatcherStartRequested)
184                             {
185                                 this.Trace();
186                                 // the `locker` is re-acquired here to
187                                 // make sure that dispose-related code of all usings
188                                 // is executed before setting `dispatcherThread` to
189                                 // null (what allows to start new dispatcher thread);
190                                 // otherwise there could be a race condition when
191                                 // new thread enters usings (e.g., activates source side)
192                                 // and then the old one exits them (deactivating source
193                                 // side as a result)
194                                 Monitor.Enter(locker, ref isLocked);
195                                 break;
196                             }
197 
198                             dispatcherStartRequested = false;
199                             this.Trace();
200                         }
201                     }
202                 }
203             }
204             finally
205             {
206                 dispatcherThread = null;
207                 if(isLocked)
208                 {
209                     this.Trace();
210                     Monitor.Exit(locker);
211                 }
212                 this.Trace();
213             }
214         }
215 
DispatchInner()216         private bool DispatchInner()
217         {
218             if(!TimeHandle.RequestTimeInterval(out var intervalGranted))
219             {
220                 this.Trace("Time interval request interrupted");
221                 return false;
222             }
223 
224             if(isPaused)
225             {
226                 this.Trace("Handle paused");
227                 TimeHandle.ReportBackAndBreak(intervalGranted);
228                 return true;
229             }
230 
231             var quantum = Quantum;
232             this.Trace($"Current QUANTUM is {quantum.Ticks} ticks");
233             var timeLeft = intervalGranted;
234 
235             while(waitingForSlave || (timeLeft >= quantum && isStarted))
236             {
237                 waitingForSlave = false;
238                 var syncPointReached = InnerExecute(out var elapsed);
239                 timeLeft -= elapsed;
240                 if(!syncPointReached)
241                 {
242                     // we should not ask for time grant since the current one is not finished yet
243                     waitingForSlave = true;
244                     TimeHandle.ReportBackAndBreak(timeLeft);
245                     return true;
246                 }
247             }
248 
249             TimeHandle.ReportBackAndContinue(timeLeft);
250             return true;
251         }
252 
StartDispatcher()253         private void StartDispatcher()
254         {
255             lock(locker)
256             {
257                 if(dispatcherThread != null || TimeHandle == null)
258                 {
259                     return;
260                 }
261 
262                 isStarted = true;
263                 dispatcherThread = new Thread(Dispatch) { IsBackground = true, Name = "SlaveTimeSource Dispatcher Thread" };
264                 dispatcherThread.Start();
265             }
266         }
267 
StopDispatcher()268         private void StopDispatcher()
269         {
270             Thread threadToJoin = null;
271             lock(locker)
272             {
273                 isStarted = false;
274                 if(dispatcherThread != null && Thread.CurrentThread.ManagedThreadId != dispatcherThread.ManagedThreadId)
275                 {
276                     threadToJoin = dispatcherThread;
277                 }
278             }
279             threadToJoin?.Join();
280         }
281 
HandleTimePassed(TimeInterval diff)282         private void HandleTimePassed(TimeInterval diff)
283         {
284             TimeHandle?.ReportProgress(diff);
285         }
286 
287         [Transient]
288         private Thread dispatcherThread;
289         private TimeHandle timeHandle;
290         private bool waitingForSlave;
291         private bool dispatcherStartRequested;
292         private readonly object locker;
293     }
294 }
295