Vai al contenuto principale

Uno schermo bloccato non è un addio

8 minuti di lettura

Primo piano delle ventole di una scheda grafica in luce rossa

Sono tornato dal caffè, ho sbloccato lo schermo e ho trovato BeeBEEP aperto, proprio come l'avevo lasciato. Ma i miei colleghi mi avevano visto offline per tutto il tempo, come se avessi spento il computer e fossi andato a casa.

Il computer non era mai stato spento. Era acceso, collegato, con BeeBEEP in funzione. Solo lo schermo era bloccato. Che cosa era successo?

E la cosa era più grave di quanto sembrasse. Il mio computer ha due schede di rete, una su ciascuna di due reti che non si vedono tra loro, e BeeBEEP sta su tutte e due. Questo ne fa un relay: un computer con un piede in ogni rete porta i messaggi da una parte all'altra, così chi sta da un lato può parlare con chi sta dall'altro. Mentre il mio schermo era bloccato, il mio BeeBEEP, il relay tra le due reti, non portava più niente. Non ero sparito solo io. Era sparito il ponte.

Una persona che si alza dalla scrivania non dovrebbe sparire, e non dovrebbe portarsi via il ponte. Non era un piccolo bug. Era BeeBEEP che falliva nell'unica cosa che un messenger deve fare.

In attesa di un fotogramma

Ho bloccato di nuovo lo schermo, questa volta apposta, e mentre BeeBEEP era fermo ho fotografato ogni thread del programma. La risposta era lì, un'attesa dentro l'altra.

Il thread che fa funzionare la finestra aspettava il thread che la disegna. Il thread che disegna aspettava il driver della scheda grafica. E il driver aspettava il server X, il programma che su quel tipo di desktop Linux possiede lo schermo. E il server X aveva smesso di rispondere.

Su quel desktop, bloccare lo schermo vuol dire passare a un altro terminale virtuale, quello in cui si chiede la password. Il terminale dell'utente resta indietro, inattivo, e finché la password non viene scritta il suo server X lascia senza risposta alcune richieste grafiche. Un secondo programma, avviato su quello schermo durante il blocco, si è fermato prima ancora di aprire la sua prima finestra. Non era quindi una mia impostazione da cambiare: tutta la parte grafica di quello schermo dormiva.

E il thread che la aspettava non era solo quello della finestra. Era anche il thread di Core, la parte di BeeBEEP che parla con la rete, consegna i messaggi e risponde alla console. Un fotogramma che non si riusciva a disegnare, e Core, con la rete, i messaggi e la console, si era fermato con la finestra.

Perché per anni aveva funzionato

Anche BeeBEEP 5 fa tutto su un solo thread, e questo problema non l'ha mai avuto. In tutti quegli anni la sua finestra non l'ha mai fermato.

La differenza sta nel modo in cui la finestra viene disegnata. BeeBEEP 5 usa i widget classici di Qt, che dipingono con il processore: i widget non aspettano mai la scheda grafica. BeeBEEP 6 usa QtQuick, che disegna con la scheda grafica, su un thread suo, e con QtQuick la finestra aspetta ogni fotogramma. È più veloce e più fluido, e funziona sui telefoni. Ma ha dato allo schermo un potere che prima non aveva: fermare tutto il resto.

Tenere Core lontano dalla finestra era l'idea fin dall'inizio. Lo diceva perfino il codice: un commento in cima a Core diceva che era "fatto per vivere su un thread suo". L'avevo messa da parte, perché un solo thread aveva servito bene per anni. Ora avevo il motivo per non aspettare oltre.

Uno schermo bloccato non è un addio

C'era una via d'uscita più facile: mettere in pausa BeeBEEP quando lo schermo si blocca, come fa quando il computer va in sospensione. Ho detto di no.

Uno schermo bloccato è un'assenza breve. Un pranzo, una riunione, un caffè. BeeBEEP non ha un server: i computer si passano i messaggi tra loro, e alcuni, come il mio, sono il ponte tra due reti. Se ogni computer bloccato sparisse, la rete perderebbe i suoi ponti e i suoi "consegnato" ogni volta che qualcuno va a prendere un caffè. Così la decisione è stata questa: mentre lo schermo è bloccato BeeBEEP resta vivo. Continua a ricevere, consegnare e passare i messaggi. Smette di disegnare solo la finestra.

Ho provato prima la metà più economica: non disegnare niente mentre la sessione è assente. Ha aiutato, e vale la pena averla comunque: niente scheda grafica né energia sprecate per uno schermo che nessuno guarda. Ma non disegnare niente non bastava: un fotogramma che si sta disegnando nell'istante del cambio resta bloccato, e il cambio arriva un terzo di secondo prima che il sistema dica a qualcuno che la sessione è bloccata. In una delle mie prove la finestra ha perso quella gara ed è rimasta ferma per tutto il blocco, quasi cinquantadue secondi. È tornata allo sblocco, come sempre. Core, sullo stesso thread, non sarebbe stato così fortunato.

Una porta sola

Così Core ha avuto un thread suo. Spostarlo è stata la parte piccola. Districarlo è stata quella grande.

