Skip to content

Archive

Event Loops

22 articles
Linux 19 Sep 2026 5 min read

timerfd Reads Count Periodic Expirations

A periodic timer can expire several times before a busy event loop gets CPU time again. Linux timerfd does not compress that delay into a bare “timer fired” notification. A successful read() returns an unsigned 64-bit count of expirations accumulated since the timer was armed or since the preceding successful read. That counter changes the semantics of delayed timer handling. Readiness says at least one expiration is pending; the value read from the descriptor says how many periods elapsed.

Linux 19 Sep 2026 4 min read

signalfd Converts Linux Signals into File-Descriptor Events

A conventional POSIX signal can interrupt a thread at almost any instruction boundary and transfer control to a signal handler. That asynchronous control flow imposes strict limits on handler code and complicates programs whose main control plane already runs through epoll, poll, or select. Linux signalfd() provides a different delivery interface. A process blocks selected signals in the normal signal mask, creates a signalfd for that set, and receives pending signals by reading structured signalfd_siginfo records from the descriptor. The signal remains a signal at the kernel interface; only its consumption moves into ordinary file-descriptor I/O.

Software Engineering 18 Sep 2026 5 min read

timerfd Turns Timer Expirations into Descriptor Readiness

A timerfd becomes readable after its timer expires. The notification is not a signal handler invocation and not a byte-stream message. Linux records pending expirations on a timer object and exposes that state through a file descriptor, so a timer can occupy the same readiness boundary as sockets, pipes, and other descriptors. That interface does more than replace one notification mechanism with another. Clock selection determines the time domain, arming flags determine whether a deadline is relative or absolute, and each successful read reports the number of expirations accumulated since the preceding successful read or timer reconfiguration.

Linux 18 Sep 2026 4 min read

timerfd Counts Expirations Through Descriptor I/O

A periodic timerfd does not require one userspace wakeup for every timer expiration. If several expirations occur before the descriptor is read, Linux accumulates them and returns the count in one 8-byte integer. That behavior makes timer state fit the same readiness model used for sockets, pipes, and other descriptors. It also gives delayed event loops explicit information about missed periods rather than collapsing several expirations into one notification. Expiration state becomes readable descriptor data timerfd_create() creates a timer object and returns a file descriptor referring to it. The selected clock defines the timer’s time base. Common choices include CLOCK_MONOTONIC, CLOCK_REALTIME, and CLOCK_BOOTTIME.

Software Engineering 18 Sep 2026 4 min read

eventfd Turns Counter State into Descriptor Readiness

An eventfd descriptor becomes readable when its kernel-maintained counter is greater than zero. A write does not enqueue a variable-length message. It adds an unsigned 64-bit value to that counter, turning accumulated notification state into ordinary file-descriptor readiness. This boundary is useful in systems where a thread or kernel facility must wake an event loop without introducing a byte-stream protocol. The state carried by the descriptor is deliberately narrow: a counter, a readiness condition, and two possible consumption semantics.

Linux 18 Sep 2026 5 min read

eventfd Aggregates Notifications in a Kernel Counter

An eventfd can absorb several notification writes before userspace services the descriptor. The kernel stores those writes in a 64-bit counter, so readiness represents pending counter state rather than a queue containing one record per notification. That distinction matters in event loops. A producer can add values while a consumer is occupied, and the next read can collapse accumulated state into one result. With EFD_SEMAPHORE, the same object exposes a different consumption rule without changing its readiness model.

Software Engineering 17 Sep 2026 4 min read

timerfd Counts Expirations Through Descriptor Reads

A periodic timerfd can expire several times before user space reads it. The next successful read() does not report only the most recent tick: it returns an unsigned 64-bit count of expirations accumulated since the previous successful read, or since the timer was configured if no read has completed yet. That behavior makes a timer an event-loop object without converting each expiration into a signal. The descriptor becomes readable when at least one expiration is pending, and the same descriptor can participate in poll(), select(), or epoll() beside sockets, pipes, and other descriptor-backed event sources.

Software Engineering 17 Sep 2026 5 min read

signalfd Turns Pending Signals into Readable Records

