From 019a9fb24aaf33c0aa9df9606ea1e7d2764adf81 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 13 Nov 2020 11:27:47 +0100 Subject: [PATCH 1/7] bpo-41001: Add os.eventfd() --- Doc/library/os.rst | 52 ++++++++++++++++++ Lib/test/test_os.py | 43 +++++++++++++++ .../2020-06-17-12-24-26.bpo-41001.5mi7b0.rst | 2 + Modules/clinic/posixmodule.c.h | 53 ++++++++++++++++++- Modules/posixmodule.c | 40 ++++++++++++++ configure | 33 +++++++++++- configure.ac | 14 ++++- pyconfig.h.in | 6 +++ 8 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-06-17-12-24-26.bpo-41001.5mi7b0.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 3ffcfa04ffa752..30c1591ca21dfe 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3276,6 +3276,58 @@ features: .. versionadded:: 3.8 +.. function:: eventfd(initval[, flags=os.EFD_CLOEXEC]) + + Create and return a file descriptor for event notification. An eventfd can + be used to implement an event wait/notify mechanisum. See man page + :manpage:`eventfd(2)` for more information. By default, the new file + descriptor is :ref:`non-inheritable `. + + The *initval* is limited to an unsigned 32-bit integer. The Kernel object + holds an unsigned 64-bit integer with a maximum value of 2**64 - 2. The + input value to :func:`~os.write` and output value of :func:~os.read` are + are unsigned 64-bit integers (8 bytes) in native byte order. Eventfds + support waiting for I/O completion with :mod:`select` module. + + Example:: + + import os + import struct + + format = "@Q" # native uint64_t + size = struct.calcsize(format) # 8 bytes + + # semaphore with start value '1' + fd = os.eventfd(1, os.EFD_SEMAPHORE | os.EFC_CLOEXEC) + try: + # acquire semaphore + v = os.read(fd, size) + try: + assert v == struct.pack(format, 1) + do_work() + finally: + # release semaphore + os.write(fd, v) + finally: + os.close(fd) + + .. availability:: Linux 2.27 or newer with glibc 2.8 or newer. + + .. versionadded:: 3.10 + + +.. data:: EFD_CLOEXEC + EFD_NONBLOCK + EFD_SEMAPHORE + + These flags can be passed to :func:`eventfd`. + + .. availability:: Linux 2.27 or newer with glibc 2.8 or newer. The + ``EFD_SEMAPHORE`` flag is available since Linux 2.30. + + .. versionadded:: 3.10 + + Linux extended attributes ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 5126c84cf30c68..dbe3d44a4ffe7a 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -19,6 +19,7 @@ import signal import socket import stat +import struct import subprocess import sys import sysconfig @@ -3528,6 +3529,48 @@ def test_memfd_create(self): self.assertFalse(os.get_inheritable(fd2)) +@unittest.skipUnless(hasattr(os, 'eventfd'), 'requires os.eventfd') +@support.requires_linux_version(2, 30) +class EventfdTests(unittest.TestCase): + fmt = "@Q" # native uint64_t + size = 8 # read/write 8 bytes + + def pack(self, value): + return struct.pack(self.fmt, value) + + def test_eventfd_initval(self): + initval = 42 + fd = os.eventfd(initval) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + self.assertFalse(os.get_inheritable(fd)) + + res = os.read(fd, self.size) + self.assertEqual(res, self.pack(initval)) + + os.write(fd, struct.pack(self.fmt, 23)) + res = os.read(fd, self.size) + self.assertEqual(res, self.pack(23)) + + def test_eventfd_semaphore(self): + initval = 2 + one = self.pack(1) + + 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.read(fd, self.size) + self.assertEqual(res, one) + res = os.read(fd, self.size) + self.assertEqual(res, one) + # third read would block + with self.assertRaises(BlockingIOError): + os.read(fd, self.size) + + class OSErrorTests(unittest.TestCase): def setUp(self): class Str(str): diff --git a/Misc/NEWS.d/next/Library/2020-06-17-12-24-26.bpo-41001.5mi7b0.rst b/Misc/NEWS.d/next/Library/2020-06-17-12-24-26.bpo-41001.5mi7b0.rst new file mode 100644 index 00000000000000..34ecfbf5e6692b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-06-17-12-24-26.bpo-41001.5mi7b0.rst @@ -0,0 +1,2 @@ +Add func:`os.eventfd` to provide a low level interface for Linux's event +notification file descriptor. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index df680d5738c8e8..563272ddb5e80b 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7620,6 +7620,53 @@ os_memfd_create(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj #endif /* defined(HAVE_MEMFD_CREATE) */ +#if defined(HAVE_EVENTFD) + +PyDoc_STRVAR(os_eventfd__doc__, +"eventfd($module, /, initval, flags=EFD_CLOEXEC)\n" +"--\n" +"\n"); + +#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(TERMSIZE_USE_CONIO) || defined(TERMSIZE_USE_IOCTL)) PyDoc_STRVAR(os_get_terminal_size__doc__, @@ -8884,6 +8931,10 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #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_GET_TERMINAL_SIZE_METHODDEF #define OS_GET_TERMINAL_SIZE_METHODDEF #endif /* !defined(OS_GET_TERMINAL_SIZE_METHODDEF) */ @@ -8919,4 +8970,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #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=d807dbad9890d0ef input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 6b51d8a848eab2..859b1369ae30e2 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -518,6 +518,11 @@ extern char *ctermid_r(char *); # include #endif +/* eventfd() */ +#ifdef HAVE_SYS_EVENTFD_H +# include +#endif + #ifdef _Py_MEMORY_SANITIZER # include #endif @@ -12859,6 +12864,30 @@ 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 + +[clinic start generated code]*/ + +static PyObject * +os_eventfd_impl(PyObject *module, unsigned int initval, int flags) +/*[clinic end generated code: output=ce9c9bbd1446f2de input=93c05fbffd6a8d33]*/ +{ + 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); +} +#endif + /* Terminal size querying */ PyDoc_STRVAR(TerminalSize_docstring, @@ -14619,6 +14648,7 @@ static PyMethodDef posix_methods[] = { OS_FSPATH_METHODDEF OS_GETRANDOM_METHODDEF OS_MEMFD_CREATE_METHODDEF + OS_EVENTFD_METHODDEF OS__ADD_DLL_DIRECTORY_METHODDEF OS__REMOVE_DLL_DIRECTORY_METHODDEF OS_WAITSTATUS_TO_EXITCODE_METHODDEF @@ -15127,6 +15157,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 +15256,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 diff --git a/configure b/configure index 68d692d0f6785c..b8b056e4033194 100755 --- a/configure +++ b/configure @@ -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 +#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 diff --git a/configure.ac b/configure.ac index 1edafc342b7120..e499cb4da023ec 100644 --- a/configure.ac +++ b/configure.ac @@ -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 +#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 diff --git a/pyconfig.h.in b/pyconfig.h.in index d71ad3fdc8684c..8a5e9455eca76c 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -308,6 +308,9 @@ /* Define to 1 if you have the 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 header file. */ #undef HAVE_SYS_EPOLL_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_EVENTFD_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_EVENT_H From 2732046770a23ced4d094ce5d6052ecd1274e7d5 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 18 Jun 2020 09:55:17 +0200 Subject: [PATCH 2/7] Add eventfd_read/write, fix value type The ``eventfd_read`` and ``eventfd_write`` functions are convenient wrappers that perform proper read/write and conversion from/to uint64_t. Signed-off-by: Christian Heimes --- Doc/library/os.rst | 81 +++++++++++++++++------------ Lib/test/test_os.py | 57 ++++++++++++++------- Modules/clinic/posixmodule.c.h | 93 +++++++++++++++++++++++++++++++++- Modules/posixmodule.c | 61 +++++++++++++++++++++- 4 files changed, 236 insertions(+), 56 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 30c1591ca21dfe..ba9cf26049ddc1 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3278,52 +3278,67 @@ features: .. function:: eventfd(initval[, flags=os.EFD_CLOEXEC]) - Create and return a file descriptor for event notification. An eventfd can - be used to implement an event wait/notify mechanisum. See man page - :manpage:`eventfd(2)` for more information. By default, the new file - descriptor is :ref:`non-inheritable `. + 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. By default, the + new file descriptor is :ref:`non-inheritable `. - The *initval* is limited to an unsigned 32-bit integer. The Kernel object - holds an unsigned 64-bit integer with a maximum value of 2**64 - 2. The - input value to :func:`~os.write` and output value of :func:~os.read` are - are unsigned 64-bit integers (8 bytes) in native byte order. Eventfds - support waiting for I/O completion with :mod:`select` module. + If :const:`EFD_SEMAPHORE` is specified and the event counter is non-zero, + :func:`eventfd_read` returns 1 and decrements the counter by one. - Example:: + 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, :func:`eventfd_read` blocks. - import os - import struct + :func:`eventfd_write` increments the event counter. - format = "@Q" # native uint64_t - size = struct.calcsize(format) # 8 bytes + *initval* is the initial value of the event counter. It must be an + unsigned integer between 0 and 2\ :sup:`64`\ -\ 2. - # semaphore with start value '1' - fd = os.eventfd(1, os.EFD_SEMAPHORE | os.EFC_CLOEXEC) - try: - # acquire semaphore - v = os.read(fd, size) - try: - assert v == struct.pack(format, 1) - do_work() - finally: - # release semaphore - os.write(fd, v) - finally: - os.close(fd) + *flags* can be constructed from :const:`EFD_CLOEXEC`, + :const:`EFD_NONBLOCK`, and :const:`EFD_SEMAPHORE`. - .. availability:: Linux 2.27 or newer with glibc 2.8 or newer. + .. 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 + an unsigned integer between 0 and 2\ :sup:`64`\ -\ 2. The function does + not verify that *fd* is an :func:`eventfd`. + + .. versionadded:: 3.10 + +.. function:: eventfd_write(fd, value) + + Add value to an :func:`eventfd` file descriptor. *value* must be + an unsigned integer between 0 and 2\ :sup:`64`\ -\ 2. The function does + not verify that *fd* is an :func:`eventfd`. + + .. versionadded:: 3.10 .. data:: EFD_CLOEXEC - EFD_NONBLOCK - EFD_SEMAPHORE - These flags can be passed to :func:`eventfd`. + Set close-on-exec flag for new :func:`eventfd` file descriptor. + + .. versionadded:: 3.10 + +.. data:: EFD_NONBLOCK + + Set :const:`O_NONBLOCK` status flag for new :func:`eventfd` file + descriptor. + + .. 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.27 or newer with glibc 2.8 or newer. The - ``EFD_SEMAPHORE`` flag is available since Linux 2.30. + .. availability:: Linux 2.6.30 or newer .. versionadded:: 3.10 diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index dbe3d44a4ffe7a..ac8ec657011080 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3530,45 +3530,64 @@ def test_memfd_create(self): @unittest.skipUnless(hasattr(os, 'eventfd'), 'requires os.eventfd') -@support.requires_linux_version(2, 30) +@support.requires_linux_version(2, 6, 30) class EventfdTests(unittest.TestCase): - fmt = "@Q" # native uint64_t - size = 8 # read/write 8 bytes - - def pack(self, value): - return struct.pack(self.fmt, value) - 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)) - res = os.read(fd, self.size) - self.assertEqual(res, self.pack(initval)) + # 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)) - os.write(fd, struct.pack(self.fmt, 23)) - res = os.read(fd, self.size) - self.assertEqual(res, self.pack(23)) + # 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 - one = self.pack(1) - 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.read(fd, self.size) - self.assertEqual(res, one) - res = os.read(fd, self.size) - self.assertEqual(res, one) + 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.read(fd, self.size) + 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) class OSErrorTests(unittest.TestCase): diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 563272ddb5e80b..52ac393ab2dab1 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7625,7 +7625,8 @@ os_memfd_create(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj PyDoc_STRVAR(os_eventfd__doc__, "eventfd($module, /, initval, flags=EFD_CLOEXEC)\n" "--\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__}, @@ -7667,6 +7668,86 @@ os_eventfd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * #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__, @@ -8935,6 +9016,14 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #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) */ @@ -8970,4 +9059,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=d807dbad9890d0ef input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0ffd140427fab051 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 859b1369ae30e2..9e626fbac7f716 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -12871,13 +12871,18 @@ 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=93c05fbffd6a8d33]*/ +/*[clinic end generated code: output=ce9c9bbd1446f2de input=66203e3c50c4028b]*/ { int fd; + if (initval > (PY_ULLONG_MAX - 1)) { + PyErr_SetString(PyExc_OverflowError, "initval too large"); + return NULL; + } Py_BEGIN_ALLOW_THREADS fd = eventfd(initval, flags); Py_END_ALLOW_THREADS @@ -12886,7 +12891,57 @@ os_eventfd_impl(PyObject *module, unsigned int initval, int flags) } return PyLong_FromLong(fd); } -#endif + +/*[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=82da3dd0d6e62f28]*/ +{ + int result; + if (value > (PY_ULLONG_MAX - 1)) { + PyErr_SetString(PyExc_OverflowError, "value too large"); + return NULL; + } + 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 */ @@ -14649,6 +14704,8 @@ static PyMethodDef posix_methods[] = { 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 From c0e9b4bf85b72cfbfe9aa9db4bf933fc75a1aec3 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 18 Jun 2020 09:56:51 +0200 Subject: [PATCH 3/7] Add eventfd select test case Signed-off-by: Christian Heimes --- Lib/test/test_os.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index ac8ec657011080..a6e5a1c3da6d33 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -60,6 +60,11 @@ except ImportError: INT_MAX = PY_SSIZE_T_MAX = sys.maxsize +try: + import select +except ImportError: + select = None + from test.support.script_helper import assert_python_ok from test.support import unix_shell from test.support.os_helper import FakePath @@ -3589,6 +3594,31 @@ def test_eventfd_semaphore(self): with self.assertRaises(BlockingIOError): os.eventfd_read(fd) + @unittest.skipUnless( + hasattr(select, "select"), reason="test needs select.select" + ) + 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): From 9c0dff53033bc65d1277e67d3fdecc07da75a8cf Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 19 Jun 2020 11:54:25 +0200 Subject: [PATCH 4/7] Adress more code reviews - initval is a uint32_t - improve documentation - add whatsnew Signed-off-by: Christian Heimes --- Doc/library/os.rst | 38 +++++++++++++++++++++------------- Doc/whatsnew/3.10.rst | 4 ++++ Modules/clinic/posixmodule.c.h | 4 ++-- Modules/posixmodule.c | 14 ++++--------- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index ba9cf26049ddc1..5f031f5c235a8c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3283,6 +3283,14 @@ features: :func:`~select.select`, :func:`~select.poll` and similar. By default, the new file descriptor is :ref:`non-inheritable `. + *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. @@ -3292,13 +3300,9 @@ features: If the event counter is zero, :func:`eventfd_read` blocks. - :func:`eventfd_write` increments the event counter. - - *initval* is the initial value of the event counter. It must be an - unsigned integer between 0 and 2\ :sup:`64`\ -\ 2. - - *flags* can be constructed from :const:`EFD_CLOEXEC`, - :const:`EFD_NONBLOCK`, and :const:`EFD_SEMAPHORE`. + :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. .. availability:: Linux 2.6.27 or newer with glibc 2.8 or newer. @@ -3306,17 +3310,19 @@ features: .. function:: eventfd_read(fd) - Read value from an :func:`eventfd` file descriptor and return - an unsigned integer between 0 and 2\ :sup:`64`\ -\ 2. The function does - not verify that *fd* is an :func:`eventfd`. + 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 - an unsigned integer between 0 and 2\ :sup:`64`\ -\ 2. The function does - not verify that *fd* is an :func:`eventfd`. + 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 @@ -3324,6 +3330,8 @@ features: Set close-on-exec flag for new :func:`eventfd` file descriptor. + .. availability:: See :func:`eventfd` + .. versionadded:: 3.10 .. data:: EFD_NONBLOCK @@ -3331,6 +3339,8 @@ features: Set :const:`O_NONBLOCK` status flag for new :func:`eventfd` file descriptor. + .. availability:: See :func:`eventfd` + .. versionadded:: 3.10 .. data:: EFD_SEMAPHORE @@ -3338,7 +3348,7 @@ features: 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 + .. availability:: Linux 2.6.30 or newer with glibc 2.8 or newer. .. versionadded:: 3.10 diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 4d772005581ad4..bb0706b562a3a9 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -229,6 +229,10 @@ os Added :func:`os.cpu_count()` support for VxWorks RTOS. (Contributed by Peixing Xin in :issue:`41440`.) +A new :func:`os.eventfd` function and related helpers were added to wrap the +``eventfd2`` syscall on Linux +(Contributed by Christian Heimes in :issue:`41001`.) + py_compile ---------- diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 52ac393ab2dab1..f5826e3681251e 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7712,7 +7712,7 @@ PyDoc_STRVAR(os_eventfd_write__doc__, "eventfd_write($module, /, fd, value)\n" "--\n" "\n" -"Write eventfd value"); +"Write eventfd value."); #define OS_EVENTFD_WRITE_METHODDEF \ {"eventfd_write", (PyCFunction)(void(*)(void))os_eventfd_write, METH_FASTCALL|METH_KEYWORDS, os_eventfd_write__doc__}, @@ -9059,4 +9059,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=0ffd140427fab051 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=49b7ed768242ef7c input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 9e626fbac7f716..0764453f412d56 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -12877,12 +12877,10 @@ Creates and returns an event notification file descriptor. 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; - if (initval > (PY_ULLONG_MAX - 1)) { - PyErr_SetString(PyExc_OverflowError, "initval too large"); - return NULL; - } Py_BEGIN_ALLOW_THREADS fd = eventfd(initval, flags); Py_END_ALLOW_THREADS @@ -12921,18 +12919,14 @@ os.eventfd_write fd: fildes value: unsigned_long_long -Write eventfd value +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=82da3dd0d6e62f28]*/ +/*[clinic end generated code: output=bebd9040bbf987f5 input=156de8555be5a949]*/ { int result; - if (value > (PY_ULLONG_MAX - 1)) { - PyErr_SetString(PyExc_OverflowError, "value too large"); - return NULL; - } Py_BEGIN_ALLOW_THREADS result = eventfd_write(fd, value); Py_END_ALLOW_THREADS From 4efa39c5498064d177ff6530384c05a35a81765c Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 24 Jun 2020 06:03:10 +0200 Subject: [PATCH 5/7] Always import select Signed-off-by: Christian Heimes --- Lib/test/test_os.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index a6e5a1c3da6d33..501b4a975566a7 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -15,6 +15,7 @@ import mmap import os import pickle +import select import shutil import signal import socket @@ -60,10 +61,6 @@ except ImportError: INT_MAX = PY_SSIZE_T_MAX = sys.maxsize -try: - import select -except ImportError: - select = None from test.support.script_helper import assert_python_ok from test.support import unix_shell @@ -3594,9 +3591,6 @@ def test_eventfd_semaphore(self): with self.assertRaises(BlockingIOError): os.eventfd_read(fd) - @unittest.skipUnless( - hasattr(select, "select"), reason="test needs select.select" - ) def test_eventfd_select(self): flags = os.EFD_CLOEXEC | os.EFD_NONBLOCK fd = os.eventfd(0, flags) From f3da4ed459d89596cab87d8580c9034279c7a71a Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 24 Jun 2020 06:01:45 +0200 Subject: [PATCH 6/7] Update Doc/whatsnew/3.10.rst Co-authored-by: Kyle Stanley --- Doc/whatsnew/3.10.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index bb0706b562a3a9..c1ce5f3cdc2a10 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -229,8 +229,8 @@ os Added :func:`os.cpu_count()` support for VxWorks RTOS. (Contributed by Peixing Xin in :issue:`41440`.) -A new :func:`os.eventfd` function and related helpers were added to wrap the -``eventfd2`` syscall on Linux +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 From 9c59140e7a3424f3e5d1c129a19e5f35eaddfe35 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 13 Nov 2020 11:12:53 +0100 Subject: [PATCH 7/7] Add example and mention man page --- Doc/library/os.rst | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 5f031f5c235a8c..6c7ae0c785dcd9 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3280,7 +3280,8 @@ features: 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. By default, the + :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 `. *initval* is the initial value of the event counter. The initial value @@ -3298,12 +3299,30 @@ features: non-zero, :func:`eventfd_read` returns the current event counter value and resets the counter to zero. - If the event counter is zero, :func:`eventfd_read` blocks. + 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