os.timerfd_create: Precise Linux Timers in Python

Published on: September 27, 2026
Reading time: 5 minutes
Linux workspace representing Python os.timerfd_create timers

os.timerfd_create gives Python programs direct access to Linux timer file descriptors. Instead of relying only on time.sleep(), signals, or a loop that repeatedly checks the clock, an application can create a timer represented by a regular file descriptor. That descriptor becomes readable when the configured deadline expires, so it can be monitored together with sockets, pipes, and other operating-system events.

This model is valuable in servers, monitoring agents, collectors, schedulers, networking tools, and event-driven services. In this guide, you will learn how to create a timerfd, configure one-shot and periodic timers, integrate it with select, read the expiration counter, and avoid portability and cleanup problems.

What is a timer file descriptor?

On Linux, timerfd is a kernel interface that exposes a timer as a file descriptor. When the timer expires, the descriptor becomes readable. Reading eight bytes from it returns an unsigned 64-bit integer indicating how many expirations have occurred since the previous read.

This detail matters because a periodic timer does not simply lose ticks when your process is busy. If three periods pass before your code reads the descriptor, the counter can report 3. That makes it possible to detect lag, missed processing windows, or an overloaded event loop.

The Python os module exposes this feature only on supported platforms and versions. Since timerfd is Linux-specific, production code should check for the function before using it.

import os

if not hasattr(os, "timerfd_create"):
    raise RuntimeError("timerfd is not available on this platform")

Creating the descriptor

The creation call receives a clock and optional flags. A monotonic clock is usually the correct choice for durations and intervals because it is not affected when the system’s civil time is changed. A real-time clock is appropriate when the timer must follow wall-clock time.

import os

fd = os.timerfd_create(os.CLOCK_MONOTONIC, os.TFD_CLOEXEC)
try:
    print("Timer descriptor:", fd)
finally:
    os.close(fd)

TFD_CLOEXEC prevents the descriptor from being inherited unexpectedly by a program launched through an exec-style operation. This is a sensible default in services and command-line tools because leaked descriptors can keep resources alive and complicate debugging.

Configuring a one-shot timer

After creating the descriptor, configure it with os.timerfd_settime. The initial value controls the first expiration. The interval controls repetition. An interval of zero creates a one-shot timer.

import os
import struct

fd = os.timerfd_create(os.CLOCK_MONOTONIC, os.TFD_CLOEXEC)
try:
    os.timerfd_settime(fd, initial=2.0, interval=0.0)
    raw = os.read(fd, 8)
    expirations = struct.unpack("Q", raw)[0]
    print("Expirations:", expirations)
finally:
    os.close(fd)

The read blocks until the deadline unless the descriptor was created with the nonblocking flag. A one-shot timer will normally return 1. The descriptor remains open after that read and can be programmed again.

Creating a periodic timer

To run a tick every second, set both the initial delay and interval to one second. The kernel then tracks the schedule independently from the execution speed of your Python callback.

import os
import struct

fd = os.timerfd_create(
    os.CLOCK_MONOTONIC,
    os.TFD_CLOEXEC | os.TFD_NONBLOCK,
)
os.timerfd_settime(fd, initial=1.0, interval=1.0)

try:
    received = 0
    while received < 5:
        try:
            count = struct.unpack("Q", os.read(fd, 8))[0]
            received += count
            print("Ticks:", count)
        except BlockingIOError:
            continue
finally:
    os.close(fd)

The busy loop above is intentionally simple, but it should not be copied into a real service. Nonblocking descriptors are designed to be registered with a polling mechanism so the process sleeps efficiently until an event is ready.

Using timerfd with select or poll

The largest advantage of timerfd is that it participates in the same event loop as network and IPC descriptors. You do not need a dedicated timer thread only to wake another loop.

import os
import select
import struct

fd = os.timerfd_create(
    os.CLOCK_MONOTONIC,
    os.TFD_CLOEXEC | os.TFD_NONBLOCK,
)
os.timerfd_settime(fd, initial=0.5, interval=0.5)

