Ho un server multi-thread (pool di thread) che gestisce un numero elevato di richieste (fino a 500/sec per un nodo), utilizzando 20 thread. C'è un thread listener che accetta le connessioni in entrata e le accoda per l'elaborazione dei thread del gestore. Una volta che la risposta è pronta, i thread scrivono al client e chiudono il socket. Tutto sembrava andare bene fino a poco tempo fa, un programma di test client iniziato appeso in modo casuale dopo aver letto la risposta. Dopo molte ricerche, sembra che il close() dal server non stia effettivamente disconnettendo il socket. Ho aggiunto alcune stampe di debug al codice con il numero del descrittore di file e ottengo questo tipo di output.close() non chiude correttamente il socket
Processing request for 21
Writing to 21
Closing 21
Il valore di ritorno di close() è 0 o ci sarebbe un'altra istruzione di debug stampata. Dopo questo output con un client che si blocca, lsof sta mostrando una connessione stabilita.
SERVER 8160 radice 21U IPv4 32.754.237 TCP localhost: 9980-> localhost: 47530 (fondata)
CLIENTE 17747 radice 12u IPv4 32.754.228 TCP localhost: 47530-> localhost: 9980 (istituito)
È come se il server non invia mai la sequenza di spegnimento al client e si blocca fino a quando il client non viene ucciso, lasciando il lato server in uno stato di attesa chiuso
SERVER 8160 root 21u IPv4 32754237 TCP localhost: 9980-> localhost: 47530 (CLOSE_WAIT)
Anche se il client ha un timeout specificato, verrà sospeso anziché appeso. Posso anche eseguire manualmente
call close(21)
nel server da gdb e il client si disconnetterà. Succede forse una volta su 50.000 richieste, ma potrebbe non accadere per periodi prolungati. Versione
Linux: 2.6.21.7-2.fc8xen Centos versione: 5.4 (finale)
azioni di socket sono i seguenti
SERVER:
int client_socket; struct sockaddr_in client_addr; socklen_t client_len = sizeof (client_addr);
while(true) {
client_socket = accept(incoming_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_socket == -1)
continue;
/* insert into queue here for threads to process */
}
Quindi il thread preleva lo zoccolo e genera la risposta.
/* get client_socket from queue */
/* processing request here */
/* now set to blocking for write; was previously set to non-blocking for reading */
int flags = fcntl(client_socket, F_GETFL);
if (flags < 0)
abort();
if (fcntl(client_socket, F_SETFL, flags|O_NONBLOCK) < 0)
abort();
server_write(client_socket, response_buf, response_length);
server_close(client_socket);
server_write e server_close.
void server_write(int fd, char const *buf, ssize_t len) {
printf("Writing to %d\n", fd);
while(len > 0) {
ssize_t n = write(fd, buf, len);
if(n <= 0)
return;// I don't really care what error happened, we'll just drop the connection
len -= n;
buf += n;
}
}
void server_close(int fd) {
for(uint32_t i=0; i<10; i++) {
int n = close(fd);
if(!n) {//closed successfully
return;
}
usleep(100);
}
printf("Close failed for %d\n", fd);
}
CLIENTE:
lato client utilizza libcurl v 7.27.0
CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_tag);
res = curl_easy_perform(curl);
Niente di speciale, solo un collegamento ricciolo di base. Il client si blocca in tranfer.c (in libcurl) perché il socket non è percepito come chiuso. Sta aspettando più dati dal server.
Le cose che ho provato finora:
Shutdown prima della chiusura
shutdown(fd, SHUT_WR);
char buf[64];
while(read(fd, buf, 64) > 0);
/* then close */
Impostazione SO_LINGER a chiudere forzatamente in 1 secondo
struct linger l;
l.l_onoff = 1;
l.l_linger = 1;
if (setsockopt(client_socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1)
abort();
Questi hanno fatto alcuna differenza. Qualsiasi idea sarebbe molto apprezzata.
MODIFICA - Questo è risultato essere un problema di sicurezza dei thread all'interno di una libreria di code che ha causato il socket in modo inappropriato da più thread.
Sei positivo al 100% nessun altro thread potrebbe utilizzare il socket quando si chiama 'close' su di esso? Come fai le tue letture non bloccanti? –
Temo di aver appena effettuato l'accesso qui e di aver ricordato questo problema. Ho scoperto in seguito che c'era un problema di sicurezza del thread in una coda usata per passare le connessioni in giro. Non c'era nessun bug qui. Scusa per la disinformazione. – DavidMFrey