Skip to content

Commit cd9fed6

Browse files
tiranaeros
andauthored
bpo-41001: Add os.eventfd() (#20930)
Co-authored-by: Kyle Stanley <aeros167@gmail.com>
1 parent bbeb2d2 commit cd9fed6

File tree

9 files changed

+471
-3
lines changed

9 files changed

+471
-3
lines changed

Doc/library/os.rst

+96
Original file line numberDiff line numberDiff line change
@@ -3276,6 +3276,102 @@ features:
32763276
.. versionadded:: 3.8
32773277

32783278

3279+
.. function:: eventfd(initval[, flags=os.EFD_CLOEXEC])
3280+
3281+
Create and return an event file descriptor. The file descriptors supports
3282+
raw :func:`read` and :func:`write` with a buffer size of 8,
3283+
:func:`~select.select`, :func:`~select.poll` and similar. See man page
3284+
:manpage:`eventfd(2)` for more information. By default, the
3285+
new file descriptor is :ref:`non-inheritable <fd_inheritance>`.
3286+
3287+
*initval* is the initial value of the event counter. The initial value
3288+
must be an 32 bit unsigned integer. Please note that the initial value is
3289+
limited to a 32 bit unsigned int although the event counter is an unsigned
3290+
64 bit integer with a maximum value of 2\ :sup:`64`\ -\ 2.
3291+
3292+
*flags* can be constructed from :const:`EFD_CLOEXEC`,
3293+
:const:`EFD_NONBLOCK`, and :const:`EFD_SEMAPHORE`.
3294+
3295+
If :const:`EFD_SEMAPHORE` is specified and the event counter is non-zero,
3296+
:func:`eventfd_read` returns 1 and decrements the counter by one.
3297+
3298+
If :const:`EFD_SEMAPHORE` is not specified and the event counter is
3299+
non-zero, :func:`eventfd_read` returns the current event counter value and
3300+
resets the counter to zero.
3301+
3302+
If the event counter is zero and :const:`EFD_NONBLOCK` is not
3303+
specified, :func:`eventfd_read` blocks.
3304+
3305+
:func:`eventfd_write` increments the event counter. Write blocks if the
3306+
write operation would increment the counter to a value larger than
3307+
2\ :sup:`64`\ -\ 2.
3308+
3309+
Example::
3310+
3311+
import os
3312+
3313+
# semaphore with start value '1'
3314+
fd = os.eventfd(1, os.EFD_SEMAPHORE | os.EFC_CLOEXEC)
3315+
try:
3316+
# acquire semaphore
3317+
v = os.eventfd_read(fd)
3318+
try:
3319+
do_work()
3320+
finally:
3321+
# release semaphore
3322+
os.eventfd_write(fd, v)
3323+
finally:
3324+
os.close(fd)
3325+
3326+
.. availability:: Linux 2.6.27 or newer with glibc 2.8 or newer.
3327+
3328+
.. versionadded:: 3.10
3329+
3330+
.. function:: eventfd_read(fd)
3331+
3332+
Read value from an :func:`eventfd` file descriptor and return a 64 bit
3333+
unsigned int. The function does not verify that *fd* is an :func:`eventfd`.
3334+
3335+
.. availability:: See :func:`eventfd`
3336+
3337+
.. versionadded:: 3.10
3338+
3339+
.. function:: eventfd_write(fd, value)
3340+
3341+
Add value to an :func:`eventfd` file descriptor. *value* must be a 64 bit
3342+
unsigned int. The function does not verify that *fd* is an :func:`eventfd`.
3343+
3344+
.. availability:: See :func:`eventfd`
3345+
3346+
.. versionadded:: 3.10
3347+
3348+
.. data:: EFD_CLOEXEC
3349+
3350+
Set close-on-exec flag for new :func:`eventfd` file descriptor.
3351+
3352+
.. availability:: See :func:`eventfd`
3353+
3354+
.. versionadded:: 3.10
3355+
3356+
.. data:: EFD_NONBLOCK
3357+
3358+
Set :const:`O_NONBLOCK` status flag for new :func:`eventfd` file
3359+
descriptor.
3360+
3361+
.. availability:: See :func:`eventfd`
3362+
3363+
.. versionadded:: 3.10
3364+
3365+
.. data:: EFD_SEMAPHORE
3366+
3367+
Provide semaphore-like semantics for reads from a :func:`eventfd` file
3368+
descriptor. On read the internal counter is decremented by one.
3369+
3370+
.. availability:: Linux 2.6.30 or newer with glibc 2.8 or newer.
3371+
3372+
.. versionadded:: 3.10
3373+
3374+
32793375
Linux extended attributes
32803376
~~~~~~~~~~~~~~~~~~~~~~~~~
32813377

Doc/whatsnew/3.10.rst

+4
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ os
229229
Added :func:`os.cpu_count()` support for VxWorks RTOS.
230230
(Contributed by Peixing Xin in :issue:`41440`.)
231231

232+
Added a new function :func:`os.eventfd` and related helpers to wrap the
233+
``eventfd2`` syscall on Linux.
234+
(Contributed by Christian Heimes in :issue:`41001`.)
235+
232236
py_compile
233237
----------
234238

Lib/test/test_os.py

+86
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
import mmap
1616
import os
1717
import pickle
18+
import select
1819
import shutil
1920
import signal
2021
import socket
2122
import stat
23+
import struct
2224
import subprocess
2325
import sys
2426
import sysconfig
@@ -59,6 +61,7 @@
5961
except ImportError:
6062
INT_MAX = PY_SSIZE_T_MAX = sys.maxsize
6163

64+
6265
from test.support.script_helper import assert_python_ok
6366
from test.support import unix_shell
6467
from test.support.os_helper import FakePath
@@ -3528,6 +3531,89 @@ def test_memfd_create(self):
35283531
self.assertFalse(os.get_inheritable(fd2))
35293532

35303533

3534+
@unittest.skipUnless(hasattr(os, 'eventfd'), 'requires os.eventfd')
3535+
@support.requires_linux_version(2, 6, 30)
3536+
class EventfdTests(unittest.TestCase):
3537+
def test_eventfd_initval(self):
3538+
def pack(value):
3539+
"""Pack as native uint64_t
3540+
"""
3541+
return struct.pack("@Q", value)
3542+
size = 8 # read/write 8 bytes
3543+
initval = 42
3544+
fd = os.eventfd(initval)
3545+
self.assertNotEqual(fd, -1)
3546+
self.addCleanup(os.close, fd)
3547+
self.assertFalse(os.get_inheritable(fd))
3548+
3549+
# test with raw read/write
3550+
res = os.read(fd, size)
3551+
self.assertEqual(res, pack(initval))
3552+
3553+
os.write(fd, pack(23))
3554+
res = os.read(fd, size)
3555+
self.assertEqual(res, pack(23))
3556+
3557+
os.write(fd, pack(40))
3558+
os.write(fd, pack(2))
3559+
res = os.read(fd, size)
3560+
self.assertEqual(res, pack(42))
3561+
3562+
# test with eventfd_read/eventfd_write
3563+
os.eventfd_write(fd, 20)
3564+
os.eventfd_write(fd, 3)
3565+
res = os.eventfd_read(fd)
3566+
self.assertEqual(res, 23)
3567+
3568+
def test_eventfd_semaphore(self):
3569+
initval = 2
3570+
flags = os.EFD_CLOEXEC | os.EFD_SEMAPHORE | os.EFD_NONBLOCK
3571+
fd = os.eventfd(initval, flags)
3572+
self.assertNotEqual(fd, -1)
3573+
self.addCleanup(os.close, fd)
3574+
3575+
# semaphore starts has initval 2, two reads return '1'
3576+
res = os.eventfd_read(fd)
3577+
self.assertEqual(res, 1)
3578+
res = os.eventfd_read(fd)
3579+
self.assertEqual(res, 1)
3580+
# third read would block
3581+
with self.assertRaises(BlockingIOError):
3582+
os.eventfd_read(fd)
3583+
with self.assertRaises(BlockingIOError):
3584+
os.read(fd, 8)
3585+
3586+
# increase semaphore counter, read one
3587+
os.eventfd_write(fd, 1)
3588+
res = os.eventfd_read(fd)
3589+
self.assertEqual(res, 1)
3590+
# next read would block, too
3591+
with self.assertRaises(BlockingIOError):
3592+
os.eventfd_read(fd)
3593+
3594+
def test_eventfd_select(self):
3595+
flags = os.EFD_CLOEXEC | os.EFD_NONBLOCK
3596+
fd = os.eventfd(0, flags)
3597+
self.assertNotEqual(fd, -1)
3598+
self.addCleanup(os.close, fd)
3599+
3600+
# counter is zero, only writeable
3601+
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
3602+
self.assertEqual((rfd, wfd, xfd), ([], [fd], []))
3603+
3604+
# counter is non-zero, read and writeable
3605+
os.eventfd_write(fd, 23)
3606+
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
3607+
self.assertEqual((rfd, wfd, xfd), ([fd], [fd], []))
3608+
self.assertEqual(os.eventfd_read(fd), 23)
3609+
3610+
# counter at max, only readable
3611+
os.eventfd_write(fd, (2**64) - 2)
3612+
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
3613+
self.assertEqual((rfd, wfd, xfd), ([fd], [], []))
3614+
os.eventfd_read(fd)
3615+
3616+
35313617
class OSErrorTests(unittest.TestCase):
35323618
def setUp(self):
35333619
class Str(str):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add func:`os.eventfd` to provide a low level interface for Linux's event
2+
notification file descriptor.

Modules/clinic/posixmodule.c.h

+141-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)