poller = select.poll()
poller.register(fd, select.POLLIN)

try:
    handled = 0
    while handled < 3:
        for descriptor, mask in poller.poll(2000):
            if descriptor == fd and mask & select.POLLIN:
                count = struct.unpack("Q", os.read(fd, 8))[0]
                handled += count
                print("Timer event:", count)
finally:
    poller.unregister(fd)
    os.close(fd)

In a real application, the same poller could also contain listening sockets, client connections, pipes, or eventfd descriptors. Timer events then become one more event type in a predictable dispatcher.

Monotonic versus real-time clocks

Use CLOCK_MONOTONIC for timeouts, retries, health checks, refresh intervals, and recurring maintenance. It advances steadily even if NTP or an administrator adjusts the system’s calendar time.

Use CLOCK_REALTIME only when the deadline is tied to wall-clock time. Even then, consider how daylight-saving changes, timezone conversion, and clock corrections should affect the operation. For business scheduling, storing the intended date and revalidating it may be safer than trusting one long-lived timer.

Disarming and reprogramming

A timer can be disabled by setting the initial value to zero. You can later configure the same descriptor again.

os.timerfd_settime(fd, initial=0.0, interval=0.0)

Reusing descriptors can simplify resource management, but ownership must be clear. If multiple components reprogram the same timer without coordination, one deadline can silently replace another. Encapsulate the descriptor in a class or keep timer ownership inside one event-loop component.

Error handling and cleanup

Always read exactly eight bytes and decode the value as a 64-bit unsigned integer. Handle BlockingIOError when using TFD_NONBLOCK. Treat a counter larger than 1 as meaningful operational information rather than ignoring it.

Always close the descriptor in finally, a context manager, or a dedicated lifecycle method. Also use TFD_CLOEXEC unless descriptor inheritance is explicitly required.

A timerfd is not persistent. If the process or machine restarts, the timer disappears. Jobs that must survive restarts belong in systemd timers, cron, a durable task queue, or an application scheduler backed by storage.

When timerfd is the right tool

timerfd is a strong choice for Linux daemons, proxies, collectors, high-performance network services, simulators, and programs already built around descriptors. For a small script, time.sleep() is often clearer. For portable asynchronous application code, review asyncio in Python. For precise measurements, see Python perf_counter_ns. You may also find datetime handling and zoneinfo time zones useful.

The official Python os documentation describes the API available in your interpreter. The Linux timerfd_create(2) manual page explains kernel semantics and flags.

Conclusion

os.timerfd_create brings Python into Linux’s descriptor-based timing model. It offers accurate deadlines, natural integration with poll-based loops, and an expiration counter that reveals delayed processing. A reliable implementation checks platform support, prefers a monotonic clock for durations, uses safe flags, reads and decodes the eight-byte counter correctly, and closes the descriptor under every exit path. With those practices, timerfd becomes an efficient building block for infrastructure and event-driven software.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Python process debugging in a terminal with source code
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: Debug Running Python Processes

    Learn how to attach pdb to a running Python process, inspect stacks, and diagnose hangs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Python programming code for exact fractions
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: Convert Numbers to Fractions

    Learn Python fractions.from_number for exact rational conversion, float and Decimal handling, approximation, validation, and safe arithmetic.

    Ler mais

    Tempo de leitura: 4 minutos
    09/10/2026
    Developer configuring a Python HTTPS server and TLS certificate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: Build a Local HTTPS Server in Python

    Learn Python HTTPSServer for local HTTPS services, TLS certificates, threaded handling, testing, and practical security limits.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Protected files representing secure TAR extraction with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: Extract TAR Safely

    Learn Python tarfile extraction_filter for safer TAR extraction, path validation, links, permissions, and resource limits.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Python code showing warnings controlled with catch_warnings
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: Capture Python Warnings in Tests

    Learn Python catch_warnings to capture, test, and control warnings with focused filters and safe temporary scopes.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Python code representing persistent pickle references
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: Serialize External References

    Learn Python pickle persistent_id for stable external references, validation, security, performance, and long-term compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026