PDA

View Full Version : [C] terminazione pulita di un server multithread


agosteeno
12-07-2011, 09:24
Salve a tutti ho una questione da sottoporvi, per avere qualche consiglio. Per un esame ho un server multithread. La terminazione dell'esecuzione deve avvenire tramite il trattamento del segnale SIGINT e SIGTERM. Il problema nasce dal fatto che quando si chiede di terminare l'esecuzione, si devono terminare tutte le operazioni in coda richieste dai vari client (per ognuno dei quali c'e' un thread apposito che lo serve) piu' un thread particolare che si attiva ogni tot secondi e serve tutte le richieste in coda. Quando arriva una di questi segnali questo thread dovrebbe iniziare la sua esecuzione, soddisfare le ultime richieste in coda e poi chiudere il server in maniera pulita (quindi chiudere la socket, chiudere file aperti e cose del genere). Questo thread a regime esegue un ciclo infinito e una sleep di tot secondi per regolare la temporizzazione. Il trattamento dei segnali e' gestito installando dei gestori, tramite una funzione particolare. Come potrei sistemare il gestore di SIGINT e SIGTERM per fare in modo che il thread si renda conto che nn ha semplicemente eseguito una sleep e quindi possa terminare il ciclo infinito e terminare il server?

WarDuck
12-07-2011, 10:46
Cosa fai di preciso all'interno del ciclo in cui è inserita la sleep?

Potresti pensare di impostare opportunamente una variabile globale all'arrivo del segnale in questione, ad esempio un booleano.


..

bool Run = TRUE;

while ( Run )
{
// codice con sleep
}

/* Cleanup */
..


All'arrivo del segnale specificato Run viene posta a FALSE, così al successivo ciclo utile il while termina e puoi eseguire la cleanup.

agosteeno
12-07-2011, 10:57
Grazie per la risposta. Allora, nel codice della sleep il thread esegue sostanzialemente le operazioni chieste dal client. Nel mio caso particolare si tratta di un server che gestisce richieste e offerte di carsharing. Il problema pero' nn e' solo questo. Mi spiego meglio: il thread in questione ha un ciclo infinito e avevo gia' pensato si risolverlo in una maniera simile a come hai suggerito. Il fatto e' che anche il main esegue un ciclo infinito nel quale esegue una accept per accettare nuovi client e dunque dovrei terminare anche il suo ciclo. La variabile potrebbe essere globale nel senso che potrei usarla sia sul ciclo del main che del thread particolare? La cleanup la farebbe direttamente il thread che esegue le operazioni in modo tale da essere sicuro da soddisfare tutte le richieste pendenti...

agosteeno
12-07-2011, 11:04
Tra l'altro ripensandoci potrei, invece di chiamare la pthread_detach appena creo il thread particolare, chiamare dal main la pthread_join quando esco dal ciclo, in modo da poter garantire la corretta terminazione. Infatti se nn sbaglio all'uscita del main dovrebbe terminare tutto il processo.

agosteeno
12-07-2011, 11:22
Piccolo problema: per il main nn posso usare questa soluzione perche' in realta' lui e' bloccato sulla accept. Di conseguenza dovrebbe per lo meno accettare una nuova richiesta e poi all'iterazione successiva, quando ricontrolla la variabile uscirebbe dal ciclo. Ma questo non e' il comportamento che voglio. Senza contare il fatto che questa richiesta potrebbe nn arrivare mai e dunque il server non terminare...

WarDuck
12-07-2011, 12:33
Relativamente alla accept

Se non ricordo male alla ricezione dei segnali alcune chiamate a funzione possono terminare.

Per evitare ciò ci sono due modi, uno è mascherare i segnali, mentre l'altro è semplicemente riavviare la funzione (credo ci sia un flag da passare da qualche parte).

Ora non ricordo esattamente i dettagli di questa cosa, però potresti sfruttare questo comportamento per la accept (che a quel punto terminerebbe e dunque il ciclo verrebbe sbloccato).

Una alternativa a questa cosa potrebbe essere effettuare una connessione locale al server e mandare un messaggio preciso di chiusura.

Edit

Ho trovato:

http://linux.die.net/man/2/sigaction

Mi ricordavo bene, il flag SA_RESTART ti consente di far ripartire la funzione.

Se tu prepari un handler separato per i segnali che ti interessano e chiami questa funzione senza passargli il flag SA_RESTART, alla ricezione dei segnali le chiamate in attesa dovrebbero terminare.

agosteeno
12-07-2011, 13:34
Devo giusto precisare che si tratta di un client server gia' locale. Locale nel senso che le socket usate sono le AF_UNIX. Si tratta di un progetto per il laboratorio di sistemi operativi. Non ho ben capito come dovrebbe funzionare il flag sa_restart: in pratica con un handler nel quale modifico una variabile (in questo caso particolare) basterebbe a risolvere la situazione perche' le chiamate che sono in attesa verrebbero sbloccate?

WarDuck
12-07-2011, 16:55
Devo giusto precisare che si tratta di un client server gia' locale. Locale nel senso che le socket usate sono le AF_UNIX. Si tratta di un progetto per il laboratorio di sistemi operativi. Non ho ben capito come dovrebbe funzionare il flag sa_restart: in pratica con un handler nel quale modifico una variabile (in questo caso particolare) basterebbe a risolvere la situazione perche' le chiamate che sono in attesa verrebbero sbloccate?

Di default alla ricezione di un segnale alcune chiamate di sistema vengono interrotte (dunque falliscono e riportano codice di errore EINT), quel flag serve per farle ripartire.

