diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 4f4d8c99023529d..357bbf69f13dc9d 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3780,6 +3780,174 @@ features: .. versionadded:: 3.10 +.. function:: timerfd_create(clockid, flags) + + Create and return an timerfd file descriptor. The file descriptors supports + raw :func:`read` with a buffer size of 8, :func:`~select.select`, :func:`~select.poll` + and similar. See man page :manpage:`timerfd_create(2)` for more information. + + Refer to :ref:`time-clock-id-constants` for a list of accepted values for *clk_id*. + + *flags* can be constructed from :const:`TFD_NONBLOCK` and :const:`TFD_CLOEXEC`. + + If the timerfd counter is zero and :const:`TFD_NONBLOCK` is not + specified, :func:`read` blocks. + + Example:: + + import time + import os + + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + + # timer interval 0.5 second + inteval = 0.5 + + # timer start in 1 second + value = 1 + + # start timer + _, _ = os.timerfd_settime(fd, 0, inteval, value) + + try: + while: + # wait timerfd expired + _ = fd.read(8) + do_work() + finally: + os.close(fd) + + Example:: + + import time + import os + + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + + # timer interval 0.5 second + inteval_ns = 10**9 // 2 + + # timer start in 1 second + value_ns = 10**9 + + # start timer in nano second unit + _, _ = os.timerfd_settime_ns(fd, 0, inteval_ns, value_ns) + + try: + while: + # wait timerfd expired + _ = fd.read(8) + do_work() + finally: + os.close(fd) + + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + + +.. function:: timerfd_settime(fd, flags, interval, value) + + Start/stop timer for an timerfd file descriptor. + See man page :manpage:`timerfd_settime(2)` for more information. + + ``interval`` coresponds to ``it_interval`` in ``struct itimerspec`` and ``value`` coresponds to + ``it_value`` in ``struct itimerspec``. They are both in seconds unit and types are double. + + ``interval`` is calculated like ``it_interval.tv_sec + it_interval.tv_nsec * 1e-9``, + ``value`` is calculated like ``it_interval.tv_sec + it_interval.tv_nsec * 1e-9`` internally. + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + + + +.. function:: timerfd_settime_ns(fd, flags, interval_ns, value_ns) + + Start/stop timer for an timerfd file descriptor. + See man page :manpage:`timerfd_settime(2)` for more information. + + ``interval_ns`` coresponds to ``it_interval`` in ``struct itimerspec`` and ``value_ns`` coresponds to + ``it_value`` in ``struct itimerspec``. They are both in nano second unit and types are long long. + + ``interval`` is calculated like ``it_interval.tv_sec * 10**9 + it_interval.tv_nsec``, + ``value`` is calculated like ``it_interval.tv_sec * 10**9 + it_interval.tv_nsec`` internally. + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + + +.. function:: timerfd_gettime(fd) + + Returns a tuple ``interval`` which and ``value`` which is the time until next expiration. + See man page :manpage:`timerfd_gettime(2)` for more information. + + ``interval`` coresponds to ``it_interval`` in ``struct itimerspec`` and ``value`` coresponds to + ``it_value`` in ``struct itimerspec``. They are both in seconds unit and types are double. + + ``interval`` is calculated like ``it_interval.tv_sec + it_interval.tv_nsec * 1e-9``, + ``value`` is calculated like ``it_interval.tv_sec + it_interval.tv_nsec * 1e-9`` internally. + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + + +.. function:: timerfd_gettime_ns(fd) + + Returns a tuple ``interval`` which and ``value`` which is the time until next expiration. + See man page :manpage:`timerfd_gettime(2)` for more information. + + ``interval`` coresponds to ``it_interval`` in ``struct itimerspec`` and ``value`` coresponds to + ``it_value`` in ``struct itimerspec``. They are both in nano second unit and types are long long. + + ``interval`` is calculated like ``it_interval.tv_sec * 10**9 + it_interval.tv_nsec``, + ``value`` is calculated like ``it_interval.tv_sec * 10**9 + it_interval.tv_nsec`` internally. + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + +.. data:: TFD_CLOEXEC + + Set close-on-exec flag for new :func:`timerfd_create` file descriptor. + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + +.. data:: TFD_NONBLOCK + + Set :const:`O_NONBLOCK` status flag for new :func:`timerfd_create` file + descriptor. + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + +.. data:: TFD_TIMER_ABSTIME + + Set :const:`TFD_TIMER_ABSTIME` flags for :func:`timerfd_settime` or + :func:`timerfd_settime_ns`. + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + + +.. data:: TFD_TIMER_CANCEL_ON_SET + + Set :const:`TFD_TIMER_CANCEL_ON_SET` flags for :func:`timerfd_settime` or + :func:`timerfd_settime_ns`. + + .. availability:: Linux >= 2.6.27 with glibc >= 2.8 + + .. versionadded:: 3.13 + + Linux extended attributes ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 2f930babf6aad40..6098c683e43bd53 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -836,6 +836,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(certfile)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(check_same_thread)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(clear)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(clockid)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(close)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(closed)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(closefd)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 5a0cd1a02ba5615..43ffb288cce4d5d 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -325,6 +325,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(certfile) STRUCT_FOR_ID(check_same_thread) STRUCT_FOR_ID(clear) + STRUCT_FOR_ID(clockid) STRUCT_FOR_ID(close) STRUCT_FOR_ID(closed) STRUCT_FOR_ID(closefd) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 694a409fbf4f479..4f8030f356f3caa 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -834,6 +834,7 @@ extern "C" { INIT_ID(certfile), \ INIT_ID(check_same_thread), \ INIT_ID(clear), \ + INIT_ID(clockid), \ INIT_ID(close), \ INIT_ID(closed), \ INIT_ID(closefd), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 841eb7870112710..62dff2b318bc763 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -816,6 +816,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(clear); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(clockid); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(close); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 99e9ed213e5615c..2764bea184b3731 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3915,6 +3915,231 @@ def test_eventfd_select(self): self.assertEqual((rfd, wfd, xfd), ([fd], [], [])) os.eventfd_read(fd) +@unittest.skipUnless(hasattr(os, 'timerfd_create'), 'requires os.timerfd_create') +@support.requires_linux_version(2, 6, 30) +class TimerfdTests(unittest.TestCase): + def test_timerfd_initval(self): + limit_error = 1e-3 + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + interval = 0.125 + value = 0.25 + + # 1st call + interval2, value2 = os.timerfd_settime(fd, 0, interval, value) + self.assertLessEqual(interval2, limit_error) + self.assertLessEqual(value2, limit_error) + + # 2nd call + interval2, value2 = os.timerfd_settime(fd, 0, interval, value) + self.assertLess(abs(interval2 - interval), limit_error) + self.assertLess(abs(value2 - value), limit_error) + + # timerfd_gettime + interval2, value2 = os.timerfd_gettime(fd) + self.assertLess(abs(interval2 - interval), limit_error) + self.assertLess(abs(value2 - value), limit_error) + + def test_timerfd_interval(self): + size = 8 # read 8 bytes + limit_error = 1e-3 + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + # 0.5 second + interval = 0.5 + # 1 second + value = 1 + + _, _ = os.timerfd_settime(fd, 0, interval, value) + + # timerfd_gettime + interval2, value2 = os.timerfd_gettime(fd) + self.assertLess(abs(interval2 - interval), limit_error) + self.assertLess(abs(value2 - value), limit_error) + + count = 3 + t = time.perf_counter() + for _ in range(count): + _ = os.read(fd, size) + t = time.perf_counter() - t + + total_time = value + interval * (count - 1) + self.assertGreater(t, total_time) + + def test_timerfd_TFD_TIMER_ABSTIME(self): + size = 8 # read 8 bytes + limit_error = 1e-3 + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + now = time.clock_gettime(time.CLOCK_REALTIME) + + # not interval timer + interval = 0 + # 1 second later from now. + offset = 1 + value = now + offset + + _, _ = os.timerfd_settime(fd, os.TFD_TIMER_ABSTIME, interval, value) + + # timerfd_gettime + # Note: timerfd_gettime returns relative values even if TFD_TIMER_ABSTIME is specified. + interval2, value2 = os.timerfd_gettime(fd) + self.assertLess(abs(interval2 - interval), limit_error) + self.assertLess(abs(value2 - offset), limit_error) + + t = time.perf_counter() + _ = os.read(fd, size) + t = time.perf_counter() - t + + self.assertGreater(t, offset) + + def test_timerfd_select(self): + size = 8 # read 8 bytes + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + rfd, wfd, xfd = select.select([fd], [fd], [fd], 0) + self.assertEqual((rfd, wfd, xfd), ([], [], [])) + + # 0.125 second + interval = 0.125 + # 0.25 second + value = 0.25 + + _, _ = os.timerfd_settime(fd, 0, interval, value) + + count = 3 + t = time.perf_counter() + for _ in range(count): + rfd, wfd, xfd = select.select([fd], [fd], [fd], value + interval) + self.assertEqual((rfd, wfd, xfd), ([fd], [], [])) + _ = os.read(fd, size) + t = time.perf_counter() - t + + total_time = value + interval * (count - 1) + self.assertGreater(t, total_time) + + def test_timerfd_ns_initval(self): + one_sec_in_nsec = 10**9 + limit_error = one_sec_in_nsec // 10**3 + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + # 1st call + interval_ns = one_sec_in_nsec // 1000 + value_ns = 0 + interval_ns2, value_ns2 = os.timerfd_settime_ns(fd, 0, interval_ns, value_ns) + self.assertEqual(interval_ns2, 0) + self.assertEqual(value_ns2, 0) + + # 2nd call + interval_ns2, value_ns2 = os.timerfd_settime_ns(fd, 0, interval_ns, value_ns) + self.assertEqual(interval_ns2, interval_ns) + self.assertEqual(value_ns2, value_ns) + + # timerfd_gettime + interval_ns2, value_ns2 = os.timerfd_gettime_ns(fd) + self.assertEqual(interval_ns2, interval_ns) + self.assertLessEqual(value_ns2, value_ns) + + self.assertLess(abs(value_ns2 - value_ns), limit_error) + + def test_timerfd_ns_interval(self): + size = 8 # read 8 bytes + one_sec_in_nsec = 10**9 + limit_error = one_sec_in_nsec // 10**3 + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + # 0.5 second + interval_ns = one_sec_in_nsec // 2 + # 1 second + value_ns = one_sec_in_nsec + + _, _ = os.timerfd_settime_ns(fd, 0, interval_ns, value_ns) + + # timerfd_gettime + interval_ns2, value_ns2 = os.timerfd_gettime_ns(fd) + self.assertEqual(interval_ns2, interval_ns) + self.assertLessEqual(value_ns2, value_ns) + + count = 3 + t = time.perf_counter_ns() + for _ in range(count): + _ = os.read(fd, size) + t = time.perf_counter_ns() - t + + total_time_ns = value_ns + interval_ns * (count - 1) + self.assertGreater(t, total_time_ns) + + + def test_timerfd_ns_TFD_TIMER_ABSTIME(self): + size = 8 # read 8 bytes + one_sec_in_nsec = 10**9 + limit_error = one_sec_in_nsec // 10**3 + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + now_ns = time.clock_gettime_ns(time.CLOCK_REALTIME) + + # not interval timer + interval_ns = 0 + # 1 second later from now. + offset_ns = one_sec_in_nsec + value_ns = now_ns + offset_ns + + _, _ = os.timerfd_settime_ns(fd, os.TFD_TIMER_ABSTIME, interval_ns, value_ns) + + # timerfd_gettime + # Note: timerfd_gettime returns relative values even if TFD_TIMER_ABSTIME is specified. + interval_ns2, value_ns2 = os.timerfd_gettime_ns(fd) + self.assertLess(abs(interval_ns2 - interval_ns), limit_error) + self.assertLess(abs(value_ns2 - offset_ns), limit_error) + + t = time.perf_counter_ns() + _ = os.read(fd, size) + t = time.perf_counter_ns() - t + + self.assertGreater(t, offset_ns) + + def test_timerfd_ns_select(self): + size = 8 # read 8 bytes + one_sec_in_nsec = 10**9 + + fd = os.timerfd_create(time.CLOCK_REALTIME, 0) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + rfd, wfd, xfd = select.select([fd], [fd], [fd], 0) + self.assertEqual((rfd, wfd, xfd), ([], [], [])) + + # 0.125 second + interval_ns = one_sec_in_nsec // 8 + # 0.25 second + value_ns = one_sec_in_nsec // 4 + + _, _ = os.timerfd_settime_ns(fd, 0, interval_ns, value_ns) + + count = 3 + t = time.perf_counter_ns() + for _ in range(count): + rfd, wfd, xfd = select.select([fd], [fd], [fd], (value_ns + interval_ns) / 1e9 ) + self.assertEqual((rfd, wfd, xfd), ([fd], [], [])) + _ = os.read(fd, size) + t = time.perf_counter_ns() - t + + total_time_ns = value_ns + interval_ns * (count - 1) + self.assertGreater(t, total_time_ns) class OSErrorTests(unittest.TestCase): def setUp(self): diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 9b3fa8ded26766c..dc29ea617c6e996 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -5986,6 +5986,262 @@ os_times(PyObject *module, PyObject *Py_UNUSED(ignored)) #endif /* defined(HAVE_TIMES) */ +#if defined(HAVE_TIMERFD_CREATE) + +PyDoc_STRVAR(os_timerfd_create__doc__, +"timerfd_create($module, /, clockid, flags)\n" +"--\n" +"\n" +"Creates and returns an timerfd notification file descriptor."); + +#define OS_TIMERFD_CREATE_METHODDEF \ + {"timerfd_create", _PyCFunction_CAST(os_timerfd_create), METH_FASTCALL|METH_KEYWORDS, os_timerfd_create__doc__}, + +static PyObject * +os_timerfd_create_impl(PyObject *module, int clockid, int flags); + +static PyObject * +os_timerfd_create(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(clockid), &_Py_ID(flags), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"clockid", "flags", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "timerfd_create", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + int clockid; + int flags; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { + goto exit; + } + clockid = _PyLong_AsInt(args[0]); + if (clockid == -1 && PyErr_Occurred()) { + goto exit; + } + flags = _PyLong_AsInt(args[1]); + if (flags == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = os_timerfd_create_impl(module, clockid, flags); + +exit: + return return_value; +} + +#endif /* defined(HAVE_TIMERFD_CREATE) */ + +#if defined(HAVE_TIMERFD_CREATE) + +PyDoc_STRVAR(os_timerfd_gettime__doc__, +"timerfd_gettime($module, fd, /)\n" +"--\n" +"\n" +"Read timerfd value in second."); + +#define OS_TIMERFD_GETTIME_METHODDEF \ + {"timerfd_gettime", (PyCFunction)os_timerfd_gettime, METH_O, os_timerfd_gettime__doc__}, + +static PyObject * +os_timerfd_gettime_impl(PyObject *module, int fd); + +static PyObject * +os_timerfd_gettime(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = os_timerfd_gettime_impl(module, fd); + +exit: + return return_value; +} + +#endif /* defined(HAVE_TIMERFD_CREATE) */ + +#if defined(HAVE_TIMERFD_CREATE) + +PyDoc_STRVAR(os_timerfd_settime__doc__, +"timerfd_settime($module, fd, flags, it_interval=0.0, it_value=0.0, /)\n" +"--\n" +"\n" +"Write timerfd value in second."); + +#define OS_TIMERFD_SETTIME_METHODDEF \ + {"timerfd_settime", _PyCFunction_CAST(os_timerfd_settime), METH_FASTCALL, os_timerfd_settime__doc__}, + +static PyObject * +os_timerfd_settime_impl(PyObject *module, int fd, int flags, + double it_interval, double it_value); + +static PyObject * +os_timerfd_settime(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int fd; + int flags; + double it_interval = 0.0; + double it_value = 0.0; + + if (!_PyArg_CheckPositional("timerfd_settime", nargs, 2, 4)) { + goto exit; + } + if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + goto exit; + } + flags = _PyLong_AsInt(args[1]); + if (flags == -1 && PyErr_Occurred()) { + goto exit; + } + if (nargs < 3) { + goto skip_optional; + } + if (PyFloat_CheckExact(args[2])) { + it_interval = PyFloat_AS_DOUBLE(args[2]); + } + else + { + it_interval = PyFloat_AsDouble(args[2]); + if (it_interval == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + if (nargs < 4) { + goto skip_optional; + } + if (PyFloat_CheckExact(args[3])) { + it_value = PyFloat_AS_DOUBLE(args[3]); + } + else + { + it_value = PyFloat_AsDouble(args[3]); + if (it_value == -1.0 && PyErr_Occurred()) { + goto exit; + } + } +skip_optional: + return_value = os_timerfd_settime_impl(module, fd, flags, it_interval, it_value); + +exit: + return return_value; +} + +#endif /* defined(HAVE_TIMERFD_CREATE) */ + +#if defined(HAVE_TIMERFD_CREATE) + +PyDoc_STRVAR(os_timerfd_gettime_ns__doc__, +"timerfd_gettime_ns($module, fd, /)\n" +"--\n" +"\n" +"Read timerfd value in ns"); + +#define OS_TIMERFD_GETTIME_NS_METHODDEF \ + {"timerfd_gettime_ns", (PyCFunction)os_timerfd_gettime_ns, METH_O, os_timerfd_gettime_ns__doc__}, + +static PyObject * +os_timerfd_gettime_ns_impl(PyObject *module, int fd); + +static PyObject * +os_timerfd_gettime_ns(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = os_timerfd_gettime_ns_impl(module, fd); + +exit: + return return_value; +} + +#endif /* defined(HAVE_TIMERFD_CREATE) */ + +#if defined(HAVE_TIMERFD_CREATE) + +PyDoc_STRVAR(os_timerfd_settime_ns__doc__, +"timerfd_settime_ns($module, fd, flags, it_interval_ns=0, it_value_ns=0,\n" +" /)\n" +"--\n" +"\n" +"Write timerfd value."); + +#define OS_TIMERFD_SETTIME_NS_METHODDEF \ + {"timerfd_settime_ns", _PyCFunction_CAST(os_timerfd_settime_ns), METH_FASTCALL, os_timerfd_settime_ns__doc__}, + +static PyObject * +os_timerfd_settime_ns_impl(PyObject *module, int fd, int flags, + long long it_interval_ns, long long it_value_ns); + +static PyObject * +os_timerfd_settime_ns(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int fd; + int flags; + long long it_interval_ns = 0; + long long it_value_ns = 0; + + if (!_PyArg_CheckPositional("timerfd_settime_ns", nargs, 2, 4)) { + goto exit; + } + if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + goto exit; + } + flags = _PyLong_AsInt(args[1]); + if (flags == -1 && PyErr_Occurred()) { + goto exit; + } + if (nargs < 3) { + goto skip_optional; + } + it_interval_ns = PyLong_AsLongLong(args[2]); + if (it_interval_ns == -1 && PyErr_Occurred()) { + goto exit; + } + if (nargs < 4) { + goto skip_optional; + } + it_value_ns = PyLong_AsLongLong(args[3]); + if (it_value_ns == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional: + return_value = os_timerfd_settime_ns_impl(module, fd, flags, it_interval_ns, it_value_ns); + +exit: + return return_value; +} + +#endif /* defined(HAVE_TIMERFD_CREATE) */ + #if defined(HAVE_GETSID) PyDoc_STRVAR(os_getsid__doc__, @@ -11727,6 +11983,26 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #define OS_TIMES_METHODDEF #endif /* !defined(OS_TIMES_METHODDEF) */ +#ifndef OS_TIMERFD_CREATE_METHODDEF + #define OS_TIMERFD_CREATE_METHODDEF +#endif /* !defined(OS_TIMERFD_CREATE_METHODDEF) */ + +#ifndef OS_TIMERFD_GETTIME_METHODDEF + #define OS_TIMERFD_GETTIME_METHODDEF +#endif /* !defined(OS_TIMERFD_GETTIME_METHODDEF) */ + +#ifndef OS_TIMERFD_SETTIME_METHODDEF + #define OS_TIMERFD_SETTIME_METHODDEF +#endif /* !defined(OS_TIMERFD_SETTIME_METHODDEF) */ + +#ifndef OS_TIMERFD_GETTIME_NS_METHODDEF + #define OS_TIMERFD_GETTIME_NS_METHODDEF +#endif /* !defined(OS_TIMERFD_GETTIME_NS_METHODDEF) */ + +#ifndef OS_TIMERFD_SETTIME_NS_METHODDEF + #define OS_TIMERFD_SETTIME_NS_METHODDEF +#endif /* !defined(OS_TIMERFD_SETTIME_NS_METHODDEF) */ + #ifndef OS_GETSID_METHODDEF #define OS_GETSID_METHODDEF #endif /* !defined(OS_GETSID_METHODDEF) */ @@ -11990,4 +12266,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=9a5f78bb65470528 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fee228de318beffb input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 8026080912a7d87..e432d38ac07c9d6 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -543,6 +543,11 @@ extern char *ctermid_r(char *); # include #endif +/* timerfd_create() */ +#ifdef HAVE_SYS_TIMERFD_H +# include +#endif + #ifdef _Py_MEMORY_SANITIZER # include #endif @@ -10014,6 +10019,174 @@ os_times_impl(PyObject *module) #endif /* HAVE_TIMES */ +#if defined(HAVE_TIMERFD_CREATE) +#define ONE_SECOND_IN_NS (1000 * 1000 * 1000) +#define EXTRACT_NSEC(value) (long)( ( (double)(value) - (time_t)(value) ) * 1e9) + +static PyObject * +build_itimerspec(const struct itimerspec* curr_value) +{ + return PyTuple_Pack( + 2, + PyFloat_FromDouble((double)(curr_value->it_interval.tv_sec) + (double)curr_value->it_interval.tv_nsec * 1e-9), + PyFloat_FromDouble((double)(curr_value->it_value.tv_sec) + (double)curr_value->it_value.tv_nsec * 1e-9) + ); +} + +static PyObject * +build_itimerspec_ns(const struct itimerspec* curr_value) +{ + return PyTuple_Pack( + 2, + PyLong_FromLongLong((long long)curr_value->it_interval.tv_sec * ONE_SECOND_IN_NS + (long long)curr_value->it_interval.tv_nsec), + PyLong_FromLongLong((long long)curr_value->it_value.tv_sec * ONE_SECOND_IN_NS + (long long)curr_value->it_value.tv_nsec) + ); +} + +/*[clinic input] +os.timerfd_create + + clockid: int + flags: int + +Creates and returns an timerfd notification file descriptor. +[clinic start generated code]*/ + +static PyObject * +os_timerfd_create_impl(PyObject *module, int clockid, int flags) +/*[clinic end generated code: output=1caae80fb168004a input=9ebed3e587e2ccd6]*/ + +{ + int fd; + Py_BEGIN_ALLOW_THREADS + fd = timerfd_create(clockid, flags); + Py_END_ALLOW_THREADS + if (fd == -1) { + return PyErr_SetFromErrno(PyExc_OSError); + } + return PyLong_FromLong(fd); +} + + +/*[clinic input] +os.timerfd_gettime + + fd: fildes + / + +Read timerfd value in second. +[clinic start generated code]*/ + +static PyObject * +os_timerfd_gettime_impl(PyObject *module, int fd) +/*[clinic end generated code: output=ec5a94a66cfe6ab4 input=30e69f73d8b73cb2]*/ +{ + struct itimerspec curr_value; + int result; + Py_BEGIN_ALLOW_THREADS + result = timerfd_gettime(fd, &curr_value); + Py_END_ALLOW_THREADS + if (result == -1) { + return PyErr_SetFromErrno(PyExc_OSError); + } + return build_itimerspec(&curr_value); +} + +/*[clinic input] +os.timerfd_settime + + fd: fildes + flags: int + it_interval: double = 0.0 + it_value: double = 0.0 + / + +Write timerfd value in second. +[clinic start generated code]*/ + +static PyObject * +os_timerfd_settime_impl(PyObject *module, int fd, int flags, + double it_interval, double it_value) +/*[clinic end generated code: output=24030b5b2c5b539c input=24f0b47f6847a2c3]*/ +{ + struct itimerspec new_value; + struct itimerspec old_value; + int result; + new_value.it_interval.tv_sec = (time_t)it_interval; + new_value.it_interval.tv_nsec = EXTRACT_NSEC(it_interval); + new_value.it_value.tv_sec = (time_t)it_value; + new_value.it_value.tv_nsec = EXTRACT_NSEC(it_value); + Py_BEGIN_ALLOW_THREADS + result = timerfd_settime(fd, flags, &new_value, &old_value); + Py_END_ALLOW_THREADS + if (result == -1) { + return PyErr_SetFromErrno(PyExc_OSError); + } + return build_itimerspec(&old_value); +} + +/*[clinic input] +os.timerfd_gettime_ns + + fd: fildes + / + +Read timerfd value in ns +[clinic start generated code]*/ + +static PyObject * +os_timerfd_gettime_ns_impl(PyObject *module, int fd) +/*[clinic end generated code: output=580633a4465f39fe input=03806d61a48536a9]*/ +{ + struct itimerspec curr_value; + int result; + Py_BEGIN_ALLOW_THREADS + result = timerfd_gettime(fd, &curr_value); + Py_END_ALLOW_THREADS + if (result == -1) { + return PyErr_SetFromErrno(PyExc_OSError); + } + return build_itimerspec_ns(&curr_value); +} + +/*[clinic input] +os.timerfd_settime_ns + + fd: fildes + flags: int + it_interval_ns: long_long = 0 + it_value_ns: long_long = 0 + / + +Write timerfd value. +[clinic start generated code]*/ + +static PyObject * +os_timerfd_settime_ns_impl(PyObject *module, int fd, int flags, + long long it_interval_ns, long long it_value_ns) +/*[clinic end generated code: output=8c2801053004f896 input=715746fc69e55bc7]*/ +{ + struct itimerspec new_value; + struct itimerspec old_value; + int result; + new_value.it_interval.tv_sec = it_interval_ns / ONE_SECOND_IN_NS; + new_value.it_interval.tv_nsec = it_interval_ns % ONE_SECOND_IN_NS; + new_value.it_value.tv_sec = it_value_ns / ONE_SECOND_IN_NS; + new_value.it_value.tv_nsec = it_value_ns % ONE_SECOND_IN_NS; + Py_BEGIN_ALLOW_THREADS + result = timerfd_settime(fd, flags, &new_value, &old_value); + Py_END_ALLOW_THREADS + if (result == -1) { + return PyErr_SetFromErrno(PyExc_OSError); + } + return build_itimerspec_ns(&old_value); +} + +#undef ONE_SECOND_IN_NS +#undef EXTRACT_NSEC + +#endif /* HAVE_TIMERFD_CREATE */ + #ifdef HAVE_GETSID /*[clinic input] os.getsid @@ -15920,6 +16093,11 @@ static PyMethodDef posix_methods[] = { OS_WAITSTATUS_TO_EXITCODE_METHODDEF OS_SETNS_METHODDEF OS_UNSHARE_METHODDEF + OS_TIMERFD_CREATE_METHODDEF + OS_TIMERFD_SETTIME_METHODDEF + OS_TIMERFD_GETTIME_METHODDEF + OS_TIMERFD_SETTIME_NS_METHODDEF + OS_TIMERFD_GETTIME_NS_METHODDEF OS__PATH_ISDEVDRIVE_METHODDEF OS__PATH_ISDIR_METHODDEF @@ -16235,6 +16413,19 @@ all_ins(PyObject *m) if (PyModule_AddIntMacro(m, SF_NOCACHE)) return -1; #endif +#ifdef TFD_NONBLOCK + if (PyModule_AddIntMacro(m, TFD_NONBLOCK)) return -1; +#endif +#ifdef TFD_CLOEXEC + if (PyModule_AddIntMacro(m, TFD_CLOEXEC)) return -1; +#endif +#ifdef TFD_TIMER_ABSTIME + if (PyModule_AddIntMacro(m, TFD_TIMER_ABSTIME)) return -1; +#endif +#ifdef TFD_TIMER_CANCEL_ON_SET + if (PyModule_AddIntMacro(m, TFD_TIMER_CANCEL_ON_SET)) return -1; +#endif + /* constants for posix_fadvise */ #ifdef POSIX_FADV_NORMAL if (PyModule_AddIntMacro(m, POSIX_FADV_NORMAL)) return -1; @@ -16633,6 +16824,16 @@ static const struct have_function { {"HAVE_EVENTFD", NULL}, #endif +#ifdef HAVE_TIMERFD_CREATE + {"HAVE_TIMERFD_CREATE", NULL}, +#endif +#ifdef HAVE_TIMERFD_SETTIME + {"HAVE_TIMERFD_SETTIME", NULL}, +#endif +#ifdef HAVE_TIMERFD_GETTIME + {"HAVE_TIMERFD_GETTIME", NULL}, +#endif + #ifdef HAVE_FACCESSAT { "HAVE_FACCESSAT", probe_faccessat }, #endif diff --git a/configure b/configure index f78b45a2c72dd2a..e9ca5d9cf8f988f 100755 --- a/configure +++ b/configure @@ -10864,6 +10864,12 @@ if test "x$ac_cv_header_sys_times_h" = xyes then : printf "%s\n" "#define HAVE_SYS_TIMES_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "sys/timerfd.h" "ac_cv_header_sys_timerfd_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_timerfd_h" = xyes +then : + printf "%s\n" "#define HAVE_SYS_TIMERFD_H 1" >>confdefs.h + fi ac_fn_c_check_header_compile "$LINENO" "sys/types.h" "ac_cv_header_sys_types_h" "$ac_includes_default" if test "x$ac_cv_header_sys_types_h" = xyes @@ -18936,6 +18942,138 @@ fi + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for timerfd_create" >&5 +printf %s "checking for timerfd_create... " >&6; } +if test ${ac_cv_func_timerfd_create+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#ifdef HAVE_SYS_TIMERFD_H +#include +#endif + +int +main (void) +{ +void *x=timerfd_create + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_func_timerfd_create=yes +else $as_nop + ac_cv_func_timerfd_create=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_timerfd_create" >&5 +printf "%s\n" "$ac_cv_func_timerfd_create" >&6; } + if test "x$ac_cv_func_timerfd_create" = xyes +then : + +printf "%s\n" "#define HAVE_TIMERFD_CREATE 1" >>confdefs.h + +fi + + + + + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for timerfd_settime" >&5 +printf %s "checking for timerfd_settime... " >&6; } +if test ${ac_cv_func_timerfd_settime+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#ifdef HAVE_SYS_TIMERFD_H +#include +#endif + +int +main (void) +{ +void *x=timerfd_settime + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_func_timerfd_settime=yes +else $as_nop + ac_cv_func_timerfd_settime=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_timerfd_settime" >&5 +printf "%s\n" "$ac_cv_func_timerfd_settime" >&6; } + if test "x$ac_cv_func_timerfd_settime" = xyes +then : + +printf "%s\n" "#define HAVE_TIMERFD_SETTIME 1" >>confdefs.h + +fi + + + + + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for timerfd_gettime" >&5 +printf %s "checking for timerfd_gettime... " >&6; } +if test ${ac_cv_func_timerfd_gettime+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#ifdef HAVE_SYS_TIMERFD_H +#include +#endif + +int +main (void) +{ +void *x=timerfd_gettime + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_func_timerfd_gettime=yes +else $as_nop + ac_cv_func_timerfd_gettime=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_timerfd_gettime" >&5 +printf "%s\n" "$ac_cv_func_timerfd_gettime" >&6; } + if test "x$ac_cv_func_timerfd_gettime" = xyes +then : + +printf "%s\n" "#define HAVE_TIMERFD_GETTIME 1" >>confdefs.h + +fi + + + + # 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 fd18a452f2b610d..bcb37f74a6248d8 100644 --- a/configure.ac +++ b/configure.ac @@ -2855,7 +2855,7 @@ AC_CHECK_HEADERS([ \ sys/endian.h sys/epoll.h sys/event.h sys/eventfd.h sys/file.h sys/ioctl.h sys/kern_control.h \ sys/loadavg.h sys/lock.h sys/memfd.h sys/mkdev.h sys/mman.h sys/modem.h sys/param.h sys/poll.h \ sys/random.h sys/resource.h sys/select.h sys/sendfile.h sys/socket.h sys/soundcard.h sys/stat.h \ - sys/statvfs.h sys/sys_domain.h sys/syscall.h sys/sysmacros.h sys/termio.h sys/time.h sys/times.h \ + sys/statvfs.h sys/sys_domain.h sys/syscall.h sys/sysmacros.h sys/termio.h sys/time.h sys/times.h sys/timerfd.h \ sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h sys/xattr.h sysexits.h syslog.h \ termios.h util.h utime.h utmp.h \ ]) @@ -4908,6 +4908,24 @@ PY_CHECK_FUNC([eventfd], [ #endif ]) +PY_CHECK_FUNC([timerfd_create], [ +#ifdef HAVE_SYS_TIMERFD_H +#include +#endif +]) + +PY_CHECK_FUNC([timerfd_settime], [ +#ifdef HAVE_SYS_TIMERFD_H +#include +#endif +]) + +PY_CHECK_FUNC([timerfd_gettime], [ +#ifdef HAVE_SYS_TIMERFD_H +#include +#endif +]) + # 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 418ccade8e8bb15..fe2b606ecea3b8b 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1366,6 +1366,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TERMIO_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TIMERFD_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TIMES_H @@ -1408,6 +1411,15 @@ /* Define to 1 if you have the `timegm' function. */ #undef HAVE_TIMEGM +/* Define if you have the 'timerfd_create' function. */ +#undef HAVE_TIMERFD_CREATE + +/* Define if you have the 'timerfd_gettime' function. */ +#undef HAVE_TIMERFD_GETTIME + +/* Define if you have the 'timerfd_settime' function. */ +#undef HAVE_TIMERFD_SETTIME + /* Define to 1 if you have the `times' function. */ #undef HAVE_TIMES