blob: 2bd7cef626205bdb8b03dc169f7b9401c74984f3 [file] [log] [blame]
Alex Perrycb7da4b2019-08-28 19:35:56 -07001#include "aos/events/event_scheduler.h"
2
3#include <algorithm>
4#include <deque>
5
6#include "aos/events/event_loop.h"
Tyler Chatow67ddb032020-01-12 14:30:04 -08007#include "aos/logging/implementations.h"
Alex Perrycb7da4b2019-08-28 19:35:56 -07008
9namespace aos {
10
Austin Schuhef8f1ae2021-12-11 12:35:05 -080011EventScheduler::Token EventScheduler::Schedule(monotonic_clock::time_point time,
12 Event *callback) {
James Kuszmaul86e86c32022-07-21 17:39:47 -070013 CHECK_LE(monotonic_clock::epoch(), time);
Alex Perrycb7da4b2019-08-28 19:35:56 -070014 return events_list_.emplace(time, callback);
15}
16
17void EventScheduler::Deschedule(EventScheduler::Token token) {
Brian Silverman7026e2d2021-11-11 16:15:35 -080018 // We basically want to DCHECK some nontrivial logic. Guard it with NDEBUG to
19 // ensure the compiler realizes it's all unnecessary when not doing debug
20 // checks.
Brian Silvermanbd405c02020-06-23 16:25:23 -070021#ifndef NDEBUG
22 {
23 bool found = false;
24 auto i = events_list_.begin();
25 while (i != events_list_.end()) {
26 if (i == token) {
27 CHECK(!found) << ": The same iterator is in the multimap twice??";
28 found = true;
29 }
30 ++i;
31 }
32 CHECK(found) << ": Trying to deschedule an event which is not scheduled";
33 }
34#endif
Alex Perrycb7da4b2019-08-28 19:35:56 -070035 events_list_.erase(token);
36}
37
Austin Schuhe12b5eb2022-08-29 12:39:27 -070038std::pair<distributed_clock::time_point, monotonic_clock::time_point>
39EventScheduler::OldestEvent() {
James Kuszmaul86e86c32022-07-21 17:39:47 -070040 // If we haven't started yet, schedule a special event for the epoch to allow
41 // ourselves to boot.
42 if (!called_started_) {
Austin Schuhe12b5eb2022-08-29 12:39:27 -070043 if (!cached_epoch_) {
44 cached_epoch_ = ToDistributedClock(monotonic_clock::epoch());
45 }
46 return std::make_pair(*cached_epoch_, monotonic_clock::epoch());
James Kuszmaul86e86c32022-07-21 17:39:47 -070047 }
48
Austin Schuh8bd96322020-02-13 21:18:22 -080049 if (events_list_.empty()) {
Austin Schuhe12b5eb2022-08-29 12:39:27 -070050 return std::make_pair(distributed_clock::max_time,
51 monotonic_clock::max_time);
Austin Schuh39788ff2019-12-01 18:22:57 -080052 }
Austin Schuh8bd96322020-02-13 21:18:22 -080053
Austin Schuhe12b5eb2022-08-29 12:39:27 -070054 const monotonic_clock::time_point monotonic_time = events_list_.begin()->first;
55 if (cached_event_list_monotonic_time_ != monotonic_time) {
56 cached_event_list_time_ = ToDistributedClock(monotonic_time);
57 cached_event_list_monotonic_time_ = monotonic_time;
58 }
59
60 return std::make_pair(cached_event_list_time_, monotonic_time);
Alex Perrycb7da4b2019-08-28 19:35:56 -070061}
62
James Kuszmaul86e86c32022-07-21 17:39:47 -070063void EventScheduler::Shutdown() {
64 CHECK(!is_running_);
65 on_shutdown_();
66}
Austin Schuh58646e22021-08-23 23:51:46 -070067
68void EventScheduler::Startup() {
69 ++boot_count_;
James Kuszmaul86e86c32022-07-21 17:39:47 -070070 CHECK(!is_running_);
71 MaybeRunOnStartup();
72 CHECK(called_started_);
Austin Schuh58646e22021-08-23 23:51:46 -070073}
74
Austin Schuh8bd96322020-02-13 21:18:22 -080075void EventScheduler::CallOldestEvent() {
James Kuszmaul86e86c32022-07-21 17:39:47 -070076 if (!called_started_) {
77 // If we haven't started, start.
78 MaybeRunOnStartup();
79 MaybeRunOnRun();
80 CHECK(called_started_);
81 return;
82 }
83 CHECK(is_running_);
Austin Schuh8bd96322020-02-13 21:18:22 -080084 CHECK_GT(events_list_.size(), 0u);
85 auto iter = events_list_.begin();
Austin Schuh58646e22021-08-23 23:51:46 -070086 const logger::BootTimestamp t =
87 FromDistributedClock(scheduler_scheduler_->distributed_now());
Austin Schuhc1ee1b62022-03-22 17:09:52 -070088 VLOG(2) << "Got time back " << t;
Austin Schuh58646e22021-08-23 23:51:46 -070089 CHECK_EQ(t.boot, boot_count_);
90 CHECK_EQ(t.time, iter->first) << ": Time is wrong on node " << node_index_;
Austin Schuh8bd96322020-02-13 21:18:22 -080091
Austin Schuhef8f1ae2021-12-11 12:35:05 -080092 Event *callback = iter->second;
Austin Schuh8bd96322020-02-13 21:18:22 -080093 events_list_.erase(iter);
Austin Schuhef8f1ae2021-12-11 12:35:05 -080094 callback->Handle();
Austin Schuhb7c8d2a2021-07-19 19:22:12 -070095
96 converter_->ObserveTimePassed(scheduler_scheduler_->distributed_now());
Austin Schuh8bd96322020-02-13 21:18:22 -080097}
98
99void EventScheduler::RunOnRun() {
James Kuszmaul86e86c32022-07-21 17:39:47 -0700100 CHECK(is_running_);
Austin Schuhe33c08d2022-02-03 18:15:21 -0800101 while (!on_run_.empty()) {
102 std::function<void()> fn = std::move(*on_run_.begin());
103 on_run_.erase(on_run_.begin());
104 fn();
Austin Schuh39788ff2019-12-01 18:22:57 -0800105 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700106}
107
Austin Schuhe33c08d2022-02-03 18:15:21 -0800108void EventScheduler::RunOnStartup() noexcept {
109 while (!on_startup_.empty()) {
James Kuszmaul86e86c32022-07-21 17:39:47 -0700110 CHECK(!is_running_);
Austin Schuhe33c08d2022-02-03 18:15:21 -0800111 std::function<void()> fn = std::move(*on_startup_.begin());
112 on_startup_.erase(on_startup_.begin());
113 fn();
Austin Schuh057d29f2021-08-21 23:05:15 -0700114 }
Austin Schuh057d29f2021-08-21 23:05:15 -0700115}
116
Austin Schuhe33c08d2022-02-03 18:15:21 -0800117void EventScheduler::RunStarted() {
James Kuszmaul86e86c32022-07-21 17:39:47 -0700118 CHECK(!is_running_);
Austin Schuhe33c08d2022-02-03 18:15:21 -0800119 if (started_) {
120 started_();
121 }
James Kuszmaul86e86c32022-07-21 17:39:47 -0700122 is_running_ = true;
Austin Schuhe33c08d2022-02-03 18:15:21 -0800123}
124
James Kuszmaul86e86c32022-07-21 17:39:47 -0700125void EventScheduler::MaybeRunStopped() {
126 CHECK(is_running_);
127 is_running_ = false;
128 if (called_started_) {
129 called_started_ = false;
130 if (stopped_) {
131 stopped_();
132 }
133 }
134}
135
136void EventScheduler::MaybeRunOnStartup() {
137 CHECK(!called_started_);
138 CHECK(!is_running_);
139 const logger::BootTimestamp t =
140 FromDistributedClock(scheduler_scheduler_->distributed_now());
141 if (t.boot == boot_count_ && t.time >= monotonic_clock::epoch()) {
142 called_started_ = true;
143 RunOnStartup();
144 }
145}
146
147void EventScheduler::MaybeRunOnRun() {
148 if (called_started_) {
149 RunStarted();
150 RunOnRun();
Austin Schuhe33c08d2022-02-03 18:15:21 -0800151 }
152}
Austin Schuh58646e22021-08-23 23:51:46 -0700153
Austin Schuhac0771c2020-01-07 18:36:30 -0800154std::ostream &operator<<(std::ostream &stream,
155 const aos::distributed_clock::time_point &now) {
156 // Print it the same way we print a monotonic time. Literally.
157 stream << monotonic_clock::time_point(now.time_since_epoch());
158 return stream;
159}
160
Austin Schuh8bd96322020-02-13 21:18:22 -0800161void EventSchedulerScheduler::AddEventScheduler(EventScheduler *scheduler) {
162 CHECK(std::find(schedulers_.begin(), schedulers_.end(), scheduler) ==
163 schedulers_.end());
164 CHECK(scheduler->scheduler_scheduler_ == nullptr);
Austin Schuh58646e22021-08-23 23:51:46 -0700165 CHECK_EQ(scheduler->node_index(), schedulers_.size());
Austin Schuh8bd96322020-02-13 21:18:22 -0800166
167 schedulers_.emplace_back(scheduler);
168 scheduler->scheduler_scheduler_ = this;
169}
170
James Kuszmaul86e86c32022-07-21 17:39:47 -0700171void EventSchedulerScheduler::MaybeRunStopped() {
172 CHECK(!is_running_);
173 for (EventScheduler *scheduler : schedulers_) {
174 if (scheduler->is_running()) {
175 scheduler->MaybeRunStopped();
176 }
177 }
178}
179
180bool EventSchedulerScheduler::RunUntil(
181 realtime_clock::time_point end_time, EventScheduler *scheduler,
182 std::function<std::chrono::nanoseconds()> fn_realtime_offset) {
183 logging::ScopedLogRestorer prev_logger;
184 MaybeRunOnStartup();
185
186 bool reached_end_time = false;
187
188 RunMaybeRealtimeLoop([this, scheduler, end_time, fn_realtime_offset,
189 &reached_end_time]() {
190 std::tuple<distributed_clock::time_point, EventScheduler *> oldest_event =
191 OldestEvent();
192 aos::distributed_clock::time_point oldest_event_time_distributed =
193 std::get<0>(oldest_event);
194 logger::BootTimestamp test_time_monotonic =
195 scheduler->FromDistributedClock(oldest_event_time_distributed);
196 realtime_clock::time_point oldest_event_realtime(
197 test_time_monotonic.time_since_epoch() + fn_realtime_offset());
198
199 if ((std::get<0>(oldest_event) == distributed_clock::max_time) ||
200 (oldest_event_realtime > end_time &&
201 (reboots_.empty() ||
202 std::get<0>(reboots_.front()) > oldest_event_time_distributed))) {
203 is_running_ = false;
204 reached_end_time = true;
205
206 // We have to nudge our time back to the distributed time
207 // corresponding to our desired realtime time.
Austin Schuhe12b5eb2022-08-29 12:39:27 -0700208 const monotonic_clock::time_point end_monotonic =
209 monotonic_clock::epoch() + end_time.time_since_epoch() -
James Kuszmaul86e86c32022-07-21 17:39:47 -0700210 fn_realtime_offset();
211 const aos::distributed_clock::time_point end_time_distributed =
212 scheduler->ToDistributedClock(end_monotonic);
213
214 now_ = end_time_distributed;
215
216 return;
217 }
218
219 if (!reboots_.empty() &&
220 std::get<0>(reboots_.front()) <= std::get<0>(oldest_event)) {
221 // Reboot is next.
222 CHECK_LE(now_,
223 std::get<0>(reboots_.front()) + std::chrono::nanoseconds(1))
224 << ": Simulated time went backwards by too much. Please "
225 "investigate.";
226 now_ = std::get<0>(reboots_.front());
227 Reboot();
228 reboots_.erase(reboots_.begin());
229 return;
230 }
231
232 // We get to pick our tradeoffs here. Either we assume that there are
233 // no backward step changes in our time function for each node, or we
234 // have to let time go backwards. We currently only really see this
235 // happen when 2 events are scheduled for "now", time changes, and
236 // there is a nanosecond or two of rounding due to integer math.
237 //
238 // //aos/events/logging:logger_test triggers this.
239 CHECK_LE(now_, std::get<0>(oldest_event) + std::chrono::nanoseconds(1))
240 << ": Simulated time went backwards by too much. Please "
241 "investigate.";
242
243 now_ = std::get<0>(oldest_event);
244
245 std::get<1>(oldest_event)->CallOldestEvent();
246 });
247
248 MaybeRunStopped();
249
250 return reached_end_time;
251}
252
Austin Schuh58646e22021-08-23 23:51:46 -0700253void EventSchedulerScheduler::Reboot() {
254 const std::vector<logger::BootTimestamp> &times =
255 std::get<1>(reboots_.front());
256 CHECK_EQ(times.size(), schedulers_.size());
257
258 VLOG(1) << "Rebooting at " << now_;
259 for (const auto &time : times) {
260 VLOG(1) << " " << time;
261 }
262
263 is_running_ = false;
264
265 // Shut everything down.
266 std::vector<size_t> rebooted;
267 for (size_t node_index = 0; node_index < schedulers_.size(); ++node_index) {
268 if (schedulers_[node_index]->boot_count() == times[node_index].boot) {
269 continue;
270 } else {
271 rebooted.emplace_back(node_index);
272 CHECK_EQ(schedulers_[node_index]->boot_count() + 1,
273 times[node_index].boot);
James Kuszmaul86e86c32022-07-21 17:39:47 -0700274 schedulers_[node_index]->MaybeRunStopped();
Austin Schuh58646e22021-08-23 23:51:46 -0700275 schedulers_[node_index]->Shutdown();
276 }
277 }
278
279 // And start it back up again to reboot. When something starts back up
280 // (especially message_bridge), it could try to send stuff out. We want
281 // to move everything over to the new boot before doing that.
282 for (const size_t node_index : rebooted) {
Austin Schuh58646e22021-08-23 23:51:46 -0700283 schedulers_[node_index]->Startup();
284 }
Austin Schuh58646e22021-08-23 23:51:46 -0700285 for (const size_t node_index : rebooted) {
James Kuszmaul86e86c32022-07-21 17:39:47 -0700286 schedulers_[node_index]->MaybeRunOnRun();
Austin Schuh58646e22021-08-23 23:51:46 -0700287 }
288 is_running_ = true;
289}
290
Austin Schuh8bd96322020-02-13 21:18:22 -0800291void EventSchedulerScheduler::RunFor(distributed_clock::duration duration) {
292 distributed_clock::time_point end_time = now_ + duration;
293 logging::ScopedLogRestorer prev_logger;
James Kuszmaul86e86c32022-07-21 17:39:47 -0700294 MaybeRunOnStartup();
Austin Schuh8bd96322020-02-13 21:18:22 -0800295
296 // Run all the sub-event-schedulers.
James Kuszmaulb67409b2022-06-20 16:25:03 -0700297 RunMaybeRealtimeLoop([this, end_time]() {
Austin Schuh8bd96322020-02-13 21:18:22 -0800298 std::tuple<distributed_clock::time_point, EventScheduler *> oldest_event =
299 OldestEvent();
Austin Schuh58646e22021-08-23 23:51:46 -0700300 if (!reboots_.empty() &&
301 std::get<0>(reboots_.front()) <= std::get<0>(oldest_event)) {
302 // Reboot is next.
303 if (std::get<0>(reboots_.front()) > end_time) {
304 // Reboot is after our end time, give up.
305 is_running_ = false;
James Kuszmaulb67409b2022-06-20 16:25:03 -0700306 return;
Austin Schuh58646e22021-08-23 23:51:46 -0700307 }
308
309 CHECK_LE(now_,
310 std::get<0>(reboots_.front()) + std::chrono::nanoseconds(1))
311 << ": Simulated time went backwards by too much. Please "
312 "investigate.";
313 now_ = std::get<0>(reboots_.front());
314 Reboot();
315 reboots_.erase(reboots_.begin());
James Kuszmaulb67409b2022-06-20 16:25:03 -0700316 return;
Austin Schuh58646e22021-08-23 23:51:46 -0700317 }
318
Austin Schuh8bd96322020-02-13 21:18:22 -0800319 // No events left, bail.
320 if (std::get<0>(oldest_event) == distributed_clock::max_time ||
321 std::get<0>(oldest_event) > end_time) {
322 is_running_ = false;
James Kuszmaulb67409b2022-06-20 16:25:03 -0700323 return;
Austin Schuh8bd96322020-02-13 21:18:22 -0800324 }
325
326 // We get to pick our tradeoffs here. Either we assume that there are no
327 // backward step changes in our time function for each node, or we have to
Austin Schuh2f8fd752020-09-01 22:38:28 -0700328 // let time go backwards. We currently only really see this happen when 2
329 // events are scheduled for "now", time changes, and there is a nanosecond
330 // or two of rounding due to integer math.
331 //
332 // //aos/events/logging:logger_test triggers this.
333 CHECK_LE(now_, std::get<0>(oldest_event) + std::chrono::nanoseconds(1))
Austin Schuh8bd96322020-02-13 21:18:22 -0800334 << ": Simulated time went backwards by too much. Please investigate.";
James Kuszmaul86e86c32022-07-21 17:39:47 -0700335 // push time forwards
Austin Schuh8bd96322020-02-13 21:18:22 -0800336 now_ = std::get<0>(oldest_event);
337
338 std::get<1>(oldest_event)->CallOldestEvent();
James Kuszmaulb67409b2022-06-20 16:25:03 -0700339 });
Austin Schuh8bd96322020-02-13 21:18:22 -0800340
341 now_ = end_time;
Austin Schuhe33c08d2022-02-03 18:15:21 -0800342
James Kuszmaul86e86c32022-07-21 17:39:47 -0700343 MaybeRunStopped();
Austin Schuh8bd96322020-02-13 21:18:22 -0800344}
345
346void EventSchedulerScheduler::Run() {
347 logging::ScopedLogRestorer prev_logger;
James Kuszmaul86e86c32022-07-21 17:39:47 -0700348 MaybeRunOnStartup();
349
350 // Run all the sub-event-schedulers.
James Kuszmaulb67409b2022-06-20 16:25:03 -0700351 RunMaybeRealtimeLoop([this]() {
Austin Schuh8bd96322020-02-13 21:18:22 -0800352 std::tuple<distributed_clock::time_point, EventScheduler *> oldest_event =
353 OldestEvent();
Austin Schuh58646e22021-08-23 23:51:46 -0700354 if (!reboots_.empty() &&
355 std::get<0>(reboots_.front()) <= std::get<0>(oldest_event)) {
356 // Reboot is next.
357 CHECK_LE(now_,
358 std::get<0>(reboots_.front()) + std::chrono::nanoseconds(1))
359 << ": Simulated time went backwards by too much. Please "
360 "investigate.";
361 now_ = std::get<0>(reboots_.front());
362 Reboot();
363 reboots_.erase(reboots_.begin());
James Kuszmaulb67409b2022-06-20 16:25:03 -0700364 return;
Austin Schuh58646e22021-08-23 23:51:46 -0700365 }
Austin Schuh8bd96322020-02-13 21:18:22 -0800366 // No events left, bail.
367 if (std::get<0>(oldest_event) == distributed_clock::max_time) {
James Kuszmaulb67409b2022-06-20 16:25:03 -0700368 is_running_ = false;
369 return;
Austin Schuh8bd96322020-02-13 21:18:22 -0800370 }
371
372 // We get to pick our tradeoffs here. Either we assume that there are no
373 // backward step changes in our time function for each node, or we have to
Austin Schuh2f8fd752020-09-01 22:38:28 -0700374 // let time go backwards. We currently only really see this happen when 2
375 // events are scheduled for "now", time changes, and there is a nanosecond
376 // or two of rounding due to integer math.
377 //
378 // //aos/events/logging:logger_test triggers this.
379 CHECK_LE(now_, std::get<0>(oldest_event) + std::chrono::nanoseconds(1))
Austin Schuh8bd96322020-02-13 21:18:22 -0800380 << ": Simulated time went backwards by too much. Please investigate.";
381 now_ = std::get<0>(oldest_event);
382
383 std::get<1>(oldest_event)->CallOldestEvent();
James Kuszmaulb67409b2022-06-20 16:25:03 -0700384 });
Austin Schuhe33c08d2022-02-03 18:15:21 -0800385
James Kuszmaul86e86c32022-07-21 17:39:47 -0700386 MaybeRunStopped();
Austin Schuh8bd96322020-02-13 21:18:22 -0800387}
388
James Kuszmaulb67409b2022-06-20 16:25:03 -0700389template <typename F>
390void EventSchedulerScheduler::RunMaybeRealtimeLoop(F loop_body) {
391 internal::TimerFd timerfd;
392 CHECK_LT(0.0, replay_rate_) << "Replay rate must be positive.";
393 distributed_clock::time_point last_distributed_clock =
394 std::get<0>(OldestEvent());
395 monotonic_clock::time_point last_monotonic_clock = monotonic_clock::now();
396 timerfd.SetTime(last_monotonic_clock, std::chrono::seconds(0));
397 epoll_.OnReadable(
398 timerfd.fd(), [this, &last_distributed_clock, &last_monotonic_clock,
399 &timerfd, loop_body]() {
400 const uint64_t read_result = timerfd.Read();
401 if (!is_running_) {
402 epoll_.Quit();
403 return;
404 }
405 CHECK_EQ(read_result, 1u);
406 // Call loop_body() at least once; if we are in infinite-speed replay,
407 // we don't actually want/need the context switches from the epoll
408 // setup, so just loop.
409 // Note: The performance impacts of this code have not been carefully
410 // inspected (e.g., how much does avoiding the context-switch help; does
411 // the timerfd_settime call matter).
412 // This is deliberately written to support the user changing replay
413 // rates dynamically.
414 do {
415 loop_body();
416 if (is_running_) {
417 const monotonic_clock::time_point next_trigger =
418 last_monotonic_clock +
419 std::chrono::duration_cast<std::chrono::nanoseconds>(
420 (now_ - last_distributed_clock) / replay_rate_);
421 timerfd.SetTime(next_trigger, std::chrono::seconds(0));
422 last_monotonic_clock = next_trigger;
423 last_distributed_clock = now_;
424 } else {
425 epoll_.Quit();
426 }
427 } while (replay_rate_ == std::numeric_limits<double>::infinity() &&
428 is_running_);
429 });
430
431 epoll_.Run();
432 epoll_.DeleteFd(timerfd.fd());
433}
434
Austin Schuh8bd96322020-02-13 21:18:22 -0800435std::tuple<distributed_clock::time_point, EventScheduler *>
436EventSchedulerScheduler::OldestEvent() {
437 distributed_clock::time_point min_event_time = distributed_clock::max_time;
438 EventScheduler *min_scheduler = nullptr;
439
440 // TODO(austin): Don't linearly search... But for N=3, it is probably the
441 // fastest way to do this.
442 for (EventScheduler *scheduler : schedulers_) {
Austin Schuhe12b5eb2022-08-29 12:39:27 -0700443 const std::pair<distributed_clock::time_point, monotonic_clock::time_point>
444 event_time = scheduler->OldestEvent();
445 if (event_time.second != monotonic_clock::max_time) {
446 if (event_time.first < min_event_time) {
447 min_event_time = event_time.first;
Austin Schuh8bd96322020-02-13 21:18:22 -0800448 min_scheduler = scheduler;
449 }
450 }
451 }
452
Austin Schuh87dd3832021-01-01 23:07:31 -0800453 if (min_scheduler) {
Austin Schuhc1ee1b62022-03-22 17:09:52 -0700454 VLOG(2) << "Oldest event " << min_event_time << " on scheduler "
Austin Schuh87dd3832021-01-01 23:07:31 -0800455 << min_scheduler->node_index_;
456 }
Austin Schuh8bd96322020-02-13 21:18:22 -0800457 return std::make_tuple(min_event_time, min_scheduler);
458}
459
Austin Schuhe33c08d2022-02-03 18:15:21 -0800460void EventSchedulerScheduler::TemporarilyStopAndRun(std::function<void()> fn) {
461 const bool was_running = is_running_;
462 if (is_running_) {
463 is_running_ = false;
James Kuszmaul86e86c32022-07-21 17:39:47 -0700464 MaybeRunStopped();
Austin Schuhe33c08d2022-02-03 18:15:21 -0800465 }
466 fn();
467 if (was_running) {
James Kuszmaul86e86c32022-07-21 17:39:47 -0700468 MaybeRunOnStartup();
469 }
470}
471
472void EventSchedulerScheduler::MaybeRunOnStartup() {
473 is_running_ = true;
474 for (EventScheduler *scheduler : schedulers_) {
475 scheduler->MaybeRunOnStartup();
476 }
477 // We must trigger all the OnRun's *after* all the OnStartup callbacks are
478 // triggered because that is the contract that we have stated.
479 for (EventScheduler *scheduler : schedulers_) {
480 scheduler->MaybeRunOnRun();
Austin Schuhe33c08d2022-02-03 18:15:21 -0800481 }
482}
483
Alex Perrycb7da4b2019-08-28 19:35:56 -0700484} // namespace aos