A signal included in a signalfd mask can make a file descriptor readable instead of invoking an asynchronous handler, provided that signal is blocked from ordinary delivery. A successful read() then consumes pending signal state and returns one or more signalfd_siginfo records. This changes the interface used to receive selected signals, but it does not replace Linux signal semantics. Signal masks, process-directed versus thread-directed delivery, standard-signal coalescing, and the special status of SIGKILL and SIGSTOP still define the boundary around the descriptor.

Software Engineering 17 Sep 2026 5 min read

signalfd Consumes Blocked Signals Through Descriptor Reads

A Linux signalfd becomes readable when a signal selected by its mask is pending for the reading context. A successful read() does more than observe that state: it consumes the returned signal occurrences, removing them from pending signal state. That behavior gives signals a descriptor-facing consumption path. It does not convert the signal subsystem into a byte stream, and it does not replace the signal mask that controls ordinary delivery.

Software Engineering 17 Sep 2026 4 min read

pidfd Keeps Process Identity Stable Across PID Reuse

A numeric Linux PID can be reused after its process exits. A PID file descriptor instead refers to a specific task, so later operations through that descriptor do not silently retarget a different process that receives the same numeric PID. This changes process identity from a lookup repeated at each operation into a kernel-held reference with descriptor semantics. The distinction matters for signaling, exit monitoring, and event loops that retain process handles across asynchronous work.

Software Engineering 17 Sep 2026 4 min read

Linux timerfd Read Reports Accumulated Expirations

A periodic Linux timerfd can expire several times before an event loop reads it. The next successful read() does not return one record per wakeup. It returns one host-order uint64_t containing the number of expirations accumulated since the timer was last armed or since the preceding successful read. That count makes timerfd readiness a notification that timer state is consumable, not a one-to-one mapping between scheduler wakeups and timer periods.

Software Engineering 17 Sep 2026 6 min read

Linux timerfd Makes Missed Periods Observable as Expiration Counts

A periodic Linux timerfd can expire several times before an event loop runs again. The next successful read() does not merely report that the timer fired; it returns an unsigned 64-bit count of expirations accumulated since the previous successful read or since the timer was last configured. Delayed dispatch therefore becomes observable as a count rather than a sequence of queued timer records. That contract separates timer schedule from consumer execution. A process may be descheduled, an event loop may spend time on other descriptors, or several periods may pass before the timerfd is consumed. The kernel tracks expirations, while application code decides what multiple expirations mean for the work associated with them.

Software Engineering 17 Sep 2026 5 min read

Linux signalfd Turns Pending Signals Into Readable State

A Linux process can block selected signals and receive them by reading a file descriptor instead of running an asynchronous handler. signalfd() makes those pending signals visible through the same readiness interfaces used for sockets, pipes, and other descriptors, including poll() and epoll. That conversion is not a replacement for signal masking. The descriptor has its own signal set, while each thread retains a signal mask that controls ordinary delivery. A robust design depends on both states remaining aligned.

Software Engineering 17 Sep 2026 5 min read

Linux eventfd Makes Counter State Pollable

A Linux eventfd can collapse many notifications into one kernel-maintained counter while still participating in poll(), select(), and epoll. Writers add unsigned 64-bit values to the counter; readiness reports whether that state can be consumed. The interface carries arithmetic state rather than a byte stream or a queue of individual messages. That distinction matters at process and thread boundaries. A wakeup says that the counter is nonzero. It does not preserve the number of write operations, writer identities, or ordering among independent notification sources.

Software Engineering 17 Sep 2026 6 min read

Linux eventfd Couples Counter State With Descriptor Readiness

An eventfd object combines a kernel-maintained unsigned 64-bit counter with file-descriptor readiness. A write adds to the counter when the addition is permitted; a read consumes counter state. Because the same object participates in poll(), select(), and epoll(), a counter transition can also become an event-loop notification without a byte stream or message framing layer. That compact interface has sharp semantics. Default reads drain the current value to zero, EFD_SEMAPHORE reads consume one unit, writes can block near the counter limit, and readiness indicates which operation can proceed rather than the number of logical events an application may have assigned to the counter.

Software Engineering 17 Sep 2026 4 min read

inotify Rename Cookies Correlate Move Events Without Making Them Atomic

