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