Roc Toolkit internal modules
Roc Toolkit: real-time audio streaming
|
Control task queue. More...
#include <control_task_queue.h>
Public Member Functions | |
ControlTaskQueue () | |
Initialize. More... | |
virtual | ~ControlTaskQueue () |
Destroy. More... | |
bool | is_valid () const |
Check if the object was successfully constructed. More... | |
void | schedule (ControlTask &task, IControlTaskExecutor &executor, IControlTaskCompleter *completer) |
Enqueue a task for asynchronous execution as soon as possible. More... | |
void | schedule_at (ControlTask &task, core::nanoseconds_t deadline, IControlTaskExecutor &executor, IControlTaskCompleter *completer) |
Enqueue a task for asynchronous execution at given point of time. More... | |
void | resume (ControlTask &task) |
Resume task if it's paused. More... | |
void | async_cancel (ControlTask &task) |
Try to cancel scheduled task execution, if it's not executed yet. More... | |
void | wait (ControlTask &task) |
Wait until the task is completed. More... | |
void | stop_and_wait () |
Stop thread and wait until it terminates. More... | |
Control task queue.
This class implements a thread-safe task queue, allowing lock-free scheduling of tasks for immediate or delayed execution on the background thread, as well as lock-free task cancellation and re-scheduling (changing deadline).
It also supports tasks to be paused and resumed. Task resuming is lock-free too.
Note that those operations are lock-free only if core::Timer::try_set_deadline() is so, which however is true on modern platforms.
In the current implementation, priority is given to fast scheduling and cancellation over the strict observance of the scheduling deadlines. In other words, during contention or peak load, scheduling and cancellation will be always fast, but task execution may be delayed.
This design was considered acceptable because the actual users of control task queue are more sensitive to delays than the tasks they schedule. The task queue is used by network and pipeline threads, which should never block and use the task queue to schedule low-priority delayed work.
The implementation uses three queues internally:
task_mutex_ should be acquired to process tasks and/or to access sleeping_queue_ and pause_queue_, as well as non-atomic task fields.
wakeup_timer_ (core::Timer) is used to set or wait for the next wakeup time of the background thread. This time is set to zero when ready_queue_ is non-empty, otherwise it is set to the deadline of the first task in sleeping_queue_ if it's non-empty, and otherwise is set to infinity (-1). The timer allows to update the deadline concurrently from any thread.
When the task is scheduled, re-scheduled, or canceled, there are two ways to complete the operation:
The current task state is defined by its atomic field "state_". Various task queue operations move task from one state to another. The move is always performed using atomic CAS or exchange to handle concurrent lock-free updates correctly.
There is also "flags_" field that provides additional information about task that is preserved across transitions between states; for example that task is being resumed.
Here are some example flows of the task states:
The meaning of the states is the following:
Definition at line 125 of file control_task_queue.h.
roc::ctl::ControlTaskQueue::ControlTaskQueue | ( | ) |
Initialize.
|
virtual |
Destroy.
void roc::ctl::ControlTaskQueue::async_cancel | ( | ControlTask & | task | ) |
Try to cancel scheduled task execution, if it's not executed yet.
When the task is being canceled instead of completed, if it has completer, the completer is invoked.
bool roc::ctl::ControlTaskQueue::is_valid | ( | ) | const |
Check if the object was successfully constructed.
void roc::ctl::ControlTaskQueue::resume | ( | ControlTask & | task | ) |
Resume task if it's paused.
If resume is called one or multiple times before task execution, those calls are ignored. Only calls made during or after task execution are honored, and only if the task execution leaved task in paused state.
Subsequent resume calls between task executions are collapsed into one; even if resume was called multiple after task paused and before it's executed again, next pause will need a new resume call.
void roc::ctl::ControlTaskQueue::schedule | ( | ControlTask & | task, |
IControlTaskExecutor & | executor, | ||
IControlTaskCompleter * | completer | ||
) |
Enqueue a task for asynchronous execution as soon as possible.
This is like schedule_at(), but the deadline is "as soon as possible".
void roc::ctl::ControlTaskQueue::schedule_at | ( | ControlTask & | task, |
core::nanoseconds_t | deadline, | ||
IControlTaskExecutor & | executor, | ||
IControlTaskCompleter * | completer | ||
) |
Enqueue a task for asynchronous execution at given point of time.
deadline
should be in the same domain as core::timestamp(). It can't be negative. Zero deadline means "execute as soon as possible".
The executor
is used to invoke the task function. It allows to implement tasks in different classes. If a class T wants to implement tasks, it should inherit ControlTaskExecutor<T>.
If completer
is present, the task should not be destroyed until completer is invoked. The completer is invoked on event loop thread after once and only once, after the task completes or is canceled. Completer should never block.
The event loop thread assumes that the task may be destroyed right after it is completed and it's completer is called (if it's present), and don't touch task after this, unless the user explicitly reschedules the task.
void roc::ctl::ControlTaskQueue::stop_and_wait | ( | ) |
Stop thread and wait until it terminates.
All tasks should be completed before calling stop_and_wait(). stop_and_wait() should be called before calling destructor.
void roc::ctl::ControlTaskQueue::wait | ( | ControlTask & | task | ) |
Wait until the task is completed.
Blocks until the task is completed or canceled. Does NOT wait until the task completer is called.
Can not be called concurrently for the same task (will cause crash). Can not be called from the task completion handler (will cause deadlock).
If this method is called, the task should not be destroyed until this method returns (as well as until the completer is invoked, if it's present).