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>
This commit is contained in:
Masaru Tsuchiyama 2023-10-08 02:33:22 +09:00 committed by GitHub
parent 64f158e7b0
commit de2a4036cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1527 additions and 5 deletions

View File

@ -33,4 +33,5 @@ Currently, the HOWTOs are:
perf_profiling.rst perf_profiling.rst
annotations.rst annotations.rst
isolating-extensions.rst isolating-extensions.rst
timerfd.rst

230
Doc/howto/timerfd.rst Normal file
View File

@ -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

View File

@ -3781,6 +3781,217 @@ features:
.. versionadded:: 3.10 .. 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 Linux extended attributes
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -180,6 +180,14 @@ os
usable by the calling thread of the current process. usable by the calling thread of the current process.
(Contributed by Victor Stinner in :gh:`109649`.) (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 pathlib
------- -------

View File

@ -993,6 +993,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(instructions)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(instructions));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intern)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intern));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(interval));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_running)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_running));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isinstance)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isinstance));

View File

@ -482,6 +482,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(instructions) STRUCT_FOR_ID(instructions)
STRUCT_FOR_ID(intern) STRUCT_FOR_ID(intern)
STRUCT_FOR_ID(intersection) STRUCT_FOR_ID(intersection)
STRUCT_FOR_ID(interval)
STRUCT_FOR_ID(is_running) STRUCT_FOR_ID(is_running)
STRUCT_FOR_ID(isatty) STRUCT_FOR_ID(isatty)
STRUCT_FOR_ID(isinstance) STRUCT_FOR_ID(isinstance)

View File

@ -991,6 +991,7 @@ extern "C" {
INIT_ID(instructions), \ INIT_ID(instructions), \
INIT_ID(intern), \ INIT_ID(intern), \
INIT_ID(intersection), \ INIT_ID(intersection), \
INIT_ID(interval), \
INIT_ID(is_running), \ INIT_ID(is_running), \
INIT_ID(isatty), \ INIT_ID(isatty), \
INIT_ID(isinstance), \ INIT_ID(isinstance), \

View File

@ -143,6 +143,10 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimespec(
// Export for '_socket' shared extension. // Export for '_socket' shared extension.
PyAPI_FUNC(_PyTime_t) _PyTime_FromSeconds(int seconds); 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. // Macro to create a timestamp from a number of seconds, no integer overflow.
// Only use the macro for small values, prefer _PyTime_FromSeconds(). // Only use the macro for small values, prefer _PyTime_FromSeconds().
#define _PYTIME_FROMSECONDS(seconds) \ #define _PYTIME_FROMSECONDS(seconds) \
@ -241,7 +245,7 @@ PyAPI_FUNC(int) _PyTime_AsTimevalTime_t(
#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_KQUEUE) #if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_KQUEUE)
// Create a timestamp from a timespec structure. // Create a timestamp from a timespec structure.
// Raise an exception and return -1 on overflow, return 0 on success. // 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). // Convert a timestamp to a timespec structure (nanosecond resolution).
// tv_nsec is always positive. // tv_nsec is always positive.

View File

@ -1287,6 +1287,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(intersection); string = &_Py_ID(intersection);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string); _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(interval);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(is_running); string = &_Py_ID(is_running);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string); _PyUnicode_InternInPlace(interp, &string);

View File

@ -3926,6 +3926,356 @@ class EventfdTests(unittest.TestCase):
self.assertEqual((rfd, wfd, xfd), ([fd], [], [])) self.assertEqual((rfd, wfd, xfd), ([fd], [], []))
os.eventfd_read(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): class OSErrorTests(unittest.TestCase):
def setUp(self): def setUp(self):

View File

@ -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.

View File

@ -6022,6 +6022,376 @@ os_times(PyObject *module, PyObject *Py_UNUSED(ignored))
#endif /* defined(HAVE_TIMES) */ #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) #if defined(HAVE_GETSID)
PyDoc_STRVAR(os_getsid__doc__, PyDoc_STRVAR(os_getsid__doc__,
@ -11761,6 +12131,26 @@ exit:
#define OS_TIMES_METHODDEF #define OS_TIMES_METHODDEF
#endif /* !defined(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 #ifndef OS_GETSID_METHODDEF
#define OS_GETSID_METHODDEF #define OS_GETSID_METHODDEF
#endif /* !defined(OS_GETSID_METHODDEF) */ #endif /* !defined(OS_GETSID_METHODDEF) */
@ -12024,4 +12414,4 @@ exit:
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#endif /* !defined(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]*/

View File

@ -550,6 +550,11 @@ extern char *ctermid_r(char *);
# include <sys/eventfd.h> # include <sys/eventfd.h>
#endif #endif
/* timerfd_create() */
#ifdef HAVE_SYS_TIMERFD_H
# include <sys/timerfd.h>
#endif
#ifdef _Py_MEMORY_SANITIZER #ifdef _Py_MEMORY_SANITIZER
# include <sanitizer/msan_interface.h> # include <sanitizer/msan_interface.h>
#endif #endif
@ -10096,6 +10101,227 @@ os_times_impl(PyObject *module)
#endif /* HAVE_TIMES */ #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 #ifdef HAVE_GETSID
/*[clinic input] /*[clinic input]
os.getsid os.getsid
@ -16028,6 +16254,11 @@ static PyMethodDef posix_methods[] = {
OS_WAITSTATUS_TO_EXITCODE_METHODDEF OS_WAITSTATUS_TO_EXITCODE_METHODDEF
OS_SETNS_METHODDEF OS_SETNS_METHODDEF
OS_UNSHARE_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_ISDEVDRIVE_METHODDEF
OS__PATH_ISDIR_METHODDEF OS__PATH_ISDIR_METHODDEF
@ -16343,6 +16574,19 @@ all_ins(PyObject *m)
if (PyModule_AddIntMacro(m, SF_NOCACHE)) return -1; if (PyModule_AddIntMacro(m, SF_NOCACHE)) return -1;
#endif #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 */ /* constants for posix_fadvise */
#ifdef POSIX_FADV_NORMAL #ifdef POSIX_FADV_NORMAL
if (PyModule_AddIntMacro(m, POSIX_FADV_NORMAL)) return -1; if (PyModule_AddIntMacro(m, POSIX_FADV_NORMAL)) return -1;
@ -16741,6 +16985,10 @@ static const struct have_function {
{"HAVE_EVENTFD", NULL}, {"HAVE_EVENTFD", NULL},
#endif #endif
#ifdef HAVE_TIMERFD_CREATE
{"HAVE_TIMERFD_CREATE", NULL},
#endif
#ifdef HAVE_FACCESSAT #ifdef HAVE_FACCESSAT
{ "HAVE_FACCESSAT", probe_faccessat }, { "HAVE_FACCESSAT", probe_faccessat },
#endif #endif

View File

@ -470,7 +470,7 @@ _PyTime_FromNanosecondsObject(_PyTime_t *tp, PyObject *obj)
#ifdef HAVE_CLOCK_GETTIME #ifdef HAVE_CLOCK_GETTIME
static int 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; _PyTime_t t, tv_nsec;
@ -493,7 +493,7 @@ pytime_fromtimespec(_PyTime_t *tp, struct timespec *ts, int raise_exc)
} }
int int
_PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts) _PyTime_FromTimespec(_PyTime_t *tp, const struct timespec *ts)
{ {
return pytime_fromtimespec(tp, ts, 1); return pytime_fromtimespec(tp, ts, 1);
} }
@ -635,6 +635,16 @@ _PyTime_AsNanosecondsObject(_PyTime_t t)
return PyLong_FromLongLong((long long)ns); 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 static _PyTime_t
pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k)

