PTC29008: Timeouts

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar

Próxima aula


O protocolo de enlace projetado possui limites de tempo para suas sequências de ações em ao menos estes casos:

  • Enquadramento: há um limite de tempo para a recepção de um novo byte de um quadro. Se esse tempo for excedido, considera-se que os bytes restantes foram perdidos e assim o quadro incompleto deve ser descartado.
  • ARQ: há um limite de tempo para a recepção de uma confirmação de um quadro de dados. Se esse tempo for excedido, uma retransmissão deve ser feita.

Os prazos para realizações de ações, ou para esperas, se chamam timeouts. Do ponto de vista do comportamento do protocolo, um timeout é representado como um evento a ser tratado na máquina de estados correspondente. A programação de timeouts envolve interromper a execução normal do protocolo quando um prazo for excedido.


Timeouts podem ser implementados em sistemas POSIX de diferentes formas:

  1. Usando setitimer ou alarm combinado com sinal SIGALRM
  2. Usando timer_create combinado com um sinal definido pelo programador
  3. Usando select ou poll
  4. Em um loop que lê o relógio a cada iteração, desviando o fluxo de execução se o timeout ocorrer
  5. ... e possivelmente outras !


A ideia é escolher a técnica mais apropriada para o software em que o timeout será gerado. As técnicas 1 e 2 acima disparam aviso de timeout de forma assíncrona, assemelhando-se a interrupções por software. A técnica 3 avisa sobre um timeout de forma síncrona, sendo útil quando existe uma espera bloqueada com limite de tempo. A técnica 4 é genérica, e pode ser usada praticamente em qualquer sistema (não somente POSIX !), inclusive no Arduino.

Implementação de timeout

Investigue as técnicas citadas, e escolha aquela que considerar adequada para uso em seu protocolo.

Timeout com alarm e signal

A função alarm programa um temporizador para o envio de um sinal do tipo SIGALRM. Esse sinal deve ser capturado e tratado, para que se obtenha o efeito de um timeout. A captura do sinal envolve usar a chamada de sistema signal, informando como parâmetros o tipo de sinal a ser capturado e uma função que funciona como tratador de sinal.

Exemplo de timeout com alarm e signal
#include <signal.h>
#include <unistd.h>
#include <iostream>

using namespace std;

// este é o tratador do timeout
void timeout(int s) {
  cout << "Timeout !!!" << endl;
  _exit(0);
}

int main() {
  char buffer[128];

  cout << "Digite algo no prazo de 5 segundos: ";
  cout.flush();

  // programa o tratador do timeout, o qual deve ser uma função
  // ou método estático de classe
  signal(SIGALRM,timeout);

  // programa um timeout de 5 segundos
  alarm(5);

  string algo;
  getline(cin, algo);

  // cancela o timeout
  alarm(0);

  cout << "Você digitou: " << algo << endl;

  return 0;
}

Timeout com timer_create e signal

A API Posix oferece timers (temporizadores), que são mais flexíveis que alarm (ou setitimer). Ao contrário de alarm, um processo pode possuir múltiplos timers Posix. Esses timers, ao dispararem, podem notificar um processo ou thread por meio de um sinal.

Exemplo de timeout com timer_create
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <iostream>

using namespace std;

// este é o tratador de sinal
void timeout(int s) {
  cout << "Timeout !!!" << endl;
  _exit(0);
}

int main() {
  sigevent_t ev;
  timer_t timer;

  // especifica o timer: ele vai gerar um sinal do tipo SIGUSR2
  ev.sigev_notify = SIGEV_SIGNAL;
  ev.sigev_signo = SIGUSR2;

  // cria o timer
  if (timer_create(CLOCK_REALTIME, &ev, &timer) < 0) {
    perror("");
    return errno;
  }
  
  // especifica que o timeout deve disparar uma única vez,
  // e após 5 segundos
  struct itimerspec ts = {{0, 0}, {5,0}};
  timer_settime(timer, 0, &ts, NULL);

  // programa o tratador de sinal
  struct sigaction int_handler;
  int_handler.sa_handler=timeout;
  sigaction(SIGUSR2,&int_handler,0);

  cout << "Digite algo no prazo de 5 segundos: ";
  cout.flush();

  string algo;
  getline(cin, algo);

  // cancela o timeout
  ts.it_value.tv_sec = 0;
  timer_settime(timer, 0, &ts, NULL);

  cout << "Você digitou: " << algo << endl;
  return 0;
}

Timeout com select

A chamada select possibilita esperar por dados em múltiplos descritores de arquivos, os quais podem ser arquivos abertos, dispositivos de entrada e saída ou mesmo sockets. Essa espera pode ser limitada a um prazo, desta forma tendo-se uma forma de timeout.

O exemplo a seguir mostra o uso de 'select' para esperar que o usuário digite alguma coisa. Se nada for digitado num prazo de 5 segundos, o programa termina com uma mensagem.

Exemplo de timeout com select (C++)
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <iostream>
#include <fcntl.h>
 
using namespace std;
 
int main() {
   int fd = 0; // o descritor de arquivo da entrada padrão
  
   // faz com que fd opere em modo não-bloqueante
   int op = fcntl(fd, F_GETFL);
   fcntl(fd, F_SETFL, op | O_NONBLOCK);
 
   // cria um conjunto de descritores
   fd_set r;
 
   // inicia o conjunto de descritores, e nele
   // acrescenta fd
   FD_ZERO(&r);
   FD_SET(fd, &r);
 
   cout << "Digite alguma coisa ... você tem 5 segundos: ";
   cout.flush();

   // chama select para monitorar o conjunto de descritores,
   // com timeout de 5 segundos
   // O valor de retorno de seelct é a quantidade de 
   // descritores prontos para serem acessados
   timeval timeout = {5,0};
   int n = select(fd+1, &r, NULL, NULL, &timeout);

   if (n > 0) { // algo foi digitado dentro do prazo 
     char buffer[1024];
     int n = read(fd, buffer, 1024);
     cout << "Você digitou: ";
     cout.write(buffer, n);
     cout << endl;
   } else {
    cout << "Timeout !!!" << endl;
   }
}
Exemplo do select em Python
#!/usr/bin/python3

import select
import sys
import fcntl
import os

fd = 0

# coloca em modo nao-bloqueante
op = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, op | os.O_NONBLOCK)

print('Digite alguma coisa ... voce tem 5 segundos: ', end='')
sys.stdout.flush()

r,w,e = select.select([0], [], [], 5)

if r:
  dados = sys.stdin.read()
  print('Voce digitou: ', dados)
else:
  print('Timeout !')

Atividade

  1. Remodele sua MEF para que possa diferenciar os eventos a serem tratados
  2. Implemente timeouts no Enquadramento
  3. Implemente timeouts no ARQ

Timeouts para o protocolo:

  • Enquadramento (recepção de byte): 50 ms
  • ARQ (recepção de quadro): 1000 ms