diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 3ffcfa04ffa..6c7ae0c785d 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -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 `. + + *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 ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 4d772005581..c1ce5f3cdc2 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`.) +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 ---------- diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 5126c84cf30..501b4a97556 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -15,10 +15,12 @@ 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 @@ 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 @@ def test_memfd_create(self): 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): 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 00000000000..34ecfbf5e66 --- /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 df680d5738c..f5826e36812 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -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]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 6b51d8a848e..0764453f412 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,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 diff --git a/configure b/configure index 68d692d0f67..b8b056e4033 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 1edafc342b7..e499cb4da02 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 d71ad3fdc86..8a5e9455eca 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