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.Diagnostics;
9 using System.Threading;
10 using Antmicro.Migrant;
11 using Antmicro.Renode.Debugging;
12 using Antmicro.Renode.Logging;
13 
14 namespace Antmicro.Renode.Time
15 {
16     /// <summary>
17     /// Represents an object that can be used to interruptably suspend execution of the current thread for a given period.
18     /// </summary>
19     public class Sleeper
20     {
Sleeper()21         public Sleeper()
22         {
23             locker = new object();
24             stopwatch = new Stopwatch();
25             cancellationToken = new CancellationTokenSource();
26         }
27 
28         /// <summary>
29         /// Suspends execution of the current thread for <see cref="time"> period. This can be interrupted by calling <see cref="Disable"> method on this object from other thread.
30         /// </summary>
31         /// <returns>
32         /// The flag informing if sleeping was interrupted.
33         /// See <see cref="timeElapsed"> for the actual time spent sleeping before interruption.
34         /// </returns>
Sleep(TimeSpan time, out TimeSpan timeElapsed, bool preserveInterruptRequest = false)35         public bool Sleep(TimeSpan time, out TimeSpan timeElapsed, bool preserveInterruptRequest = false)
36         {
37             stopwatch.Restart();
38             var timeLeft = time;
39             var tokenSource = cancellationToken;
40             this.Trace($"Asked to sleep for {timeLeft}");
41             while(timeLeft.Ticks > 0 && !tokenSource.IsCancellationRequested)
42             {
43                 this.Trace($"Sleeping for {timeLeft}");
44                 tokenSource.Token.WaitHandle.WaitOne(timeLeft);
45                 timeLeft = time - stopwatch.Elapsed;
46             }
47             stopwatch.Stop();
48 
49             timeElapsed = stopwatch.Elapsed > time ? time : stopwatch.Elapsed;
50             lock(locker)
51             {
52                 if(tokenSource.IsCancellationRequested && preserveInterruptRequest)
53                 {
54                     // Cancel the new token so that the next Sleep will pick up the cancellation
55                     // that interrupted this one
56                     Disable();
57                 }
58                 return tokenSource.IsCancellationRequested;
59             }
60         }
61 
62         /// <summary>
63         /// Disables sleeping.
64         /// </summary>
65         /// <remarks>
66         /// All subsequent calls to <see cref="Sleep"> will finish immediately after calling this method.
67         /// In order to be able to use <see cref="Sleep"> method again call to <see cref="Enable"> is necessary.
68         /// </remarks>
Disable()69         public void Disable()
70         {
71             lock(locker)
72             {
73                 cancellationToken.Cancel();
74             }
75         }
76 
77         /// <summary>
78         /// Enables sleeping.
79         /// </summary>
80         /// <remarks>
81         /// Use this method to re-enable sleeping after calling <see cref="Disable">.
82         /// </remarks>
Enable()83         public void Enable()
84         {
85             lock(locker)
86             {
87                 cancellationToken?.Cancel();
88                 cancellationToken = new CancellationTokenSource();
89             }
90         }
91 
92         /// <summary>
93         /// Interrupts sleeping.
94         /// </summary>
95         /// <remarks>
96         /// Calling this method will wake up the sleeping thread if the sleeper is enabled.
97         /// If the thread is not sleeping at the moment of calling this method, it has no effects.
98         /// </remarks>
Interrupt()99         public void Interrupt()
100         {
101             lock(locker)
102             {
103                 if(cancellationToken.IsCancellationRequested)
104                 {
105                     return;
106                 }
107 
108                 Disable();
109                 Enable();
110             }
111         }
112 
113         [Constructor]
114         private CancellationTokenSource cancellationToken;
115         private readonly Stopwatch stopwatch;
116         private readonly object locker;
117     }
118 }
119