venerdì, maggio 30, 2008

Socket error: “The I/O operation has been aborted because of either a thread exit or an application request”


In aprile 2008 mi sono ritrovato a scrivere una piccola applicazione multithreaded (in C++), che utilizza i socket (TCP/IP). Il problema in oggetto mi e’ accaduto nel momento in cui l’applicazione dopo aver accettato una richiesta di connessione, e attivato un nuovo thread (quello che nei dettagli sotto chiamo THREAD2) che passa a un altro thread gia’ attivo (piu’ sotto chiamato THREAD1) l’handle del nuovo socket, attiva una ricezione non bloccante (overlapped) e poi termina. Non mi era mai capitato un caso del genere ma non ho saputo interpretare immediatamente il messaggio di errore e capire quale fosse la causa. Per di piu’ sul web ho trovato solo il solito pattume che non mi e’ stato affatto utile per capire il problema (e’ questa la ragione principale per cui sto scrivendo). La causa, come apparra’ forse evidente leggendo, e’ che la ricezione appena attivata, viene interrotta dall’uscita del thread. Gia’, ma il mio problema era: cosa c’entra il thread col canale? Il canale infatti continuava a restare attivo e veniva gestito nell’altro thread e funzionava correttamente in quanto scambiava messaggi senza problemi. In certe situazioni pero’ andava in errore. Le situazioni in cui andava in errore erano quelle in cui il thread terminava prima che il chiamante avesse fatto in tempo a mandare il primo messaggio, cioe’ facendo riferimento ala figura, prima che A (nel ruolo di client chiamante) potesse mandare a M un messaggio e quindi far “terminare” la ricezione appena attivata. Insomma io non sono uno che riesce ad arrivare a livello del kernel di windows nelle analisi, ma si deduce che la ragione e’ che una ricezione non bloccante e il processo che l’ha generata, sono tra loro legati a livello di sistema operativo, e se il processo termina la ricezione va in errore. Quindi per risolvere ho semplicemente bloccato l'uscita del thread condizionandola al completamento della prima ricezione. Ovviamente poi l'evento su cui e' rimasto in attesa non viene resettato ne' automaticamente ne' manualmente in modo che poi la segnalazione che qualcosa va ricevuto dal canale resti attiva e al momento opportuno verra' gestita.


Se vi interessa qualche dettaglio in piu’ sul contesto in cui mi e’ capitato il problema lo riporto qui di seguito. Questa parte non aggiunge pero’ nulla per la comprensione del problema sopra descritto, quindi la potete tranquillamente saltare, se non vi interessa.

L’applicazione che ho scritto e’ un po’ particolare perche’ sta in mezzo tra un programma che chiamiamo per comodita’ “A” che fa da server e accetta delle chiamate da un PC remoto sul quale girano due applicazioni, una applicazione client “B” e una applicazione server “C”. L’applicazione B fa la chiamata iniziale, il server A la riceve e per eseguire la richiesta deve “parlare” (e quindi fare una chiamata nel ruolodi client) con l’applicazione C. Per farlo deve fare una chiamata verso lo stesso PC dove sta B. Qui entra in gioco l’applicazione di cui sopra che chiameremo per comodita’ “M”. L’applicazione B in realta’ chiama M, anziche’ chiamare direttamente A, e quando A deve chiamare C in realta’ chiama sempre M (che a sua volta e’ quindi sia un socket client che un socket server). M invece di aprire un nuovo canale verso C, riutilizza lo stesso canale che gia’ e’ attivo tra B e A e manda i messaggi diretti a C su quel canale. In realta’ tra B e M la chiamata non e’ diretta, ma c’e’ un piccolo router che entra in gioco proprio in questo momento poiche’ vedendo il mesasggio diretto a C glielo gira e quindi B resta al di fuori della comunicazione perche’ non lo riguarda (cosi’ come C restera’ fuori dalla comunicazione, nel senso che i messaggi non gli verranno girati, pur rimanendo collegato al router, quando tali messaggi saranno diretti a B). La comunicazione inizialmente stabilita tra B e A, viene gestita da un thread in M (che per comodita’ chiameremo THREAD1) che gira i messaggi in arrivo da B verso A e viceversa. L’applicazione M, nel momento in cui arriva la chiamata da A verso C, attiva la ricezione sul canale stabilito con A, e questo processo lo esegue in un thread (THREAD2) che parte all’arrivo della chiamata da A e termina nel momento in cui la ricezione (mediante funzione ReadFile) sul nuovo canale e’ stata attivata; esso termina perche’ tale canale verra’ aggiunto alla lista di canali gestiti dal THREAD1 e quindi THREAD2 ha terminato il suo compito. THREAD1 pensera’ quindi a girare i messaggi provenienti da B (client) verso A (server) e quelli provenienti da A (client) verso C (server). Il problema in oggetto mi e’ accaduto nel momento in cui il THREAD2 e’ terminato.

Nessun commento: