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.
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
| Name | Type | Required | Description |
|---|---|---|---|
| initval | int | yes | eventfd: starting value of the counter; must fit in 32 bits (the counter itself is 64-bit). |
| flags | int | no (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). |
| name | str | yes | memfd_create: a debugging label, shown prefixed with memfd: in the /proc/self/fd/<fd> link; it does not have to be unique. |
| clockid | int | yes | timerfd_create: time.CLOCK_REALTIME, time.CLOCK_MONOTONIC or time.CLOCK_BOOTTIME. |
| initial, interval | float | no (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
184467440737095516145. MFD_HUGE_2MB = log2(page size) << MFD_HUGE_SHIFT
MFD_HUGE_SHIFT = 26
((2 * 1024 * 1024).bit_length() - 1) << MFD_HUGE_SHIFT
Returns
14092861446. 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
20971527. 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.