A Linux rename() observed through inotify can produce two records carrying the same nonzero cookie: IN_MOVED_FROM for the old directory entry and IN_MOVED_TO for the new one. The cookie correlates those records, but it does not turn them into one atomic queue item. That boundary matters for software maintaining a pathname index, synchronizing directory state, or converting filesystem notifications into higher-level change records. A rename is one filesystem operation while its inotify representation can be a pair whose delivery has weaker grouping properties.

Software Engineering 17 Sep 2026 5 min read

eventfd Counter Coalesces Notifications Before Read

A Linux eventfd can receive several writes before any consumer runs, yet the descriptor does not retain those writes as separate messages. Each accepted write adds its unsigned 64-bit value to a kernel-maintained counter. Without EFD_SEMAPHORE, one successful read returns the current counter and resets it to zero. That behavior makes eventfd a counter-backed notification primitive rather than a message queue. Readiness indicates that the counter is nonzero; it does not preserve the number, ordering, or boundaries of individual write operations.

Software Engineering 17 Sep 2026 5 min read

EPOLLET Reports Readiness Transitions, Not Buffer Drainage

With EPOLLET, a file descriptor can still contain unread data after its readiness event has already been consumed. A later epoll_wait() is not required to report that descriptor again merely because the old readable state persists. That behavior is the central boundary of edge-triggered epoll: notification tracks changes in readiness, while the underlying I/O object retains its own state independently. Readiness state and event delivery are separate An epoll instance maintains an interest list and a ready list. Registration through epoll_ctl() defines which open file descriptions matter and which event classes are relevant. epoll_wait() returns entries that have reached the ready list.

Software Engineering 17 Sep 2026 9 min read

Epoll Edge Triggering Turns Readiness Into a Drain Obligation

An edge-triggered epoll registration can stop producing notifications while unread bytes still remain in a socket or pipe. The descriptor is still usable for I/O, but the event loop has already consumed the notification associated with the readiness transition. If the handler reads only part of the available data and returns to epoll_wait(), no fresh transition is required to occur, so the pending bytes can remain untouched indefinitely. This behavior makes EPOLLET more than a notification preference. It changes the contract between the kernel’s ready list and application state. A level-triggered loop can repeatedly receive a descriptor while the requested I/O condition remains true. An edge-triggered loop must preserve enough local state to treat a delivered event as an obligation to exhaust the currently available nonblocking I/O, normally until an operation reports EAGAIN.

Linux 05 Sep 2026 11 min read

Wake Linux Event Loops from Other Threads with eventfd

A file-descriptor event loop can wait efficiently for sockets, pipes, timers, and other kernel objects. A common problem appears when work originates somewhere that is not already represented by a file descriptor: another thread changes shared state and needs the loop to wake immediately. Polling shared state on a timer adds latency or wastes wakeups. A condition variable can wake a thread, but it cannot be placed directly in the same poll() or epoll wait set as a socket. A pipe can bridge the two models, but using a pipe only as a wakeup signal means maintaining a read end, a write end, and byte-buffer semantics that the application may not actually need.

Linux 04 Sep 2026 11 min read

Integrate Timers into Linux Event Loops with timerfd

Event loops work best when unrelated kinds of work have one common waiting mechanism. Sockets become readable. Pipes become writable. A child-process descriptor or signal descriptor can become ready. Timers are often the awkward exception. A program can call sleep() or nanosleep(), but that blocks the thread instead of letting it wait for I/O. It can pass a timeout to poll() or epoll_wait(), but one timeout becomes difficult to manage when the program has several independent deadlines. Traditional POSIX timers can deliver signals, which introduces a second asynchronous control path.

Linux 04 Sep 2026 9 min read

Handle Linux Signals in Event Loops with signalfd

Unix signals are asynchronous by design: a signal can interrupt a program between ordinary instructions and transfer control to a signal handler. That model is useful, but it creates an awkward boundary for event-driven programs. A network server may already spend most of its time inside poll(), epoll_wait(), or another readiness API. Its sockets, pipes, and timers appear as file-descriptor events, while SIGTERM and SIGHUP arrive through a separate execution path with much stricter rules about what code may safely run.