SA_RESTART and select syscall on Linux

Poco tempo fa mi sono imbattuto in un comportamento anomalo relativamente all’uso di una select() con timeout e un handler per il segnale SIGALRM. Il comportamento anomalo (documentato in parte su man 2 select) prevede che nel caso in cui la select() venisse interrotta da un segnale (SIGALRM nel mio caso) avrebbe come conseguenza la modifica del timeout passato come riferimento. Altra anomalia è data dal fatto che anche impostando la flag SA_RESTART, che forza la ri-esecuzione di una syscall interrotta da un segnale, la select() non si comporta come dovrebbe.

Fornisco un codice che ho prodotto per spiegare meglio la situazione…

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

typedef void (*sighandler_t)(int);

void alarm_hand()
{
 // do something
}

int main(int argc, char **argv)
{
 int res;
 int alarm_timeout, select_timeout;
 fd_set fset;
 struct timeval tv;
 struct sigaction act;

 if(argc != 3) {
  printf("usage: %s <alarm timeout> <select timeout>\n");
  exit(EXIT_FAILURE);
 }

 alarm_timeout = atoi(argv[1]);
 select_timeout = atoi(argv[2]);

 /* signal definition */
 act.sa_handler = alarm_hand;
 sigemptyset(&act.sa_mask);
 /* this flag should restart the interrupted syscall */
 act.sa_flags = SA_RESTART;
 sigaction(SIGALRM, &act, NULL);

 /* starting SIGALRM countdown */
 alarm(alarm_timeout);

 tv.tv_sec = select_timeout;
 tv.tv_usec = 0;

 FD_ZERO(&fset);
 FD_SET(STDIN_FILENO, &fset);

 /* this is a blocking syscall */
 res = select(STDIN_FILENO + 1, &fset, NULL, NULL, &tv);

 /* select error */
 if (res == -1) {
  printf("select error: %d seconds left!\n", tv.tv_sec);
  exit(EXIT_FAILURE);
 }
 /* select timeout */
 else if (res == 0) {
  printf("select timeout!\n");
  exit(EXIT_FAILURE);
 }
 /* there are something in stdin */
 else if (FD_ISSET(STDIN_FILENO, &fset)) {
  printf("stdin has you!\n");

  int n;
  char a;
  do {
   n = read(STDIN_FILENO, &a, 1);
   if(n!=0)
    printf("%c", a);
  } while((n!=0) && (a != '\n'));
 }

 exit(EXIT_SUCCESS);

}

Dopo averlo compilato con

$ gcc example.c -o example

il programmino che accetta un input da tastiera viene lanciato attraverso i due parametri alarm timeout e select timeout:

$ ./example <alarm timeout> <select timeout>

Si possono verificare i seguenti scenari:

  • Si fornisce un input prima dello scadere di uno dei due timeout (Es. “ciao” seguito da RETURN): il programma termina correttamente e stampa l’input.
  • Si inserisce alarm timeout maggiore di select timeout (Es. ./example 5 2): la select() termina correttamente essendo scaduto il suo timeout.
  • Si inserisce select timeout maggiore di alarm timeout (Es. ./example 1 20): il processo riceve SIGALRM e la select termina in maniera anomala (non viene considerato il SA_RESTART).

Quindi se fate uso di select per la gestione di socket, file, ecc.. in concomitanza di un handler per SIGALRM prestate molta attenzione e riavviate “a mano” la select. Uno dei modi più eleganti è impostare una flag (di tipo sig_atomic_t!) dentro il gestore associato al segnale e gestirla all’interno del controllo d’errore della select().

5 pensieri su “SA_RESTART and select syscall on Linux

  1. salve,
    mi permetto di intervenire dato che la select la conosco abbastanza bene, specie su Linux e mi interessa moltissimo parlarne

    avevo notato anche io e confermo il comportamento indicato, ma sul man di sigaction si legge:
    “SA_RESTART – Provide behaviour compatible with BSD signal semantics by making certain system calls restartable across signals”
    credo che tutto si giochi su “certain system call”, evidentemente la select non e’ tra queste (?). Sarebbe interessante avere il tempo di tuffarsi nei sorgenti delle lib di sistema per capire meglio…🙂
    Riguardo poi il “comportamento anomalo”, il codice di errore ritornato e’ effettivamente 4: interrupted system call, e ci posso convivere (sapendolo).

    mi spieghi meglio la storia di sig_atomic_t?

  2. @henomis
    ho scoperto che e’ da molto che si dibatte su questo argomento.
    sembra che le cose stiano cosi: in un S.O. full-POSIX-compliant la select _dovrebbe_ riprendere usando SA_RESTART; tuttavia “ufficialmente” dipende dalla specifica implementazione (come per la faccenda di ritornare il timeout rimanente).
    In Linux, come altri O.S., si e’ scelto di ritornare EINTR in questi casi per evidenziare il fatto che la chiamata e’ stata interrotta e lasciare al programmatore la liberta’ di gestire l’errore.

    per sig_atomic_t non era il tipo che non avevo capito, ma il perche’ usarlo in qusto caso, ma alla fine ci sono arrivato da solo, grazie lo stesso.

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...