File indexing completed on 2024-05-12 17:15:21

0001 /*
0002    Copyright (C) 2013 Andreas Hartmetz <ahartmetz@gmail.com>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LGPL.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 
0019    Alternatively, this file is available under the Mozilla Public License
0020    Version 1.1.  You may obtain a copy of the License at
0021    http://www.mozilla.org/MPL/
0022 */
0023 
0024 #include "eventdispatcher.h"
0025 #include "icompletionlistener.h"
0026 #include "platformtime.h"
0027 #include "timer.h"
0028 
0029 #include "../testutil.h"
0030 
0031 #include <cstring>
0032 #include <iostream>
0033 
0034 class BamPrinter : public ICompletionListener
0035 {
0036 public:
0037     BamPrinter(const char *customMessage, uint64 startTime)
0038        : m_customMessage(customMessage), m_startTime(startTime) {}
0039     void handleCompletion(void *task) override
0040     {
0041         uint64 timeDiff = PlatformTime::monotonicMsecs() - m_startTime;
0042         std::cout << "BAM " << task << ' ' << timeDiff << ' ' << m_customMessage << " #" << m_counter++ << '\n';
0043     }
0044     const char *m_customMessage;
0045     uint64 m_startTime;
0046     int m_counter = 0;
0047 };
0048 
0049 // supposed to print some output to prove timers are working, and not crash :)
0050 static void testBasic()
0051 {
0052     EventDispatcher dispatcher;
0053     uint64 baseTime = PlatformTime::monotonicMsecs();
0054 
0055     const char *customMessage1 = "Hello, world 1!";
0056     BamPrinter printer1(customMessage1, baseTime);
0057 
0058     Timer t(&dispatcher);
0059     t.setCompletionListener(&printer1);
0060     t.setInterval(231);
0061     t.setRunning(true);
0062 
0063     const char *customMessage2 = "Hello, world 2!";
0064     BamPrinter printer2(customMessage2, baseTime);
0065 
0066     Timer t2(&dispatcher);
0067     t2.setCompletionListener(&printer2);
0068     t2.setInterval(100);
0069     t2.setRunning(true);
0070 
0071 
0072     const char *customMessage3 = "Hello, other world!";
0073     int booCounter = 0;
0074     CompletionFunc booPrinter([baseTime, customMessage3, &booCounter, &dispatcher, &t] (void *task)
0075     {
0076         uint64 timeDiff = PlatformTime::monotonicMsecs() - baseTime;
0077         std::cout << "boo " << task << ' ' << timeDiff << ' ' << customMessage3 << " #" << booCounter
0078                   << " - Timer 1 remaining time: " << t.remainingTime() << '\n';
0079         if (booCounter >= 4) {
0080             dispatcher.interrupt();
0081         }
0082         booCounter++;
0083     });
0084 
0085     Timer t3(&dispatcher);
0086     t3.setCompletionListener(&booPrinter);
0087     t3.setInterval(420);
0088     t3.setRunning(true);
0089 
0090     while (dispatcher.poll()) {
0091     }
0092 }
0093 
0094 class AccuracyTester : public ICompletionListener
0095 {
0096 public:
0097     AccuracyTester()
0098        : m_lastTriggerTime(PlatformTime::monotonicMsecs())
0099     {}
0100     void handleCompletion(void *task) override
0101     {
0102         Timer *timer = reinterpret_cast<Timer *>(task);
0103         uint64 currentTime = PlatformTime::monotonicMsecs();
0104         int timeDiff = int64(currentTime) - int64(m_lastTriggerTime);
0105         m_lastTriggerTime = currentTime;
0106 
0107         std::cout << timer->interval() << ' ' << timeDiff << std::endl;
0108         // ### This test is somewhat unreliable. It is supposed to catch wildly wrong timer trigger times,
0109         // but it can fail randomly due to high system load or simply Windows scheduling timeouts with only
0110         // about 15 ms resolution (for energy efficiency).
0111 #ifdef _WIN32
0112         TEST(std::abs(timeDiff - timer->interval()) < 35); // this seems to prevent most random failures :>
0113 #else
0114         TEST(std::abs(timeDiff - timer->interval()) < 5);
0115 #endif
0116         m_count++;
0117         TEST(m_count < 26); // event loop should have stopped right at 25
0118 
0119         if (m_count == 25) {
0120             timer->eventDispatcher()->interrupt();
0121         }
0122     }
0123     uint64 m_lastTriggerTime;
0124     uint m_count = 0;
0125 };
0126 
0127 static void testAccuracy()
0128 {
0129     // this test is likely to fail spuriously on a machine under load
0130     EventDispatcher dispatcher;
0131 
0132     AccuracyTester at1;
0133     Timer t1(&dispatcher);
0134     t1.setCompletionListener(&at1);
0135     t1.setInterval(225);
0136     t1.setRunning(true);
0137 
0138     AccuracyTester at2;
0139     Timer t2(&dispatcher);
0140     t2.setCompletionListener(&at2);
0141     t2.setInterval(42);
0142     t2.setRunning(true);
0143 
0144     while (dispatcher.poll()) {
0145     }
0146 }
0147 
0148 // this not only bounds how long the dispatcher runs, it also creates another timer to make the
0149 // situation more interesting
0150 class EventDispatcherInterruptor : public ICompletionListener
0151 {
0152 public:
0153     EventDispatcherInterruptor(EventDispatcher *ed, int timeout)
0154        : m_ttl(ed)
0155     {
0156         m_ttl.setInterval(timeout);
0157         m_ttl.setCompletionListener(this);
0158         m_ttl.setRunning(true);
0159     }
0160     void handleCompletion(void * /*task*/) override
0161     {
0162         m_ttl.eventDispatcher()->interrupt();
0163         m_ttl.setRunning(false);
0164     }
0165     Timer m_ttl;
0166 };
0167 
0168 static void testDeleteOrDisableInTrigger(bool deleteTimer)
0169 {
0170     EventDispatcher dispatcher;
0171 
0172     bool alreadyCalled = false;
0173     CompletionFunc deleter([&alreadyCalled, deleteTimer] (void *task)
0174     {
0175         TEST(!alreadyCalled);
0176         alreadyCalled = true;
0177         Timer *timer = reinterpret_cast<Timer *>(task);
0178         if (deleteTimer) {
0179             delete timer;
0180         } else {
0181             timer->setRunning(false);
0182         }
0183     });
0184 
0185     Timer *t1 = new Timer(&dispatcher);
0186     t1->setCompletionListener(&deleter);
0187     t1->setRunning(true);
0188 
0189     EventDispatcherInterruptor interruptor(&dispatcher, 50);
0190 
0191     while (dispatcher.poll()) {
0192     }
0193 
0194     if (!deleteTimer) {
0195         delete t1;
0196     }
0197 }
0198 
0199 static void testDeleteInTrigger()
0200 {
0201     testDeleteOrDisableInTrigger(true);
0202 }
0203 
0204 static void testDisableInTrigger()
0205 {
0206     testDeleteOrDisableInTrigger(false);
0207 }
0208 
0209 static void testAddInTrigger()
0210 {
0211     // A timer added from the callback of another timer should not trigger in the same event loop
0212     // iteration, otherwise there could be an (accidental or intended) infinite cascade of zero interval
0213     // timers adding zero interval timers
0214 
0215     // since this test has a (small) false negative (note: negative == no problem found) rate - if
0216     // the current millisecond changes at certain points, it can mask a problem - just run it a couple
0217     // of times...
0218     for (int i = 0; i < 5; i++) {
0219         EventDispatcher dispatcher;
0220         int dispatchCounter = 0;
0221         int t2Counter = 0;
0222 
0223         CompletionFunc iterChecker([&dispatchCounter, &t2Counter] (void * /*task*/)
0224         {
0225             TEST(dispatchCounter > 0);
0226             t2Counter++;
0227         });
0228 
0229         Timer t1(&dispatcher);
0230         Timer *t2 = nullptr;
0231         CompletionFunc adder([&dispatcher, &t2, &iterChecker] (void * /*task*/)
0232         {
0233             if (!t2) {
0234                 t2 = new Timer(&dispatcher);
0235                 t2->setCompletionListener(&iterChecker);
0236                 t2->setRunning(true);
0237                 // this could go wrong because we manipulate the due time in EventDispatcher::addTimer(),
0238                 // but should be caught in Timer::remainingTime()
0239                 TEST(t2->remainingTime() == 0);
0240             }
0241         });
0242 
0243         t1.setInterval(10);
0244         t1.setRunning(true);
0245         t1.setCompletionListener(&adder);
0246 
0247         EventDispatcherInterruptor interruptor(&dispatcher, 50);
0248 
0249         while (dispatcher.poll()) {
0250             dispatchCounter++;
0251         }
0252         TEST(t2Counter > 1);
0253         delete t2;
0254     }
0255 }
0256 
0257 static void testReAddInTrigger()
0258 {
0259     // - Add a timer
0260     //   - Remove it
0261     //   - Remove it, then add it
0262     //   - Remove, add, remove
0263     //   - Remove, add, remove, add
0264     // - Check timer's isRunning() considering whether last action was add or remove
0265     // - Check if the timer triggers next time or not, consistent with previous point
0266 
0267 
0268     // Repeat the tests that include re-adding with "pointer aliased" timers, i.e. add a new timer created
0269     // at the same memory location as the old one. That tests whether a known difficulty of the chosen
0270     // implementation is handled correctly.
0271 
0272     // Use the array to ensure we have pointer aliasing or no pointer aliasing
0273     std::aligned_storage<sizeof(Timer)>::type timerStorage[2];
0274     memset(timerStorage, 0, sizeof(timerStorage));
0275 
0276     Timer *const timerArray = reinterpret_cast<Timer *>(timerStorage);
0277 
0278     for (int i = 0; i < 2; i++) {
0279         const bool withAliasing = i == 1;
0280 
0281         for (int j = 0; j < 5; j++) { // j = number of add / remove ops
0282             EventDispatcher dispatcher;
0283 
0284             Timer *t = &timerArray[0];
0285             bool removeTimer = false;
0286             bool checkTrigger = false;
0287             bool didTrigger = false;
0288 
0289             CompletionFunc addRemove([&] (void * /*task*/) {
0290                 if (checkTrigger) {
0291                     didTrigger = true;
0292                     return;
0293                 }
0294 
0295                 for (int k = 0; k < j; k++) {
0296                     removeTimer = (k & 1) == 0;
0297                     if (removeTimer) {
0298                         TEST(t->isRunning());
0299                         t->~Timer();
0300                         // ensure that it can't trigger - of course if Timer
0301                         // relies on that we should find it in valgrind...
0302                         memset(static_cast<void *>(t), 0, sizeof(Timer));
0303                     } else {
0304                         if (!withAliasing) {
0305                             if (t == &timerArray[0]) {
0306                                 t = &timerArray[1];
0307                             } else {
0308                                 t = &timerArray[0];
0309                             }
0310                         }
0311                         new(t) Timer(&dispatcher);
0312                         t->setCompletionListener(&addRemove);
0313                         t->start(0);
0314                         TEST(t->isRunning());
0315                     }
0316                 }
0317             });
0318 
0319 
0320             Timer dummy1(&dispatcher);
0321             dummy1.start(0);
0322 
0323             new(t) Timer(&dispatcher);
0324             t->start(0);
0325 
0326             Timer dummy2(&dispatcher);
0327             dummy2.start(0);
0328 
0329             dispatcher.poll(); // this seems like a good idea for the test...
0330 
0331             // run and test the add / remove sequence
0332             t->setCompletionListener(&addRemove);
0333             dispatcher.poll();
0334 
0335             // Test that the timer triggers when it should. Triggering when it should not will likely
0336             // cause a segfault or other error because the Timer's memory has been cleared.
0337 
0338             checkTrigger = true;
0339             dispatcher.poll();
0340             TEST(didTrigger != removeTimer);
0341 
0342             // clean up
0343             if (!removeTimer) {
0344                 t->~Timer();
0345             }
0346             memset(timerStorage, 0, sizeof(timerStorage));
0347         }
0348     }
0349 }
0350 
0351 // Test that all 0 msec timers trigger equally often regardless how long their triggered handler takes
0352 static void testTriggerOnlyOncePerDispatch()
0353 {
0354     EventDispatcher dispatcher;
0355     int dispatchCounter = 0;
0356     int triggerCounter1 = 0;
0357     int triggerCounter2 = 0;
0358     int hardWorkCounter = 0;
0359 
0360     Timer counter1Timer(&dispatcher);
0361     counter1Timer.setRunning(true);
0362 
0363     Timer hardWorkTimer(&dispatcher);
0364     hardWorkTimer.setRunning(true);
0365 
0366     Timer counter2Timer(&dispatcher);
0367     counter2Timer.setRunning(true);
0368 
0369     CompletionFunc countTriggers([&triggerCounter1, &triggerCounter2, &dispatchCounter,
0370                                   &counter1Timer, &counter2Timer] (void *task) {
0371         if (task == &counter1Timer) {
0372             TEST(triggerCounter1 == dispatchCounter);
0373             triggerCounter1++;
0374         } else {
0375             TEST(task == &counter2Timer);
0376             TEST(triggerCounter2 == dispatchCounter);
0377             triggerCounter2++;
0378         }
0379     });
0380     counter1Timer.setCompletionListener(&countTriggers);
0381     counter2Timer.setCompletionListener(&countTriggers);
0382 
0383     CompletionFunc hardWorker([&hardWorkCounter, &dispatchCounter] (void * /*task*/)
0384     {
0385         TEST(hardWorkCounter == dispatchCounter);
0386         uint64 startTime = PlatformTime::monotonicMsecs();
0387         // waste ten milliseconds, trying not to spend all time in PlatformTime::monotonicMsecs()
0388         do {
0389             for (volatile int i = 0; i < 20000; i++) {}
0390         } while (PlatformTime::monotonicMsecs() < startTime + 10);
0391         hardWorkCounter++;
0392     });
0393     hardWorkTimer.setCompletionListener(&hardWorker);
0394 
0395     EventDispatcherInterruptor interruptor(&dispatcher, 200);
0396 
0397     while (dispatcher.poll()) {
0398         dispatchCounter++;
0399     }
0400 
0401     TEST(triggerCounter1 == dispatchCounter || triggerCounter1 == dispatchCounter - 1);
0402     TEST(triggerCounter2 == dispatchCounter || triggerCounter2 == dispatchCounter - 1);
0403     TEST(hardWorkCounter == dispatchCounter || hardWorkCounter == dispatchCounter - 1);
0404 }
0405 
0406 static void testReEnableNonRepeatingInTrigger()
0407 {
0408     EventDispatcher dispatcher;
0409 
0410     int slowCounter = 0;
0411     CompletionFunc slowReEnabler([&slowCounter] (void *task)
0412     {
0413         slowCounter++;
0414         Timer *timer = reinterpret_cast<Timer *>(task);
0415         TEST(!timer->isRunning());
0416         timer->setRunning(true);
0417         TEST(timer->isRunning());
0418         TEST(timer->interval() == 5);
0419     });
0420 
0421     Timer slow(&dispatcher);
0422     slow.setCompletionListener(&slowReEnabler);
0423     slow.setRepeating(false);
0424     slow.setInterval(5);
0425     slow.setRunning(true);
0426 
0427     int fastCounter = 0;
0428     CompletionFunc fastReEnabler([&fastCounter] (void *task) {
0429         fastCounter++;
0430         Timer *timer = reinterpret_cast<Timer *>(task);
0431         TEST(!timer->isRunning());
0432         timer->setRunning(true);
0433         TEST(timer->isRunning());
0434         TEST(timer->interval() == 0);
0435     });
0436 
0437     Timer fast(&dispatcher);
0438     fast.setCompletionListener(&fastReEnabler);
0439     fast.setRepeating(false);
0440     fast.setInterval(0);
0441     fast.setRunning(true);
0442 
0443     // also make sure that setRepeating(false) has any effect at all...
0444     int noRepeatCounter = 0;
0445     CompletionFunc noRepeatCheck([&noRepeatCounter] (void * /*task*/) {
0446         noRepeatCounter++;
0447     });
0448     Timer noRepeat(&dispatcher);
0449     noRepeat.setCompletionListener(&noRepeatCheck);
0450     noRepeat.setRepeating(false);
0451     noRepeat.setInterval(10);
0452     noRepeat.setRunning(true);
0453 
0454     EventDispatcherInterruptor interruptor(&dispatcher, 50);
0455 
0456     while (dispatcher.poll()) {
0457     }
0458 
0459     //std::cout << "\nfastCounter: " << fastCounter << " slowCounter: " << slowCounter << '\n';
0460 
0461     TEST(noRepeatCounter == 1);
0462 #ifdef _WIN32
0463     TEST(slowCounter >= 4 && slowCounter <= 12);
0464 #else
0465     TEST(slowCounter >= 8 && slowCounter <= 12);
0466 #endif
0467     TEST(fastCounter >= 200); // ### hopefully low enough even for really slow machines and / or valgrind
0468 }
0469 
0470 static void testSerialWraparound()
0471 {
0472     EventDispatcher dispatcher;
0473 
0474     constexpr int timersCount = 17;
0475     Timer* timers[timersCount];
0476     int lastTriggeredTimer;
0477 
0478     CompletionFunc orderCheck([&timers, &lastTriggeredTimer] (void *task) {
0479         int timerIndex = 0;
0480         for (; timerIndex < timersCount; timerIndex++) {
0481             if (timers[timerIndex] == task) {
0482                 break;
0483             }
0484         }
0485         TEST(timerIndex < timersCount);
0486         TEST(++lastTriggeredTimer == timerIndex);
0487         if (timerIndex % 4 == 0) {
0488             delete timers[timerIndex];
0489             timers[timerIndex] = nullptr;
0490         } else if (timerIndex % 2 == 0) {
0491             timers[timerIndex]->setRunning(false);
0492         }
0493     });
0494 
0495     // Glassbox testing: we know that the maximum timer serials is 1023, so testing 10k * 17 timers
0496     // is plenty. This should be adapted if / when the implementation changes.
0497     for (int i = 0; i < 10000; i++) {
0498         for (int j = 0; j < 17; j++) {
0499             timers[j] = new Timer(&dispatcher);
0500             timers[j]->setCompletionListener(&orderCheck);
0501             timers[j]->setRunning(true);
0502         }
0503 
0504         lastTriggeredTimer = -1;
0505 
0506         dispatcher.poll();
0507 
0508         TEST(lastTriggeredTimer == timersCount - 1);
0509 
0510         for (int j = 0; j < 17; j++) {
0511             delete timers[j];
0512             timers[j] = nullptr;
0513         }
0514     }
0515 }
0516 
0517 int main(int, char *[])
0518 {
0519     testBasic();
0520     testAccuracy();
0521     testDeleteInTrigger();
0522     testDisableInTrigger();
0523     testAddInTrigger();
0524     testReAddInTrigger();
0525     testTriggerOnlyOncePerDispatch();
0526     testReEnableNonRepeatingInTrigger();
0527     testSerialWraparound();
0528     std::cout << "Passed!\n";
0529 }