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:
# 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 float type,
timer file descriptors allow specifying initial expiration and interval
in integer nanoseconds with _ns variants of the functions.
This example shows how epoll() can be used with timer file
descriptors to wait until the file descriptor is ready for reading:
import os, time, select, socket, sys
# epoll 객체 생성
ep = select.epoll()
# 이 예제에서는 루프백 주소를 사용하여 서버에 "stop" 명령을 보냅니다.
#
# $ telnet 127.0.0.1 1234
# 127.0.0.1에 연결 시도 중...
# 127.0.0.1에 연결되었습니다.
# 이스케이프 문자는 '^]'입니다.
# stop
# 원격 호스트에 의해 연결이 종료되었습니다.
#
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)
# 논블로킹 모드로 타이머 파일 디스크립터들을 생성합니다.
num = 3
fds = []
for _ in range(num):
fd = os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
fds.append(fd)
# 읽기 이벤트에 대해 타이머 파일 디스크립터를 등록합니다.
ep.register(fd, select.EPOLLIN)
# os.timerfd_settime_ns()를 사용하여 나노초 단위로 타이머를 시작합니다.
# 타이머 1은 0.25초마다 작동하고, 타이머 2는 0.5초마다 작동합니다.
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:
# 타이머 만료를 3초간 대기합니다.
# epoll.poll()은 (fd, event) 쌍의 리스트를 반환합니다.
# fd는 파일 디스크립터입니다.
# sock 및 conn[=socket.accept()의 반환 값]은 파일 디스크립터가 아닌 소켓 객체입니다.
# 따라서 fileno()를 사용하여 파일 디스크립터를 얻습니다.
events = ep.poll(timeout)
# 하나 이상의 타이머 파일 디스크립터가 한 번에 읽기 준비가 되면,
# epoll.poll()은 (fd, event) 쌍의 리스트를 반환합니다.
#
# 이 예제의 설정에서는,
# 1번째 타이머는 0.25초마다 작동합니다. (0.25, 0.5, 0.75, 1.0, ...)
# 2번째 타이머는 0.5초마다 작동합니다. (0.5, 1.0, 1.5, 2.0, ...)
# 3번째 타이머는 0.75초마다 작동합니다. (0.75, 1.5, 2.25, 3.0, ...)
#
# 0.25초 시점에는 1번째 타이머만 작동합니다.
# 0.5초 시점에는 1번째 타이머와 2번째 타이머가 동시에 작동합니다.
# 0.75초 시점에는 1번째 타이머와 3번째 타이머가 동시에 작동합니다.
# 1.5초 시점에는 1번째, 2번째, 3번째 타이머가 모두 동시에 작동합니다.
#
# 마지막 os.read() 호출 이후 타이머 파일 디스크립터가
# 여러 번 신호를 받은 경우, os.read()는 신호가 발생한 횟수를
# 바이트 클래스의 호스트 바이트 순서(host order)로 반환합니다.
print(f"Signaled events={events}")
for fd, event in events:
if event & select.EPOLLIN:
if fd == sock.fileno():
# 연결 요청이 있는지 확인합니다.
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():
# 읽을 데이터가 있는지 확인합니다.
print(f"Reading data {fd}")
data = conn.recv(1024)
if data:
# 안전을 위해 UnicodeDecodeError 예외를 포착해야 합니다.
cmd = data.decode()
if cmd.startswith("stop"):
print(f"Stopping server")
is_active = False
else:
print(f"Unknown command: {cmd}")
else:
# 더 이상 데이터가 없으면 연결을 닫습니다.
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 select() can be used with timer file
descriptors to wait until the file descriptor is ready for reading:
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