├── .clang-format ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── cpptime.h └── tests ├── catch.hpp └── timer_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Mozilla 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: false 6 | AlignEscapedNewlinesLeft: true 7 | AlignOperands: true 8 | AlignTrailingComments: true 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: true 12 | AllowShortIfStatementsOnASingleLine: true 13 | AllowShortLoopsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AlwaysBreakAfterDefinitionReturnType: false 16 | AlwaysBreakTemplateDeclarations: false 17 | AlwaysBreakBeforeMultilineStrings: false 18 | BreakBeforeBinaryOperators: None 19 | BreakBeforeTernaryOperators: true 20 | BreakConstructorInitializersBeforeComma: false 21 | BinPackParameters: true 22 | BinPackArguments: true 23 | ColumnLimit: 100 24 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 25 | ConstructorInitializerIndentWidth: 4 26 | DerivePointerAlignment: true 27 | ExperimentalAutoDetectBinPacking: false 28 | IndentCaseLabels: true 29 | IndentWrappedFunctionNames: false 30 | IndentFunctionDeclarationAfterType: false 31 | MaxEmptyLinesToKeep: 1 32 | KeepEmptyLinesAtTheStartOfBlocks: true 33 | NamespaceIndentation: None 34 | ObjCBlockIndentWidth: 2 35 | ObjCSpaceAfterProperty: true 36 | ObjCSpaceBeforeProtocolList: false 37 | PenaltyBreakBeforeFirstCallParameter: 19 38 | PenaltyBreakComment: 300 39 | PenaltyBreakString: 1000 40 | PenaltyBreakFirstLessLess: 120 41 | PenaltyExcessCharacter: 1000000 42 | PenaltyReturnTypeOnItsOwnLine: 200 43 | PointerAlignment: Left 44 | SpacesBeforeTrailingComments: 1 45 | Cpp11BracedListStyle: true 46 | Standard: Cpp11 47 | IndentWidth: 4 48 | TabWidth: 4 49 | UseTab: ForIndentation 50 | BreakBeforeBraces: Linux 51 | SpacesInParentheses: false 52 | SpacesInSquareBrackets: false 53 | SpacesInAngles: false 54 | SpaceInEmptyParentheses: false 55 | SpacesInCStyleCastParentheses: false 56 | SpaceAfterCStyleCast: false 57 | SpacesInContainerLiterals: true 58 | SpaceBeforeAssignmentOperators: true 59 | ContinuationIndentWidth: 4 60 | CommentPragmas: '^ IWYU pragma:' 61 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 62 | SpaceBeforeParens: Never 63 | DisableFormat: false 64 | ... 65 | 66 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: cpptime CI 2 | 3 | on: push 4 | 5 | jobs: 6 | unit_test: 7 | name: Run Tests 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | 14 | - name: Compile and run tests 15 | run: | 16 | g++ -std=c++11 -Wall -Wextra -o test tests/timer_test.cpp -l pthread 17 | ./test 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The test binary 2 | test 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Egli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Timeouts 2 | 3 | A portable, header-only C++11 timer component. 4 | 5 | It manages a set of timeouts that, when expired, invoke a callback. It uses 6 | features of C++11 in order to avoid platform specific code. 7 | 8 | It supports one-shot and periodic timeouts. 9 | 10 | ## Documentation 11 | 12 | Please see the documentation in [cpptime.h](./cpptime.h) for more detailed 13 | information about the implementation. 14 | 15 | ## Implementation Status 16 | 17 | We use this timer implementation in some of our products without issues. 18 | Judging from the GitHub stars and forks, it seems to be used in other projects 19 | as well. Since it was implemented in 2015 and has not seen many issue reports, 20 | we assume it is quite stable. 21 | 22 | But note that this is not a guarantee and if you find any issues, please report 23 | them. 24 | 25 | ## Use Cases and Limitations 26 | 27 | Naturally the implementation makes some trade-offs. This makes it useful for 28 | some cases, and less so for others. 29 | 30 | - The timer runs completely in user space. This makes it slightly less 31 | efficient than other solutions, such as `timer_create()` or 32 | `timerfd_create()`. However, in many cases, this overhead is acceptable. 33 | 34 | - Given a C++11 capable compiler, the code is portable. 35 | 36 | - The API to add or remove a timeout is arguably nicer than the platform 37 | specific alternatives. E.g. 38 | 39 | ```cpp 40 | timer.add(seconds(2), [](CppTime::timer_id) { ... }); 41 | ``` 42 | 43 | - The implementation is small and easy to understand. You can change or extend 44 | it to make it better suitable for your use-cases. 45 | 46 | ## Examples 47 | 48 | A one shot timer. 49 | 50 | ~~~ 51 | using namespace std::chrono; 52 | CppTime::Timer t; 53 | t.add(seconds(2), [](CppTime::timer_id) { std::cout << "yes\n"; }); 54 | std::this_thread::sleep_for(seconds(3)); 55 | ~~~ 56 | 57 | A periodic timer that is first executed after 2 seconds, and after this every 58 | second. The timeout event is then removed after 10 seconds. When a timeout 59 | event is removed, its attached handler is also freed to clean-up any attached 60 | resources. 61 | 62 | ~~~ 63 | using namespace std::chrono; 64 | CppTime::Timer t; 65 | auto id = t.add(seconds(2), [](CppTime::timer_id) { std::cout << "yes\n"; }, seconds(1)); 66 | std::this_thread::sleep_for(seconds(10)); 67 | t.remove(id); 68 | ~~~ 69 | 70 | See the tests for more examples. 71 | 72 | ## Usage 73 | 74 | To use the timer component, Simply copy [cpptime.h](./cpptime.h) into you 75 | project. Everything is contained in this single header file. 76 | 77 | Tests can be compiled and executed with the following commands, assuming you 78 | are on a POSIX machine. 79 | 80 | ~~~ 81 | g++ -std=c++11 -Wall -Wextra -o test tests/timer_test.cpp -l pthread 82 | ./test 83 | ~~~ 84 | 85 | ## Possible Features 86 | 87 | While the current implementation serves us well, there are some features that 88 | might potentially be interesting for other use cases. Contact us in case you 89 | are interested. 90 | 91 | - [x] Ability to have multiple timer components running. 92 | - [x] Distribute it as a header only library. 93 | - [ ] Optionally avoid locking. 94 | - [ ] API to use client thread instead of creating its own. 95 | - [ ] API to use client mutex instead of its own. 96 | 97 | ## Known issues 98 | 99 | GCC up to version 10 (e.g. used in Ubuntu 20.04 LTS) has [an issue][gcc-clock] where `conditional_variable` doesn't use the monotonic clock. This leads to unreliable programs when the system clock is moved backwards. See #5 for more details. The fix is to update to a GCC version with the fix applied, e.g. version 10 or greater. 100 | 101 | [gcc-clock]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41861 102 | 103 | ## Contributions 104 | 105 | Contributions, suggestions, and feature requests are welcome. Please use the 106 | GitHub issue tracker. 107 | -------------------------------------------------------------------------------- /cpptime.h: -------------------------------------------------------------------------------- 1 | #ifndef CPPTIME_H_ 2 | #define CPPTIME_H_ 3 | 4 | /** 5 | * The MIT License (MIT) 6 | * 7 | * Copyright (c) 2015 Michael Egli 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * \author Michael Egli 28 | * \copyright Michael Egli 29 | * \date 11-Jul-2015 30 | * 31 | * \file cpptime.h 32 | * 33 | * C++11 timer component 34 | * ===================== 35 | * 36 | * A portable, header-only C++11 timer component. 37 | * 38 | * Overview 39 | * -------- 40 | * 41 | * This component can be used to manage a set of timeouts. It is implemented in 42 | * pure C++11. It is therefore very portable given a compliant compiler. 43 | * 44 | * A timeout can be added with one of the `add()` functions, and removed with 45 | * the `remove()` function. A timeout can be set to be either one-shot or 46 | * periodic. If it is one-shot, the callback is invoked once and the timeout 47 | * event is then automatically removed. If the timeout is periodic, it is 48 | * always renewed and never automatically removed. 49 | * 50 | * When a timeout is removed or when a one-shot timeout expires, the handler 51 | * will be deleted to clean-up any resources. 52 | * 53 | * Removing a timeout is possible from within the callback. In this case, you 54 | * must be careful not to access any captured variables, if any, after calling 55 | * `remove()`, because they are no longer valid. 56 | * 57 | * Timeout Units 58 | * ------------- 59 | * 60 | * The preferred functions for adding timeouts are those that take a 61 | * `std::chrono::...` argument. However, for convenience, there is also an API 62 | * that takes a uint64_t. When using this API, all values are expected to be 63 | * given in microseconds (us). 64 | * 65 | * For periodic timeouts, a separate timeout can be specified for the initial 66 | * (first) timeout, and the periodicity after that. 67 | * 68 | * To avoid drifts, times are added by simply adding the period to the initially 69 | * calculated (or provided) time. Also, we use `wait until` type of API to wait 70 | * for a timeout instead of a `wait for` API. 71 | * 72 | * Data Structure 73 | * -------------- 74 | * 75 | * Internally, a std::vector is used to store timeout events. The timer_id 76 | * returned from the `add` functions are used as index to this vector. 77 | * 78 | * In addition, a std::multiset is used that holds all time points when 79 | * timeouts expire. 80 | * 81 | * Using a vector to store timeout events has some implications. It is very 82 | * fast to remove an event, because the timer_id is the vector's index. On the 83 | * other hand, this makes it also more complicated to manage the timer_ids. The 84 | * current solution is to keep track of ids that are freed in order to re-use 85 | * them. A stack is used for this. 86 | * 87 | * Examples 88 | * -------- 89 | * 90 | * More examples can be found in the `tests` folder. 91 | * 92 | * ~~~ 93 | * CppTime::Timer t; 94 | * t.add(std::chrono::seconds(1), [](CppTime::timer_id){ std::cout << "got it!"; }); 95 | * std::this_thread::sleep_for(std::chrono::seconds(2)); 96 | * ~~~ 97 | */ 98 | 99 | // Includes 100 | #include 101 | #include 102 | #include 103 | #include 104 | #include 105 | #include 106 | #include 107 | #include 108 | #include 109 | 110 | namespace CppTime 111 | { 112 | 113 | // Public types 114 | using timer_id = std::size_t; 115 | using handler_t = std::function; 116 | using clock = std::chrono::steady_clock; 117 | using timestamp = std::chrono::time_point; 118 | using duration = std::chrono::microseconds; 119 | 120 | // Private definitions. Do not rely on this namespace. 121 | namespace detail 122 | { 123 | 124 | // The event structure that holds the information about a timer. 125 | struct Event { 126 | timer_id id; 127 | timestamp start; 128 | duration period; 129 | handler_t handler; 130 | bool valid; 131 | Event() 132 | : id(0), start(duration::zero()), period(duration::zero()), handler(nullptr), valid(false) 133 | { 134 | } 135 | template 136 | Event(timer_id id, timestamp start, duration period, Func &&handler) 137 | : id(id), start(start), period(period), handler(std::forward(handler)), valid(true) 138 | { 139 | } 140 | Event(Event &&r) = default; 141 | Event &operator=(Event &&ev) = default; 142 | Event(const Event &r) = delete; 143 | Event &operator=(const Event &r) = delete; 144 | }; 145 | 146 | // A time event structure that holds the next timeout and a reference to its 147 | // Event struct. 148 | struct Time_event { 149 | timestamp next; 150 | timer_id ref; 151 | }; 152 | 153 | inline bool operator<(const Time_event &l, const Time_event &r) 154 | { 155 | return l.next < r.next; 156 | } 157 | 158 | } // end namespace detail 159 | 160 | class Timer 161 | { 162 | using scoped_m = std::unique_lock; 163 | 164 | // Thread and locking variables. 165 | std::mutex m; 166 | std::condition_variable cond; 167 | std::thread worker; 168 | 169 | // Use to terminate the timer thread. 170 | bool done = false; 171 | 172 | // The vector that holds all active events. 173 | std::vector events; 174 | // Sorted queue that has the next timeout at its top. 175 | std::multiset time_events; 176 | 177 | // A list of ids to be re-used. If possible, ids are used from this pool. 178 | std::stack free_ids; 179 | 180 | public: 181 | Timer() : m{}, cond{}, worker{}, events{}, time_events{}, free_ids{} 182 | { 183 | scoped_m lock(m); 184 | done = false; 185 | worker = std::thread([this] { run(); }); 186 | } 187 | 188 | ~Timer() 189 | { 190 | scoped_m lock(m); 191 | done = true; 192 | lock.unlock(); 193 | cond.notify_all(); 194 | worker.join(); 195 | events.clear(); 196 | time_events.clear(); 197 | while(!free_ids.empty()) { 198 | free_ids.pop(); 199 | } 200 | } 201 | 202 | /** 203 | * Add a new timer. 204 | * 205 | * \param when The time at which the handler is invoked. 206 | * \param handler The callable that is invoked when the timer fires. 207 | * \param period The periodicity at which the timer fires. Only used for periodic timers. 208 | */ 209 | timer_id add( 210 | const timestamp &when, handler_t &&handler, const duration &period = duration::zero()) 211 | { 212 | scoped_m lock(m); 213 | timer_id id = 0; 214 | // Add a new event. Prefer an existing and free id. If none is available, add 215 | // a new one. 216 | if(free_ids.empty()) { 217 | id = events.size(); 218 | detail::Event e(id, when, period, std::move(handler)); 219 | events.push_back(std::move(e)); 220 | } else { 221 | id = free_ids.top(); 222 | free_ids.pop(); 223 | detail::Event e(id, when, period, std::move(handler)); 224 | events[id] = std::move(e); 225 | } 226 | time_events.insert(detail::Time_event{when, id}); 227 | lock.unlock(); 228 | cond.notify_all(); 229 | return id; 230 | } 231 | 232 | /** 233 | * Overloaded `add` function that uses a `std::chrono::duration` instead of a 234 | * `time_point` for the first timeout. 235 | */ 236 | template 237 | inline timer_id add(const std::chrono::duration &when, handler_t &&handler, 238 | const duration &period = duration::zero()) 239 | { 240 | return add(clock::now() + std::chrono::duration_cast(when), 241 | std::move(handler), period); 242 | } 243 | 244 | /** 245 | * Overloaded `add` function that uses a uint64_t instead of a `time_point` for 246 | * the first timeout and the period. 247 | */ 248 | inline timer_id add(const uint64_t when, handler_t &&handler, const uint64_t period = 0) 249 | { 250 | return add(duration(when), std::move(handler), duration(period)); 251 | } 252 | 253 | /** 254 | * Removes the timer with the given id. 255 | */ 256 | bool remove(timer_id id) 257 | { 258 | scoped_m lock(m); 259 | if(events.size() == 0 || events.size() <= id) { 260 | return false; 261 | } 262 | events[id].valid = false; 263 | events[id].handler = nullptr; 264 | auto it = std::find_if(time_events.begin(), time_events.end(), 265 | [&](const detail::Time_event &te) { return te.ref == id; }); 266 | if(it != time_events.end()) { 267 | free_ids.push(it->ref); 268 | time_events.erase(it); 269 | } 270 | lock.unlock(); 271 | cond.notify_all(); 272 | return true; 273 | } 274 | 275 | private: 276 | void run() 277 | { 278 | scoped_m lock(m); 279 | 280 | while(!done) { 281 | 282 | if(time_events.empty()) { 283 | // Wait for work 284 | cond.wait(lock); 285 | } else { 286 | detail::Time_event te = *time_events.begin(); 287 | if(CppTime::clock::now() >= te.next) { 288 | 289 | // Remove time event 290 | time_events.erase(time_events.begin()); 291 | 292 | // Invoke the handler 293 | lock.unlock(); 294 | events[te.ref].handler(te.ref); 295 | lock.lock(); 296 | 297 | if(events[te.ref].valid && events[te.ref].period.count() > 0) { 298 | // The event is valid and a periodic timer. 299 | te.next += events[te.ref].period; 300 | time_events.insert(te); 301 | } else { 302 | // The event is either no longer valid because it was removed in the 303 | // callback, or it is a one-shot timer. 304 | events[te.ref].valid = false; 305 | events[te.ref].handler = nullptr; 306 | free_ids.push(te.ref); 307 | } 308 | } else { 309 | cond.wait_until(lock, te.next); 310 | } 311 | } 312 | } 313 | } 314 | }; 315 | 316 | } // end namespace CppTime 317 | 318 | #endif // CPPTIME_H_ 319 | -------------------------------------------------------------------------------- /tests/timer_test.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Michael Egli 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | * \author Michael Egli 25 | * \copyright Michael Egli 26 | * \date 11-Jul-2015 27 | * 28 | * \file timer_test.cpp 29 | * 30 | * Tests for cpptime component. Compile with 31 | * 32 | * ~~~ 33 | * g++ -std=c++11 -Wall -Wextra -o test timer_test.cpp -l pthread 34 | * ~~~ 35 | * 36 | */ 37 | 38 | #define CATCH_CONFIG_MAIN 39 | 40 | // Includes 41 | #include "../cpptime.h" 42 | #include "catch.hpp" 43 | #include 44 | #include 45 | 46 | using namespace std::chrono; 47 | 48 | TEST_CASE("Test start and stop.") 49 | { 50 | { 51 | CppTime::Timer t; 52 | } 53 | } 54 | 55 | TEST_CASE("Tests with two argument add") 56 | { 57 | CppTime::Timer t; 58 | 59 | SECTION("Test uint64_t timeout argument") 60 | { 61 | int i = 0; 62 | t.add(100000, [&](CppTime::timer_id) { i = 42; }); 63 | std::this_thread::sleep_for(milliseconds(120)); 64 | REQUIRE(i == 42); 65 | } 66 | 67 | SECTION("Test duration timeout argument") 68 | { 69 | int i = 0; 70 | t.add(milliseconds(100), [&](CppTime::timer_id) { i = 43; }); 71 | std::this_thread::sleep_for(milliseconds(120)); 72 | REQUIRE(i == 43); 73 | } 74 | 75 | SECTION("Test time_point timeout argument") 76 | { 77 | int i = 0; 78 | t.add(CppTime::clock::now() + milliseconds(100), [&](CppTime::timer_id) { i = 44; }); 79 | std::this_thread::sleep_for(milliseconds(120)); 80 | REQUIRE(i == 44); 81 | } 82 | } 83 | 84 | TEST_CASE("Tests with three argument add") 85 | { 86 | CppTime::Timer t; 87 | 88 | SECTION("Test uint64_t timeout argument") 89 | { 90 | size_t count = 0; 91 | auto id = t.add( 92 | 100000, [&](CppTime::timer_id) { ++count; }, 10000); 93 | std::this_thread::sleep_for(milliseconds(125)); 94 | t.remove(id); 95 | REQUIRE(count == 3); 96 | } 97 | 98 | SECTION("Test duration timeout argument") 99 | { 100 | size_t count = 0; 101 | auto id = t.add( 102 | milliseconds(100), [&](CppTime::timer_id) { ++count; }, microseconds(10000)); 103 | std::this_thread::sleep_for(milliseconds(135)); 104 | t.remove(id); 105 | REQUIRE(count == 4); 106 | } 107 | } 108 | 109 | TEST_CASE("Test delete timer in callback") 110 | { 111 | CppTime::Timer t; 112 | 113 | SECTION("Delete one timer") 114 | { 115 | size_t count = 0; 116 | t.add( 117 | milliseconds(10), 118 | [&](CppTime::timer_id id) { 119 | ++count; 120 | t.remove(id); 121 | }, 122 | milliseconds(10)); 123 | std::this_thread::sleep_for(milliseconds(50)); 124 | REQUIRE(count == 1); 125 | } 126 | 127 | SECTION("Ensure that the correct timer is freed and reused") 128 | { 129 | auto id1 = t.add(milliseconds(40), [](CppTime::timer_id) {}); 130 | auto id2 = t.add(milliseconds(10), [&](CppTime::timer_id id) { t.remove(id); }); 131 | std::this_thread::sleep_for(milliseconds(30)); 132 | auto id3 = t.add(microseconds(100), [](CppTime::timer_id) {}); 133 | auto id4 = t.add(microseconds(100), [](CppTime::timer_id) {}); 134 | REQUIRE(id3 == id2); 135 | REQUIRE(id4 != id1); 136 | REQUIRE(id4 != id2); 137 | std::this_thread::sleep_for(milliseconds(20)); 138 | } 139 | 140 | SECTION("Ensure that the correct timer is freed and reused - different ordering") 141 | { 142 | auto id1 = t.add(milliseconds(10), [&](CppTime::timer_id id) { t.remove(id); }); 143 | auto id2 = t.add(milliseconds(40), [](CppTime::timer_id) {}); 144 | std::this_thread::sleep_for(milliseconds(30)); 145 | auto id3 = t.add(microseconds(100), [](CppTime::timer_id) {}); 146 | auto id4 = t.add(microseconds(100), [](CppTime::timer_id) {}); 147 | REQUIRE(id3 == id1); 148 | REQUIRE(id4 != id1); 149 | REQUIRE(id4 != id2); 150 | std::this_thread::sleep_for(milliseconds(20)); 151 | } 152 | } 153 | 154 | TEST_CASE("Test two identical timeouts") 155 | { 156 | int i = 0; 157 | int j = 0; 158 | CppTime::Timer t; 159 | CppTime::timestamp ts = CppTime::clock::now() + milliseconds(40); 160 | t.add(ts, [&](CppTime::timer_id) { i = 42; }); 161 | t.add(ts, [&](CppTime::timer_id) { j = 43; }); 162 | std::this_thread::sleep_for(milliseconds(50)); 163 | REQUIRE(i == 42); 164 | REQUIRE(j == 43); 165 | } 166 | 167 | TEST_CASE("Test timeouts from the past.") 168 | { 169 | CppTime::Timer t; 170 | 171 | SECTION("Test negative timeouts") 172 | { 173 | int i = 0; 174 | int j = 0; 175 | CppTime::timestamp ts1 = CppTime::clock::now() - milliseconds(10); 176 | CppTime::timestamp ts2 = CppTime::clock::now() - milliseconds(20); 177 | t.add(ts1, [&](CppTime::timer_id) { i = 42; }); 178 | t.add(ts2, [&](CppTime::timer_id) { j = 43; }); 179 | std::this_thread::sleep_for(microseconds(20)); 180 | REQUIRE(i == 42); 181 | REQUIRE(j == 43); 182 | } 183 | 184 | SECTION("Test time overflow when blocking timer thread.") 185 | { 186 | int i = 0; 187 | CppTime::timestamp ts1 = CppTime::clock::now() + milliseconds(10); 188 | CppTime::timestamp ts2 = CppTime::clock::now() + milliseconds(20); 189 | t.add(ts1, [&](CppTime::timer_id) { std::this_thread::sleep_for(milliseconds(20)); }); 190 | t.add(ts2, [&](CppTime::timer_id) { i = 42; }); 191 | std::this_thread::sleep_for(milliseconds(50)); 192 | REQUIRE(i == 42); 193 | } 194 | } 195 | 196 | TEST_CASE("Test order of multiple timeouts") 197 | { 198 | int i = 0; 199 | CppTime::Timer t; 200 | t.add(10000, [&](CppTime::timer_id) { i = 42; }); 201 | t.add(20000, [&](CppTime::timer_id) { i = 43; }); 202 | t.add(30000, [&](CppTime::timer_id) { i = 44; }); 203 | t.add(40000, [&](CppTime::timer_id) { i = 45; }); 204 | std::this_thread::sleep_for(milliseconds(50)); 205 | REQUIRE(i == 45); 206 | } 207 | 208 | TEST_CASE("Test with multiple timers") 209 | { 210 | int i = 0; 211 | CppTime::Timer t1; 212 | CppTime::Timer t2; 213 | 214 | SECTION("Update the same value at different times with different timers") 215 | { 216 | t1.add(milliseconds(20), [&](CppTime::timer_id) { i = 42; }); 217 | t1.add(milliseconds(40), [&](CppTime::timer_id) { i = 43; }); 218 | std::this_thread::sleep_for(milliseconds(30)); 219 | REQUIRE(i == 42); 220 | std::this_thread::sleep_for(milliseconds(20)); 221 | REQUIRE(i == 43); 222 | } 223 | 224 | SECTION("Remove one timer without affecting the other") 225 | { 226 | auto id1 = t1.add(milliseconds(20), [&](CppTime::timer_id) { i = 42; }); 227 | t1.add(milliseconds(40), [&](CppTime::timer_id) { i = 43; }); 228 | std::this_thread::sleep_for(milliseconds(10)); 229 | t1.remove(id1); 230 | std::this_thread::sleep_for(milliseconds(20)); 231 | REQUIRE(i == 0); 232 | std::this_thread::sleep_for(milliseconds(20)); 233 | REQUIRE(i == 43); 234 | } 235 | } 236 | 237 | TEST_CASE("Test remove timer_id") 238 | { 239 | CppTime::Timer t; 240 | 241 | SECTION("Remove out of range timer_id") 242 | { 243 | auto id = t.add(milliseconds(20), [](CppTime::timer_id) {}); 244 | std::this_thread::sleep_for(microseconds(10)); 245 | auto res = t.remove(id + 1); 246 | REQUIRE(res == false); 247 | } 248 | 249 | SECTION("Remove timer_id and ensure handler is freed when calling remove") 250 | { 251 | auto shared = std::make_shared(10); 252 | CppTime::handler_t func = [=](CppTime::timer_id) { auto shared2 = shared; }; 253 | auto id = t.add(milliseconds(20), std::move(func)); 254 | REQUIRE(shared.use_count() == 2); // shared is copied 255 | std::this_thread::sleep_for(microseconds(10)); 256 | auto res = t.remove(id); 257 | REQUIRE(res == true); 258 | REQUIRE(shared.use_count() == 1); // shared in the lambda is cleaned. 259 | } 260 | 261 | SECTION("Remove timer_id and ensure handler is freed for one-shot timeouts") 262 | { 263 | auto shared = std::make_shared(10); 264 | CppTime::handler_t func = [=](CppTime::timer_id) { auto shared2 = shared; }; 265 | t.add(milliseconds(20), std::move(func)); 266 | REQUIRE(shared.use_count() == 2); // shared is copied 267 | std::this_thread::sleep_for(milliseconds(30)); 268 | REQUIRE(shared.use_count() == 1); // shared in the lambda is cleaned. 269 | } 270 | } 271 | 272 | TEST_CASE("Pass an argument to an action") 273 | { 274 | struct PushMe { 275 | int i{0}; 276 | }; 277 | auto push_me = std::make_shared(); 278 | push_me->i = 41; 279 | 280 | CppTime::Timer t; 281 | int res = 0; 282 | 283 | // Share the shared_ptr with the lambda 284 | t.add(milliseconds(20), [&res, push_me](CppTime::timer_id) { res = push_me->i + 1; }); 285 | 286 | REQUIRE(res == 0); 287 | std::this_thread::sleep_for(milliseconds(30)); 288 | REQUIRE(res == 42); 289 | } 290 | --------------------------------------------------------------------------------