os.eventfd

Linux only. eventfd: Linux 2.6.27+ with glibc 2.8+ (Python 3.10+). memfd_create: Linux 3.17+ with glibc 2.27+ (3.8+; MFD_HUGE_* need Linux 4.14+). timerfd_*: Linux 2.6.27+ with glibc 2.8+, new in Python 3.13. All three give you an ordinary fd that works with select, poll and selectors.

os functionPython 3.8+ (memfd_create), 3.10+ (eventfd), 3.13+ (timerfd)
Common call
fd = os.eventfd(0)
Returns
int fd (non-inheritable)
Replaces
a self-pipe used only to wake up a select loop
Watch out
Linux only; os.close(fd) when done
os.eventfd(initval[, flagsflags — eventfd: EFD_CLOEXEC, EFD_NONBLOCK, EFD_SEMAPHORE OR-ed together. memfd_create: MFD_* flags (default MFD_CLOEXEC). timerfd_create: TFD_NONBLOCK, TFD_CLOEXEC (default 0).type: int · default: os.EFD_CLOEXEC=os.EFD_CLOEXEC])
→ int

Parameters

NameTypeRequiredDescription
initvalintyeseventfd: starting value of the counter; must fit in 32 bits (the counter itself is 64-bit).
flagsintno (os.EFD_CLOEXEC)eventfd: EFD_CLOEXEC, EFD_NONBLOCK, EFD_SEMAPHORE OR-ed together. memfd_create: MFD_* flags (default MFD_CLOEXEC). timerfd_create: TFD_NONBLOCK, TFD_CLOEXEC (default 0).
namestryesmemfd_create: a debugging label, shown prefixed with memfd: in the /proc/self/fd/<fd> link; it does not have to be unique.
clockidintyestimerfd_create: time.CLOCK_REALTIME, time.CLOCK_MONOTONIC or time.CLOCK_BOOTTIME.
initial, intervalfloatno (0.0)timerfd_settime: seconds to the first expiry (0 disarms) and the repeat period (0 = fire once). The _ns variants take integer nanoseconds.

Return value

int — A new, non-inheritable file descriptor. Close it with os.close().

Common patterns

Wake a selector from another thread
eventfd_write makes the fd readable; the loop drains it with eventfd_read.
import os, selectors
wake = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
sel = selectors.DefaultSelector()
sel.register(wake, selectors.EVENT_READ)
# other thread: os.eventfd_write(wake, 1)
for key, _ in sel.select():
    if key.fd == wake:
        os.eventfd_read(wake)
Semaphore from the docs
With EFD_SEMAPHORE each read takes 1 from the counter.
import os
fd = os.eventfd(1, os.EFD_SEMAPHORE | os.EFD_CLOEXEC)
try:
    v = os.eventfd_read(fd)
    try:
        do_work()
    finally:
        os.eventfd_write(fd, v)
finally:
    os.close(fd)
An in-memory file with a real fd
memfd_create gives a file that lives in RAM, can be passed to subprocesses or mmap-ed.
import os
fd = os.memfd_create('scratch')
with os.fdopen(fd, 'w+b') as f:
    f.write(b'hello')
    f.seek(0)
    data = f.read()
Periodic timer you can select on (Python 3.13+)
Fire after 1 s, then every 0.5 s; read returns the expiration count.
import os, sys, time
fd = os.timerfd_create(time.CLOCK_MONOTONIC)
try:
    os.timerfd_settime(fd, initial=1.0, interval=0.5)
    expirations = int.from_bytes(os.read(fd, 8), sys.byteorder)
finally:
    os.close(fd)

Examples

1. How an eventfd counter behaves (pure Python model)
class EventCounter: def __init__(self, initval=0, semaphore=False): self.value, self.semaphore = initval, semaphore def write(self, n): self.value += n def read(self): if self.value == 0: raise BlockingIOError if self.semaphore: self.value -= 1 return 1 n, self.value = self.value, 0 return n c = EventCounter() c.write(3) c.write(4) (c.read(), c.value)
Returns
(7, 0)
2. EFD_SEMAPHORE: each read takes 1
class Sem: def __init__(self, n): self.value = n def read(self): if self.value == 0: raise BlockingIOError self.value -= 1 return 1 s = Sem(2) reads = [s.read(), s.read()] try: s.read() except BlockingIOError: reads.append('would block') reads
Returns
[1, 1, 'would block']
3. The counter travels as 8 bytes in host byte order
import sys raw = (7).to_bytes(8, sys.byteorder) (len(raw), int.from_bytes(raw, sys.byteorder))
Returns
(8, 7)
4. Largest value the counter can hold
2 ** 64 - 2
Returns
18446744073709551614
5. MFD_HUGE_2MB = log2(page size) << MFD_HUGE_SHIFT
MFD_HUGE_SHIFT = 26 ((2 * 1024 * 1024).bit_length() - 1) << MFD_HUGE_SHIFT
Returns
1409286144
6. Decode the page size back out of a flag
MFD_HUGE_SHIFT, MFD_HUGE_MASK = 26, 63 flag = 1409286144 1 << ((flag >> MFD_HUGE_SHIFT) & MFD_HUGE_MASK)
Returns
2097152
7. Portable cousin of memfd: an unnamed temporary file
import tempfile with tempfile.TemporaryFile() as f: f.write(b'hello') f.seek(0) data = f.read() data
Returns
b'hello'

