OpenShot Audio Library | OpenShotAudio  0.6.0
juce_HighResolutionTimer.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2022 - Raw Material Software Limited
6 
7  JUCE is an open source library subject to commercial or open-source
8  licensing.
9 
10  The code included in this file is provided under the terms of the ISC license
11  http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12  To use, copy, modify, and/or distribute this software for any purpose with or
13  without fee is hereby granted provided that the above copyright notice and
14  this permission notice appear in all copies.
15 
16  JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17  EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18  DISCLAIMED.
19 
20  ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 //==============================================================================
27 class HighResolutionTimer::Impl : private PlatformTimerListener
28 {
29 public:
30  explicit Impl (HighResolutionTimer& o)
31  : owner { o } {}
32 
33  void startTimer (int newIntervalMs)
34  {
35  shouldCancelCallbacks.store (true);
36 
37  const auto shouldWaitForPendingCallbacks = [&]
38  {
39  const std::scoped_lock lock { timerMutex };
40 
41  if (timer.getIntervalMs() > 0)
42  timer.cancelTimer();
43 
44  jassert (timer.getIntervalMs() == 0);
45 
46  if (newIntervalMs > 0)
47  timer.startTimer (jmax (0, newIntervalMs));
48 
49  return callbackThreadId != std::this_thread::get_id()
50  && timer.getIntervalMs() <= 0;
51  }();
52 
53  if (shouldWaitForPendingCallbacks)
54  std::scoped_lock lock { callbackMutex };
55  }
56 
57  int getIntervalMs() const
58  {
59  const std::scoped_lock lock { timerMutex };
60  return timer.getIntervalMs();
61  }
62 
63  bool isTimerRunning() const
64  {
65  return getIntervalMs() > 0;
66  }
67 
68 private:
69  void onTimerExpired() final
70  {
71  callbackThreadId.store (std::this_thread::get_id());
72 
73  {
74  std::scoped_lock lock { callbackMutex };
75 
76  if (isTimerRunning())
77  {
78  try
79  {
80  owner.hiResTimerCallback();
81  }
82  catch (...)
83  {
84  // Exceptions thrown in a timer callback won't be
85  // propagated to the main thread, it's best to find
86  // a way to avoid them if possible
87  jassertfalse;
88  }
89  }
90  }
91 
92  callbackThreadId.store ({});
93  }
94 
95  HighResolutionTimer& owner;
96  mutable std::mutex timerMutex;
97  std::mutex callbackMutex;
98  std::atomic<std::thread::id> callbackThreadId{};
99  std::atomic<bool> shouldCancelCallbacks { false };
100  PlatformTimer timer { *this };
101 
102  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Impl)
103  JUCE_DECLARE_NON_MOVEABLE (Impl)
104 };
105 
106 //==============================================================================
108  : impl (std::make_unique<Impl> (*this)) {}
109 
111 {
112  // You *must* call stopTimer from the derived class destructor to
113  // avoid data races on the timer's vtable
114  jassert (! isTimerRunning());
115  stopTimer();
116 }
117 
118 void HighResolutionTimer::startTimer (int newIntervalMs)
119 {
120  impl->startTimer (newIntervalMs);
121 }
122 
124 {
125  impl->startTimer (0);
126 }
127 
129 {
130  return impl->getIntervalMs();
131 }
132 
134 {
135  return impl->isTimerRunning();
136 }
137 
138 //==============================================================================
139 #if JUCE_UNIT_TESTS
140 
141 class HighResolutionTimerTests final : public UnitTest
142 {
143 public:
144  HighResolutionTimerTests()
145  : UnitTest ("HighResolutionTimer", UnitTestCategories::threads) {}
146 
147  void runTest() override
148  {
149  constexpr int maximumTimeoutMs {30'000};
150 
151  beginTest ("Start/stop a timer");
152  {
153  WaitableEvent timerFiredOnce;
154  WaitableEvent timerFiredTwice;
155 
156  Timer timer {[&, callbackCount = 0]() mutable
157  {
158  switch (++callbackCount)
159  {
160  case 1: timerFiredOnce.signal(); return;
161  case 2: timerFiredTwice.signal(); return;
162  default: return;
163  }
164  }};
165 
166  expect (! timer.isTimerRunning());
167  expect (timer.getTimerInterval() == 0);
168 
169  timer.startTimer (1);
170  expect (timer.isTimerRunning());
171  expect (timer.getTimerInterval() == 1);
172  expect (timerFiredOnce.wait (maximumTimeoutMs));
173  expect (timerFiredTwice.wait (maximumTimeoutMs));
174 
175  timer.stopTimer();
176  expect (! timer.isTimerRunning());
177  expect (timer.getTimerInterval() == 0);
178  }
179 
180  beginTest ("Stop a timer from the timer callback");
181  {
182  WaitableEvent stoppedTimer;
183 
184  auto timerCallback = [&] (Timer& timer)
185  {
186  expect (timer.isTimerRunning());
187  timer.stopTimer();
188  expect (! timer.isTimerRunning());
189  stoppedTimer.signal();
190  };
191 
192  Timer timer {[&]{ timerCallback (timer); }};
193  timer.startTimer (1);
194  expect (stoppedTimer.wait (maximumTimeoutMs));
195  }
196 
197  beginTest ("Restart a timer from the timer callback");
198  {
199  WaitableEvent restartTimer;
200  WaitableEvent timerRestarted;
201  WaitableEvent timerFiredAfterRestart;
202 
203  Timer timer {[&, callbackCount = 0]() mutable
204  {
205  switch (++callbackCount)
206  {
207  case 1:
208  expect (restartTimer.wait (maximumTimeoutMs));
209  expect (timer.getTimerInterval() == 1);
210 
211  timer.startTimer (2);
212  expect (timer.getTimerInterval() == 2);
213  timerRestarted.signal();
214  return;
215 
216  case 2:
217  expect (timer.getTimerInterval() == 2);
218  timerFiredAfterRestart.signal();
219  return;
220 
221  default:
222  return;
223  }
224  }};
225 
226  timer.startTimer (1);
227  expect (timer.getTimerInterval() == 1);
228 
229  restartTimer.signal();
230  expect (timerRestarted.wait (maximumTimeoutMs));
231  expect (timer.getTimerInterval() == 2);
232  expect (timerFiredAfterRestart.wait (maximumTimeoutMs));
233 
234  timer.stopTimer();
235  }
236 
237  beginTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish");
238  {
239  WaitableEvent timerCallbackStarted;
240  WaitableEvent stoppingTimer;
241  std::atomic<bool> timerCallbackFinished { false };
242 
243  Timer timer {[&, callbackCount = 0]() mutable
244  {
245  switch (++callbackCount)
246  {
247  case 1:
248  timerCallbackStarted.signal();
249  expect (stoppingTimer.wait (maximumTimeoutMs));
250  Thread::sleep (10);
251  timerCallbackFinished = true;
252  return;
253 
254  default:
255  return;
256  }
257  }};
258 
259  timer.startTimer (1);
260  expect (timerCallbackStarted.wait (maximumTimeoutMs));
261 
262  stoppingTimer.signal();
263  timer.stopTimer();
264  expect (timerCallbackFinished);
265  }
266 
267  beginTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish, even if the timer callback calls stopTimer first");
268  {
269  WaitableEvent stoppedFromInsideTimerCallback;
270  WaitableEvent stoppingFromOutsideTimerCallback;
271  std::atomic<bool> timerCallbackFinished { false };
272 
273  Timer timer {[&]()
274  {
275  timer.stopTimer();
276  stoppedFromInsideTimerCallback.signal();
277  expect (stoppingFromOutsideTimerCallback.wait (maximumTimeoutMs));
278  Thread::sleep (10);
279  timerCallbackFinished = true;
280 
281  }};
282 
283  timer.startTimer (1);
284  expect (stoppedFromInsideTimerCallback.wait (maximumTimeoutMs));
285 
286  stoppingFromOutsideTimerCallback.signal();
287  timer.stopTimer();
288  expect (timerCallbackFinished);
289  }
290 
291  beginTest ("Adjusting a timer period from outside the timer callback doesn't cause data races");
292  {
293  WaitableEvent timerCallbackStarted;
294  WaitableEvent timerRestarted;
295  WaitableEvent timerFiredAfterRestart;
296  std::atomic<int> lastCallbackCount {0};
297 
298  Timer timer {[&, callbackCount = 0]() mutable
299  {
300  switch (++callbackCount)
301  {
302  case 1:
303  expect (timer.getTimerInterval() == 1);
304  timerCallbackStarted.signal();
305  Thread::sleep (10);
306  lastCallbackCount = 1;
307  return;
308 
309  case 2:
310  expect (timerRestarted.wait (maximumTimeoutMs));
311  expect (timer.getTimerInterval() == 2);
312  lastCallbackCount = 2;
313  timerFiredAfterRestart.signal();
314  return;
315 
316  default:
317  return;
318  }
319  }};
320 
321  timer.startTimer (1);
322  expect (timerCallbackStarted.wait (maximumTimeoutMs));
323 
324  timer.startTimer (2);
325  timerRestarted.signal();
326 
327  expect (timerFiredAfterRestart.wait (maximumTimeoutMs));
328  expect (lastCallbackCount == 2);
329 
330  timer.stopTimer();
331  expect (lastCallbackCount == 2);
332  }
333 
334  beginTest ("A timer can be restarted externally, after being stopped internally");
335  {
336  WaitableEvent timerStopped;
337  WaitableEvent timerFiredAfterRestart;
338 
339  Timer timer {[&, callbackCount = 0]() mutable
340  {
341  switch (++callbackCount)
342  {
343  case 1:
344  timer.stopTimer();
345  timerStopped.signal();
346  return;
347 
348  case 2:
349  timerFiredAfterRestart.signal();
350  return;
351 
352  default:
353  return;
354  }
355  }};
356 
357  expect (! timer.isTimerRunning());
358  timer.startTimer (1);
359  expect (timer.isTimerRunning());
360 
361  expect (timerStopped.wait (maximumTimeoutMs));
362  expect (! timer.isTimerRunning());
363 
364  timer.startTimer (1);
365  expect (timer.isTimerRunning());
366  expect (timerFiredAfterRestart.wait (maximumTimeoutMs));
367  }
368 
369  beginTest ("Calls to `startTimer` and `getTimerInterval` succeed while a callback is blocked");
370  {
371  WaitableEvent timerBlocked;
372  WaitableEvent unblockTimer;
373 
374  Timer timer {[&]
375  {
376  timerBlocked.signal();
377  unblockTimer.wait();
378  timer.stopTimer();
379  }};
380 
381  timer.startTimer (1);
382  timerBlocked.wait();
383 
384  expect (timer.getTimerInterval() == 1);
385  timer.startTimer (2);
386  expect (timer.getTimerInterval() == 2);
387 
388  unblockTimer.signal();
389  timer.stopTimer();
390  }
391 
392  beginTest ("Stress test");
393  {
394  constexpr auto maxNumTimers { 100 };
395 
396  std::vector<std::unique_ptr<Timer>> timers;
397  timers.reserve (maxNumTimers);
398 
399  for (int i = 0; i < maxNumTimers; ++i)
400  {
401  auto timer = std::make_unique<Timer> ([]{});
402  timer->startTimer (1);
403 
404  if (! timer->isTimerRunning())
405  break;
406 
407  timers.push_back (std::move (timer));
408  }
409 
410  expect (timers.size() >= 16);
411  }
412  }
413 
414  class Timer final : public HighResolutionTimer
415  {
416  public:
417  explicit Timer (std::function<void()> fn)
418  : callback (std::move (fn)) {}
419 
420  ~Timer() override { stopTimer(); }
421 
422  void hiResTimerCallback() override { callback(); }
423 
424  private:
425  std::function<void()> callback;
426  };
427 };
428 
429 static HighResolutionTimerTests highResolutionTimerTests;
430 
431 #endif
432 
433 } // namespace juce
void startTimer(int intervalInMilliseconds)
static void JUCE_CALLTYPE sleep(int milliseconds)