event_loop, event_loop_n, event_loop_th

#include "dasynq.h"

namespace dasynq {
    // generic event loop
    template <typename T_Mutex> class event_loop;

    // non-threadsafe event loop
    typedef event_loop<null_mutex> event_loop_n;
    
    // threadsafe event loop
    typedef event_loop<std::mutex> event_loop_th;
}

Brief: The event_loop templates defines an event loop multiplexor which can be used to listen or poll for events. A number of different types of event are handled, including I/O readiness on file descriptors, POSIX signals, child process status changes, and timer expiry. To receive an event the client first registers a watcher for the event and event source they are interested in, and then calls the run or poll methods of the event loop. The callback function of the watcher will be called during event processing to respond to the event.

Template parameters

Additional template parameters are implementation internal.

Members

Constructors

Types

Functions

Destructor

Details and usage

Instances can be constructed using the default constructor (which performs initialisation) or the delayed initialisation constructor. If the delayed initialisation constructor is used, the instance remains uninitialised until the init function is called. Only one instance can be active (initialised) at a time in a process; libraries that require event loop functionality are encouraged to allow an event_loop reference to be passed as a parameter from the application. Construction of a second event loop while a first is still active has undefined behaviour.
Note: This limitation is a consequence of limitations in the backends (signals require handlers to be setup on some systems, and child process status requires watching for a signal).

Watchers for events of interest should be registered with the event loop, and their callbacks will be notified when the events are processed. Event processing occurs only during execution of the run() and poll() functions. Watcher callbacks can return a rearm value to perform various actions, including to re-enable the watcher in order to detect future events.

Most operations involving event watchers, such as registration, removing, enabling, and disabling, are performed via the watcher instance; consult the documentation for the various watcher types. In general, watcher operations other than registration can not fail and will not throw exceptions. This means that critical watchers can be registered early and then enabled when needed; resource issues will be identified early, at the time of registration.

Event loop initialisation

Because the event loop may need to mask certain signals for the whole process, and because there is no straight-forward way to do this once multiple threads have been created, the event loop must be initialised in the initial application thread — or alternatively, these signals must be blocked by the application on the initial thread (the signals in question are SIGCHLD and SIGALRM). An event loop with a null_mutex mutex type will use sigprocmask to mask signals and therefore has (according to POSIX) unpredictable behaviour in multi-threaded applications; an event loop with any other mutex type will instead use pthread_sigmask, which requires linking against the pthreads library. It is therefore not possible to use a single-threaded event loop in a multi-threaded program, even if it would be used only from a single thread.

Watcher constraints and state cycle

Watchers are initially in a waiting state once enabled. An enabled watcher for which a watched event is detected (during the execution of the run or poll functions) is disabled and becomes queued for notification until its callback is run; during execution of the callback, the watcher is in the processing state.

A watcher must not be re-enabled while in the queued or processing state, unless the event loop will not be polled (via run or poll) before the associated event is processed. For a client program which polls the event loop from multiple threads, this (roughly) means that watchers should not be enabled, once they have become disabled, except by returning a suitable rearm code from the callback.
Note: Technically, the requirement is that polling must not be performed if an event may be detected via an enabled watcher that is not in the waiting state.

Watchers may not be registered with more than one event loop at the same time (nor with the same loop more than once at the same time).

Event batching

Events are queued and processed in batches. Watchers can be assigned a priority value and processing of queued events occurs in priority order; however, incoming events currently do not cause additional watchers to be queued while in the processing phase, meaning that events may be processed slightly out of priority order. To mitigate this a processing batch size limit can be specified as an argument to the run or poll function. A smaller batch limit gives more accurate priority ordering, at a cost to throughput.

Note that watchers which re-queue immediately after processing (by returning rearm::REQUEUE from the callback function, or due to emulation mode of an fd_watcher watching readiness state of a regular file) would cause an unlimited processing cycle if no specific batch limit was specified; to mitigate this, the limit is reduced to the number of currently queued events, which should generally prevent a watcher from being processed more than once. However, high-priority watchers which requeue themselves may still be processed more than once since they will continue to jump to the start of the queue, and this may cause starvation for lower priority watchers.

Removal of watchers

Watchers can be requested to be removed from an event_loop, however removal does not necessarily occur immediately (except for non-threadsafe event loops; for threadsafe event loops, any delay is due to the watcher currently having its callback executed). All watcher types have a watch_removed function:

virtual void watch_removed() noexcept 

This function can be overridden by watch implementations and will be called once the watcher has been removed; at this point it is safe to delete the watcher. Naturally watcher instances must not end their lifetime while they are registered to an event loop.

Forking processes

The event loop state in the child process after forking is unspecified. The event loop should be destroyed and reconstructed in the child before it is used again.

Signals

The event loop may internally use certain signals, including SIGCHLD and SIGALRM. Blocking or installing handlers for these signals may interfere with the function of the event loop. Other signals should be safe for application use, although these can be handled easily using the event loop's support via signal watchers.


loop_traits_t (dasynq::event_loop<T>::loop_traits_t)

    // Member of dasynq::event_loop
    typedef unspecified loop_traits_t;

Brief: this traits class provides details of the supported functionality of the event loop.

Data members