= Il modulo microsock = Lo scopo del '''modulo microsock''' è quello di permettere la gestione di operazioni di I/O sui socket in un ambiente multithread cooperativo. <
> In un tale ambiente è necessario evitare l'uso di chiamate di sistema bloccanti per l'intero processo, come sono le chiamate di I/O sui socket. Quando viene creato un socket usando il wrapper {{{ntk.wrap.sock.Sock}}}, viene in effetti creato anche un oggetto {{{asyncore.dispatcher}}} che lo incapsula e questo viene registrato nella mappa di {{{asyncore}}} (un modulo standard di python stackless); inoltre viene avviato un microthread nel modulo microsock. <
> Il microthread avviato esegue la funzione {{{ManageSockets}}}. Questa richiama {{{asyncore.poll(0.05)}}} ciclicamente (intervallando con {{{micro_block}}} per passare lo scheduler agli altri) fin quando esiste un socket (nel caso di Netsukuku per sempre) nella mappa di asyncore. <
> La mappa di asyncore associa il fd (file descriptor, un numero intero) ad una istanza di {{{asyncore.dispatcher}}} che incapsula il vero oggetto sul quale vanno fatte le operazioni di I/O (che può essere un file, o un socket, ...). ''Nota'': La funzione {{{asyncore.poll}}} è bloccante. Il parametro passato è il numero di secondi dopo i quali cui si vuole cha la chiamata faccia ritorno. <
> Al suo interno la funzione richiama {{{time.sleep}}} oppure {{{select.select}}}, a seconda se ci sono o meno socket da controllare. In entrambi i casi queste funzioni usano chiamate di sistema che non impegnano la CPU, ma nemmeno permettono agli altri microthread di venire schedulati. <
> Per questo dobbiamo richiamarla con piccoli timeout intervallando con chiamate a {{{micro_block}}}. Vedi anche la trattazione dell'argomento nella pagina del [[../ModuloXtime|modulo xtime]]. Il metodo {{{asyncore.poll}}}, per prima cosa fa uso dei due metodi {{{readable}}} e {{{writable}}} di {{{asyncore.dispatcher}}} su tutti i dispatcher registrati nella sua mappa. Questi metodi possono essere overridati; nella implementazione base restituiscono {{{True}}}. Il loro obiettivo è quello di identificare i socket che hanno ''intenzione'' di inviare ({{{writable}}}) o di leggere ({{{readable}}}) qualcosa. Di seguito, per i socket che sono readable o writable secondo i significati sopra esposti, tramite il metodo {{{select.select}}} scopre quali hanno dei dati da ricevere oppure sono subito in grado di inviare dati. Cioè per quali socket le chiamate {{{recv}}} e {{{send}}} non risulterebbero bloccanti. Nel modulo microsock viene definita la classe {{{microsock.dispatcher}}} che è una classe derivata di {{{asyncore.dispatcher}}}. Grazie a questa classe possiamo definire come deve agire il programma quando un microthread richiama i classici metodi di I/O su un presunto socket. Per una analisi di dettaglio rimandiamo alle pagine sulla [[../ModuloMicrosockCreazione|creazione di un socket]], [[../ModuloMicrosockConnessione|connessione di due socket]] e [[../ModuloMicrosockTrasmissione|trasmissione dati tra due socket]]. == Il microthread che esegue ManageSockets == Abbiamo detto che fin dal primo momento in cui esiste un socket viene avviato un microthread che esegue la funzione {{{ManageSockets}}} presente in questo modulo. La vita di questo microthread è un '''aspetto critico''' di tutto il programma Netsukuku. Se per un qualsiasi motivo questo microthread dovesse bloccarsi in attesa di un evento che tarda a verificarsi (o addirittura non si verificherà mai) l'intera struttura di comunicazione di Netsukuku si blocca. Per questo motivo va usata estrema cautela in tutti i punti del codice (in particolare in questo modulo) in cui questo microthread passa e che fanno uso di chiamate bloccanti. Le chiamate bloccanti usate in questo microthread sono di due tipi. * Chiamate che bloccano l'intero programma. Sono tutte quelle che riguardano l'I/O sul socket reale. Ci sono le chiamate {{{accept}}} per i server, le chiamate {{{connect}}} per i client, le chiamate {{{send}}}, {{{sendto}}} e le chiamate {{{recv}}}, {{{recvfrom}}}. * Chiamate che bloccano questo microthread ma lasciano spazio agli altri microthread. Sono le chiamate {{{send}}} e {{{receive}}} dei {{{stackless.channel}}} o della nostra [[../ClasseChannel|classe Channel]] che li incapsula. Per quanto riguarda le chiamate di I/O sul socket, ci si affida alla funzione {{{select.select}}} la quale ci dice con certezza (e senza bloccarsi indefinitamente) se una certa chiamata risulterebbe bloccante. Quindi se usata correttamente ci può guidare ad un uso non bloccante delle altre chiamate. <
> La funzione {{{select.select}}} è chiamata nella funzione {{{asyncore.poll}}}; noi ce ne avvaliamo facendo l'override nella classe {{{microsock.dispatcher}}} dei metodi di {{{asyncore.dispatcher}}} che vengono chiamati dalla funzione {{{asyncore.poll}}} stessa. Vedi le pagine citate sopra. Per quanto riguarda la trasmissione di messaggi sui channel, bisogna: * Per le chiamate {{{receive}}} accertarsi che qualcuno stia inviando qualcosa; usare quindi l'attributo {{{balance}}}. In realtà al momento non ne vengono fatte di chiamate {{{receive}}} all'interno del microthread in questione. * Per le chiamate {{{send}}} si può agire in due modi: * Di norma non è essenziale sapere il momento esatto in cui sono state portate a compimento; si possono eseguire quindi in un separato microthread. Per questo motivo la [[../ClasseChannel|classe Channel]] può venire istruita (usando il costruttore con {{{micro_send=True}}}) di eseguire tutte le chiamate {{{send}}} sul channel incapsulato in un nuovo microthread. '''''TODO''': Domanda: molteplici chiamate a send in microthread distinti verranno poi rilette sicuramente nell'ordine in cui sono state inviate?'' * Oppure vanno fatte solo se si è sicuri che una altra tasklet sia in attesa. In questo caso va preferito il costruttore della [[../ClasseChannel|classe Channel]] con {{{prefer_sender=True}}}, perché così la chiamata {{{send}}} trasmette i dati, sblocca la tasklet ricevente, ma mantiene la schedulazione al microthread corrente.