summaryrefslogtreecommitdiffstats
path: root/Doc
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 /Doc
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>
Diffstat (limited to 'Doc')
-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
4 files changed, 450 insertions, 0 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
-------