1 //
2 // Copyright (c) 2010-2025 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.Renode.Debugging;
10 using Antmicro.Renode.Logging;
11 using Antmicro.Renode.Utilities;
12 
13 namespace Antmicro.Renode.Time
14 {
15     /// <summary>
16     /// Handle used for synchronization and communication between <see cref="ITimeSource"> and <see cref="ITimeSink">.
17     /// </summary>
18     public class TimeHandle : IdentifiableObject
19     {
20         // -------
21         // The objects of this class are used to synchronize execution of `time sources` and `time sinks`.
22         //
23         // [SOURCE SIDE]                                   [SINK SIDE]
24         //                                   Dispose
25         //                                      |
26         //                                      V
27         //                                 +--------+
28         // Latch                       ->  |        |
29         // ...                             |        |
30         // (Grant / Unblock + (Latch)) ->  |        |  <-  Request* + (Latch)
31         // ...                             |  Time  |      ...
32         // WaitUntilDone* + (Unlatch)  ->  | Handle |  <-  ReportBreak / ReportContinue
33         // ...                             |        |
34         // Unlatch                     ->  |        |
35         //                                 |        |
36         //                                 +--------+
37         //                             --- properties ---
38         //                                 +--------+
39         // SourceSideActive            =   |        |  =   SinkSideActive
40         //                                 |        |  =   Enabled
41         //                                 +--------+
42         //
43         //
44         // Methods marked with '*' are blocking:
45         // * `Request` will block until `Grant` or `Unblock`
46         // * `WaitUntilDone` will block until `ReportBreak` or `ReportContinue`
47         //
48         // Methods surrounded with '()' are executed conditionally:
49         // * `Latch` as a result of `Request` is executed only if this is the first `Request` after `ReportBreak`
50         // * `Unlatch` as a result of `WaitUntilDone` is executed only if this is the first `WaitUntilDone` after successful unblocking of the handle
51         // * `Grant` is not executed as long as the previous `WaitUntilDone` does not finish successfully, returning `true`
52         // * `Unlock` is executed only when the previous `WaitUntilDone` returned `false`
53         //
54         //
55         // SOURCE SIDE simplified algorithm:
56         // (1)  `Latch` the handle
57         // (2?) `Grant` time or `Unblock` the handle if previous `WaitUntilDone` failed
58         // (3)  Call `WaitUntilDone`
59         // (4)  `Unlatch` the handle
60         // (5)  Go to p. (1)
61         //
62         // SINK SIDE simplified algorithm:
63         // (1) 'Request' time
64         // (2) Execute the time-aware code for a given virtual time
65         //   (2.1) Finish the execution when granted virtual time is depleted using `ReportContinue`
66         //   (2.2) Stop the execution in an arbitrary moment with the intent of resuming in the future and use `ReportBreak`
67         // (3) Go to p. (1)
68         //
69         //
70         // Properties:
71         // * `SourceSideActive` - when set to `false`: `Request` returns immediately with the `false` result
72         // * `SinkSideActive`   - when set to `false`: `WaitUntilDone` returns immediately with the `false` result
73         // * `Enabled`          - when set to `false`: `WaitUntilDone` returns immediately with the `true` result
74         // * `DeferredEnabled`  - `Enabled` will be assigned this value when unlatched
75         //
76         // Internal state:
77         // * `sourceSideInProgress` - `true` from  `Grant`                            to  `WaitUntilDone` or `Dispose`
78         // * `sinkSideInProgress`   - `true` from  `Request`                          to  `ReportBreak` or `ReportContinue` or `Dispose`
79         // * `grantPending`         - `true` from  `Grant` or `Unblock`               to  `Request`
80         // * `reportPending`        - `true` from  `ReportBreak` or `ReportContinue`  to  `WaitUntilDone`
81         // * `isBlocking`           - `true` from  `ReportBreak`                      to  `Request`
82         //
83         // Additional notes:
84         //
85         // 1. `Active` means that the code on source/sink side is working and follows the above-mentioned algorithm.
86         // Going `inactive` is a signal for a handle that its operations should not block anymore as there is no chance for their successful termination in the nearest future (i.e., as long as the handle is inactive).
87         //
88         // 2. When the handle is `disabled`, it does not inform the sink about passed time but immediately reports back, thus not blocking execution of other handles.
89         //
90         // 3. The handle is not allowed to resume the execution after reporting a break, without the explicit permission obtained from the time source.
91         // This is why the call of `Request` waits for the `UnblockHandle` when executed in a blocking state.
92         // Once the permission is granted, the handle uses what is left from the previous quantum instead of waiting for a new one.
93         //
94         // 4. Latching is needed to ensure that the handle will not become disabled/re-enabled in an arbitrary moment.
95         // As described in (2), the disabled handle does not synchronize the sink side, so it cannot switch state when the sink is in progress of an execution and the sink cannot resume execution when the source side is in progress.
96         // It is possible to defer changing value of `Enabled` by using `DeferredEnabled` property - their values will be automatically synced (i.e., `Enabled` will get `DeferredEnabled` value) when unlatching the handle.
97         // -------
98 
99         /// <summary>
100         /// Creates a new time handle and associates it with <paramref name="timeSource"/>.
101         /// </summary>
TimeHandle(ITimeSource timeSource, ITimeSink timeSink)102         public TimeHandle(ITimeSource timeSource, ITimeSink timeSink)
103         {
104             innerLock = new object();
105             enabled = true;
106             DeferredEnabled = true;
107 
108             TimeSource = timeSource;
109 
110             // we should not assign this handle to TimeSink as the source might not be configured properly yet
111             TimeSink = timeSink;
112 
113             Reset();
114             this.Trace();
115         }
116 
Reset()117         public void Reset()
118         {
119             lock(innerLock)
120             {
121                 DebugHelper.Assert(TimeSource.ElapsedVirtualTime >= TotalElapsedTime, $"Trying to move time handle back in time from: {TotalElapsedTime} to {TimeSource.ElapsedVirtualTime}");
122                 TotalElapsedTime = TimeSource.ElapsedVirtualTime;
123             }
124         }
125 
126         /// <summary>
127         /// Grants a time interval to <see cref="ITimeSink"/>.
128         /// </summary>
129         /// <remarks>
130         /// This method is called by <see cref="ITimeSource"/> and results in unblocking execution of all registered <see cref="ITimeSink"/> for granted period.
131         /// It is illegal to call this method twice in a row. It must be followed by calling <see cref="WaitUntilDone"/>.
132         /// </remarks>
GrantTimeInterval(TimeInterval interval)133         public void GrantTimeInterval(TimeInterval interval)
134         {
135             this.Trace($"{interval.Ticks}");
136             lock(innerLock)
137             {
138                 DebugHelper.Assert(IsReadyForNewTimeGrant, "Interval granted, but the handle is not ready for a new one.");
139                 sourceSideInProgress = true;
140 
141                 intervalGranted = interval;
142 
143                 if(enabled)
144                 {
145                     this.Trace();
146                     grantPending = true;
147                     Monitor.PulseAll(innerLock);
148                 }
149                 else
150                 {
151                     this.Trace();
152                     // if the handle is not enabled there is a special way of handling new time grants:
153                     // they are not reported to the sink and the following 'WaitUntilDone' returns immediately behaving like the whole time was used up;
154                     // we must make sure that the handle is not enabled before the next 'WaitUntilDone' because it could change its result
155                     Latch();
156                     deferredUnlatch = true;
157                 }
158             }
159             this.Trace();
160         }
161 
162         /// <summary>
163         /// Allows to continue execution of previously granted time interval.
164         /// </summary>
165         /// <remarks>
166         /// This method is called by <see cref="ITimeSource"/> and results in unblocking execution of all registered <see cref="ITimeSink"/> in order to finish execution of previously granted period.
167         /// It is illegal to call this method twice in a row. It must be followed by calling <see cref="WaitUntilDone"/>.
168         /// </remarks>
UnblockHandle()169         public bool UnblockHandle()
170         {
171             this.Trace();
172             lock(innerLock)
173             {
174                 DebugHelper.Assert(isBlocking || !enabled, "This handle should be blocking or disabled");
175 
176                 if(!waitsToBeUnblocked)
177                 {
178                     return false;
179                 }
180 
181                 waitsToBeUnblocked = false;
182                 grantPending = true;
183 
184                 Monitor.PulseAll(innerLock);
185                 return true;
186             }
187         }
188 
189         /// <summary>
190         /// Used by the slave to requests a new time interval from the source.
191         /// This method blocks current thread until the time interval is granted.
192         /// </summary>
193         /// <remarks>
194         /// This method will return immediately when the handle is disabled or detached.
195         /// 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">.
196         /// </remarks>
197         /// <returns>
198         /// True if the interval was granted or False when this call was interrupted as a result of detaching or disabling.
199         /// 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">.
200         /// 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">.
201         /// </returns>
RequestTimeInterval(out TimeInterval interval)202         public bool RequestTimeInterval(out TimeInterval interval)
203         {
204             this.Trace();
205             lock(innerLock)
206             {
207                 DebugHelper.Assert(!sinkSideInProgress, "Requested a new time interval, but the previous one is still processed.");
208 
209                 var result = true;
210                 if(!Enabled || interrupt)
211                 {
212                     result = false;
213                 }
214                 else if(isBlocking && SourceSideActive)
215                 {
216                     if(changingEnabled)
217                     {
218                         // we test `changingEnabled` here to avoid starvation:
219                         // in order to change state of `Enabled` property the handle must not be latched,
220                         // so the operation blocks until `latchLevel` drops down to 0;
221                         // calling this method (`RequestTimeInterval`) when the handle is in a blocking state results
222                         // in latching it temporarily until `WaitUntilDone` is called;
223                         // this temporary latching/unlatching together with normal latching/unlatching in a short loop
224                         // can cause `latchLevel` to fluctuate from 1 to 2 never allowing the operation modifying `Enabled` to finish
225                         result = false;
226                     }
227                     else
228                     {
229                         // we check SourceSideActive here as otherwise unblocking will not succeed anyway
230                         DebugHelper.Assert(!grantPending, "New grant not expected when blocked.");
231                         DebugHelper.Assert(!waitsToBeUnblocked, "Should not wait to be unblocked");
232 
233                         // we cannot latch again when deferredUnlatch is still on as we could overwrite it and never unlatch again
234                         innerLock.WaitWhile(() => deferredUnlatch && SourceSideActive && !interrupt, "Waiting for previous unlatch");
235                         if(!SourceSideActive || interrupt)
236                         {
237                             result = false;
238                         }
239                         else
240                         {
241                             this.Trace("Asking time source to unblock the time handle");
242                             // latching here is to protect against disabling Enabled that would lead to making IsBlocking false while waiting for unblocking this handle
243                             Latch();
244 
245                             waitsToBeUnblocked = true;
246                             innerLock.WaitWhile(() => waitsToBeUnblocked && SourceSideActive && !interrupt, "Waiting to be unblocked");
247                             if(!SourceSideActive || interrupt)
248                             {
249                                 DebugHelper.Assert(waitsToBeUnblocked, "Expected only one condition to change");
250 
251                                 Unlatch();
252                                 waitsToBeUnblocked = false;
253                                 result = false;
254 
255                                 this.Trace("Unblocking handle is not allowed, quitting");
256                             }
257                             else
258                             {
259                                 DebugHelper.Assert(!waitsToBeUnblocked, "Should not wait to be unblocked here");
260                                 DebugHelper.Assert(!deferredUnlatch, "Unexpected value of deferredUnlatch");
261 
262                                 deferredUnlatch = true;
263                                 recentlyUnblocked = true;
264                                 isBlocking = false;
265 
266                                 this.Trace("Handle unblocked");
267                             }
268                         }
269                     }
270                 }
271                 else if(!grantPending)
272                 {
273                     // wait until a new time interval is granted or this handle is disabled/deactivated
274                     innerLock.WaitWhile(() => !grantPending && Enabled && SourceSideActive && !interrupt, "Waiting for a time grant");
275                     result = grantPending && !delayGrant && !interrupt;
276                     delayGrant = false;
277                 }
278 
279                 if(!result)
280                 {
281                     interval = TimeInterval.Empty;
282                 }
283                 else
284                 {
285                     interval = intervalGranted + slaveTimeResiduum;
286                     DebugHelper.Assert(reportedTimeResiduum == TimeInterval.Empty, "Reported time residuum should be empty at this point");
287                     reportedTimeResiduum = slaveTimeResiduum;
288                     slaveTimeResiduum = TimeInterval.Empty;
289 
290                     sinkSideInProgress = true;
291                     grantPending = false;
292                 }
293 
294                 this.Trace($"{result}, {interval.Ticks}");
295                 interrupt = false;
296                 return result;
297             }
298         }
299 
ReportProgress(TimeInterval progress)300         public void ReportProgress(TimeInterval progress)
301         {
302             if(progress.Ticks == 0)
303             {
304                 return;
305             }
306 
307             lock(innerLock)
308             {
309                 // reportedTimeResiduum represents time that
310                 // has been reported, but not yet used;
311                 // we cannot report it again
312                 if(reportedTimeResiduum >= progress)
313                 {
314                     reportedTimeResiduum -= progress;
315                     return;
316                 }
317                 if(reportedTimeResiduum != TimeInterval.Empty)
318                 {
319                     progress -= reportedTimeResiduum;
320                     reportedTimeResiduum = TimeInterval.Empty;
321                 }
322 
323                 this.Trace($"Reporting progress: {progress}");
324                 TotalElapsedTime += progress;
325                 reportedSoFar += progress;
326                 TimeSource.ReportTimeProgress();
327             }
328         }
329 
330         /// <summary>
331         /// Informs a time source that the time interval is used, i.e., no more work can be done without exceeding it, and the sink is ready for the next one.
332         /// </summary>
333         /// <remarks>
334         /// It is possible that some part of granted interval cannot be used in this round. This value must be passed in <paramref name="timeLeft"> parameter.
335         /// It is illegal to call this method without first obtaining the interval using <see cref="RequestTimeInterval">.
336         /// </remarks>
337         /// <param name="timeLeft">Amount of time not used.</param>
ReportBackAndContinue(TimeInterval timeLeft)338         public void ReportBackAndContinue(TimeInterval timeLeft)
339         {
340             this.Trace($"{timeLeft.Ticks}");
341             lock(innerLock)
342             {
343                 if(DetachRequested)
344                 {
345                     return;
346                 }
347 
348                 DebugHelper.Assert(sinkSideInProgress, "Reporting a used time, but it seems that no grant has recently been requested.");
349                 sinkSideInProgress = false;
350 
351                 DebugHelper.Assert(slaveTimeResiduum == TimeInterval.Empty, "Time residuum should be empty here.");
352                 slaveTimeResiduum = timeLeft;
353                 intervalToReport = intervalGranted;
354 
355                 reportPending = true;
356 
357                 Monitor.PulseAll(innerLock);
358                 this.Trace();
359             }
360             ReportedBack?.Invoke();
361         }
362 
363         /// <summary>
364         /// Informs a time source that the time sink interrupted the execution before finishing the granted interval.
365         /// In order to finish the job it is required to call <see cref="RequestTimeInterval"> followed by <see cref="ReportBackAndContinue">.
366         /// </summary>
367         /// <remarks>
368         /// No new time interval will be granted to this and all other time sinks in the time domain until <see cref="ReportBackAndContinue"> is called.
369         /// It is illegal to call this method without first obtaining the interval using <see cref="RequestTimeInterval">.
370         /// </remarks>
371         /// <param name="intervalLeft">Amount of time not used.</param>
ReportBackAndBreak(TimeInterval timeLeft)372         public void ReportBackAndBreak(TimeInterval timeLeft)
373         {
374             this.Trace($"{timeLeft.Ticks}");
375             lock(innerLock)
376             {
377                 if(DetachRequested)
378                 {
379                     return;
380                 }
381 
382                 DebugHelper.Assert(sinkSideInProgress, "Reporting a used time, but it seems that no grant has recently been requested.");
383                 sinkSideInProgress = false;
384 
385                 intervalToReport = intervalGranted - timeLeft;
386                 intervalGranted = timeLeft;
387                 isBlocking = true;
388 
389                 reportPending = true;
390 
391                 Monitor.PulseAll(innerLock);
392                 this.Trace();
393             }
394             ReportedBack?.Invoke();
395         }
396 
397         /// <summary>
398         /// Informs a time source that any available time is used.
399         /// </summary>
400         /// <remarks>
401         /// It is illegal to call this method if an interval is obtained, i.e. between calls to <see cref="RequestTimeInterval"> and <see cref="ReportBackAndContinue"> or <see cref="ReportBackAndBreak">.
402         /// </remarks>
TrySkipToSyncPoint(out TimeInterval intervalSkipped)403         public bool TrySkipToSyncPoint(out TimeInterval intervalSkipped)
404         {
405             lock(innerLock)
406             {
407                 if(!RequestTimeInterval(out intervalSkipped))
408                 {
409                     return false;
410                 }
411                 ReportBackAndContinue(TimeInterval.Empty);
412                 return true;
413             }
414         }
415 
416         /// <summary>
417         /// Disables the handle and requests detaching it from <see cref="ITimeSource"/>.
418         /// </summary>
Dispose()419         public void Dispose()
420         {
421             this.Trace();
422             lock(innerLock)
423             {
424                 SinkSideActive = false;
425                 SourceSideActive = false;
426 
427                 // this operation is blocking if the handle is latched
428                 // it does not allow the handle to be disposed when in use
429                 Enabled = false;
430 
431                 DetachRequested = true;
432                 sinkSideInProgress = false;
433                 sourceSideInProgress = false;
434                 reportPending = false;
435                 intervalToReport = intervalGranted;
436                 Monitor.PulseAll(innerLock);
437 
438                 PauseRequested = null;
439                 StartRequested = null;
440             }
441             this.Trace();
442         }
443 
444         /// <summary>
445         /// Blocks the execution of current thread until the slave reports back.
446         /// </summary>
447         /// <param name="intervalUsed">Amount of virtual time that passed from the perspective of a slave.</param>
448         /// <returns>
449         /// A structure containing two booleans:
450         ///     * IsDone: True if the slave completed all the work or false if the execution was interrupted (and it's blocking now).
451         ///     * IsUnblockedRecently: True if the handle has recently (i.e., since the last call to `WaitUntilDone`) been unblocked - it resumed the execution after reporting break.
452         /// </returns>
WaitUntilDone(out TimeInterval intervalUsed)453         public WaitResult WaitUntilDone(out TimeInterval intervalUsed)
454         {
455             this.Trace();
456             lock(innerLock)
457             {
458                 Debugging.DebugHelper.Assert(sourceSideInProgress, "About to wait until time is used, but it seems none has recently been granted.");
459 
460                 innerLock.WaitWhile(() => sinkSideInProgress || (SinkSideActive && grantPending), "Waiting until time is used.");
461 
462                 intervalUsed = enabled ? intervalToReport : intervalGranted;
463                 intervalToReport = TimeInterval.Empty;
464 
465                 var isDone = !isBlocking;
466                 if(enabled && !SinkSideActive && !reportPending)
467                 {
468                     Debugging.DebugHelper.Assert(!deferredUnlatch, "Unexpected state of deferredUnlatch");
469 
470                     // 'false' value of 'SinkSideActive' means that there is no point hanging and waiting in this function as there is no chance of unblocking in the nearest future
471                     // in such situation just return 'false' simulating blocked state
472                     // the only exception is if `reportPending` is set which means that we should first return value as set be the previous Report{Continue,Break}
473 
474                     this.Trace("Forcing result to be false");
475 
476                     // being here means that the sink has not yet
477                     // seen the granted interval, so we can act
478                     // as if it called ReportBackAndBreak
479                     grantPending = false;
480                     isBlocking = true;
481                     intervalUsed = TimeInterval.Empty;
482                     isDone = false;
483                     // intervalGranted does not change
484 
485                     Monitor.PulseAll(innerLock);
486                     this.Trace();
487                 }
488 
489                 Debugging.DebugHelper.Assert(reportedSoFar <= intervalUsed);
490                 // here we report the remaining part of granted time
491                 reportedTimeResiduum = TimeInterval.Empty;
492                 ReportProgress(intervalUsed - reportedSoFar);
493                 reportedSoFar = TimeInterval.Empty;
494 
495                 reportPending = false;
496 
497                 if(isDone)
498                 {
499                     sourceSideInProgress = false;
500                 }
501 
502                 var result = new WaitResult(isDone, recentlyUnblocked);
503                 recentlyUnblocked = false;
504                 if(deferredUnlatch)
505                 {
506                     deferredUnlatch = false;
507                     Unlatch();
508                 }
509 
510                 Monitor.PulseAll(innerLock);
511 
512                 this.Trace($"Reporting {intervalUsed.Ticks} ticks used. Local elapsed virtual time is {TotalElapsedTime.Ticks} ticks.");
513                 this.Trace(result.ToString());
514                 return result;
515             }
516         }
517 
518         /// <summary>
519         /// Latches the time handle, i.e., blocks any calls resulting in changing <see cref="Enabled"> property until <see cref="Unlatch"> is called.
520         /// </summary>
521         /// <remarks>
522         /// This method is intended for use by time source to ensure that all asynchronous changes to the time handle's state are masked.
523         /// </remarks>
Latch()524         public void Latch()
525         {
526             this.Trace();
527             lock(innerLock)
528             {
529                 latchLevel++;
530                 this.Trace($"Time handle latched; current level is {latchLevel}");
531             }
532             this.Trace();
533         }
534 
535         /// <summary>
536         /// Unlatches the time handle.
537         /// </summary>
538         /// <remarks>
539         /// Calling this method will result in unblocking all threads wanting to change <see cref="Enabled"> property.
540         /// </remarks>
Unlatch()541         public void Unlatch()
542         {
543             this.Trace();
544             lock(innerLock)
545             {
546                 DebugHelper.Assert(latchLevel > 0, "Tried to unlatch not latched handle");
547                 latchLevel--;
548                 this.Trace($"Time handle unlatched; current level is {latchLevel}");
549                 // since there is one place when we wait for latch to be equal to 1, we have to pulse more often than only when latchLevel is 0
550                 Monitor.PulseAll(innerLock);
551 
552                 if(latchLevel == 0)
553                 {
554                     Enabled = DeferredEnabled;
555                 }
556             }
557             this.Trace();
558         }
559 
560         /// <summary>
561         /// Calls <see cref="PauseRequested"/> event.
562         /// </summary>
RequestPause()563         public void RequestPause()
564         {
565             this.Trace();
566             PauseRequested?.Invoke();
567         }
568 
569         /// <summary>
570         /// Calls <see cref="StartRequested"/> event.
571         /// </summary>
RequestStart()572         public void RequestStart()
573         {
574             this.Trace();
575             StartRequested?.Invoke();
576         }
577 
578         /// <summary>
579         /// Interrupts the current or next call to <see cref="RequestTimeInterval"/> causing it to return 'false' immediately.
580         /// The caller of this method should check if lock was acquired within specified timeout and retry if not.
581         /// </summary>
582         /// <param name="success">True if lock was acquired within timeout, false otherwise</param>
Interrupt(ref bool success, int millisecondsTimeout = Timeout.Infinite)583         public void Interrupt(ref bool success, int millisecondsTimeout = Timeout.Infinite)
584         {
585             var timeout = TimeSpan.FromMilliseconds(millisecondsTimeout);
586 
587             try
588             {
589                 Monitor.TryEnter(innerLock, timeout, ref success);
590                 if(success)
591                 {
592                     interrupt = true;
593                     Monitor.PulseAll(innerLock);
594                 }
595             }
596             finally
597             {
598                 // Ensure that the lock is released.
599                 if(success)
600                 {
601                     Monitor.Exit(innerLock);
602                 }
603             }
604         }
605 
606         /// <summary>
607         /// Sets the value indicating if the handle is enabled, i.e., is sink interested in the time information.
608         /// </summary>
609         /// <remarks>
610         /// When the handle is disabled it behaves as if the execution on the sink was instantaneous - it never blocks other threads, but keeps track of virtual time.
611         /// Setting this property might be blocking (if the time handle is currently latched).
612         /// Disabled handle will not block on <see cref="WaitUntilDone">, returning 'true' immediately.
613         /// Disabling the handle interrupts current <see cref="RequestTimeInterval"> call and makes all following calls return immediately with 'false'.
614         /// </remarks>
615         public bool Enabled
616         {
617             get
618             {
619                 return enabled;
620             }
621 
622             set
623             {
624                 lock(innerLock)
625                 {
626                     if(enabled == value)
627                     {
628                         return;
629                     }
630 
631                     changingEnabled = true;
632                     this.Trace("About to wait for unlatching the time handle");
633                     innerLock.WaitWhile(() => latchLevel > 0, "Waiting for unlatching the time handle");
634 
635                     this.Trace($"Enabled value changed: {enabled} -> {value}");
636                     enabled = value;
637                     DeferredEnabled = value;
638                     changingEnabled = false;
639                     if(!enabled)
640                     {
641                         Monitor.PulseAll(innerLock);
642 
643                         // we have just disabled the handle - it needs to be reset it to a state like after `ReportBackAndContinue` with not time left
644                         if(isBlocking)
645                         {
646                             slaveTimeResiduum = TimeInterval.Empty;
647                             reportedTimeResiduum = TimeInterval.Empty;
648                             intervalToReport = intervalGranted;
649                             reportPending = true;
650                             isBlocking = false;
651 
652                             Monitor.PulseAll(innerLock);
653                             this.Trace();
654                         }
655                     }
656                     else
657                     {
658                         TimeSource.ReportHandleActive();
659                         RequestStart();
660                     }
661                 }
662             }
663         }
664 
665         /// <summary>
666         /// Gets or sets the value indicating if this handle is active from the source perspective, i.e., will source grant new time in the nearest future.
667         /// </summary>
668         public bool SourceSideActive
669         {
670             get
671             {
672                 return sourceSideActive;
673             }
674 
675             set
676             {
677                 lock(innerLock)
678                 {
679                     this.Trace($"{value}");
680                     sourceSideActive = value;
681                     if(!sourceSideActive)
682                     {
683                         // there is a code that waits for a change of `SourceSideActive` value using `WaitWhile`, so we must call `PulseAll` here
684                         Monitor.PulseAll(innerLock);
685                     }
686                 }
687             }
688         }
689 
690         /// <summary>
691         /// Sets the value indicating if this handle is active from the sink perspective, i.e., will sink be requesting a time grant in the nearest future.
692         /// </summary>
693         /// <remarks>
694         /// As long as the handle is not active from the sink perspective all <see cref="WaitUntilDone"> calls will return immediately with 'false'.
695         /// </remarks>
696         public bool SinkSideActive
697         {
698             get
699             {
700                 return sinkSideActive;
701             }
702 
703             set
704             {
705                 lock(innerLock)
706                 {
707                     DebugHelper.Assert(!sinkSideInProgress, "Should not change sink side active state when sink is in progress");
708 
709                     this.Trace($"{value}");
710                     sinkSideActive = value;
711                     if(!sinkSideActive)
712                     {
713                         Monitor.PulseAll(innerLock);
714                     }
715                     else
716                     {
717                         // we must inform the source that we became active so it can spin its loop again
718                         TimeSource.ReportHandleActive();
719                     }
720                 }
721             }
722         }
723 
724         /// <summary>
725         /// Gets the flag indicating if the new time interval can be granted to this handle.
726         /// </summary>
727         /// <remarks>
728         /// In order for a handle to be ready to accept a new time grant, following conditions must be met:
729         /// * previously granted time must be completely used,
730         /// * detaching must not be requested.
731         /// </remarks>
732         public bool IsReadyForNewTimeGrant
733         {
734             get
735             {
736                 lock(innerLock)
737                 {
738                     var res = !sourceSideInProgress && !DetachRequested;
739                     this.Trace($"Reading IsReadyForNewTimeGrant: {res}; sourceSideInProgress={sourceSideInProgress}, DetachRequested={DetachRequested}");
740                     return res;
741                 }
742             }
743         }
744 
745         /// <summary>
746         /// This flag set guarantees the next call to <see cref="UnblockHandle"> to succeed.
747         /// </summary>
748         public bool IsReadyToBeUnblocked => waitsToBeUnblocked || !enabled;
749 
750         /// <summary>
751         /// Gets the flag indicating if this time handle is disposed and ready to be removed from its time source.
752         /// </summary>
753         public bool DetachRequested { get; private set; }
754 
755         /// <summary>
756         /// Gets the reference to the time source associated with this handle.
757         /// </summary>
758         public ITimeSource TimeSource { get; private set; }
759 
760         /// <summary>
761         /// Gets the reference to the time sink associated with this handle.
762         /// </summary>
763         public ITimeSink TimeSink { get; private set; }
764 
765         /// <summary>
766         /// Gets the amount of virtual time that passed from the perspective of this handle.
767         /// </summary>
768         public TimeInterval TotalElapsedTime { get; private set; }
769 
770         /// <summary>
771         /// The value of the enabled property that will be set on the nearest call to <see cref="Unlatch"> method.
772         /// </summary>
773         public bool DeferredEnabled { get; set; }
774 
775         /// <summary>
776         /// Delay time grant to sink by one call to <see cref="RequestTimeInterval"> when waiting for a time grant from source.
777         /// </summary>
778         public bool DelayGrant
779         {
780             get
781             {
782                 return delayGrant;
783             }
784 
785             set
786             {
787                 lock(innerLock)
788                 {
789                     delayGrant = value;
790                 }
791             }
792         }
793 
794         /// <summary>
795         /// Is set by the source, indicates whether the sink has used all of the time interval available to the source.
796         /// </summary>
797         public bool IsDone
798         {
799             get
800             {
801                 return isDone;
802             }
803 
804             set
805             {
806                 isDone = value;
807             }
808         }
809 
810         /// <summary>
811         /// Informs the sink that the source wants to pause its execution.
812         /// </summary>
813         /// <remarks>
814         /// The sink can react to it in the middle of a granted period and pause instantly.
815         /// </remarks>
816         public event Action PauseRequested;
817 
818         /// <summary>
819         /// Informs the sink that the source is about to (re)start its execution, so it should start the dispatcher thread and get ready for new grants.
820         /// </summary>
821         public event Action StartRequested;
822 
823         /// <summary>
824         /// Call when the sink calls ReportBackAndContinue or ReportBackAndBreak.
825         /// </summary>
826         public event Action ReportedBack;
827 
828         [Antmicro.Migrant.Hooks.PreSerialization]
VerifyStateBeforeSerialization()829         private void VerifyStateBeforeSerialization()
830         {
831             lock(innerLock)
832             {
833                 DebugHelper.Assert(!sinkSideInProgress, "Trying to save a time handle that processes a time grant");
834             }
835         }
836 
837         /// <summary>
838         /// Indicates that there is a time granted, but not yet successfully waited for (i.e., with 'true' result).
839         /// </summary>
840         private bool sourceSideInProgress;
841         private bool isBlocking;
842         /// <summary>
843         /// Indicates that there is a new time granted but not yet requested.
844         /// </summary>
845         private bool grantPending;
846         private bool sinkSideInProgress;
847         private bool reportPending;
848 
849         /// <summary>
850         /// The amount of time granted last time.
851         /// </summary>
852         private TimeInterval intervalGranted;
853         /// <summary>
854         /// The amount of time to return on next <see cref="WaitUntilDone"/>.
855         /// </summary>
856         private TimeInterval intervalToReport;
857         /// <summary>
858         /// The amount of time left from previous grant that was not used but reported back in <see cref="WaitUntilDone"/>.
859         /// </summary>
860         private TimeInterval slaveTimeResiduum;
861         /// <summary>
862         /// The amount of time left from previous grant that was reported in <see cref="ReportProgress"/>.
863         /// </summary>
864         private TimeInterval reportedTimeResiduum;
865         /// <summary>
866         /// Flag is set when the handle is actively waiting to be unblocked
867         /// </summary>
868         private bool waitsToBeUnblocked;
869         ///<summary>
870         /// The amount of time already reported since last WaitUntilDone.
871         ///</summary>
872         private TimeInterval reportedSoFar;
873 
874         private bool enabled;
875         private bool sinkSideActive;
876         private bool sourceSideActive;
877 
878         private bool changingEnabled;
879         private int latchLevel;
880         private bool deferredUnlatch;
881         private bool recentlyUnblocked;
882         private bool delayGrant;
883         private bool interrupt;
884         private volatile bool isDone;
885 
886         private readonly object innerLock;
887 
888         public struct WaitResult
889         {
WaitResultAntmicro.Renode.Time.TimeHandle.WaitResult890             public WaitResult(bool isDone, bool isUnblockedRecently) : this()
891             {
892                 IsDone = isDone;
893                 IsUnblockedRecently = isUnblockedRecently;
894             }
895 
ToStringAntmicro.Renode.Time.TimeHandle.WaitResult896             public override string ToString()
897             {
898                 return $"[WaitResult(isDone: {IsDone}, isActivatedRecently: {IsUnblockedRecently})]";
899             }
900 
901             public bool IsDone { get; private set; }
902             public bool IsUnblockedRecently { get; private set; }
903         }
904     }
905 }
906