Log in | Back to darenet.org

ircd/api/events

Overview

The IRC server is built around an event loop. Until the u2.10.11 release (which is what ircd-darenet 1.x is based off of), this event loop has been rather ad-hoc; timed events are hard-coded in, signals are handled inside the signal handler, etc. All of this changed with u2.10.11. A new subsystem, the events subsystem, was introduced; the new subsystem contains a generalization of the concept of an event. An event is a signal, the expiration of a timer, or some form of activity on a network socket. This new subsystem has the potential to vastly simplify the code that is arguably the core of any network program, and makes it much simpler to support more exotic forms of network activity monitoring than the conventional select() and poll() calls.

The primary concepts that the events subsystem works with are the "event," represented by a struct Event, and the "generator." There are three types of generators: sockets, represented by a struct Socket; signals, represented by a struct Signal; and timers, represented by struct Timer. Each of these generators will be described in turn.

Signals

The signal is perhaps the simplest generator in the entire events subsystem. Basically, instead of setting a signal handler, the function signal_add() is called, specifying a function to be called when a given signal is detected. Most importantly, that call-back function is called outside the context of a signal handler, permitting the call-back to use more exotic functions that are anathema within a signal handler, such as MyMalloc(). Once a call-back for a signal has been established, it cannot be deleted; this design decision was driven by the fact that ircd never changes its signal handlers.

Whenever a signal is received, an event of type ET_SIGNAL is generated, and that event is passed to the event call-back function specified in the signal_add() call.

Timers

Execution of the call-back functions for a timer occur when that timer expires; when a timer expires depends on the type of timer and the expiration time that was used for that timer. A TT_ABSOLUTE timer, for instance, expires at exactly the time given as the expiration time. This time is a standard UNIX time_t value, measuring seconds since the UNIX epoch. The TT_ABSOLUTE timer type is complemented by the TT_RELATIVE timer; the time passed as its expiration time is relative to the current time. If a TT_RELATIVE is given an expiration time of 5, for instance, it will expire 5 seconds after the present time. Internally, TT_RELATIVE timers are converted into TT_ABSOLUTE timers, with the expiration time adjusted by addition of the current time.

These two types of timers, TT_ABSOLUTE and TT_RELATIVE, are single-shot timers. Once they expire, they are removed from the timer list unless re-added by the event call-back or through some other mechanism. There is another type of timer, however, the TT_PERIODIC timer, that is not removed from the timer list. TT_PERIODIC timers are similar to TT_RELATIVE timers, in that one passes in the expire time as a relative number of seconds, but when they expire, they are re-added to the timer list with the same relative expire time. This means that a TT_PERIODIC timer with an expire time of 5 seconds that is set at 11:50:00 will have its call-back called at 11:50:05, 11:50:10, 11:50:15, and so on.

Timers have to be run by the event engines explicitly by calling timer_run() on the generator list passed to the engine event loop. In addition, engines may determine the next (absolute) time that a timer needs to be run by calling the time_next() macro; this may be used to set a timeout on the engine's network activity monitoring function. Engines are described in detail below.

When a timer expires, an event of ET_EXPIRE is generated, and the call-back function is called. When a timer is destroyed, either as the result of an expiration or as result of an explicit timer_del() call, am event of ET_DESTROY is generated, notifying the call-back that the struct Timer can be deallocated.