Se sei nel business di costruire cose che funzionano su computer a lungo, credo che acquisirai presto una storia di bug preferita. Questa è la mia storia. Ho anche creato uno strumento interattivo dove puoi esplorare i concetti alla base di questo bug. Il bug: due emoji entrano, nessuno esce Stavo lavorando sulla migrazione di un editor legacy a un'esperienza collaborativa con il mio team. TipTap in alto (che è un wrapper intorno a ProseMirror), Yjs sotto che gestisce la magia del CRDT per la sincronizzazione in tempo reale. Funzionava bene! Per lo più. Nelle nostre giornate alpha/early release, quando era ancora quasi interno e/o utenti di rilascio iniziale, a volte l'editor si fermava semplicemente a salvare il contenuto. Silenziosamente. Continuavi a digitare e tutto sembrava bene, ma le tue modifiche non sincronizzavano più con il documento Yjs. La prossima volta che aprivi la pagina, tutto ciò che avevi scritto dopo il punto di fallimento era andato perso. Era terribilmente spaventoso, molto raro e quasi impossibile da diagnosticare perché non riuscivamo mai a riprodurlo. Abbiamo veramente provato! Le mie prime sospettazioni generalmente si concentravano su connessioni Wi-Fi instabili e comportamenti WebSocket strambi, ma nessuna quantità di rallentamento o accensione/spenta del Wi-Fi sembrava riprodurre l'errore. L'esperienza era sorprendentemente resiliente in quei casi, nella mia memoria. Sembrava che accadesse a caso, mai quando qualcuno stava guardando. Nessun errore evidente nel console, nessun traccia di stack, nessun crash. Solo... "Ehi, penso che le mie modifiche non siano state salvate." Poi un giorno il nostro manager di prodotto l'ha risolto. Non era una cosa facile da trovare. Lui aveva vissuto più di chiunque altro (probabilmente perché era il migliore a dogfoodare il nostro prodotto) e aveva iniziato a ristretto metodicamente. "Mi sento come se fossi impazzito, ma penso che sia quando tipo caratteri specifici insieme, vado indietro e inserisco un carattere tra loro..." Lui aveva utilizzato 🟢 e 🔴 nelle sue e-mail settimanali di stato del progetto per comunicare la salute generale. Verde per in linea, rosso per a rischio. Ogni settimana il modello che stava utilizzando aveva entrambi i caratteri già presenti e lui li avrebbe semplicemente rimossi (Generalmente il rosso, sono felice di dire!). In questa occasione aveva copiato il cerchio verde e incollato il rosso davanti a lui in qualche punto, o forse viceversa. Quella specifica operazione— inserendo un emoji multi-byte adiacente a un altro— stava attivando una spaccatura nella libreria CRDT sottostante, che stava dividendo una coppia surrogate a metà. Ricordo di essere stato in una chiamata quando mi ha mostrato questo a me e uno dei miei dipendenti diretti che si era occupato della transizione di editing collaborativo. Devo avermi eccitato un po' troppo—I vivo per bug esotici:""Mi sento come se fossi eccitato da questo," ha detto. Non era sbagliato. Aggiungendo divertimento, non tutti gli emoji attivavano il problema. Solo quelli sopra U+FFFF che richiedevano coppie surrogate. E non tutte le modifiche risultavano nel problema nemmeno—solo quelle che causavano una spaccatura a offset di byte esattamente sbagliato. Era un bug difficile da debuggare prima che sapessimo cosa stava succedendo. Unità di codice, punti di codice e cluster di grafemi Cos'era successo? Cosa significa "sopra U+FFFF" in quel paragrafo precedente? Quali offset di byte? Per comprendere questo bug dobbiamo introdurre tre pezzi di vocabolario: Unità di codice → Punti di codice → Cluster di grafemi Unità di codice sono i valori 16-bit crudi che JavaScript utilizza per memorizzare le stringhe internamente (UTF-16). Questo è ciò che .length conta. Questo è ciò che .slice() e charCodeAt() operano su come bene. JavaScript opera a livello di unità di codice per impostazione predefinita Punti di codice sono ciò che Unicode definisce effettivamente come un singolo carattere. Un punto di codice come U+1F920 (🤠) è un carattere in vista di Unicode, ma è troppo grande per adattarsi in un singolo 16-bit unità di codice. Quindi UTF-16 lo divide in due unità di codice chiamate una coppia surrogate: un surrogate alto e un surrogate basso. Simili caratteri ASCII e molti simboli comuni si adattano in una unità di codice, quindi la distinzione non conta per loro. Emoji, però? Quasi sempre due. Cluster di grafemi sono ciò che un essere umano percepisce come un singolo carattere. Questo è ciò che vedi quando guardi una stringa in un editor di testo. Non è necessariamente ciò che è memorizzato in memoria, però. Nel caso del nostro bug, il cluster di grafemi era due emoji, ma le unità di codice erano una coppia surrogate. L'operazione di spaccatura stava dividendo la coppia surrogate a offset di byte sbagliato, risultando nel bug.
Commenti (0)
Accedi o Registrati per candidarti