Pitfalls

1. Decoding the 8 raw bytes with a fixed byte order
os.read on an eventfd or timerfd returns the count in the host byte order (little-endian on x86-64). Use sys.byteorder — or eventfd_read, which decodes for you.
'big'
raw = (1).to_bytes(8, 'little')  # what x86 Linux returns for a count of 1
int.from_bytes(raw, 'big')
72057594037927936
sys.byteorder
import sys
raw = (1).to_bytes(8, sys.byteorder)
int.from_bytes(raw, sys.byteorder)
1
2. Reading MFD_HUGE_* as a size
The MFD_HUGE_* constants encode log2 of the huge page size in bits 26 and up; their numeric value is not a byte count. Combine them with MFD_HUGETLB as flags, and decode with MFD_HUGE_SHIFT / MFD_HUGE_MASK if you need the size.
divide
flag = 1409286144  # os.MFD_HUGE_2MB on Linux
flag // (1024 * 1024)
1344
decode
flag = 1409286144  # os.MFD_HUGE_2MB on Linux
(1 << ((flag >> 26) & 63)) // (1024 * 1024)
2

When to use

Use it
  • Waking an event loop or selector from another thread (eventfd)
  • Counting events or a cross-process semaphore on Linux (eventfd with EFD_SEMAPHORE)
  • A file that never touches disk but still has an fd for subprocesses, mmap or sealing (memfd_create)
  • Timers that integrate with select / poll / epoll (timerfd, 3.13+)
Reach for something else
  • Portable code → os.pipe for wake-ups, tempfile.TemporaryFile or io.BytesIO for scratch data, threading.Timer or asyncio for timers
  • Python-level signalling between threads → threading.Event / queue.Queue

Notes

CPython impl
Thin wrappers in Modules/posixmodule.c. eventfd_read / eventfd_write do not check that the fd really is an eventfd
Availability
eventfd, eventfd_read, eventfd_write, EFD_CLOEXEC, EFD_NONBLOCK: Linux 2.6.27+ (EFD_SEMAPHORE 2.6.30+), Python 3.10+. memfd_create and MFD_*: Linux 3.17+ with glibc 2.27+, Python 3.8+; MFD_HUGE_* since Linux 4.14. timerfd_* and TFD_*: Linux 2.6.27+ with glibc 2.8+, Python 3.13+ (not in 3.12)
eventfd reads
Without EFD_SEMAPHORE a read returns the whole counter and resets it to 0; with it, a read returns 1 and decrements. A read on a zero counter blocks, or raises BlockingIOError with EFD_NONBLOCK. Raw os.read needs an 8-byte buffer: a 4-byte read fails with OSError (EINVAL)
Defaults
eventfd and memfd_create add EFD_CLOEXEC / MFD_CLOEXEC by default, and Python always sets TFD_CLOEXEC on timerfds; the fds are non-inheritable
Values on Linux
EFD_SEMAPHORE 1, EFD_NONBLOCK 2048 (= O_NONBLOCK), EFD_CLOEXEC 524288 (= O_CLOEXEC); MFD_CLOEXEC 1, MFD_ALLOW_SEALING 2, MFD_HUGETLB 4, MFD_HUGE_SHIFT 26, MFD_HUGE_MASK 63
timerfd
timerfd_settime(fd, *, flags=0, initial=0.0, interval=0.0) returns the previous (next_expiration, interval); timerfd_gettime returns the current pair as floats, the _ns variants use integer nanoseconds. TFD_TIMER_ABSTIME makes initial an absolute clock value; TFD_TIMER_CANCEL_ON_SET (with ABSTIME on CLOCK_REALTIME) aborts reads with ECANCELED when the system clock jumps

FAQ

No. eventfd, memfd_create and timerfd are Linux system calls; the os module only has them on Linux (eventfd since Python 3.10, memfd_create since 3.8, timerfd since 3.13). This page has no live demo for that reason; the examples model the behaviour in plain Python and use portable stand-ins such as os.pipe and tempfile.TemporaryFile.