50
configure generated vendored
View File

@ -10709,6 +10709,12 @@ if test "x$ac_cv_header_sys_times_h" = xyes
then : then :
printf "%s\n" "#define HAVE_SYS_TIMES_H 1" >>confdefs.h 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 fi
ac_fn_c_check_header_compile "$LINENO" "sys/types.h" "ac_cv_header_sys_types_h" "$ac_includes_default" 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 if test "x$ac_cv_header_sys_types_h" = xyes
@ -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 # On some systems (eg. FreeBSD 5), we would find a definition of the
# functions ctermid_r, setgroups in the library, but no prototype # functions ctermid_r, setgroups in the library, but no prototype
# (e.g. because we use _XOPEN_SOURCE). See whether we can take their # (e.g. because we use _XOPEN_SOURCE). See whether we can take their

View File

@ -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/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/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/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 \ 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 \ termios.h util.h utime.h utmp.h \
]) ])
@ -4744,6 +4744,13 @@ PY_CHECK_FUNC([eventfd], [
#endif #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 # On some systems (eg. FreeBSD 5), we would find a definition of the
# functions ctermid_r, setgroups in the library, but no prototype # functions ctermid_r, setgroups in the library, but no prototype
# (e.g. because we use _XOPEN_SOURCE). See whether we can take their # (e.g. because we use _XOPEN_SOURCE). See whether we can take their

View File

@ -1363,6 +1363,9 @@
/* Define to 1 if you have the <sys/termio.h> header file. */ /* Define to 1 if you have the <sys/termio.h> header file. */
#undef HAVE_SYS_TERMIO_H #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. */ /* Define to 1 if you have the <sys/times.h> header file. */
#undef HAVE_SYS_TIMES_H #undef HAVE_SYS_TIMES_H
@ -1405,6 +1408,9 @@
/* Define to 1 if you have the `timegm' function. */ /* Define to 1 if you have the `timegm' function. */
#undef HAVE_TIMEGM #undef HAVE_TIMEGM
/* Define if you have the 'timerfd_create' function. */
#undef HAVE_TIMERFD_CREATE
/* Define to 1 if you have the `times' function. */ /* Define to 1 if you have the `times' function. */
#undef HAVE_TIMES #undef HAVE_TIMES