bpo-41001: Add os.eventfd() (#20930)

Co-authored-by: Kyle Stanley <aeros167@gmail.com>
This commit is contained in:
Christian Heimes 2020-11-13 19:48:52 +01:00 committed by GitHub
parent bbeb2d266d
commit cd9fed6afb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 471 additions and 3 deletions

View File

@ -3276,6 +3276,102 @@ features:
.. versionadded:: 3.8
.. function:: eventfd(initval[, flags=os.EFD_CLOEXEC])
Create and return an event file descriptor. The file descriptors supports
raw :func:`read` and :func:`write` with a buffer size of 8,
:func:`~select.select`, :func:`~select.poll` and similar. See man page
:manpage:`eventfd(2)` for more information. By default, the
new file descriptor is :ref:`non-inheritable <fd_inheritance>`.
*initval* is the initial value of the event counter. The initial value
must be an 32 bit unsigned integer. Please note that the initial value is
limited to a 32 bit unsigned int although the event counter is an unsigned
64 bit integer with a maximum value of 2\ :sup:`64`\ -\ 2.
*flags* can be constructed from :const:`EFD_CLOEXEC`,
:const:`EFD_NONBLOCK`, and :const:`EFD_SEMAPHORE`.
If :const:`EFD_SEMAPHORE` is specified and the event counter is non-zero,
:func:`eventfd_read` returns 1 and decrements the counter by one.
If :const:`EFD_SEMAPHORE` is not specified and the event counter is
non-zero, :func:`eventfd_read` returns the current event counter value and
resets the counter to zero.
If the event counter is zero and :const:`EFD_NONBLOCK` is not
specified, :func:`eventfd_read` blocks.
:func:`eventfd_write` increments the event counter. Write blocks if the
write operation would increment the counter to a value larger than
2\ :sup:`64`\ -\ 2.
Example::
import os
# semaphore with start value '1'
fd = os.eventfd(1, os.EFD_SEMAPHORE | os.EFC_CLOEXEC)
try:
# acquire semaphore
v = os.eventfd_read(fd)
try:
do_work()
finally:
# release semaphore
os.eventfd_write(fd, v)
finally:
os.close(fd)
.. availability:: Linux 2.6.27 or newer with glibc 2.8 or newer.
.. versionadded:: 3.10
.. function:: eventfd_read(fd)
Read value from an :func:`eventfd` file descriptor and return a 64 bit
unsigned int. The function does not verify that *fd* is an :func:`eventfd`.
.. availability:: See :func:`eventfd`
.. versionadded:: 3.10
.. function:: eventfd_write(fd, value)
Add value to an :func:`eventfd` file descriptor. *value* must be a 64 bit
unsigned int. The function does not verify that *fd* is an :func:`eventfd`.
.. availability:: See :func:`eventfd`
.. versionadded:: 3.10
.. data:: EFD_CLOEXEC
Set close-on-exec flag for new :func:`eventfd` file descriptor.
.. availability:: See :func:`eventfd`
.. versionadded:: 3.10
.. data:: EFD_NONBLOCK
Set :const:`O_NONBLOCK` status flag for new :func:`eventfd` file
descriptor.
.. availability:: See :func:`eventfd`
.. versionadded:: 3.10
.. data:: EFD_SEMAPHORE
Provide semaphore-like semantics for reads from a :func:`eventfd` file
descriptor. On read the internal counter is decremented by one.
.. availability:: Linux 2.6.30 or newer with glibc 2.8 or newer.
.. versionadded:: 3.10
Linux extended attributes
~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -229,6 +229,10 @@ os
Added :func:`os.cpu_count()` support for VxWorks RTOS.
(Contributed by Peixing Xin in :issue:`41440`.)
Added a new function :func:`os.eventfd` and related helpers to wrap the
``eventfd2`` syscall on Linux.
(Contributed by Christian Heimes in :issue:`41001`.)
py_compile
----------

View File

@ -15,10 +15,12 @@ import locale
import mmap
import os
import pickle
import select
import shutil
import signal
import socket
import stat
import struct
import subprocess
import sys
import sysconfig
@ -59,6 +61,7 @@ try:
except ImportError:
INT_MAX = PY_SSIZE_T_MAX = sys.maxsize
from test.support.script_helper import assert_python_ok
from test.support import unix_shell
from test.support.os_helper import FakePath
@ -3528,6 +3531,89 @@ class MemfdCreateTests(unittest.TestCase):
self.assertFalse(os.get_inheritable(fd2))
@unittest.skipUnless(hasattr(os, 'eventfd'), 'requires os.eventfd')
@support.requires_linux_version(2, 6, 30)
class EventfdTests(unittest.TestCase):
def test_eventfd_initval(self):
def pack(value):
"""Pack as native uint64_t
"""
return struct.pack("@Q", value)
size = 8 # read/write 8 bytes
initval = 42
fd = os.eventfd(initval)
self.assertNotEqual(fd, -1)
self.addCleanup(os.close, fd)
self.assertFalse(os.get_inheritable(fd))
# test with raw read/write
res = os.read(fd, size)
self.assertEqual(res, pack(initval))
os.write(fd, pack(23))
res = os.read(fd, size)
self.assertEqual(res, pack(23))
os.write(fd, pack(40))
os.write(fd, pack(2))
res = os.read(fd, size)
self.assertEqual(res, pack(42))
# test with eventfd_read/eventfd_write
os.eventfd_write(fd, 20)
os.eventfd_write(fd, 3)
res = os.eventfd_read(fd)
self.assertEqual(res, 23)
def test_eventfd_semaphore(self):
initval = 2
flags = os.EFD_CLOEXEC | os.EFD_SEMAPHORE | os.EFD_NONBLOCK
fd = os.eventfd(initval, flags)
self.assertNotEqual(fd, -1)
self.addCleanup(os.close, fd)
# semaphore starts has initval 2, two reads return '1'
res = os.eventfd_read(fd)
self.assertEqual(res, 1)
res = os.eventfd_read(fd)
self.assertEqual(res, 1)
# third read would block
with self.assertRaises(BlockingIOError):
os.eventfd_read(fd)
with self.assertRaises(BlockingIOError):
os.read(fd, 8)
# increase semaphore counter, read one
os.eventfd_write(fd, 1)
res = os.eventfd_read(fd)
self.assertEqual(res, 1)
# next read would block, too
with self.assertRaises(BlockingIOError):
os.eventfd_read(fd)
def test_eventfd_select(self):
flags = os.EFD_CLOEXEC | os.EFD_NONBLOCK
fd = os.eventfd(0, flags)
self.assertNotEqual(fd, -1)
self.addCleanup(os.close, fd)
# counter is zero, only writeable
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
self.assertEqual((rfd, wfd, xfd), ([], [fd], []))
# counter is non-zero, read and writeable
os.eventfd_write(fd, 23)
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
self.assertEqual((rfd, wfd, xfd), ([fd], [fd], []))
self.assertEqual(os.eventfd_read(fd), 23)
# counter at max, only readable
os.eventfd_write(fd, (2**64) - 2)
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
self.assertEqual((rfd, wfd, xfd), ([fd], [], []))
os.eventfd_read(fd)
class OSErrorTests(unittest.TestCase):
def setUp(self):
class Str(str):

View File

@ -0,0 +1,2 @@
Add func:`os.eventfd` to provide a low level interface for Linux's event
notification file descriptor.

View File

@ -7620,6 +7620,134 @@ exit:
#endif /* defined(HAVE_MEMFD_CREATE) */
#if defined(HAVE_EVENTFD)
PyDoc_STRVAR(os_eventfd__doc__,
"eventfd($module, /, initval, flags=EFD_CLOEXEC)\n"
"--\n"
"\n"
"Creates and returns an event notification file descriptor.");
#define OS_EVENTFD_METHODDEF \
{"eventfd", (PyCFunction)(void(*)(void))os_eventfd, METH_FASTCALL|METH_KEYWORDS, os_eventfd__doc__},
static PyObject *
os_eventfd_impl(PyObject *module, unsigned int initval, int flags);
static PyObject *
os_eventfd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"initval", "flags", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "eventfd", 0};
PyObject *argsbuf[2];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
unsigned int initval;
int flags = EFD_CLOEXEC;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf);
if (!args) {
goto exit;
}
if (!_PyLong_UnsignedInt_Converter(args[0], &initval)) {
goto exit;
}
if (!noptargs) {
goto skip_optional_pos;
}
flags = _PyLong_AsInt(args[1]);
if (flags == -1 && PyErr_Occurred()) {
goto exit;
}
skip_optional_pos:
return_value = os_eventfd_impl(module, initval, flags);
exit:
return return_value;
}
#endif /* defined(HAVE_EVENTFD) */
#if defined(HAVE_EVENTFD)
PyDoc_STRVAR(os_eventfd_read__doc__,
"eventfd_read($module, /, fd)\n"
"--\n"
"\n"
"Read eventfd value");
#define OS_EVENTFD_READ_METHODDEF \
{"eventfd_read", (PyCFunction)(void(*)(void))os_eventfd_read, METH_FASTCALL|METH_KEYWORDS, os_eventfd_read__doc__},
static PyObject *
os_eventfd_read_impl(PyObject *module, int fd);
static PyObject *
os_eventfd_read(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"fd", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "eventfd_read", 0};
PyObject *argsbuf[1];
int fd;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
if (!args) {
goto exit;
}
if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) {
goto exit;
}
return_value = os_eventfd_read_impl(module, fd);
exit:
return return_value;
}
#endif /* defined(HAVE_EVENTFD) */
#if defined(HAVE_EVENTFD)
PyDoc_STRVAR(os_eventfd_write__doc__,
"eventfd_write($module, /, fd, value)\n"
"--\n"
"\n"
"Write eventfd value.");
#define OS_EVENTFD_WRITE_METHODDEF \
{"eventfd_write", (PyCFunction)(void(*)(void))os_eventfd_write, METH_FASTCALL|METH_KEYWORDS, os_eventfd_write__doc__},
static PyObject *
os_eventfd_write_impl(PyObject *module, int fd, unsigned long long value);
static PyObject *
os_eventfd_write(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"fd", "value", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "eventfd_write", 0};
PyObject *argsbuf[2];
int fd;
unsigned long long value;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf);
if (!args) {
goto exit;
}
if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) {
goto exit;
}
if (!_PyLong_UnsignedLongLong_Converter(args[1], &value)) {
goto exit;
}
return_value = os_eventfd_write_impl(module, fd, value);
exit:
return return_value;
}
#endif /* defined(HAVE_EVENTFD) */
#if (defined(TERMSIZE_USE_CONIO) || defined(TERMSIZE_USE_IOCTL))
PyDoc_STRVAR(os_get_terminal_size__doc__,
@ -8884,6 +9012,18 @@ exit:
#define OS_MEMFD_CREATE_METHODDEF
#endif /* !defined(OS_MEMFD_CREATE_METHODDEF) */
#ifndef OS_EVENTFD_METHODDEF
#define OS_EVENTFD_METHODDEF
#endif /* !defined(OS_EVENTFD_METHODDEF) */
#ifndef OS_EVENTFD_READ_METHODDEF
#define OS_EVENTFD_READ_METHODDEF
#endif /* !defined(OS_EVENTFD_READ_METHODDEF) */
#ifndef OS_EVENTFD_WRITE_METHODDEF
#define OS_EVENTFD_WRITE_METHODDEF
#endif /* !defined(OS_EVENTFD_WRITE_METHODDEF) */
#ifndef OS_GET_TERMINAL_SIZE_METHODDEF
#define OS_GET_TERMINAL_SIZE_METHODDEF
#endif /* !defined(OS_GET_TERMINAL_SIZE_METHODDEF) */
@ -8919,4 +9059,4 @@ exit:
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
/*[clinic end generated code: output=936f33448cd66ccb input=a9049054013a1b77]*/
/*[clinic end generated code: output=49b7ed768242ef7c input=a9049054013a1b77]*/

View File

@ -518,6 +518,11 @@ extern char *ctermid_r(char *);
# include <linux/memfd.h>
#endif
/* eventfd() */
#ifdef HAVE_SYS_EVENTFD_H
# include <sys/eventfd.h>
#endif
#ifdef _Py_MEMORY_SANITIZER
# include <sanitizer/msan_interface.h>
#endif
@ -12859,6 +12864,79 @@ os_memfd_create_impl(PyObject *module, PyObject *name, unsigned int flags)
}
#endif
#ifdef HAVE_EVENTFD
/*[clinic input]
os.eventfd
initval: unsigned_int
flags: int(c_default="EFD_CLOEXEC") = EFD_CLOEXEC
Creates and returns an event notification file descriptor.
[clinic start generated code]*/
static PyObject *
os_eventfd_impl(PyObject *module, unsigned int initval, int flags)
/*[clinic end generated code: output=ce9c9bbd1446f2de input=66203e3c50c4028b]*/
{
/* initval is limited to uint32_t, internal counter is uint64_t */
int fd;
Py_BEGIN_ALLOW_THREADS
fd = eventfd(initval, flags);
Py_END_ALLOW_THREADS
if (fd == -1) {
return PyErr_SetFromErrno(PyExc_OSError);
}
return PyLong_FromLong(fd);
}
/*[clinic input]
os.eventfd_read
fd: fildes
Read eventfd value
[clinic start generated code]*/
static PyObject *
os_eventfd_read_impl(PyObject *module, int fd)
/*[clinic end generated code: output=8f2c7b59a3521fd1 input=110f8b57fa596afe]*/
{
eventfd_t value;
int result;
Py_BEGIN_ALLOW_THREADS
result = eventfd_read(fd, &value);
Py_END_ALLOW_THREADS
if (result == -1) {
return PyErr_SetFromErrno(PyExc_OSError);
}
return PyLong_FromUnsignedLongLong(value);
}
/*[clinic input]
os.eventfd_write
fd: fildes
value: unsigned_long_long
Write eventfd value.
[clinic start generated code]*/
static PyObject *
os_eventfd_write_impl(PyObject *module, int fd, unsigned long long value)
/*[clinic end generated code: output=bebd9040bbf987f5 input=156de8555be5a949]*/
{
int result;
Py_BEGIN_ALLOW_THREADS
result = eventfd_write(fd, value);
Py_END_ALLOW_THREADS
if (result == -1) {
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
}
#endif /* HAVE_EVENTFD */
/* Terminal size querying */
PyDoc_STRVAR(TerminalSize_docstring,
@ -14619,6 +14697,9 @@ static PyMethodDef posix_methods[] = {
OS_FSPATH_METHODDEF
OS_GETRANDOM_METHODDEF
OS_MEMFD_CREATE_METHODDEF
OS_EVENTFD_METHODDEF
OS_EVENTFD_READ_METHODDEF
OS_EVENTFD_WRITE_METHODDEF
OS__ADD_DLL_DIRECTORY_METHODDEF
OS__REMOVE_DLL_DIRECTORY_METHODDEF
OS_WAITSTATUS_TO_EXITCODE_METHODDEF
@ -15127,6 +15208,12 @@ all_ins(PyObject *m)
#ifdef MFD_HUGE_16GB
if (PyModule_AddIntMacro(m, MFD_HUGE_16GB)) return -1;
#endif
#endif /* HAVE_MEMFD_CREATE */
#ifdef HAVE_EVENTFD
if (PyModule_AddIntMacro(m, EFD_CLOEXEC)) return -1;
if (PyModule_AddIntMacro(m, EFD_NONBLOCK)) return -1;
if (PyModule_AddIntMacro(m, EFD_SEMAPHORE)) return -1;
#endif
#if defined(__APPLE__)
@ -15220,6 +15307,10 @@ static const struct have_function {
int (*probe)(void);
} have_functions[] = {
#ifdef HAVE_EVENTFD
{"HAVE_EVENTFD", NULL},
#endif
#ifdef HAVE_FACCESSAT
{ "HAVE_FACCESSAT", probe_faccessat },
#endif

33
configure vendored
View File

@ -8032,7 +8032,8 @@ sys/stat.h sys/syscall.h sys/sys_domain.h sys/termio.h sys/time.h \
sys/times.h sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h pty.h \
libutil.h sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \
linux/tipc.h linux/random.h spawn.h util.h alloca.h endian.h \
sys/endian.h sys/sysmacros.h linux/memfd.h linux/wait.h sys/memfd.h sys/mman.h
sys/endian.h sys/sysmacros.h linux/memfd.h linux/wait.h sys/memfd.h \
sys/mman.h sys/eventfd.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
@ -12098,6 +12099,36 @@ $as_echo "no" >&6; }
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for eventfd" >&5
$as_echo_n "checking for eventfd... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifdef HAVE_SYS_EVENTFD_H
#include <sys/eventfd.h>
#endif
int
main ()
{
int x = eventfd(0, EFD_CLOEXEC)
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
$as_echo "#define HAVE_EVENTFD 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
# On some systems (eg. FreeBSD 5), we would find a definition of the
# functions ctermid_r, setgroups in the library, but no prototype
# (e.g. because we use _XOPEN_SOURCE). See whether we can take their

View File

@ -2210,7 +2210,8 @@ sys/stat.h sys/syscall.h sys/sys_domain.h sys/termio.h sys/time.h \
sys/times.h sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h pty.h \
libutil.h sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \
linux/tipc.h linux/random.h spawn.h util.h alloca.h endian.h \
sys/endian.h sys/sysmacros.h linux/memfd.h linux/wait.h sys/memfd.h sys/mman.h)
sys/endian.h sys/sysmacros.h linux/memfd.h linux/wait.h sys/memfd.h \
sys/mman.h sys/eventfd.h)
AC_HEADER_DIRENT
AC_HEADER_MAJOR
@ -3803,6 +3804,17 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
[AC_MSG_RESULT(no)
])
AC_MSG_CHECKING(for eventfd)
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#ifdef HAVE_SYS_EVENTFD_H
#include <sys/eventfd.h>
#endif
]], [[int x = eventfd(0, EFD_CLOEXEC)]])],
[AC_DEFINE(HAVE_EVENTFD, 1, Define if you have the 'eventfd' function.)
AC_MSG_RESULT(yes)],
[AC_MSG_RESULT(no)
])
# On some systems (eg. FreeBSD 5), we would find a definition of the
# functions ctermid_r, setgroups in the library, but no prototype
# (e.g. because we use _XOPEN_SOURCE). See whether we can take their

View File

@ -308,6 +308,9 @@
/* Define to 1 if you have the <errno.h> header file. */
#undef HAVE_ERRNO_H
/* Define if you have the 'eventfd' function. */
#undef HAVE_EVENTFD
/* Define to 1 if you have the `execv' function. */
#undef HAVE_EXECV
@ -1119,6 +1122,9 @@
/* Define to 1 if you have the <sys/epoll.h> header file. */
#undef HAVE_SYS_EPOLL_H
/* Define to 1 if you have the <sys/eventfd.h> header file. */
#undef HAVE_SYS_EVENTFD_H
/* Define to 1 if you have the <sys/event.h> header file. */
#undef HAVE_SYS_EVENT_H