In genere può essere comodo farle ripartire quando devi gestire segnali custom tipo SIGUSR1 e SIGUSR2.

Nel tuo caso però vuoi avere il comportamento di default, con le funzioni che vengono interrotte alla ricezione di un segnale.

agosteeno
12-07-2011, 17:00
Lavorandoci su mi sono reso conto che sostanzialmente interrompe la funzione. Quindi ho fatto cosi': controllo il valore restituito. Se = -1 allora significa che la accept e' stata interrotta bruscamente. Faccio un break ed esco dal ciclo. A questo punto controllo il valore della variabile globale (ho visto che devo definirla di tipo volatile sig_atomic_t): se ha il valore dato dal gestore (che poi e' anche quello che serve per far terminare il while del thread che esegue le altre operazioni) allora significa che si tratta di una terminazione gentile. Quindi faccio la join su questo thread e lui eseguira' la pulizia. Altrimenti si tratta di una situazione anomala e vedro' di trattarla di conseguenza. Per ora sembra andare bene, per lo mena pare faccia quello che voglio io.

WarDuck
13-07-2011, 08:53
Lavorandoci su mi sono reso conto che sostanzialmente interrompe la funzione. Quindi ho fatto cosi': controllo il valore restituito. Se = -1 allora significa che la accept e' stata interrotta bruscamente. Faccio un break ed esco dal ciclo. A questo punto controllo il valore della variabile globale (ho visto che devo definirla di tipo volatile sig_atomic_t): se ha il valore dato dal gestore (che poi e' anche quello che serve per far terminare il while del thread che esegue le altre operazioni) allora significa che si tratta di una terminazione gentile. Quindi faccio la join su questo thread e lui eseguira' la pulizia. Altrimenti si tratta di una situazione anomala e vedro' di trattarla di conseguenza. Per ora sembra andare bene, per lo mena pare faccia quello che voglio io.

Senza usare break:


while ( accept(..) >= 0 )
{
...
}

/* Codice di cleanup */


Relativamente al cleanup, immagino che ogni thread abbia il suo insieme di risorse da liberare, come la gestisci questa cosa?

Riguardo alle variabili globali bisogna vedere quanti thread vogliono accedere a quella variabile e come questa variabile viene modificata dal gestore del segnale.

Se la catena degli eventi è strettamente:
- ricezione segnale
- gestione tramite sighandler
- terminazione accept

Allora non dovresti aver bisogno di accorgimenti particolari, perché quando termina accept sei sicuro che Run è cambiata e vi accedi quando nessun'altro più può cambiarla (per stare sicuro puoi mascherare a quel punto l'arrivo di nuovi segnali, anche se non dovrebbe essere necessario).

;).

agosteeno
13-07-2011, 09:15
Grazie per il consiglio sul break, tanto semplice ma nella fretta nn mi e' venuto in mente! :) Riguardo alla variabile globale verra' usata solo da 2 diversi thread in lettura e in scrittura solo dall'handler, quindi nn dovrebbe dare problemi. Per quanto riguarda le risorse che devono liberare gli altri thread ancora nn ho ben deciso anche perche' a parte il fatto di avere una connessione attiva con i client (che nn e' un dettaglio da poco) devono effettuare solo le eventuali free del caso. Anche gli altri thread cmq hanno un ciclo al loro interno, all'interno del quale ascoltano le varie richieste dei client. Usero' anche per loro un meccanismo simile a quello detto fin'ora, considerando il fatto che in realta' le risposte ai client le invia il thread di cui parlavo nei post precedenti.

agosteeno
31-08-2011, 11:29
Salve, riprendo questa discussione perche' si tratta sempre della stessa problematica.

La situazione attuale e' questa: quando effettuo un ctrl-c il server deve terminare gentilmente e questo verra' fatto da un thread che di base effettua un altro compito, ma al suo interno ha un ciclo, la cui condizione viene cambiata dal gestore del SIGINT per fare in modo che quando viene gestito esca dal ciclo ed effettui le operazioni del caso. Il main e' bloccato sulla accept perche' il suo compito e accettare nuove connessioni da parte di client (la accept e' anche la condizione del while: while ((canale = accept(socket)) >= 0)). All'uscita di questo while c'e' l'attesa per la terminazione del thread di prima (pthread_join) piu' altre operazioni del caso.
In teoria, quando il server riceve il SIGINT, siccome il main era bloccato in attesa sulla accept (in realta' si tratta di una mia funzioncina che chiama la accept, ma e' sostanzialmente identica ad una accept normale) dovrebbe sbloccarsi, restituire -1 aggiornando il valore di errno a EINTR e dunque causare l'uscita dal ciclo. Questo pero' non avviene e non riesco a capire per quale motivo. In pratica rimane bloccato (nel senso che la accept non restituisce nessun valore) ma non accetta neanche nuove connessioni e dunque e' chiaro che la ricezione del segnale ha avuto un qualche effetto.
Per la gestione dei segnali ho un file particolare che implementa una funzione che carica i gestori dei segnali interessati, tra cui il SIGINT e nn mi sembra ci siano maschere attive o flag settati.
Qualcuno ha idea di quale possa essere il problema?

agosteeno
02-09-2011, 10:49
Ok, problema risolto! Il problema era un cattivo uso delle maschere per i segnali. Oltretutto nella funzione che installa i gestori mi dimenticavo di disabilitare la maschera. Almento il tutto mi e' servito per capire bene come funziona i segnali! Grazie a tutti