
Katkenud ühendus ei tohiks kogu rakendust maha võtta — aga meie oma tegi seda
Kui meie backend kadus keset koosolekut võrgust, ei pannud see lihtsalt transkriptsiooni pausile — see pani kogu rakenduse kokku jooksma. Põhjuseks oli üksainus käsitlemata sündmus ja parandus oli kümme rida. See on väljalasete kobar, mis pani GeekBye sisselogituna ja ühendatuna püsima läbi asjade, mis teda varem tapsid.
Mõned vead katkestavad ühe funktsiooni. Kõige hullemad võtavad maha kõik nende ümber. Ühes varases GeekBye ehituses, kui meie backend kadus võrgust, samal ajal kui sina olid keset koosolekut, ei jäänud transkriptsioon lihtsalt seisma — kogu rakendus jooksis kokku. Üks katkenud ühendus, ja kogu aken oli kadunud.
Väljalasete kobar, mis selle parandas — v1.6.8 kuni v1.6.11 — on meistriklass tänamatus töös nimega "püsi sisselogituna, püsi ühendatuna". Peamine parandus oli kümme rida. Siin on, mis need kümme rida olid, ja kõik muu, mis nendega koos välja tuli.
Kümme rida katkestuse ja kokkujooksmise vahel
Kokkujooksmine elas heli koodis. Kui transkriptsiooniseanss lammutas oma WebSocketid, kutsus see neil removeAllListeners() — mõistlik koristamine. Aga siin on see Node.js detail, mis muudab koristamise katastroofiks: kuulajata error sündmust ei ignoreerita. See visatakse uuesti püüdmata erindina.
Nii et kujuta ette sündmuste jada, kui backend kaob võrgust: socketil on error sündmus järjekorras ootamas, et vallanduda. Koristamine käivitab removeAllListeners(), eemaldades käsitleja, mis oleks selle püüdnud. Hetk hiljem järjekorras olev viga vallandub — tühjusesse. Node näeb error sündmust, mida keegi ei kuula, ja teeb ainsat, mida tema leping lubab: viskab selle püüdmata erindina, mis paneb protsessi kokku jooksma. Kasutaja backend luksus kaks sekundit ja tema rakendus kadus.
Parandus (v1.6.8) on peaaegu antiklimaatiline. Kohe pärast kuulajate eemaldamist lisa uuesti sihilikult tühi error käsitleja:
this.micWebSocket.removeAllListeners()
this.micWebSocket.on('error', () => {}) // absorb late error events
See tühi funktsioon on kogu mõte. See annab ootel olevale veale koha, kuhu maanduda — püüdja — nii et Nodel on kuulaja ja see ei viska kunagi midagi uuesti. Sama commit koristas ka pooleldi avatud socketid, kui ühenduskatse ebaõnnestub poolel teel, nii et untsu läinud ühendus ei saa jätta elusat socketti rippuma koos veaga, mis ootab vallandumist. Commiti kommentaar ütleb selle otse: "Ilma selleta muutuvad ootel olevad vead püüdmata erinditeks ja panevad rakenduse kokku jooksma." Kümme rida muutsid täieliku kokkujooksmise vaikseks, taastatavaks katkestuseks.
Õppetund on väärt rohkem kui parandus ise: iga kord, kui kutsud removeAllListeners() socketil või voos, mida sa võib-olla veel lammutad, lisa esmalt uuesti tühi error püüdja. Käsitlemata error sündmus on kokkujooksmine, mitte logirida.
Sisselogituna püsimine: värskenda ette ja värskenda põrke peale
Poolel "püsi ühendatuna" oli kaksik "püsi sisselogituna". Enne seda kobarat GeekBye autentimistoken lihtsalt aegus — mõne aja pärast visati sind ilma tseremooniata tagasi sisselogimisekraanile, keset töövoogu, ilma nähtava põhjuseta.
v1.6.8 parandas selle mõlemast suunast:
- Ennetav: taimer värskendab tokeni enne selle aegumist, ajastatud aegumise miinus nutika puhvri peale (murdosa tokeni elueast, alt ja ülevalt piiratud). Sind on värskendatud, enne kui sa üldse märkaksid.
- Reaktiivne: kui päring tuleb sellegipoolest tagasi
401-ga — kella nihe, vahelejäänud taimer, külm sülearvuti — värskendab klient ühe korra ja proovib algset päringut täpselt üks kord uuesti. Üks valve-lipp piirab seda korduskatset, nii et püsiv 401 ei saa kunagi keerduda lõpmatuks tsükliks.
Peen, aga kriitiline osa: mõlemad teed on mutexi taga. Kui kümme päringut tabab 401-i samal hetkel, ei käivita nad kümmet värskendust — nad kõik ootavad seda ühte käimasolevat värskendust ja proovivad siis uuesti. Ja kui värskendust tõesti päästa ei saa, aegub rakendus seansi sujuvalt — selge kümnesekundiline teade "sinu seanss aegus, palun logi uuesti sisse" — mitte vaikne viskamine sisselogimisekraanile.
Taasühendumine: lisa see kliendile, seejärel vii backendi
v1.6.9 muutis katkenud transkriptsiooniühenduse taastatavaks, mitte saatuslikuks. Kui backend teatas, et ülesvoolu kõneühendus oli katkenud, lõpetas klient saatusliku vea kuvamise ja ühendus hoopis uuesti — teadlikult rahuliku kasutuskogemusega: üks merevaigukollane teade "Taasühendumine…" (mitte üks iga katse kohta), roheline teade "Taasühendatud", kui see tagasi tuli, ja lõplik "palun taaskäivita" viga ainult pärast seda, kui iga katse oli ammendatud.
Ja siis midagi, mis mulle selle kobara juures meeldib: üks väljalase hiljem, versioonis v1.6.10, kustutasime selle kliendipoolse taasühendumise loogika — umbes 113 rida — ja viisime taasühendumise backendi, mis nüüd väljastab vaikseid "taasühendumise" olekuteateid, mida klient lihtsalt peegeldab. Ehitasime taastumise kliendile, otsustasime, et backend on õige omanik, ja viisime selle üle. See pole kõhklemine; see on raske probleemi aus elutsükkel. Töökindluse loogika, mis on jagatud ühenduse mõlema otsa vahel, on halb märk — vali omanik. (Backendist sai lõpuks selle lõplik omanik, ja see on taasühendumise lugu, mida jutustatakse artiklis miks su AI märkmetegija peatub halva Wi-Fi peal.)
Sama väljalase muutis salvestusaja piirangud inimlikuks: toore vea asemel hoiatusteade, mis laseb salvestusel jätkuda, ja selge sõnum "Salvestuse piir saavutatud", kui see tegelikult peatub — sõbralik lause esile toodud, sisemine eesliide eemaldatud.
Üks korduskatse, mis valitseb kõiki
Versiooniks v1.6.11 oli sama korduskatse-koos-taganemisega loogika kopeeritud kolme erinevasse kohta — autentimine, uuendused, seansi lammutamine. Nii et see konsolideeriti üheks mooduliks ühe jagatud reegliga selle kohta, mida üldse tasub korrata: mööduvad võrgutõrked (ühenduse lähtestus, ajalõpp, DNS-i luksumine, socketi katkemine) proovitakse eksponentsiaalse taganemisega uuesti; tõeline serveriviga kukub kohe läbi, sest tõelise 4xx kordamine lihtsalt raiskab kasutaja aega. Üks klassifikaator, mõned nimega eelseaded, kolm kutsujat sellele üle viidud.
Mida see kobar õpetas
- Käsitlemata
errorsündmus on kokkujooksmine, mitte logirida. Node viskab uuesti error sündmused, millel pole kuulajat. Eemalda kuulajad socketilt, mida sa võib-olla veel lammutad, ja pead uuesti lisama tühja error püüdja — muidu võtab üks katkenud ühendus kogu rakenduse endaga kaasa. - Värskenda mandaate enne aegumist ja reaktiivselt 401 peale — mõlemal mutexiga. Ennetav hoiab sind sisselogituna; reaktiivne püüab servajuhtumid; mutex tähendab, et N samaaegset kutsujat põhjustab täpselt ühe värskenduse, ja korduskatse piir tähendab, et halb token ei saa lõputult tsükeldada.
- Proovi uuesti ainult mööduvate vigade puhul ja tsentraliseeri klassifikaator. "Kas seda tasub korrata?" on üks otsus, mis kuulub ühte kohta. Tõelise vea kordamine on lihtsalt aeglasem läbikukkumine.
- Otsusta, kellele kuulub taasühendumine. Lisasime selle kliendile, seejärel viisime backendi. Taastumise loogika, mis elab mõlemas otsas, on veagenraator; anna sellele üks kodu.
See on kolmas peatükk töökindluse loos, millest saab GeekBye v2. Eelmise peatüki jaoks vaata me kustutasime 5000 rida helikoodi ja transkriptid hakkasid ilmuma kaks korda (v1.6.0); selle kohta, kuhu taasühendumine lõpuks paika loksus, miks su AI märkmetegija peatub halva Wi-Fi peal; ja kogu kaare jaoks tarkvara täiuslikkuseni laevastamise anatoomia.