roo_scheduler
API Documentation for roo_scheduler
Loading...
Searching...
No Matches
roo_scheduler.h
Go to the documentation of this file.
1#pragma once
2
3/// Umbrella header for the roo_scheduler module.
4///
5/// Provides task scheduling primitives and adapters.
6
7#include <functional>
8#include <memory>
9#include <vector>
10
11#include "roo_collections.h"
12#include "roo_collections/flat_small_hash_set.h"
13#include "roo_threads.h"
14#include "roo_threads/condition_variable.h"
15#include "roo_threads/mutex.h"
16#include "roo_time.h"
17
18/// A typical Arduino use case may look like the following:
19///
20/// @code
21/// void foo();
22///
23/// using namespace roo_time;
24/// using namespace roo_scheduler;
25///
26/// Scheduler scheduler;
27/// RepetitiveTask foo_task(scheduler, foo, Seconds(5));
28///
29/// void setup() {
30/// foo_task.start();
31/// }
32///
33/// void loop() {
34/// scheduler.executeEligibleTasks();
35/// // ... other work
36/// }
37/// @endcode
38
39#ifndef ROO_SCHEDULER_IGNORE_PRIORITY
40#define ROO_SCHEDULER_IGNORE_PRIORITY 0
41#endif
42
43namespace roo_scheduler {
44
45/// Represents a unique task execution identifier.
46using ExecutionID = int32_t;
47
48/// Deprecated alias; prefer `ExecutionID`.
50
51/// Priority controls dispatch order among eligible tasks.
52///
53/// Higher-priority tasks execute first. Tasks with equal priority execute in
54/// FIFO order.
55///
56/// Priority does not affect scheduling time; it only affects dispatch order
57/// once tasks are already eligible.
58///
59/// If tasks are sufficiently spread out in time and complete quickly, they are
60/// effectively dispatched by due time regardless of priority.
61///
62/// Priority is captured when execution is scheduled and remains fixed for that
63/// scheduled execution. Re-scheduling may assign a different priority.
64///
65/// `delay()` and `delayUntil()` guarantee execution of tasks whose priority is
66/// at least the requested minimum. Lower-priority overdue tasks may remain
67/// pending.
68enum class Priority {
69 kMinimum = 0,
70 kBackground = 1,
71 kReduced = 2,
72 kNormal = 3,
73 kElevated = 4,
74 kSensitive = 5,
75 kCritical = 6,
76 kMaximum = 7,
77};
78
79/// @deprecated Use `Priority::kMinimum`.
81/// @deprecated Use `Priority::kBackground`.
83/// @deprecated Use `Priority::kReduced`.
85/// @deprecated Use `Priority::kNormal`.
87/// @deprecated Use `Priority::kElevated`.
89/// @deprecated Use `Priority::kSensitive`.
91/// @deprecated Use `Priority::kCritical`.
93/// @deprecated Use `Priority::kMaximum`.
95
96/// Abstract interface for executable tasks in the scheduler queue.
98 public:
99 virtual ~Executable() = default;
100 virtual void execute(ExecutionID id) = 0;
101};
102
103/// Schedules and dispatches delayed task executions.
104///
105/// Scheduler does not execute eligible work automatically; caller must invoke
106/// one of `executeEligibleTasks*()` methods.
108 public:
109 /// Creates an empty scheduler.
110 Scheduler();
111
112 /// Schedules execution no earlier than `when`.
113 ///
114 /// Caller retains ownership and must keep `task` alive until execution or
115 /// cancellation.
116 ExecutionID scheduleOn(roo_time::Uptime when, Executable& task,
117 Priority priority = Priority::kNormal);
118
119 /// Schedules execution no earlier than `when`.
120 ///
121 /// Scheduler takes ownership of `task` and destroys it after execution or
122 /// cancellation.
123 ExecutionID scheduleOn(roo_time::Uptime when,
124 std::unique_ptr<Executable> task,
125 Priority priority = Priority::kNormal);
126
127 /// Schedules callable execution no earlier than `when`.
128 ExecutionID scheduleOn(roo_time::Uptime when, std::function<void()> task,
129 Priority priority = Priority::kNormal);
130
131#ifndef ROO_SCHEDULER_NO_DEPRECATED
132 /// @deprecated Use `scheduleOn(when, task, priority)`.
133 ExecutionID scheduleOn(Executable* task, roo_time::Uptime when,
134 Priority priority = Priority::kNormal) {
135 return scheduleOn(when, *task, priority);
136 }
137#endif
138
139 /// Schedules execution after `delay` elapses.
140 ///
141 /// Caller retains ownership and must keep `task` alive until execution or
142 /// cancellation.
143 ExecutionID scheduleAfter(roo_time::Duration delay, Executable& task,
144 Priority priority = Priority::kNormal);
145
146 /// Schedules execution after `delay` elapses.
147 ///
148 /// Scheduler takes ownership of `task` and destroys it after execution or
149 /// cancellation.
150 ExecutionID scheduleAfter(roo_time::Duration delay,
151 std::unique_ptr<Executable> task,
152 Priority priority = Priority::kNormal);
153
154 /// Schedules callable execution after `delay` elapses.
155 ExecutionID scheduleAfter(roo_time::Duration delay,
156 std::function<void()> task,
157 Priority priority = Priority::kNormal);
158
159#ifndef ROO_SCHEDULER_NO_DEPRECATED
160 /// @deprecated Use `scheduleAfter(delay, task, priority)`.
161 ExecutionID scheduleAfter(Executable* task, roo_time::Duration delay,
162 Priority priority = Priority::kNormal) {
163 return scheduleAfter(delay, *task, priority);
164 }
165#endif
166
167 /// Schedules execution as soon as possible.
168 ///
169 /// Caller retains ownership and must keep `task` alive until execution or
170 /// cancellation.
172 Priority priority = Priority::kNormal) {
173 return scheduleOn(roo_time::Uptime::Now(), task, priority);
174 }
175
176 /// Schedules execution as soon as possible.
177 ///
178 /// Scheduler takes ownership of `task` and destroys it after execution or
179 /// cancellation.
180 ExecutionID scheduleNow(std::unique_ptr<Executable> task,
181 Priority priority = Priority::kNormal) {
182 return scheduleOn(roo_time::Uptime::Now(), std::move(task), priority);
183 }
184
185 /// Schedules callable execution as soon as possible.
186 ExecutionID scheduleNow(std::function<void()> task,
187 Priority priority = Priority::kNormal) {
188 return scheduleOn(roo_time::Uptime::Now(), std::move(task), priority);
189 }
190
191 /// Executes up to `max_count` eligible tasks due no later than now.
192 ///
193 /// Tasks below `min_priority` are ignored (not executed).
194 ///
195 /// @return true if no eligible executions remain in queue; false otherwise.
197 int max_count = -1) {
198 return executeEligibleTasksUpTo(roo_time::Uptime::Now(), min_priority,
199 max_count);
200 }
201
202 /// Executes up to `max_count` eligible tasks due no later than `deadline`.
203 ///
204 /// Tasks below `min_priority` are ignored (not executed).
205 ///
206 /// @return true if no eligible executions remain in queue; false otherwise.
207 bool executeEligibleTasksUpTo(roo_time::Uptime deadline,
208 Priority min_priority = Priority::kMinimum,
209 int max_count = -1);
210
211 /// Executes up to `max_count` eligible tasks with at least `min_priority`.
212 ///
213 /// @return true if no eligible executions remain in queue; false otherwise.
214 bool executeEligibleTasks(Priority min_priority, int max_count = -1);
215
216 /// Executes up to `max_count` eligible tasks.
217 ///
218 /// @return true if no eligible executions remain in queue; false otherwise.
219 bool executeEligibleTasks(int max_count = -1) {
220 return executeEligibleTasks(Priority::kMinimum, max_count);
221 }
222
223 /// Returns due time of the nearest upcoming execution.
224 roo_time::Uptime getNearestExecutionTime() const;
225
226 /// Returns delay to the nearest upcoming execution.
227 roo_time::Duration getNearestExecutionDelay() const;
228
229 /// Marks execution identified by `id` as canceled.
230 ///
231 /// Canceled entries may remain in queue until pruned, but will not run.
232 void cancel(ExecutionID);
233
234 /// Removes canceled executions from the queue.
235 ///
236 /// This is linear-time and should be used sparingly.
237 void pruneCanceled();
238
239 /// Returns true iff no pending (non-canceled) executions exist.
240 bool empty() const { return queue_.empty(); }
241
242 /// Delays for at least `delay` while executing scheduled work.
243 ///
244 /// Tasks due by the requested return time (now + `delay`) with priority >=
245 /// `min_priority` are guaranteed to execute before return. Lower-priority
246 /// overdue tasks may remain pending.
247 ///
248 /// Note: because scheduled callbacks execute on the caller's stack, this mode
249 /// can increase stack usage compared with explicit event-loop dispatch.
250 void delay(roo_time::Duration delay,
251 Priority min_priority = Priority::kNormal);
252
253 /// Delays until `deadline` while executing scheduled work.
254 ///
255 /// Tasks due by `deadline` with priority >= `min_priority` are guaranteed to
256 /// execute before return. Lower-priority overdue tasks may remain pending.
257 void delayUntil(roo_time::Uptime deadline,
258 Priority min_priority = Priority::kNormal);
259
260 /// Runs scheduler event loop forever.
261 void run();
262
263 private:
264 class Entry {
265 public:
266#if !ROO_SCHEDULER_IGNORE_PRIORITY
267 Entry()
268 : id_(0),
269 task_(nullptr),
270 when_(roo_time::Uptime::Max()),
271 priority_(Priority::kNormal),
272 owns_task_(false) {}
273
274 Entry(ExecutionID id, Executable* task, bool owns_task,
275 roo_time::Uptime when, Priority priority)
276 : id_(id),
277 task_(task),
278 when_(when),
279 priority_(priority),
280 owns_task_(owns_task) {}
281
282 Entry(Entry&& other)
283 : id_(other.id_),
284 task_(other.task_),
285 when_(other.when_),
286 priority_(other.priority_),
287 owns_task_(other.owns_task_) {
288 other.task_ = nullptr;
289 other.owns_task_ = false;
290 }
291
292 Entry& operator=(Entry&& other) {
293 if (this == &other) return *this;
294 if (owns_task_) {
295 delete task_;
296 }
297 id_ = other.id_;
298 task_ = other.task_;
299 when_ = other.when_;
300 priority_ = other.priority_;
301 owns_task_ = other.owns_task_;
302 other.task_ = nullptr;
303 other.owns_task_ = false;
304 return *this;
305 }
306
307#else
308 Entry()
309 : id_(0),
310 task_(nullptr),
311 when_(roo_time::Uptime::Max()),
312 owns_task_(false) {}
313
314 Entry(ExecutionID id, Executable* task, bool owns_task,
315 roo_time::Uptime when, Priority priority)
316 : id_(id), task_(task), when_(when), owns_task_(owns_task) {}
317
318 Entry(Entry&& other)
319 : id_(other.id_),
320 task_(other.task_),
321 when_(other.when_),
322 owns_task_(other.owns_task_) {
323 other.owns_task_ = false;
324 }
325
326 Entry& operator=(Entry&& other) {
327 if (this == &other) return *this;
328 if (owns_task_) {
329 delete task_;
330 }
331 id_ = other.id_;
332 task_ = other.task_;
333 when_ = other.when_;
334 owns_task_ = other.owns_task_;
335 other.task_ = nullptr;
336 other.owns_task_ = false;
337 return *this;
338 }
339#endif
340
341 Entry(const Entry& other) = delete;
342 Entry& operator=(const Entry& other) = delete;
343
344 ~Entry() {
345 if (owns_task_) {
346 delete task_;
347 }
348 }
349
350 roo_time::Uptime when() const { return when_; }
351 Executable* task() const { return task_; }
352 ExecutionID id() const { return id_; }
353
354 Priority priority() const {
355#if !ROO_SCHEDULER_IGNORE_PRIORITY
356 return priority_;
357#else
358 return Priority::kNormal;
359#endif
360 }
361
362 bool owns_task() const { return owns_task_; }
363
364 private:
365 friend struct TimeComparator;
366
367 ExecutionID id_;
368 Executable* task_;
369 roo_time::Uptime when_;
370
371#if !ROO_SCHEDULER_IGNORE_PRIORITY
372 Priority priority_;
373#endif
374 bool owns_task_;
375 };
376
377 // Orders scheduled tasks in the queue by their nearest execution time.
378 struct TimeComparator {
379 bool operator()(const Entry& a, const Entry& b) {
380 return a.when() > b.when() ||
381 (a.when() == b.when() && a.id() - b.id() > 0);
382 }
383 };
384
385 // Used for tasks that are already due, ordering them by priority.
386 struct PriorityComparator {
387 bool operator()(const Entry& a, const Entry& b) {
388 return a.priority() < b.priority() ||
389 (a.priority() == b.priority() &&
390 (a.when() > b.when() ||
391 (a.when() == b.when() && a.id() - b.id() > 0)));
392 }
393 };
394
395 roo_time::Uptime getNearestExecutionTimeWithLockHeld() const;
396
397 roo_time::Duration getNearestExecutionDelayWithLockHeld() const;
398
399 ExecutionID push(roo_time::Uptime when, Executable* task, bool owns_task,
400 Priority priority);
401 void pop();
402
403 // Returns true if has been executed; false if there was no eligible
404 // execution.
405 bool runOneEligibleExecution(roo_time::Uptime deadline,
406 Priority min_priority);
407
408 // Entries in the queue_ are stored as a heap. (We're not directly using
409 // std::priority_queue in order to support cancellation; see prune()). Since
410 // the entries are stored in a vector, when the number of scheduled executions
411 // is bounded, there will be no dynamic allocation once the vector reaches
412 // sufficient capacity. At the same time, even if executions are dynamically
413 // created, the queue can accommodate them, as long as there is sufficient
414 // amount of memory.
415 //
416 // We maintain the invariant that the top (front) of the queue is a
417 // non-canceled execution.
418 std::vector<Entry> queue_;
419
420#if !ROO_SCHEDULER_IGNORE_PRIORITY
421 // Tasks that are due. Heap, ordered by priority.
422 std::vector<Entry> ready_;
423#endif
424
425 ExecutionID next_execution_id_;
426
427 // Deferred cancellation set, containing IDs of scheduled executions that have
428 // been canceled. They will not run when due, and the tasks they refer to can
429 // be safely destroyed.
430 //
431 // Calling pruneCanceled() removes all canceled executions from the queue, and
432 // clears this set.
433 roo_collections::FlatSmallHashSet<ExecutionID> canceled_;
434
435 mutable roo::mutex mutex_;
436 roo::condition_variable nonempty_;
437};
438
439/// Convenience adapter for one-time execution of an arbitrary callable.
440class Task : public Executable {
441 public:
442 Task(std::function<void()> task) : task_(task) {}
443 void execute(ExecutionID id) override { task_(); }
444
445 private:
446 std::function<void()> task_;
447};
448
449/// Convenience adapter for repetitive callable execution.
450///
451/// Subsequent executions are scheduled with constant delay between runs.
453 public:
454 RepetitiveTask(Scheduler& scheduler, roo_time::Duration delay,
455 std::function<void()> task,
457
458#ifndef ROO_SCHEDULER_NO_DEPRECATED
459 /// @deprecated Use `RepetitiveTask(scheduler, delay, task, priority)`.
460 RepetitiveTask(Scheduler& scheduler, std::function<void()> task,
461 roo_time::Duration delay,
463 : RepetitiveTask(scheduler, delay, std::move(task), priority) {}
464#endif
465
466 bool is_active() const { return active_; }
467
468 Priority priority() const { return priority_; }
469
470 /// Starts task using configured periodic delay.
471 ///
472 /// @return false if already active.
473 bool start() { return start(delay_); }
474
475 /// Starts task immediately.
476 ///
477 /// @return false if already active.
478 bool startInstantly() { return start(roo_time::Millis(0)); }
479
480 /// Starts task with custom initial delay.
481 ///
482 /// @return false if already active.
483 bool start(roo_time::Duration initial_delay);
484
485 bool stop();
486
487 void execute(ExecutionID id) override;
488
489 void setPriority(Priority priority) { priority_ = priority; }
490
492
493 private:
494 Scheduler& scheduler_;
495 std::function<void()> task_;
496 ExecutionID id_;
497 bool active_;
498 Priority priority_;
499 roo_time::Duration delay_;
500};
501
502/// Convenience adapter for periodic callable execution.
503///
504/// Uses fixed target schedule to keep average execution frequency stable.
505class PeriodicTask : public Executable {
506 public:
507 PeriodicTask(Scheduler& scheduler, roo_time::Duration period,
508 std::function<void()> task,
510
511#ifndef ROO_SCHEDULER_NO_DEPRECATED
512 /// @deprecated Use `PeriodicTask(scheduler, period, task, priority)`.
513 PeriodicTask(Scheduler& scheduler, std::function<void()> task,
514 roo_time::Duration period, Priority priority = Priority::kNormal)
515 : PeriodicTask(scheduler, period, std::move(task), priority) {}
516#endif
517
518 bool is_active() const { return active_; }
519
520 Priority priority() const { return priority_; }
521
522 bool start(roo_time::Uptime when = roo_time::Uptime::Now());
523
524 bool stop();
525
526 void execute(ExecutionID id) override;
527
528 void setPriority(Priority priority) { priority_ = priority; }
529
531
532 private:
533 Scheduler& scheduler_;
534 std::function<void()> task_;
535 ExecutionID id_;
536 bool active_;
537 Priority priority_;
538 roo_time::Duration period_;
539 roo_time::Uptime next_;
540};
541
542/// Convenience adapter for cancelable and replaceable single pending work.
543class SingletonTask : public Executable {
544 public:
545 SingletonTask(Scheduler& scheduler, std::function<void()> task);
546
547 bool is_scheduled() const { return scheduled_; }
548
549 /// Schedules or reschedules task at absolute time `when`.
550 ///
551 /// Any previously pending execution is canceled.
552 void scheduleOn(roo_time::Uptime when, Priority priority = Priority::kNormal);
553
554 /// Schedules or reschedules task after `delay`.
555 ///
556 /// Any previously pending execution is canceled.
557 void scheduleAfter(roo_time::Duration delay,
558 Priority priority = Priority::kNormal);
559
560 /// Schedules or reschedules task for immediate execution.
561 ///
562 /// Any previously pending execution is canceled.
563 void scheduleNow(Priority priority = Priority::kNormal);
564
565 void cancel() { scheduled_ = false; }
566
567 void execute(ExecutionID id) override;
568
570
571 private:
572 Scheduler& scheduler_;
573 std::function<void()> task_;
574 ExecutionID id_;
575 bool scheduled_;
576};
577
578class IteratingTask : public Executable {
579 public:
580 class Iterator {
581 public:
582 virtual ~Iterator() = default;
583 virtual int64_t next() = 0;
584 };
585
586 IteratingTask(Scheduler& scheduler, Iterator& iterator,
587 std::function<void()> done_cb = std::function<void()>());
588
589 bool start(roo_time::Uptime when = roo_time::Uptime::Now());
590
591 void execute(ExecutionID id) override;
592
593 bool is_active() const { return id_ >= 0; }
594
596
597 private:
598 Scheduler& scheduler_;
599 Iterator& itr_;
600 ExecutionID id_;
601
602 /// Called when iterator finishes; callback may delete the iterating task.
603 std::function<void()> done_cb_;
604};
605
606} // namespace roo_scheduler
Abstract interface for executable tasks in the scheduler queue.
virtual ~Executable()=default
virtual void execute(ExecutionID id)=0
void execute(ExecutionID id) override
bool start(roo_time::Uptime when=roo_time::Uptime::Now())
Convenience adapter for periodic callable execution.
void execute(ExecutionID id) override
void setPriority(Priority priority)
bool start(roo_time::Uptime when=roo_time::Uptime::Now())
PeriodicTask(Scheduler &scheduler, std::function< void()> task, roo_time::Duration period, Priority priority=Priority::kNormal)
Convenience adapter for repetitive callable execution.
bool startInstantly()
Starts task immediately.
bool start()
Starts task using configured periodic delay.
RepetitiveTask(Scheduler &scheduler, std::function< void()> task, roo_time::Duration delay, Priority priority=Priority::kNormal)
void execute(ExecutionID id) override
void setPriority(Priority priority)
Schedules and dispatches delayed task executions.
bool executeEligibleTasksUpTo(roo_time::Uptime deadline, Priority min_priority=Priority::kMinimum, int max_count=-1)
Executes up to max_count eligible tasks due no later than deadline.
Scheduler()
Creates an empty scheduler.
ExecutionID scheduleNow(std::function< void()> task, Priority priority=Priority::kNormal)
Schedules callable execution as soon as possible.
ExecutionID scheduleAfter(Executable *task, roo_time::Duration delay, Priority priority=Priority::kNormal)
ExecutionID scheduleAfter(roo_time::Duration delay, Executable &task, Priority priority=Priority::kNormal)
Schedules execution after delay elapses.
void cancel(ExecutionID)
Marks execution identified by id as canceled.
void run()
Runs scheduler event loop forever.
ExecutionID scheduleNow(Executable &task, Priority priority=Priority::kNormal)
Schedules execution as soon as possible.
bool executeEligibleTasksUpToNow(Priority min_priority=Priority::kMinimum, int max_count=-1)
Executes up to max_count eligible tasks due no later than now.
void delay(roo_time::Duration delay, Priority min_priority=Priority::kNormal)
Delays for at least delay while executing scheduled work.
void delayUntil(roo_time::Uptime deadline, Priority min_priority=Priority::kNormal)
Delays until deadline while executing scheduled work.
roo_time::Uptime getNearestExecutionTime() const
Returns due time of the nearest upcoming execution.
roo_time::Duration getNearestExecutionDelay() const
Returns delay to the nearest upcoming execution.
void pruneCanceled()
Removes canceled executions from the queue.
bool executeEligibleTasks(int max_count=-1)
Executes up to max_count eligible tasks.
ExecutionID scheduleNow(std::unique_ptr< Executable > task, Priority priority=Priority::kNormal)
Schedules execution as soon as possible.
ExecutionID scheduleOn(roo_time::Uptime when, Executable &task, Priority priority=Priority::kNormal)
Schedules execution no earlier than when.
bool empty() const
Returns true iff no pending (non-canceled) executions exist.
bool executeEligibleTasks(Priority min_priority, int max_count=-1)
Executes up to max_count eligible tasks with at least min_priority.
ExecutionID scheduleOn(Executable *task, roo_time::Uptime when, Priority priority=Priority::kNormal)
Convenience adapter for cancelable and replaceable single pending work.
void scheduleAfter(roo_time::Duration delay, Priority priority=Priority::kNormal)
Schedules or reschedules task after delay.
void scheduleNow(Priority priority=Priority::kNormal)
Schedules or reschedules task for immediate execution.
void execute(ExecutionID id) override
void scheduleOn(roo_time::Uptime when, Priority priority=Priority::kNormal)
Schedules or reschedules task at absolute time when.
Convenience adapter for one-time execution of an arbitrary callable.
Task(std::function< void()> task)
void execute(ExecutionID id) override
constexpr Priority PRIORITY_BACKGROUND
constexpr Priority PRIORITY_MINIMUM
constexpr Priority PRIORITY_CRITICAL
constexpr Priority PRIORITY_ELEVATED
constexpr Priority PRIORITY_MAXIMUM
ExecutionID EventID
Deprecated alias; prefer ExecutionID.
Priority
Priority controls dispatch order among eligible tasks.
constexpr Priority PRIORITY_NORMAL
constexpr Priority PRIORITY_REDUCED
constexpr Priority PRIORITY_SENSITIVE
int32_t ExecutionID
Represents a unique task execution identifier.