I work with two different kinds of events: explicitly queued, and asynchronous.
Interrupts and POSIX signal handlers are asynchronous events: their handlers can be called at any point in time.
Interrupts often do not have any payload, but POSIX signals handlers can have one (of
siginfo_t type, including an user-defined arbitrary pointer or corresponding integer value per
sigqueue()).
(Technically, asynchronous events can also be queued, in the sense that when e.g. a signal or interrupt is blocked, it will be postponed until not blocked anymore; and if a POSIX realtime signal arrives while the thread is already handling that signal, it won't be dropped but postponed until the signal handler returns. I'm sure there are better terms for the two (queued vs. asynchronous), I just don't recall/realize what they are when writing this.)
Queued events are what GUI toolkits like Qt, Gtk, etc. use, and what things like X11 (X Windows) are based on. You have one or more queues, and a call that blocks until an event object is available from one of them, returning the first one (or the one with highest priority). The object itself is polymorphic, or a tuple (type, object), so that new events can be added without recompiling the applications.
Imperative languages like C, C++, Python, Perl, Ruby, Rust, etc. implement the former using callbacks (or closures) and special requirements (see e.g.
async-signal-safe functions in POSIX C: the C library interfaces that can be used in signal handlers), and the latter using explicit queue operations and a superloop. (Javascript is also an imperative language used with event-based interfaces, but it does not have signal or interrupt support. It's "superloop" is inherent in its runtime.)
In my event-based
sort command example in the other thread, I showed that things like records emitted from a datastore can be usefully treated as events as well ("datastore record (
this) available"), but I am not actually certain if that is better considered
an event source, or just a normal
queue.
This is exactly the unknown region where real-world experimentation and research ought to yield useful results and information: exactly how can be express things like "let this datastore generate an event, emitting its contents one record at a time in order, until it is empty", in a way that requires minimal runtime support? Imperative languages do this via polling, and that part is prone to bugs (especially when event priority is involved; see e.g.
priority inversion).
I firmly believe that experimenting on scenarios like this –– event-based
sort utility that reads records into a min/max-heap and then emits them to standard output –– with an
imagined language, experimenting on the syntax while also roughly sketching out what kind of machine code that would compile to, is the only way to find the answers this kind of discussion threads are looking for. I do not believe such work has been done yet, but I have already used event-based libraries and programming paradigm in several languages, so that I know the underlying idea is sound: the question is, how such concepts best map to human-written linear language forms (i.e. textual source code) –– and without involving abstractions that require heavy runtime or more RAM than is available on small microcontrollers.
There are even conceptual things that are very important when looking at the low-level, machine-code implementation. One is, does a hardware/interrupt/signal event handler need to be re-entrant? I do not ever recall writing an interrupt or signal handler that would have been hindered by not being re-entrant. If one does not need to be re-entrant, it can run off a static context; essentially a tiny dedicated stack. In POSIX, one can even set up an alternate stack for (selected) signal handlers; see
sigaltstack(). In practice, if signals/interrupts with the same priority cannot interrupt each other, the maximum nesting in the alternate stack is defined by the number of unique priorities, and the exact maximum alternate stack size can be statically determined (sum of maximum stack sizes needed at each priority level).