Wenn du lange genug im Geschäft bist, Dinge auf Computern zu bauen, denke ich, wirst du irgendwann eine Lieblingsfehler-Geschichte haben. Dies ist meine Geschichte. Ich habe auch eine interaktive Werkzeug erstellt, mit dem du die Konzepte hinter diesem Bug untersuchen kannst. Der Bug: zwei Emoji eintreten, keines geht raus Ich arbeitete an der Migration eines Legacy-Editors zu einer gemeinsamen Bearbeitungserfahrung mit meinem Team. TipTap oben (das ist ein Wrapper um ProseMirror), Yjs unten, das die CRDT-Magie für die Echtzeit-Synchronisierung handhabt. Es funktionierte gut! Im Großen und Ganzen. In unseren alpha/early-Release-Tagen, als es noch fast ausschließlich intern oder frühzeitig bei den Nutzern war, passierte es manchmal, dass der Editor einfach aufhörte, den Inhalt zu speichern. Stumm. Du schreibst weiter und alles sieht gut aus, aber deine Änderungen wurden nicht synchronisiert mit dem Yjs-Dokument. Die nächste Zeit, als du die Seite öffnest, ist alles, was du seit dem Fehlerpunkt geschrieben hast, verschwunden. Es war furchtbar, sehr selten und fast unmöglich zu diagnostizieren, weil wir es nie wiederholen konnten. Wir haben es wirklich versucht! Meine ersten Vermutungen konzentrierten sich auf instabile Wi-Fi-Verbindungen und ungewöhnliche WebSocket-Verhaltensweisen, aber keine Menge an Raten oder das Einschalten/Ausschalten des Wi-Fi schien das Problem zu reproduzieren. Die Erfahrung war überraschend widerstandsfähig in diesen Fällen, in meiner Erinnerung. Es schien, als wäre es zufällig passiert, nie, wenn jemand hinsah. Keine offensichtlichen Fehler in der Konsole, keine Stack-Trace, kein Crash. Nur... "Hey, denke, meine Änderungen wurden nicht gespeichert." Dann einmal hat unser Produktmanager es gelöst. Das war keine einfache Sache zu finden. Er hatte es mehr als jeder andere (wahrscheinlich weil er der Beste bei der Verwendung unseres Produkts war) und hatte es methodisch eingeschränkt. "Ich fühle mich, als ob ich verrückt bin, aber ich denke, es ist, wenn ich bestimmte Zeichen zusammen tippe, zurückgehe und einen Charakter zwischen ihnen einsetze..." Er hatte 🟢 und 🔴 in seinen wöchentlichen Projektstatus-E-Mails verwendet, um die allgemeine Gesundheit zu kommunizieren. Grün für in Ordnung, rot für gefährdet. Jede Woche hatte der Template, den er verwendete, beide Zeichen bereits und er würde sie einfach entfernen (Normalerweise den roten, bin ich froh zu sagen!). In dieser Gelegenheit hatte er den grünen Kreis kopiert und den roten vor ihn eingesetzt in einem Punkt, oder vielleicht umgekehrt. Diese bestimmte Operation— ein Multi-Byte-Emoji an einem anderen anfügen— aktiviert eine Spaltung in der unterliegenden CRDT-Bibliothek, die eine Surrogatpaar in der Mitte spaltete. Ich erinnere mich, dass ich in einer Besprechung war, als er mir und einem meiner direkten Vorgesetzten zeigte, der sich mit der gemeinsamen Bearbeitungstransition beschäftigte. Ich muss mich ein bisschen zu sehr aufgeregt haben—I lebe für exotische Bugs:""Ich fühle mich, als ob du von diesem aufgeregt bist," sagte er. Er hatte Recht. Zusätzlich zum Spaß, nicht alle Emojis lösten das Problem. Nur die, die über U+FFFF lagen und Surrogatpaare erforderten. Und nicht alle Änderungen resultierten im Problem—nur die, die eine Spaltung an genau dem falschen Byte-Offset verursachten. Es war ein Bug, der schwer zu debuggen war, bevor wir wussten, was los war. Code-Einheiten, Code-Punkte und Grafemcluster Was war los? Was bedeutet "über U+FFFF" in dem letzten Absatz? Was sind die Byte-Offsets? Um diesen Bug zu verstehen, müssen wir drei Begriffe einführen: Code-Einheiten → Code-Punkte → Grafemcluster Code-Einheiten sind die rohen 16-Bit-Werte, die JavaScript zur Speicherung von Zeichenketten intern verwendet (UTF-16). Dies ist, was .length zählt. Dies ist, was .slice() und charCodeAt() auf diese Weise arbeiten. JavaScript arbeitet auf Code-Einheiten-Ebene standardmäßig Code-Punkte sind, was Unicode als ein einzelnes Zeichen definiert. Ein Code-Punkt wie U+1F920 (🤠) ist ein Zeichen in der Sicht von Unicode, aber es ist zu groß, um in einem einzelnen 16-Bit-Code-Einheit zu passen. Also teilt UTF-16 es in zwei Code-Einheiten auf, die als Surrogatpaar bezeichnet werden: ein Surrogat-Hoch und ein Surrogat-Tief. Einfache ASCII-Zeichen und viele Symbole passen in eine Code-Einheit, also spielt die Unterscheidung keine Rolle für sie. Emoji, aber? Fast immer zwei. Grafemcluster sind, was ein Mensch als ein einzelnes Zeichen wahrnimmt. Dies ist, was du siehst, wenn du eine Zeichenkette in einem Texteditor ansiehst. Es ist nicht notwendigerweise das, was in der Speicherung gespeichert wird, aber. Im Fall unseres Bugs war der Grafemcluster zwei Emoji, aber die Code-Einheiten waren ein Surrogatpaar. Die Spaltung der Operation teilte das Surrogatpaar an einem falschen Byte-Offset, was zum Bug führte.
Kommentare (0)
Login or Register to apply