event loop | support status |
---|---|
boost-asio | supported |
wx-widgets | supported |
fltk | supported |
ev | supported |
std-thread | supported |
libevent | planned |
libuv | planned |
gtk | planned |
qt | planned |
If you need some other event loop or speedup inclusion of a planned one, please file an issue.
event loop | support status |
---|---|
linux | supported |
windows | supported |
macos | supported |
This backend was developed to support blocking operations, which have the same effect as std::this_thread::sleep_for
invocation. This is suitable for disk I/O operations, working with database or using third-party libraries with synchronous semantics.
It should be noted, that using blocking operations have some consequences, e.g. actors on a thread, which executes blocking operation, will not be able to respond to messages, namely, to cancellation requests. There is no universal remedy, however, this can be smoothed: split long I/O operation into "continuation-message" with series of smaller I/O chunks and when the current chunk is complete, just send self a message with all work and index of the next chunk. It will make an actor more responsive, giving it some breadth to pull external messages, trigger timeouts, and, finally, give it a chance to react to cancellation notice.
When there are no messages, the backend uses sleep_for
/ sleep_until
functions to wait either external message message or until nearest timeout occurs. To let the things work properly, the message handlers with blocking operations should specially marked (tag_io()
), to correctly update timers before, after and inside the handler.
See, Blocking I/O multiplexing
in Patterns
.
rotor
is designed to be integrated with event loops, which actually perform some I/O, spawn and trigger timers and other asynchronous operations, which can be invoked from an actor or a supervisor. The key accessor here is a supervisor.
First, the final (concrete) supervisor have to be obtained. Then, for safety, the intrusive pointer to the actor should be created(2), and then loop-specific async operation should be initiated(3), where the pointer should be moved(4). Finally, when the result of I/O operation is transformed into rotor message or messages(5), which should be sent, the supervisor should be asked to perform rotor mechanics, i.e. deliver message(s)(6). Let's illustrate it on timer using boost-asio.
Adding new event loop to rotor
is rather simple: the new supervisor
class should be derived from supervisor_t
, and the following methods should be implemented:
The enqueue
method is responsible for putting an message
into supervisor
inbound queue probably in thread-safe manner to allow accept messages from other supervisors/loops (thread-safety requirement) or just from some outer context, when supervisor is still not running on the loop (can be thread-unsafe). The second requirement is to let the supervisor process all it's inbound messages in a loop context, may be with supervisor-specific context.
The start
and shutdown
are just convenient methods to start processing messages in event loop context (for start
) and send a shutdown messages and process event loop in event loop context .
do_start_timer
should strate a new timer, whose id (request_id_t) can be get via the timer_handler_base_t
. The do_cancel_timer
should cancel timer and immediately invoke the timer_handler with cancelled = true
. The backend timer cancel implementation can be delayed, but that's actually outsize of rotor
.
Here is an skeleton example for enqueue
:
How to get loop and what method invoke on it, is the implementation-specific information. For example, loop reference can be passed on supervisor
constructor. The invoke_later
(alternative names: postpone
, CallAfter
, delay
, dispatch
) is loop-specific method how to invoke something on in a thread-safe way. Please note, that supervisor
instance is captured via intrusive pointer to make sure it is alive in the loop context invocations.
The timer-related methods are loop- or application-specific.