Descritor de arquivo de temporizador

Versão:

1.13

Este documento discute o suporte do Python para o descritor de arquivo de temporizador do Linux.

Exemplos

O exemplo a seguir mostra como usar um descritor de arquivo de temporizador para executar uma função duas vezes por segundo:

# Scripts práticos devem realmente usar um timer sem bloqueio.
# Usamos um timer com bloqueio aqui para simplificar.
import os, time

# Cria um descritor de arquivo de temporizador
fd = os.timerfd_create(time.CLOCK_REALTIME)

# Inicia o temporizador em 1 segundo, com um intervalo de meio segundo
os.timerfd_settime(fd, initial=1, interval=0.5)

try:
    # Processa eventos do temporizador quatro vezes.
    for _ in range(4):
        # read() ser bloqueado até o temporizador expirar
        _ = os.read(fd, 8)
        print("Timer expired")
finally:
    # Lembre-se de fechar o descritor de arquivo de temporizador!
    os.close(fd)

Para evitar a perda de precisão causada pelo tipo float, os descritores de arquivos de temporizador permitem especificar a expiração inicial e o intervalo em nanossegundos inteiros com variantes _ns das funções.

Este exemplo mostra como epoll() pode ser usado com descritores de arquivo de temporizador para esperar até que o descritor de arquivo esteja pronto para leitura:

import os, time, select, socket, sys

# Cria um objeto epoll
ep = select.epoll()

# Neste exemplo, usa o endereço de loopback para enviar o comando "stop" ao servidor.
#
# $ 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)

# Cria descritores de arquivo de temporizador no modo não bloqueante.
num = 3
fds = []
for _ in range(num):
    fd = os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
    fds.append(fd)
    # Registra o descritor de arquivo do temporizador para eventos de leitura
    ep.register(fd, select.EPOLLIN)

# Inicia o temporizador com os.timerfd_settime_ns() em nanossegundos.
# O temporizador 1 dispara a cada 0,25 segundos; o timer 2 a cada 0,5 segundos; 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:
        # Aguarda o temporizador expirar por 3 segundos.
        # epoll.poll() retorna uma lista de pares (fd, event).
        # fd é um descritor de arquivo.
        # sock e conn[=valor retornado de socket.accept()] são objetos de socket, não descritores de arquivo.
        # Então use sock.fileno() e conn.fileno() para obter os descritores de arquivo.
        events = ep.poll(timeout)

        # Se mais de um descritor de arquivo de timer estiver pronto para leitura ao mesmo tempo,
        # epoll.poll() retorna uma lista de pares (fd, event).
        #
        # Neste exemplo de configurações,
        #    O 1º temporizador dispara a cada 0,25 segundos em 0,25 segundos. (0,25, 0,5, 0,75, 1,0, ...)
        #    O 2º temporizador dispara a cada 0,5 segundos em 0,5 segundos. (0,5, 1,0, 1,5, 2,0, ...)
        #    O 3º temporizador dispara a cada 0,75 segundos em 0,75 segundos. (0,75, 1,5, 2,25, 3,0, ...)
        #
        #    Em 0,25 segundos, apenas o 1º temporizador dispara.
        #    Em 0,5 segundos, o 1º temporizador e o 2º temporizador disparam ao mesmo tempo.
        #    Em 0,75 segundos, o 1º temporizador e o 3º temporizador disparam ao mesmo tempo.
        #    Em 1,5 segundos, o 1º temporizador, o 2º temporizador e o 3º temporizador disparam ao mesmo tempo.
        #
        # Se um descritor de arquivo de temporizador for sinalizado mais de uma vez desde
        # a última chamada de os.read(), os.read() retornará o número de sinalizados
        # como ordem de host de bytes de classe.
        print(f"Signaled events={events}")
        for fd, event in events:
            if event & select.EPOLLIN:
                if fd == sock.fileno():
                    # Verifica se há uma requisição de conexão.
                    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():
                    # Verifica se há dados para ler.
                    print(f"Reading data {fd}")
                    data = conn.recv(1024)
                    if data:
                        # Você deveria capturar exceção UnicodeDecodeError por segurança.
                        cmd = data.decode()
                        if cmd.startswith("stop"):
                            print(f"Stopping server")
                            is_active = False
                        else:
                            print(f"Unknown command: {cmd}")
                    else:
                        # Acabaram os dados, fecha a conexão
                        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()

Este exemplo mostra como select() pode ser usado com descritores de arquivo de temporizador para esperar até que o descritor de arquivo esteja pronto para leitura:

import os, time, select, socket, sys

# Neste exemplo, usa o endereço de loopback para enviar o comando "stop" ao servidor.
#
# $ 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)

# Cria descritores de arquivo de temporizador no modo não bloqueante.
num = 3
fds = [os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
       for _ in range(num)]
select_fds = fds + [sock]

# Inicia o temporizador com os.timerfd_settime() em segundos.
# O temporizador 1 dispara a cada 0,25 segundos; o timer 2 a cada 0,5 segundos; 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:
       # Aguarda o temporizador expirar por 3 segundos.
       # select.select() retorna uma lista de objetos ou descritores de arquivos.
       rfd, wfd, xfd = select.select(select_fds, select_fds, select_fds, timeout)
       for fd in rfd:
           if fd == sock:
               # Verifica se há uma requisição de conexão.
               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:
               # Verifica se há dados para ler.
               print(f"Reading data {fd}")
               data = conn.recv(1024)
               if data:
                   # Você deveria capturar exceção UnicodeDecodeError por segurança.
                   cmd = data.decode()
                   if cmd.startswith("stop"):
                       print(f"Stopping server")
                       is_active = False
                   else:
                       print(f"Unknown command: {cmd}")
               else:
                   # Acabaram os dados, fecha a conexão
                   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