10000 gh-108277: Add os.timerfd_create() function (#108382) · python/cpython@de2a403 · GitHub
[go: up one dir, main page]

Skip to content

Commit de2a403

Browse files
m-tmatmaserhiy-storchakaAA-Turnererlend-aaslandvstinner
authored
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>
1 parent 64f158e commit de2a403

17 files changed

+1527
-5
lines changed

Doc/howto/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ Currently, the HOWTOs are:
3333
perf_profiling.rst
3434
annotations.rst
3535
isolating-extensions.rst
36+
timerfd.rst
3637

Doc/howto/timerfd.rst

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
.. _timerfd-howto:
2+
3+
*****************************
4+
timer file descriptor HOWTO
5+
*****************************
6+
7+
:Release: 1.13
8+
9+
This HOWTO discusses Python's support for the linux timer file descriptor.
10+
11+
12+
Examples
13+
========
14+
15+
The following example shows how to use a timer file descriptor
16+
to execute a function twice a second:
17+
18+
.. code-block:: python
19+
20+
# Practical scripts should use really use a non-blocking timer,
21+
# we use a blocking timer here for simplicity.
22+
import os, time
23+
24+
# Create the timer file descriptor
25+
fd = os.timerfd_create(time.CLOCK_REALTIME)
26+
27+
# Start the timer in 1 second, with an interval of half a second
28+
os.timerfd_settime(fd, initial=1, interval=0.5)
29+
30+
try:
31+
# Process timer events four times.
32+
for _ in range(4):
33+
# read() will block until the timer expires
34+
_ = os.read(fd, 8)
35+
print("Timer expired")
36+
finally:
37+
# Remember to close the timer file descriptor!
38+
os.close(fd)
39+
40+
To avoid the precision loss caused by the :class:`float` type,
41+
timer file descriptors allow specifying initial expiration and interval
42+
in integer nanoseconds with ``_ns`` variants of the functions.
43+
44+
This example shows how :func:`~select.epoll` can be used with timer file
45+
descriptors to wait until the file descriptor is ready for reading:
46+
47+
.. code-block:: python
48+
49+
import os, time, select, socket, sys
50+
51+
# Create an epoll object
52+
ep = select.epoll()
53+
54+
# In this example, use loopback address to send "stop" command to the server.
55+
#
56+
# $ telnet 127.0.0.1 1234
57+
# Trying 127.0.0.1...
58+
# Connected to 127.0.0.1.
59+
# Escape character is '^]'.
60+
# stop
61+
# Connection closed by foreign host.
62+
#
63+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
64+
sock.bind(("127.0.0.1", 1234))
65+
sock.setblocking(False)
66+
sock.listen(1)
67+
ep.register(sock, select.EPOLLIN)
68+
69+
# Create timer file descriptors in non-blocking mode.
70+
num = 3
71+
fds = []
72+
for _ in range(num):
73+
fd = os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
74+
fds.append(fd)
75+
# Register the timer file descriptor for read events
76+
ep.register(fd, select.EPOLLIN)
77+
78+
# Start the timer with os.timerfd_settime_ns() in nanoseconds.
79+
# Timer 1 fires every 0.25 seconds; timer 2 every 0.5 seconds; etc
80+
for i, fd in enumerate(fds, start=1):
81+
one_sec_in_nsec = 10**9
82+
i = i * one_sec_in_nsec
83+
os.timerfd_settime_ns(fd, initial=i//4, interval=i//4)
84+
85+
timeout = 3
86+
try:
87+
conn = None
88+
is_active = True
89+
while is_active:
90+
# Wait for the timer to expire for 3 seconds.
91+
# epoll.poll() returns a list of (fd, event) pairs.
92+
# fd is a file descriptor.
93+
# sock and conn[=returned value of socket.accept()] are socket objects, not file descriptors.
94+
# So use sock.fileno() and conn.fileno() to get the file descriptors.
95+
events = ep.poll(timeout)
EF5E 96+
97+
# If more than one timer file descriptors are ready for reading at once,
98+
# epoll.poll() returns a list of (fd, event) pairs.
99+
#
100+
# In this example settings,
101+
# 1st timer fires every 0.25 seconds in 0.25 seconds. (0.25, 0.5, 0.75, 1.0, ...)
102+
# 2nd timer every 0.5 seconds in 0.5 seconds. (0.5, 1.0, 1.5, 2.0, ...)
103+
# 3rd timer every 0.75 seconds in 0.75 seconds. (0.75, 1.5, 2.25, 3.0, ...)
104+
#
105+
# In 0.25 seconds, only 1st timer fires.
106+
# In 0.5 seconds, 1st timer and 2nd timer fires at once.
107+
# In 0.75 seconds, 1st timer and 3rd timer fires at once.
108+
# In 1.5 seconds, 1st timer, 2nd timer and 3rd timer fires at once.
109+
#
110+
# If a timer file descriptor is signaled more than once since
111+
# the last os.read() call, os.read() returns the nubmer of signaled
112+
# as host order of class bytes.
113+
print(f"Signaled events={events}")
114+
for fd, event in events:
115+
if event & select.EPOLLIN:
116+
if fd == sock.fileno():
117+
# Check if there is a connection request.
118+
print(f"Accepting connection {fd}")
119+
conn, addr = sock.accept()
120+
conn.setblocking(False)
121+
print(f"Accepted connection {conn} from {addr}")
122+
ep.register(conn, select.EPOLLIN)
123+
elif conn and fd == conn.fileno():
124+
# Check if there is data to read.
125+
print(f"Reading data {fd}")
126+
data = conn.recv(1024)
127+
if data:
128+
# You should catch UnicodeDecodeError exception for safety.
129+
cmd = data.decode()
130+
if cmd.startswith("stop"):
131+
print(f"Stopping server")
132+
is_active = False
133+
else:
134+
print(f"Unknown command: {cmd}")
135+
else:
136+
# No more data, close connection
137+
print(f"Closing connection {fd}")
138+
ep.unregister(conn)
139+
conn.close()
140+
conn = None
141+
elif fd in fds:
142+
print(f"Reading timer {fd}")
143+
count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)
144+
print(f"Timer {fds.index(fd) + 1} expired {count} times")
145+
else:
146+
print(f"Unknown file descriptor {fd}")
147+
finally:
148+
for fd in fds:
149+
ep.unregister(fd)
150+
os.close(fd)
151+
ep.close()
152+
153+
This example shows how :func:`~select.select` can be used with timer file
154+
descriptors to wait until the file descriptor is ready for reading:
155+
156+
.. code-block:: python
157+
158+
import os, time, select, socket, sys
159+
160+
# In this example, use loopback address to send "stop" command to the server.
161+
#
162+
# $ telnet 127.0.0.1 1234
163+
# Trying 127.0.0.1...
164+
# Connected to 127.0.0.1.
165+
# Escape character is '^]'.
166+
# stop
167< 10000 /code>+
# Connection closed by foreign host.
168+
#
169+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
170+
sock.bind(("127.0.0.1", 1234))
171+
sock.setblocking(False)
172+
sock.listen(1)
173+
174+
# Create timer file descriptors in non-blocking mode.
175+
num = 3
176+
fds = [os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
177+
for _ in range(num)]
178+
select_fds = fds + [sock]
179+
180+
# Start the timers with os.timerfd_settime() in seconds.
181+
# Timer 1 fires every 0.25 seconds; timer 2 every 0.5 seconds; etc
182+
for i, fd in enumerate(fds, start=1):
183+
os.timerfd_settime(fd, initial=i/4, interval=i/4)
184+
185+
timeout = 3
186+
try:
187+
conn = None
188+
is_active = True
189+
while is_active:
190+
# Wait for the timer to expire for 3 seconds.
191+
# select.select() returns a list of file descriptors or objects.
192+
rfd, wfd, xfd = select.select(select_fds, select_fds, select_fds, timeout)
193+
for fd in rfd:
194+
if fd == sock:
195+
# Check if there is a connection request.
196+
print(f"Accepting connection {fd}")
197+
conn, addr = sock.accept()
198+
conn.setblocking(False)
199+
print(f"Accepted connection {conn} from {addr}")
200+
select_fds.append(conn)
201+
elif conn and fd == conn:
202+
# Check if there is data to read.
203+
print(f"Reading data {fd}")
204+
data = conn.recv(1024)
205+
if data:
206+
# You should catch UnicodeDecodeError exception for safety.
207+
cmd = data.decode()
208+
if cmd.startswith("stop"):
209+
print(f"Stopping server")
210+
is_active = False
211+
else:
212+
print(f"Unknown command: {cmd}")
213+
else:
214+
# No more data, close connection
215+
print(f"Closing connection {fd}")
216+
select_fds.remove(conn)
217+
conn.close()
218+
conn = None
219+
elif fd in fds:
220+
print(f"Reading timer {fd}")
221+
count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)
222+
print(f"Timer {fds.index(fd) + 1} expired {count} times")
223+
else:
224+
print(f"Unknown file descriptor {fd}")
225+
finally:
226+
for fd in fds:
227+
os.close(fd)
228+
sock.close()
229+
sock = None
230+

0 commit comments

Comments
 (0)
0