Ogni parte dell'interfaccia parlava con Core direttamente: lo teneva per mano e leggeva quello che le serviva, quando le serviva. Tra due thread questo non è più permesso. Così adesso l'interfaccia raggiunge Core da una porta sola, e ogni schermata passa da lì. Da quella porta l'interfaccia legge una fotografia dello stato di Core, scattata alla fine di ogni suo turno e mai più cambiata. Manda le sue richieste, che Core esegue sul suo thread. E quando una schermata ha bisogno di una risposta la chiede, e la risposta arriva sempre dopo lo stato a cui si riferisce, mai prima.

Le regole sono poche. Core non aspetta mai la finestra. La finestra aspetta Core solo dove il sistema operativo non lascia scelta, all'avvio, alla chiusura, quando il computer va in sospensione, e ogni volta con un tempo massimo stabilito. Un test controlla che nessun file dell'interfaccia vada oltre la porta. La sua lista di eccezioni ora è vuota.

Quando la finestra torna

Con Core libero è venuta fuori una domanda nuova. Che cosa fa Core mentre la finestra è ferma?

Continua a lavorare, e dopo ogni turno manda alla finestra un aggiornamento. Nessuno li prende, e si accumulano. L'ho misurato: un minuto di blocco, con dieci messaggi al secondo in arrivo. Al ritorno c'erano 901 aggiornamenti in attesa. La finestra ha impiegato quasi quattro secondi per rimettersi in pari, e la memoria è salita a 1,2 GB. Con cinquanta messaggi al secondo erano 4501 aggiornamenti, 35 secondi, 5,6 GB.

La risposta: Core smette di parlare a chi non ascolta. Adesso la finestra conferma ogni aggiornamento che applica. Se un aggiornamento resta senza conferma per più di un secondo, Core si ferma: tiene il suo stato più recente e non manda altro. Quando la finestra torna a confermare, Core le manda un'unica fotografia del presente. Le liste si rimettono in pari confrontando la fotografia con quello che mostrano, riga per riga, senza ricominciare da capo e senza saltare in cima. E quello che gli eventi persi volevano dire per te viene calcolato una volta sola: un avviso per ogni chat con messaggi nuovi, una chiamata persa come chiamata persa, al massimo un beep.

Lo stesso minuto di blocco, a dieci messaggi al secondo: la finestra mostra il suo primo fotogramma 30 millisecondi dopo il ritorno, e la memoria resta a 131 MB. A cinquanta al secondo, 39 millisecondi e 134 MB. Per quanto lungo sia il blocco, per quanto sia trafficata la rete, adesso il ritorno costa al massimo circa un ventesimo di secondo.

Quello che è venuto fuori per strada

Misurare ha fatto trovare altri tre bug. La maggior parte di quei quattro secondi non era affatto la coda. Ogni messaggio nuovo sposta la sua chat in alto nella lista, e ogni spostamento ricostruiva ogni riga della lista, anche nell'uso normale, non solo dopo un blocco. Una lista che mantiene il suo ordine e sposta solo la riga che si è mossa ha portato lo stesso recupero da 3,9 secondi a un quarto di secondo, prima ancora che la fotografia esistesse.

Un bug gemello: ogni volta che qualcuno entrava o usciva dalla rete, la lista intera veniva ricostruita e tornava in cima. La prova con una persona che entra ed esce di continuo impiegava 16,7 secondi e 3,2 GB per assestarsi. Adesso bastano 50 millisecondi.

E uno più silenzioso, visto una volta su nove prove: se BeeBEEP veniva fermato nell'istante tra la richiesta del suo database e la risposta, la parte di rete poteva avviarsi dopo l'arresto, e il programma andava in crash all'uscita. Adesso ogni avvio porta un numero, e una risposta destinata a un avvio vecchio viene scartata.

Una cosa resta com'era, di proposito: lo squillo di una chiamata e il beep di un messaggio suonano ancora sul thread della finestra, e aspettano con lei. Se sei lontano dallo schermo, non dovrebbe suonare.

Di nuovo il caffè

Poi la prova che contava: lo stesso computer, lo stesso desktop, lo stesso blocco dello schermo, e dall'altra parte un telefono con BeeBEEP. Ho bloccato lo schermo per più di cinque minuti, e intanto il telefono e il computer si scambiavano messaggi.

La console ha risposto a ogni domanda, una al secondo, senza un solo silenzio. I messaggi, trentatré per parte, sono stati tutti consegnati, e quelli per il computer bloccato in meno di un decimo di secondo. Il telefono ha visto il computer online per tutto il tempo. Allo sblocco la finestra era lì, aggiornata. Il computer bloccato non era più sparito.

In quella prima prova il telefono era collegato al computer con il cavo, perché alla scrivania non c'era il Wi-Fi. L'ho ripetuta senza cavo, con il telefono in rete come chiunque, ed è andata allo stesso modo: i messaggi hanno solo impiegato un po' di più ad arrivare. La storia che avevo trovato tornando dal caffè è finita lì: uno schermo bloccato adesso è solo uno schermo bloccato.