summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMasaru Tsuchiyama <m.tmatma@gmail.com>2023-10-07 17:33:22 (GMT)
committerGitHub <noreply@github.com>2023-10-07 17:33:22 (GMT)
commitde2a4036cbfd5e41a5bdd2b81122b7765729af83 (patch)
tree44b739676b2707fddf3404882ae4b0e870cf0ce0
parent64f158e7b09e67d0bf5c8603ff88c86ed4e8f8fd (diff)
downloadcpython-de2a4036cbfd5e41a5bdd2b81122b7765729af83.zip
cpython-de2a4036cbfd5e41a5bdd2b81122b7765729af83.tar.gz
cpython-de2a4036cbfd5e41a5bdd2b81122b7765729af83.tar.bz2
gh-108277: Add os.timerfd_create() function (#108382)
Add wrapper for timerfd_create, timerfd_settime, and timerfd_gettime to os module. Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> Co-authored-by: Victor Stinner <vstinner@python.org>
-rw-r--r--Doc/howto/index.rst1
-rw-r--r--Doc/howto/timerfd.rst230
-rw-r--r--Doc/library/os.rst211
-rw-r--r--Doc/whatsnew/3.13.rst8
-rw-r--r--Include/internal/pycore_global_objects_fini_generated.h1
-rw-r--r--Include/internal/pycore_global_strings.h1
-rw-r--r--Include/internal/pycore_runtime_init_generated.h1
-rw-r--r--Include/internal/pycore_time.h6
-rw-r--r--Include/internal/pycore_unicodeobject_generated.h3
-rw-r--r--Lib/test/test_os.py350
-rw-r--r--Misc/NEWS.d/next/Library/2023-08-23-22-08-32.gh-issue-108277.KLV-6T.rst1
-rw-r--r--Modules/clinic/posixmodule.c.h392
-rw-r--r--Modules/posixmodule.c248
-rw-r--r--Python/pytime.c14
-rwxr-xr-xconfigure50
-rw-r--r--configure.ac9
-rw-r--r--pyconfig.h.in6
17 files changed, 1527 insertions, 5 deletions
diff --git a/Doc/howto/index.rst b/Doc/howto/index.rst
index f521276..9e321be 100644
--- a/Doc/howto/index.rst
+++ b/Doc/howto/index.rst
@@ -33,4 +33,5 @@ Currently, the HOWTOs are:
perf_profiling.rst
annotations.rst
isolating-extensions.rst
+ timerfd.rst
diff --git a/Doc/howto/timerfd.rst b/Doc/howto/timerfd.rst
new file mode 100644
index 0000000..98f0294
--- /dev/null
+++ b/Doc/howto/timerfd.rst
@@ -0,0 +1,230 @@
+.. _timerfd-howto:
+
+*****************************
+ timer file descriptor HOWTO
+*****************************
+
+:Release: 1.13
+
+This HOWTO discusses Python's support for the linux timer file descriptor.
+
+
+Examples
+========
+
+The following example shows how to use a timer file descriptor
+to execute a function twice a second:
+
+.. code-block:: python
+
+ # Practical scripts should use really use a non-blocking timer,
+ # we use a blocking timer here for simplicity.
+ import os, time
+
+ # Create the timer file descriptor
+ fd = os.timerfd_create(time.CLOCK_REALTIME)
+
+ # Start the timer in 1 second, with an interval of half a second
+ os.timerfd_settime(fd, initial=1, interval=0.5)
+
+ try:
+ # Process timer events four times.
+ for _ in range(4):
+ # read() will block until the timer expires
+ _ = os.read(fd, 8)
+ print("Timer expired")
+ finally:
+ # Remember to close the timer file descriptor!
+ os.close(fd)
+
+To avoid the precision loss caused by the :class:`float` type,
+timer file descriptors allow specifying initial expiration and interval
+in integer nanoseconds with ``_ns`` variants of the functions.
+
+This example shows how :func:`~select.epoll` can be used with timer file
+descriptors to wait until the file descriptor is ready for reading:
+
+.. code-block:: python
+
+ import os, time, select, socket, sys
+
+ # Create an epoll object
+ ep = select.epoll()
+
+ # In this example, use loopback address to send "stop" command to the server.
+ #
+ # $ telnet 127.0.0.1 1234
+ # Trying 127.0.0.1...
+ # Connected to 127.0.0.1.
+ # Escape character is '^]'.
+ # stop
+ # Connection closed by foreign host.
+ #
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.bind(("127.0.0.1", 1234))
+ sock.setblocking(False)
+ sock.listen(1)
+ ep.register(sock, select.EPOLLIN)
+
+ # Create timer file descriptors in non-blocking mode.
+ num = 3
+ fds = []
+ for _ in range(num):
+ fd = os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
+ fds.append(fd)
+ # Register the timer file descriptor for read events
+ ep.register(fd, select.EPOLLIN)
+
+ # Start the timer with os.timerfd_settime_ns() in nanoseconds.
+ # Timer 1 fires every 0.25 seconds; timer 2 every 0.5 seconds; etc
+ for i, fd in enumerate(fds, start=1):
+ one_sec_in_nsec = 10**9
+ i = i * one_sec_in_nsec
+ os.timerfd_settime_ns(fd, initial=i//4, interval=i//4)
+
+ timeout = 3
+ try:
+ conn = None
+ is_active = True
+ while is_active:
+ # Wait for the timer to expire for 3 seconds.
+ # epoll.poll() returns a list of (fd, event) pairs.
+ # fd is a file descriptor.
+ # sock and conn[=returned value of socket.accept()] are socket objects, not file descriptors.
+ # So use sock.fileno() and conn.fileno() to get the file descriptors.
+ events = ep.poll(timeout)
+
+ # If more than one timer file descriptors are ready for reading at once,
+ # epoll.poll() returns a list of (fd, event) pairs.
+ #
+ # In this example settings,
+ # 1st timer fires every 0.25 seconds in 0.25 seconds. (0.25, 0.5, 0.75, 1.0, ...)
+ # 2nd timer every 0.5 seconds in 0.5 seconds. (0.5, 1.0, 1.5, 2.0, ...)
+ # 3rd timer every 0.75 seconds in 0.75 seconds. (0.75, 1.5, 2.25, 3.0, ...)
+ #
+ # In 0.25 seconds, only 1st timer fires.
+ # In 0.5 seconds, 1st timer and 2nd timer fires at once.
+ # In 0.75 seconds, 1st timer and 3rd timer fires at once.
+ # In 1.5 seconds, 1st timer, 2nd timer and 3rd timer fires at once.
+ #
+ # If a timer file descriptor is signaled more than once since
+ # the last os.read() call, os.read() returns the nubmer of signaled
+ # as host order of class bytes.
+ print(f"Signaled events={events}")
+ for fd, event in events:
+ if event & select.EPOLLIN:
+ if fd == sock.fileno():
+ # Check if there is a connection request.
+ print(f"Accepting connection {fd}")
+ conn, addr = sock.accept()
+ conn.setblocking(False)
+ print(f"Accepted connection {conn} from {addr}")
+ ep.register(conn, select.EPOLLIN)
+ elif conn and fd == conn.fileno():
+ # Check if there is data to read.
+ print(f"Reading data {fd}")
+ data = conn.recv(1024)
+ if data:
+ # You should catch UnicodeDecodeError exception for safety.
+ cmd = data.decode()
+ if cmd.startswith("stop"):
+ print(f"Stopping server")
+ is_active = False
+ else:
+ print(f"Unknown command: {cmd}")
+ else:
+ # No more data, close connection
+ print(f"Closing connection {fd}")
+ ep.unregister(conn)
+ conn.close()
+ conn = None
+ elif fd in fds:
+ print(f"Reading timer {fd}")
+ count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)
+ print(f"Timer {fds.index(fd) + 1} expired {count} times")
+ else:
+ print(f"Unknown file descriptor {fd}")
+ finally:
+ for fd in fds:
+ ep.unregister(fd)
+ os.close(fd)
+ ep.close()
+
+This example shows how :func:`~select.select` can be used with timer file
+descriptors to wait until the file descriptor is ready for reading:
+
+.. code-block:: python
+
+ import os, time, select, socket, sys
+
+ # In this example, use loopback address to send "stop" command to the server.
+ #
+ # $ telnet 127.0.0.1 1234
+ # Trying 127.0.0.1...
+ # Connected to 127.0.0.1.
+ # Escape character is '^]'.
+ # stop
+ # Connection closed by foreign host.
+ #
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.bind(("127.0.0.1", 1234))
+ sock.setblocking(False)
+ sock.listen(1)
+
+ # Create timer file descriptors in non-blocking mode.
+ num = 3
+ fds = [os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
+ for _ in range(num)]
+ select_fds = fds + [sock]
+
+ # Start the timers with os.timerfd_settime() in seconds.
+ # Timer 1 fires every 0.25 seconds; timer 2 every 0.5 seconds; etc
+ for i, fd in enumerate(fds, start=1):
+ os.timerfd_settime(fd, initial=i/4, interval=i/4)
+
+ timeout = 3
+ try:
+ conn = None
+ is_active = True
+ while is_active:
+ # Wait for the timer to expire for 3 seconds.
+ # select.select() returns a list of file descriptors or objects.
+ rfd, wfd, xfd = select.select(select_fds, select_fds, select_fds, timeout)
+ for fd in rfd:
+ if fd == sock:
+ # Check if there is a connection request.
+ print(f"Accepting connection {fd}")
+ conn, addr = sock.accept()
+ conn.setblocking(False)
+ print(f"Accepted connection {conn} from {addr}")
+ select_fds.append(conn)
+ elif conn and fd == conn:
+ # Check if there is data to read.
+ print(f"Reading data {fd}")
+ data = conn.recv(1024)
+ if data:
+ # You should catch UnicodeDecodeError exception for safety.
+ cmd = data.decode()
+ if cmd.startswith("stop"):
+ print(f"Stopping server")
+ is_active = False
+ else:
+ print(f"Unknown command: {cmd}")
+ else:
+ # No more data, close connection
+ print(f"Closing connection {fd}")
+ select_fds.remove(conn)
+ conn.close()
+ conn = None
+ elif fd in fds:
+ print(f"Reading timer {fd}")
+ count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)
+ print(f"Timer {fds.index(fd) + 1} expired {count} times")
+ else:
+ print(f"Unknown file descriptor {fd}")
+ finally:
+ for fd in fds:
+ os.close(fd)
+ sock.close()
+ sock = None
+
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index 141ab0b..a1595df 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -3781,6 +3781,217 @@ features:
.. versionadded:: 3.10
+Timer File Descriptors
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.13
+
+These functions provide support for Linux's *timer file descriptor* API.
+Naturally, they are all only available on Linux.
+
+.. function:: timerfd_create(clockid, /, *, flags=0)
+
+ Create and return a timer file descriptor (*timerfd*).
+
+ The file descriptor returned by :func:`timerfd_create` supports:
+
+ - :func:`read`
+ - :func:`~select.select`
+ - :func:`~select.poll`.
+
+ The file descriptor's :func:`read` method can be called with a buffer size
+ of 8. If the timer has already expired one or more times, :func:`read`
+ returns the number of expirations with the host's endianness, which may be
+ converted to an :class:`int` by ``int.from_bytes(x, byteorder=sys.byteorder)``.
+
+ :func:`~select.select` and :func:`~select.poll` can be used to wait until
+ timer expires and the file descriptor is readable.
+
+ *clockid* must be a valid :ref:`clock ID <time-clock-id-constants>`,
+ as defined in the :py:mod:`time` module:
+
+ - :const:`time.CLOCK_REALTIME`
+ - :const:`time.CLOCK_MONOTONIC`
+ - :const:`time.CLOCK_BOOTTIME` (Since Linux 3.15 for timerfd_create)
+
+ If *clockid* is :const:`time.CLOCK_REALTIME`, a settable system-wide
+ real-time clock is used. If system clock is changed, timer setting need
+ to be updated. To cancel timer when system clock is changed, see
+ :const:`TFD_TIMER_CANCEL_ON_SET`.
+
+ If *clockid* is :const:`time.CLOCK_MONOTONIC`, a non-settable monotonically
+ increasing clock is used. Even if the system clock is changed, the timer
+ setting will not be affected.
+
+ If *clockid* is :const:`time.CLOCK_BOOTTIME`, same as :const:`time.CLOCK_MONOTONIC`
+ except it includes any time that the system is suspended.
+
+ The file descriptor's behaviour can be modified by specifying a *flags* value.
+ Any of the following variables may used, combined using bitwise OR
+ (the ``|`` operator):
+
+ - :const:`TFD_NONBLOCK`
+ - :const:`TFD_CLOEXEC`
+
+ If :const:`TFD_NONBLOCK` is not set as a flag, :func:`read` blocks until
+ the timer expires. If it is set as a flag, :func:`read` doesn't block, but
+ If there hasn't been an expiration since the last call to read,
+ :func:`read` raises :class:`OSError` with ``errno`` is set to
+ :const:`errno.EAGAIN`.
+
+ :const:`TFD_CLOEXEC` is always set by Python automatically.
+
+ The file descriptor must be closed with :func:`os.close` when it is no
+ longer needed, or else the file descriptor will be leaked.
+
+ .. seealso:: The :manpage:`timerfd_create(2)` man page.
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+
+.. function:: timerfd_settime(fd, /, *, flags=flags, initial=0.0, interval=0.0)
+
+ Alter a timer file descriptor's internal timer.
+ This function operates the same interval timer as :func:`timerfd_settime_ns`.
+
+ *fd* must be a valid timer file descriptor.
+
+ The timer's behaviour can be modified by specifying a *flags* value.
+ Any of the following variables may used, combined using bitwise OR
+ (the ``|`` operator):
+
+ - :const:`TFD_TIMER_ABSTIME`
+ - :const:`TFD_TIMER_CANCEL_ON_SET`
+
+ The timer is disabled by setting *initial* to zero (``0``).
+ If *initial* is equal to or greater than zero, the timer is enabled.
+ If *initial* is less than zero, it raises an :class:`OSError` exception
+ with ``errno`` set to :const:`errno.EINVAL`
+
+ By default the timer will fire when *initial* seconds have elapsed.
+ (If *initial* is zero, timer will fire immediately.)
+
+ However, if the :const:`TFD_TIMER_ABSTIME` flag is set,
+ the timer will fire when the timer's clock
+ (set by *clockid* in :func:`timerfd_create`) reaches *initial* seconds.
+
+ The timer's interval is set by the *interval* :py:class:`float`.
+ If *interval* is zero, the timer only fires once, on the initial expiration.
+ If *interval* is greater than zero, the timer fires every time *interval*
+ seconds have elapsed since the previous expiration.
+ If *interval* is less than zero, it raises :class:`OSError` with ``errno``
+ set to :const:`errno.EINVAL`
+
+ If the :const:`TFD_TIMER_CANCEL_ON_SET` flag is set along with
+ :const:`TFD_TIMER_ABSTIME` and the clock for this timer is
+ :const:`time.CLOCK_REALTIME`, the timer is marked as cancelable if the
+ real-time clock is changed discontinuously. Reading the descriptor is
+ aborted with the error ECANCELED.
+
+ Linux manages system clock as UTC. A daylight-savings time transition is
+ done by changing time offset only and doesn't cause discontinuous system
+ clock change.
+
+ Discontinuous system clock change will be caused by the following events:
+
+ - ``settimeofday``
+ - ``clock_settime``
+ - set the system date and time by ``date`` command
+
+ Return a two-item tuple of (``next_expiration``, ``interval``) from
+ the previous timer state, before this function executed.
+
+ .. seealso::
+
+ :manpage:`timerfd_create(2)`, :manpage:`timerfd_settime(2)`,
+ :manpage:`settimeofday(2)`, :manpage:`clock_settime(2)`,
+ and :manpage:`date(1)`.
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+
+.. function:: timerfd_settime_ns(fd, /, *, flags=0, initial=0, interval=0)
+
+ Similar to :func:`timerfd_settime`, but use time as nanoseconds.
+ This function operates the same interval timer as :func:`timerfd_settime`.
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+
+.. function:: timerfd_gettime(fd, /)
+
+ Return a two-item tuple of floats (``next_expiration``, ``interval``).
+
+ ``next_expiration`` denotes the relative time until next the timer next fires,
+ regardless of if the :const:`TFD_TIMER_ABSTIME` flag is set.
+
+ ``interval`` denotes the timer's interval.
+ If zero, the timer will only fire once, after ``next_expiration`` seconds
+ have elapsed.
+
+ .. seealso:: :manpage:`timerfd_gettime(2)`
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+
+.. function:: timerfd_gettime_ns(fd, /)
+
+ Similar to :func:`timerfd_gettime`, but return time as nanoseconds.
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+.. data:: TFD_NONBLOCK
+
+ A flag for the :func:`timerfd_create` function,
+ which sets the :const:`O_NONBLOCK` status flag for the new timer file
+ descriptor. If :const:`TFD_NONBLOCK` is not set as a flag, :func:`read` blocks.
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+.. data:: TFD_CLOEXEC
+
+ A flag for the :func:`timerfd_create` function,
+ If :const:`TFD_CLOEXEC` is set as a flag, set close-on-exec flag for new file
+ descriptor.
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+.. data:: TFD_TIMER_ABSTIME
+
+ A flag for the :func:`timerfd_settime` and :func:`timerfd_settime_ns` functions.
+ If this flag is set, *initial* is interpreted as an absolute value on the
+ timer's clock (in UTC seconds or nanoseconds since the Unix Epoch).
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+.. data:: TFD_TIMER_CANCEL_ON_SET
+
+ A flag for the :func:`timerfd_settime` and :func:`timerfd_settime_ns`
+ functions along with :const:`TFD_TIMER_ABSTIME`.
+ The timer is cancelled when the time of the underlying clock changes
+ discontinuously.
+
+ .. availability:: Linux >= 2.6.27 with glibc >= 2.8
+
+ .. versionadded:: 3.13
+
+
Linux extended attributes
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index d5987ae..73975b0 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -180,6 +180,14 @@ os
usable by the calling thread of the current process.
(Contributed by Victor Stinner in :gh:`109649`.)
+* Add a low level interface for Linux's timer notification file descriptors
+ via :func:`os.timerfd_create`,
+ :func:`os.timerfd_settime`, :func:`os.timerfd_settime_ns`,
+ :func:`os.timerfd_gettime`, and :func:`os.timerfd_gettime_ns`,
+ :const:`os.TFD_NONBLOCK`, :const:`os.TFD_CLOEXEC`,
+ :const:`os.TFD_TIMER_ABSTIME`, and :const:`os.TFD_TIMER_CANCEL_ON_SET`
+ (Contributed by Masaru Tsuchiyama in :gh:`108277`.)
+
pathlib
-------
diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h
index 6361f5d..8fb22f7 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -993,6 +993,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(instructions));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intern));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(interval));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_running));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isinstance));
diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index 504008d..39ffda8 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -482,6 +482,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(instructions)
STRUCT_FOR_ID(intern)
STRUCT_FOR_ID(intersection)
+ STRUCT_FOR_ID(interval)
STRUCT_FOR_ID(is_running)
STRUCT_FOR_ID(isatty)
STRUCT_FOR_ID(isinstance)
diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h
index 8cc3287..43a8243 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -991,6 +991,7 @@ extern "C" {
INIT_ID(instructions), \
INIT_ID(intern), \
INIT_ID(intersection), \
+ INIT_ID(interval), \
INIT_ID(is_running), \
INIT_ID(isatty), \
INIT_ID(isinstance), \
diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h
index d8ea63a..46713f9 100644
--- a/Include/internal/pycore_time.h
+++ b/Include/internal/pycore_time.h
@@ -143,6 +143,10 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimespec(
// Export for '_socket' shared extension.
PyAPI_FUNC(_PyTime_t) _PyTime_FromSeconds(int seconds);
+// Create a timestamp from a number of seconds in double.
+// Export for '_socket' shared extension.
+PyAPI_FUNC(_PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round);
+
// Macro to create a timestamp from a number of seconds, no integer overflow.
// Only use the macro for small values, prefer _PyTime_FromSeconds().
#define _PYTIME_FROMSECONDS(seconds) \
@@ -241,7 +245,7 @@ PyAPI_FUNC(int) _PyTime_AsTimevalTime_t(
#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_KQUEUE)
// Create a timestamp from a timespec structure.
// Raise an exception and return -1 on overflow, return 0 on success.
-extern int _PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts);
+extern int _PyTime_FromTimespec(_PyTime_t *tp, const struct timespec *ts);
// Convert a timestamp to a timespec structure (nanosecond resolution).
// tv_nsec is always positive.
diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h
index 50400db..729d54b 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -1287,6 +1287,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(intersection);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(interval);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(is_running);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 669e27c..5149b0d 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -3926,6 +3926,356 @@ class EventfdTests(unittest.TestCase):
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 timerfd_create(self, *args, **kwargs):
+ fd = os.timerfd_create(*args, **kwargs)
+ self.assertGreaterEqual(fd, 0)
+ self.assertFalse(os.get_inheritable(fd))
+ self.addCleanup(os.close, fd)
+ return fd
+
+ def test_timerfd_initval(self):
+ fd = self.timerfd_create(time.CLOCK_REALTIME)
+
+ initial_expiration = 0.25
+ interval = 0.125
+
+ # 1st call
+ next_expiration, interval2 = os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
+ self.assertAlmostEqual(interval2, 0.0, places=3)
+ self.assertAlmostEqual(next_expiration, 0.0, places=3)
+
+ # 2nd call
+ next_expiration, interval2 = os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
+ self.assertAlmostEqual(interval2, interval, places=3)
+ self.assertAlmostEqual(next_expiration, initial_expiration, places=3)
+
+ # timerfd_gettime
+ next_expiration, interval2 = os.timerfd_gettime(fd)
+ self.assertAlmostEqual(interval2, interval, places=3)
+ self.assertAlmostEqual(next_expiration, initial_expiration, places=3)
+
+ def test_timerfd_non_blocking(self):
+ size = 8 # read 8 bytes
+ fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
+
+ # 0.1 second later
+ initial_expiration = 0.1
+ _, _ = os.timerfd_settime(fd, initial=initial_expiration, interval=0)
+
+ # read() raises OSError with errno is EAGAIN for non-blocking timer.
+ with self.assertRaises(OSError) as ctx:
+ _ = os.read(fd, size)
+ self.assertEqual(ctx.exception.errno, errno.EAGAIN)
+
+ # Wait more than 0.1 seconds
+ time.sleep(initial_expiration + 0.1)
+
+ # confirm if timerfd is readable and read() returns 1 as bytes.
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, 1)
+
+ def test_timerfd_negative(self):
+ one_sec_in_nsec = 10**9
+ fd = self.timerfd_create(time.CLOCK_REALTIME)
+
+ # Any of 'initial' and 'interval' is negative value.
+ for initial, interval in ( (-1, 0), (1, -1), (-1, -1), (-0.1, 0), (1, -0.1), (-0.1, -0.1)):
+ for flags in (0, os.TFD_TIMER_ABSTIME, os.TFD_TIMER_ABSTIME|os.TFD_TIMER_CANCEL_ON_SET):
+ with self.subTest(flags=flags, initial=initial, interval=interval):
+ with self.assertRaises(OSError) as context:
+ _, _ = os.timerfd_settime(fd, flags=flags, initial=initial, interval=interval)
+ self.assertEqual(context.exception.errno, errno.EINVAL)
+
+ with self.assertRaises(OSError) as context:
+ initial_ns = int( one_sec_in_nsec * initial )
+ interval_ns = int( one_sec_in_nsec * interval )
+ _, _ = os.timerfd_settime_ns(fd, flags=flags, initial=initial_ns, interval=interval_ns)
+ self.assertEqual(context.exception.errno, errno.EINVAL)
+
+ def test_timerfd_interval(self):
+ size = 8 # read 8 bytes
+ fd = self.timerfd_create(time.CLOCK_REALTIME)
+
+ # 1 second
+ initial_expiration = 1
+ # 0.5 second
+ interval = 0.5
+
+ _, _ = os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
+
+ # timerfd_gettime
+ next_expiration, interval2 = os.timerfd_gettime(fd)
+ self.assertAlmostEqual(interval2, interval, places=3)
+ self.assertAlmostEqual(next_expiration, initial_expiration, places=3)
+
+ count = 3
+ t = time.perf_counter()
+ for _ in range(count):
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, 1)
+ t = time.perf_counter() - t
+
+ total_time = initial_expiration + interval * (count - 1)
+ self.assertGreater(t, total_time)
+
+ # wait 3.5 time of interval
+ time.sleep( (count+0.5) * interval)
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, count)
+
+ def test_timerfd_TFD_TIMER_ABSTIME(self):
+ size = 8 # read 8 bytes
+ fd = self.timerfd_create(time.CLOCK_REALTIME)
+
+ now = time.clock_gettime(time.CLOCK_REALTIME)
+
+ # 1 second later from now.
+ offset = 1
+ initial_expiration = now + offset
+ # not interval timer
+ interval = 0
+
+ _, _ = os.timerfd_settime(fd, flags=os.TFD_TIMER_ABSTIME, initial=initial_expiration, interval=interval)
+
+ # timerfd_gettime
+ # Note: timerfd_gettime returns relative values even if TFD_TIMER_ABSTIME is specified.
+ next_expiration, interval2 = os.timerfd_gettime(fd)
+ self.assertAlmostEqual(interval2, interval, places=3)
+ self.assertAlmostEqual(next_expiration, offset, places=3)
+
+ t = time.perf_counter()
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ t = time.perf_counter() - t
+ self.assertEqual(count_signaled, 1)
+
+ self.assertGreater(t, offset)
+
+ def test_timerfd_select(self):
+ size = 8 # read 8 bytes
+ fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
+
+ rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
+ self.assertEqual((rfd, wfd, xfd), ([], [], []))
+
+ # 0.25 second
+ initial_expiration = 0.25
+ # every 0.125 second
+ interval = 0.125
+
+ _, _ = os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
+
+ count = 3
+ t = time.perf_counter()
+ for _ in range(count):
+ rfd, wfd, xfd = select.select([fd], [fd], [fd], initial_expiration + interval)
+ self.assertEqual((rfd, wfd, xfd), ([fd], [], []))
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, 1)
+ t = time.perf_counter() - t
+
+ total_time = initial_expiration + interval * (count - 1)
+ self.assertGreater(t, total_time)
+
+ def test_timerfd_epoll(self):
+ size = 8 # read 8 bytes
+ fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
+
+ ep = select.epoll()
+ ep.register(fd, select.EPOLLIN)
+ self.addCleanup(ep.close)
+
+ # 0.25 second
+ initial_expiration = 0.25
+ # every 0.125 second
+ interval = 0.125
+
+ _, _ = os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
+
+ count = 3
+ t = time.perf_counter()
+ for i in range(count):
+ timeout_margin = interval
+ if i == 0:
+ timeout = initial_expiration + interval + timeout_margin
+ else:
+ timeout = interval + timeout_margin
+ # epoll timeout is in seconds.
+ events = ep.poll(timeout)
+ self.assertEqual(events, [(fd, select.EPOLLIN)])
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, 1)
+
+ t = time.perf_counter() - t
+
+ total_time = initial_expiration + interval * (count - 1)
+ self.assertGreater(t, total_time)
+ ep.unregister(fd)
+
+ def test_timerfd_ns_initval(self):
+ one_sec_in_nsec = 10**9
+ limit_error = one_sec_in_nsec // 10**3
+ fd = self.timerfd_create(time.CLOCK_REALTIME)
+
+ # 1st call
+ initial_expiration_ns = 0
+ interval_ns = one_sec_in_nsec // 1000
+ next_expiration_ns, interval_ns2 = os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
+ self.assertEqual(interval_ns2, 0)
+ self.assertEqual(next_expiration_ns, 0)
+
+ # 2nd call
+ next_expiration_ns, interval_ns2 = os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
+ self.assertEqual(interval_ns2, interval_ns)
+ self.assertEqual(next_expiration_ns, initial_expiration_ns)
+
+ # timerfd_gettime
+ next_expiration_ns, interval_ns2 = os.timerfd_gettime_ns(fd)
+ self.assertEqual(interval_ns2, interval_ns)
+ self.assertLessEqual(next_expiration_ns, initial_expiration_ns)
+
+ self.assertAlmostEqual(next_expiration_ns, initial_expiration_ns, delta=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 = self.timerfd_create(time.CLOCK_REALTIME)
+
+ # 1 second
+ initial_expiration_ns = one_sec_in_nsec
+ # every 0.5 second
+ interval_ns = one_sec_in_nsec // 2
+
+ _, _ = os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
+
+ # timerfd_gettime
+ next_expiration_ns, interval_ns2 = os.timerfd_gettime_ns(fd)
+ self.assertEqual(interval_ns2, interval_ns)
+ self.assertLessEqual(next_expiration_ns, initial_expiration_ns)
+
+ count = 3
+ t = time.perf_counter_ns()
+ for _ in range(count):
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, 1)
+ t = time.perf_counter_ns() - t
+
+ total_time_ns = initial_expiration_ns + interval_ns * (count - 1)
+ self.assertGreater(t, total_time_ns)
+
+ # wait 3.5 time of interval
+ time.sleep( (count+0.5) * interval_ns / one_sec_in_nsec)
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, count)
+
+
+ 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 = self.timerfd_create(time.CLOCK_REALTIME)
+
+ now_ns = time.clock_gettime_ns(time.CLOCK_REALTIME)
+
+ # 1 second later from now.
+ offset_ns = one_sec_in_nsec
+ initial_expiration_ns = now_ns + offset_ns
+ # not interval timer
+ interval_ns = 0
+
+ _, _ = os.timerfd_settime_ns(fd, flags=os.TFD_TIMER_ABSTIME, initial=initial_expiration_ns, interval=interval_ns)
+
+ # timerfd_gettime
+ # Note: timerfd_gettime returns relative values even if TFD_TIMER_ABSTIME is specified.
+ next_expiration_ns, interval_ns2 = os.timerfd_gettime_ns(fd)
+ self.assertLess(abs(interval_ns2 - interval_ns), limit_error)
+ self.assertLess(abs(next_expiration_ns - offset_ns), limit_error)
+
+ t = time.perf_counter_ns()
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ t = time.perf_counter_ns() - t
+ self.assertEqual(count_signaled, 1)
+
+ self.assertGreater(t, offset_ns)
+
+ def test_timerfd_ns_select(self):
+ size = 8 # read 8 bytes
+ one_sec_in_nsec = 10**9
+
+ fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
+
+ rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
+ self.assertEqual((rfd, wfd, xfd), ([], [], []))
+
+ # 0.25 second
+ initial_expiration_ns = one_sec_in_nsec // 4
+ # every 0.125 second
+ interval_ns = one_sec_in_nsec // 8
+
+ _, _ = os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
+
+ count = 3
+ t = time.perf_counter_ns()
+ for _ in range(count):
+ rfd, wfd, xfd = select.select([fd], [fd], [fd], (initial_expiration_ns + interval_ns) / 1e9 )
+ self.assertEqual((rfd, wfd, xfd), ([fd], [], []))
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, 1)
+ t = time.perf_counter_ns() - t
+
+ total_time_ns = initial_expiration_ns + interval_ns * (count - 1)
+ self.assertGreater(t, total_time_ns)
+
+ def test_timerfd_ns_epoll(self):
+ size = 8 # read 8 bytes
+ one_sec_in_nsec = 10**9
+ fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
+
+ ep = select.epoll()
+ ep.register(fd, select.EPOLLIN)
+ self.addCleanup(ep.close)
+
+ # 0.25 second
+ initial_expiration_ns = one_sec_in_nsec // 4
+ # every 0.125 second
+ interval_ns = one_sec_in_nsec // 8
+
+ _, _ = os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
+
+ count = 3
+ t = time.perf_counter_ns()
+ for i in range(count):
+ timeout_margin_ns = interval_ns
+ if i == 0:
+ timeout_ns = initial_expiration_ns + interval_ns + timeout_margin_ns
+ else:
+ timeout_ns = interval_ns + timeout_margin_ns
+
+ # epoll timeout is in seconds.
+ events = ep.poll(timeout_ns / one_sec_in_nsec)
+ self.assertEqual(events, [(fd, select.EPOLLIN)])
+ n = os.read(fd, size)
+ count_signaled = int.from_bytes(n, byteorder=sys.byteorder)
+ self.assertEqual(count_signaled, 1)
+
+ t = time.perf_counter_ns() - t
+
+ total_time = initial_expiration_ns + interval_ns * (count - 1)
+ self.assertGreater(t, total_time)
+ ep.unregister(fd)
class OSErrorTests(unittest.TestCase):
def setUp(self):
diff --git a/Misc/NEWS.d/next/Library/2023-08-23-22-08-32.gh-issue-108277.KLV-6T.rst b/Misc/NEWS.d/next/Library/2023-08-23-22-08-32.gh-issue-108277.KLV-6T.rst
new file mode 100644
index 0000000..6f99e0b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-08-23-22-08-32.gh-issue-108277.KLV-6T.rst
@@ -0,0 +1 @@
+Add :func:`os.timerfd_create`, :func:`os.timerfd_settime`, :func:`os.timerfd_gettime`, :func:`os.timerfd_settime_ns`, and :func:`os.timerfd_gettime_ns` to provide a low level interface for Linux's timer notification file descriptor.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 0238d3a..1791327 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -6022,6 +6022,376 @@ 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=0)\n"
+"--\n"
+"\n"
+"Create and return a timer file descriptor.\n"
+"\n"
+" clockid\n"
+" A valid clock ID constant as timer file descriptor.\n"
+"\n"
+" time.CLOCK_REALTIME\n"
+" time.CLOCK_MONOTONIC\n"
+" time.CLOCK_BOOTTIME\n"
+" flags\n"
+" 0 or a bit mask of os.TFD_NONBLOCK or os.TFD_CLOEXEC.\n"
+"\n"
+" os.TFD_NONBLOCK\n"
+" If *TFD_NONBLOCK* is set as a flag, read doesn\'t blocks.\n"
+" If *TFD_NONBLOCK* is not set as a flag, read block until the timer fires.\n"
+"\n"
+" os.TFD_CLOEXEC\n"
+" If *TFD_CLOEXEC* is set as a flag, enable the close-on-exec flag");
+
+#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 1
+ 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(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[] = {"", "flags", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "timerfd_create",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+ int clockid;
+ int flags = 0;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ clockid = PyLong_AsInt(args[0]);
+ if (clockid == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ flags = PyLong_AsInt(args[1]);
+ if (flags == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ 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_settime__doc__,
+"timerfd_settime($module, fd, /, *, flags=0, initial=0.0, interval=0.0)\n"
+"--\n"
+"\n"
+"Alter a timer file descriptor\'s internal timer in seconds.\n"
+"\n"
+" fd\n"
+" A timer file descriptor.\n"
+" flags\n"
+" 0 or a bit mask of TFD_TIMER_ABSTIME or TFD_TIMER_CANCEL_ON_SET.\n"
+" initial\n"
+" The initial expiration time, in seconds.\n"
+" interval\n"
+" The timer\'s interval, in seconds.");
+
+#define OS_TIMERFD_SETTIME_METHODDEF \
+ {"timerfd_settime", _PyCFunction_CAST(os_timerfd_settime), METH_FASTCALL|METH_KEYWORDS, os_timerfd_settime__doc__},
+
+static PyObject *
+os_timerfd_settime_impl(PyObject *module, int fd, int flags, double initial,
+ double interval);
+
+static PyObject *
+os_timerfd_settime(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 3
+ 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(flags), &_Py_ID(initial), &_Py_ID(interval), },
+ };
+ #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[] = {"", "flags", "initial", "interval", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "timerfd_settime",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[4];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+ int fd;
+ int flags = 0;
+ double initial = 0.0;
+ double interval = 0.0;
+
+ 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;
+ }
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ if (args[1]) {
+ flags = PyLong_AsInt(args[1]);
+ if (flags == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ if (args[2]) {
+ if (PyFloat_CheckExact(args[2])) {
+ initial = PyFloat_AS_DOUBLE(args[2]);
+ }
+ else
+ {
+ initial = PyFloat_AsDouble(args[2]);
+ if (initial == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ if (PyFloat_CheckExact(args[3])) {
+ interval = PyFloat_AS_DOUBLE(args[3]);
+ }
+ else
+ {
+ interval = PyFloat_AsDouble(args[3]);
+ if (interval == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+skip_optional_kwonly:
+ return_value = os_timerfd_settime_impl(module, fd, flags, initial, interval);
+
+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=0, initial=0, interval=0)\n"
+"--\n"
+"\n"
+"Alter a timer file descriptor\'s internal timer in nanoseconds.\n"
+"\n"
+" fd\n"
+" A timer file descriptor.\n"
+" flags\n"
+" 0 or a bit mask of TFD_TIMER_ABSTIME or TFD_TIMER_CANCEL_ON_SET.\n"
+" initial\n"
+" initial expiration timing in seconds.\n"
+" interval\n"
+" interval for the timer in seconds.");
+
+#define OS_TIMERFD_SETTIME_NS_METHODDEF \
+ {"timerfd_settime_ns", _PyCFunction_CAST(os_timerfd_settime_ns), METH_FASTCALL|METH_KEYWORDS, os_timerfd_settime_ns__doc__},
+
+static PyObject *
+os_timerfd_settime_ns_impl(PyObject *module, int fd, int flags,
+ long long initial, long long interval);
+
+static PyObject *
+os_timerfd_settime_ns(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 3
+ 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(flags), &_Py_ID(initial), &_Py_ID(interval), },
+ };
+ #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[] = {"", "flags", "initial", "interval", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "timerfd_settime_ns",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[4];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+ int fd;
+ int flags = 0;
+ long long initial = 0;
+ long long interval = 0;
+
+ 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;
+ }
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ if (args[1]) {
+ flags = PyLong_AsInt(args[1]);
+ if (flags == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ if (args[2]) {
+ initial = PyLong_AsLongLong(args[2]);
+ if (initial == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ interval = PyLong_AsLongLong(args[3]);
+ if (interval == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = os_timerfd_settime_ns_impl(module, fd, flags, initial, interval);
+
+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"
+"Return a tuple of a timer file descriptor\'s (interval, next expiration) in float seconds.\n"
+"\n"
+" fd\n"
+" A timer file descriptor.");
+
+#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_gettime_ns__doc__,
+"timerfd_gettime_ns($module, fd, /)\n"
+"--\n"
+"\n"
+"Return a tuple of a timer file descriptor\'s (interval, next expiration) in nanoseconds.\n"
+"\n"
+" fd\n"
+" A timer file descriptor.");
+
+#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_GETSID)
PyDoc_STRVAR(os_getsid__doc__,
@@ -11761,6 +12131,26 @@ exit:
#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_SETTIME_METHODDEF
+ #define OS_TIMERFD_SETTIME_METHODDEF
+#endif /* !defined(OS_TIMERFD_SETTIME_METHODDEF) */
+
+#ifndef OS_TIMERFD_SETTIME_NS_METHODDEF
+ #define OS_TIMERFD_SETTIME_NS_METHODDEF
+#endif /* !defined(OS_TIMERFD_SETTIME_NS_METHODDEF) */
+
+#ifndef OS_TIMERFD_GETTIME_METHODDEF
+ #define OS_TIMERFD_GETTIME_METHODDEF
+#endif /* !defined(OS_TIMERFD_GETTIME_METHODDEF) */
+
+#ifndef OS_TIMERFD_GETTIME_NS_METHODDEF
+ #define OS_TIMERFD_GETTIME_NS_METHODDEF
+#endif /* !defined(OS_TIMERFD_GETTIME_NS_METHODDEF) */
+
#ifndef OS_GETSID_METHODDEF
#define OS_GETSID_METHODDEF
#endif /* !defined(OS_GETSID_METHODDEF) */
@@ -12024,4 +12414,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=a36904281a8a7507 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=7c3058135ed49d20 input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 2c32a45..0975ef7 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -550,6 +550,11 @@ extern char *ctermid_r(char *);
# include <sys/eventfd.h>
#endif
+/* timerfd_create() */
+#ifdef HAVE_SYS_TIMERFD_H
+# include <sys/timerfd.h>
+#endif
+
#ifdef _Py_MEMORY_SANITIZER
# include <sanitizer/msan_interface.h>
#endif
@@ -10096,6 +10101,227 @@ 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)
+#define CONVERT_SEC_AND_NSEC_TO_DOUBLE(sec, nsec) ( (double)(sec) + (double)(nsec) * 1e-9 )
+
+static PyObject *
+build_itimerspec(const struct itimerspec* curr_value)
+{
+ double _value = CONVERT_SEC_AND_NSEC_TO_DOUBLE(curr_value->it_value.tv_sec,
+ curr_value->it_value.tv_nsec);
+ PyObject *value = PyFloat_FromDouble(_value);
+ if (value == NULL) {
+ return NULL;
+ }
+ double _interval = CONVERT_SEC_AND_NSEC_TO_DOUBLE(curr_value->it_interval.tv_sec,
+ curr_value->it_interval.tv_nsec);
+ PyObject *interval = PyFloat_FromDouble(_interval);
+ if (interval == NULL) {
+ Py_DECREF(value);
+ return NULL;
+ }
+ PyObject *tuple = PyTuple_Pack(2, value, interval);
+ Py_DECREF(interval);
+ Py_DECREF(value);
+ return tuple;
+}
+
+static PyObject *
+build_itimerspec_ns(const struct itimerspec* curr_value)
+{
+ _PyTime_t value, interval;
+ if (_PyTime_FromTimespec(&value, &curr_value->it_value) < 0) {
+ return NULL;
+ }
+ if (_PyTime_FromTimespec(&interval, &curr_value->it_interval) < 0) {
+ return NULL;
+ }
+ return Py_BuildValue("LL", value, interval);
+}
+
+/*[clinic input]
+os.timerfd_create
+
+ clockid: int
+ A valid clock ID constant as timer file descriptor.
+
+ time.CLOCK_REALTIME
+ time.CLOCK_MONOTONIC
+ time.CLOCK_BOOTTIME
+ /
+ *
+ flags: int = 0
+ 0 or a bit mask of os.TFD_NONBLOCK or os.TFD_CLOEXEC.
+
+ os.TFD_NONBLOCK
+ If *TFD_NONBLOCK* is set as a flag, read doesn't blocks.
+ If *TFD_NONBLOCK* is not set as a flag, read block until the timer fires.
+
+ os.TFD_CLOEXEC
+ If *TFD_CLOEXEC* is set as a flag, enable the close-on-exec flag
+
+Create and return a timer 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=64b7020c5ac0b8f4]*/
+
+{
+ int fd;
+ Py_BEGIN_ALLOW_THREADS
+ flags |= TFD_CLOEXEC; // PEP 446: always create non-inheritable FD
+ 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_settime
+
+ fd: fildes
+ A timer file descriptor.
+ /
+ *
+ flags: int = 0
+ 0 or a bit mask of TFD_TIMER_ABSTIME or TFD_TIMER_CANCEL_ON_SET.
+ initial: double = 0.0
+ The initial expiration time, in seconds.
+ interval: double = 0.0
+ The timer's interval, in seconds.
+
+Alter a timer file descriptor's internal timer in seconds.
+[clinic start generated code]*/
+
+static PyObject *
+os_timerfd_settime_impl(PyObject *module, int fd, int flags, double initial,
+ double interval)
+/*[clinic end generated code: output=0dda31115317adb9 input=6c24e47e7a4d799e]*/
+{
+ struct itimerspec new_value;
+ struct itimerspec old_value;
+ int result;
+ if (_PyTime_AsTimespec(_PyTime_FromSecondsDouble(initial, _PyTime_ROUND_FLOOR), &new_value.it_value) < 0) {
+ PyErr_SetString(PyExc_ValueError, "invalid initial value");
+ return NULL;
+ }
+ if (_PyTime_AsTimespec(_PyTime_FromSecondsDouble(interval, _PyTime_ROUND_FLOOR), &new_value.it_interval) < 0) {
+ PyErr_SetString(PyExc_ValueError, "invalid interval value");
+ return NULL;
+ }
+ 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_settime_ns
+
+ fd: fildes
+ A timer file descriptor.
+ /
+ *
+ flags: int = 0
+ 0 or a bit mask of TFD_TIMER_ABSTIME or TFD_TIMER_CANCEL_ON_SET.
+ initial: long_long = 0
+ initial expiration timing in seconds.
+ interval: long_long = 0
+ interval for the timer in seconds.
+
+Alter a timer file descriptor's internal timer in nanoseconds.
+[clinic start generated code]*/
+
+static PyObject *
+os_timerfd_settime_ns_impl(PyObject *module, int fd, int flags,
+ long long initial, long long interval)
+/*[clinic end generated code: output=6273ec7d7b4cc0b3 input=261e105d6e42f5bc]*/
+{
+ struct itimerspec new_value;
+ struct itimerspec old_value;
+ int result;
+ if (_PyTime_AsTimespec(initial, &new_value.it_value) < 0) {
+ PyErr_SetString(PyExc_ValueError, "invalid initial value");
+ return NULL;
+ }
+ if (_PyTime_AsTimespec(interval, &new_value.it_interval) < 0) {
+ PyErr_SetString(PyExc_ValueError, "invalid interval value");
+ return NULL;
+ }
+ 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);
+}
+
+/*[clinic input]
+os.timerfd_gettime
+
+ fd: fildes
+ A timer file descriptor.
+ /
+
+Return a tuple of a timer file descriptor's (interval, next expiration) in float seconds.
+[clinic start generated code]*/
+
+static PyObject *
+os_timerfd_gettime_impl(PyObject *module, int fd)
+/*[clinic end generated code: output=ec5a94a66cfe6ab4 input=8148e3430870da1c]*/
+{
+ 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_gettime_ns
+
+ fd: fildes
+ A timer file descriptor.
+ /
+
+Return a tuple of a timer file descriptor's (interval, next expiration) in nanoseconds.
+[clinic start generated code]*/
+
+static PyObject *
+os_timerfd_gettime_ns_impl(PyObject *module, int fd)
+/*[clinic end generated code: output=580633a4465f39fe input=a825443e4c6b40ac]*/
+{
+ 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);
+}
+
+#undef ONE_SECOND_IN_NS
+#undef EXTRACT_NSEC
+
+#endif /* HAVE_TIMERFD_CREATE */
+
#ifdef HAVE_GETSID
/*[clinic input]
os.getsid
@@ -16028,6 +16254,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_SETTIME_NS_METHODDEF
+ OS_TIMERFD_GETTIME_METHODDEF
+ OS_TIMERFD_GETTIME_NS_METHODDEF
OS__PATH_ISDEVDRIVE_METHODDEF
OS__PATH_ISDIR_METHODDEF
@@ -16343,6 +16574,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;
@@ -16741,6 +16985,10 @@ static const struct have_function {
{"HAVE_EVENTFD", NULL},
#endif
+#ifdef HAVE_TIMERFD_CREATE
+ {"HAVE_TIMERFD_CREATE", NULL},
+#endif
+
#ifdef HAVE_FACCESSAT
{ "HAVE_FACCESSAT", probe_faccessat },
#endif
diff --git a/Python/pytime.c b/Python/pytime.c
index d1e29e5..e4813d4 100644
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -470,7 +470,7 @@ _PyTime_FromNanosecondsObject(_PyTime_t *tp, PyObject *obj)
#ifdef HAVE_CLOCK_GETTIME
static int
-pytime_fromtimespec(_PyTime_t *tp, struct timespec *ts, int raise_exc)
+pytime_fromtimespec(_PyTime_t *tp, const struct timespec *ts, int raise_exc)
{
_PyTime_t t, tv_nsec;
@@ -493,7 +493,7 @@ pytime_fromtimespec(_PyTime_t *tp, struct timespec *ts, int raise_exc)
}
int
-_PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts)
+_PyTime_FromTimespec(_PyTime_t *tp, const struct timespec *ts)
{
return pytime_fromtimespec(tp, ts, 1);
}
@@ -635,6 +635,16 @@ _PyTime_AsNanosecondsObject(_PyTime_t t)
return PyLong_FromLongLong((long long)ns);
}
+_PyTime_t
+_PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round)
+{
+ _PyTime_t tp;
+ if(pytime_from_double(&tp, seconds, round, SEC_TO_NS) < 0) {
+ return -1;
+ }
+ return tp;
+}
+
static _PyTime_t
pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k)
diff --git a/configure b/configure
index 0e5f3f6..7c5fdec 100755
--- a/configure
+++ b/configure
@@ -10710,6 +10710,12 @@ 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
then :
@@ -18781,6 +18787,50 @@ 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 <sys/timerfd.h>
+#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
+
+
+
+
# 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 4938681..6093afa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2691,7 +2691,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 \
])
@@ -4744,6 +4744,13 @@ PY_CHECK_FUNC([eventfd], [
#endif
])
+PY_CHECK_FUNC([timerfd_create], [
+#ifdef HAVE_SYS_TIMERFD_H
+#include <sys/timerfd.h>
+#endif
+],
+[HAVE_TIMERFD_CREATE])
+
# 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 c2c75c9..9924a90 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -1363,6 +1363,9 @@
/* Define to 1 if you have the <sys/termio.h> header file. */
#undef HAVE_SYS_TERMIO_H
+/* Define to 1 if you have the <sys/timerfd.h> header file. */
+#undef HAVE_SYS_TIMERFD_H
+
/* Define to 1 if you have the <sys/times.h> header file. */
#undef HAVE_SYS_TIMES_H
@@ -1405,6 +1408,9 @@
/* 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 to 1 if you have the `times' function. */
#undef HAVE_TIMES