Steven
Steven6 min lasīšana

Pārtraukts savienojums nedrīkstētu avarēt visu lietotni — bet mūsējo tas avarēja

Kad mūsu backend pazuda tiešsaistes sapulces vidū, tas ne tikai apturēja transkripciju — tas avarēja visu lietotni. Cēlonis bija viens neapstrādāts notikums, un labojums bija desmit rindas. Šī ir izlaidumu kopa, kas panāca, ka GeekBye paliek pieteicies un savienots cauri lietām, kas to agrāk nogalināja.

Izstrāde
Uzticamība
Electron
GeekBye izlaidumi
Pārtraukts savienojums nedrīkstētu avarēt visu lietotni — bet mūsējo tas avarēja

Dažas kļūdas pārtrauc kādu funkciju. Ļaunākās nogāž visu, kas ap tām atrodas. Kādā agrīnā GeekBye būvējumā, ja mūsu backend pazuda, kamēr tu biji sapulces vidū, transkripcija ne tikai apstājās — avarēja visa lietotne. Viens pārtraukts savienojums, un viss logs bija pazudis.

Izlaidumu kopa, kas to novērsa — no v1.6.8 līdz v1.6.11 — ir meistarklase neefektīgajā darbā ar devīzi "paliec pieteicies, paliec savienots". Galvenais labojums bija desmit rindas. Lūk, kādas bija šīs desmit rindas un viss pārējais, kas tika izlaists tām līdzās.

Desmit rindas starp savienojuma zudumu un avāriju

Avārija mita audio kodā. Kad transkripcijas sesija nojauca savus WebSocket savienojumus, tā uz tiem izsauca removeAllListeners() — saprātīga sakārtošana. Bet lūk tā Node.js detaļa, kas sakārtošanu pārvērš katastrofā: error notikums bez klausītāja netiek ignorēts. Tas tiek pārmests kā neuztverts izņēmums.

Tāpēc iztēlojies notikumu secību, kad backend pazūd: ligzdai ir error notikums, kas stāv rindā, lai nostrādātu. Sakārtošana izpilda removeAllListeners(), noņemot apstrādātāju, kas to būtu uztvēris. Mirkli vēlāk rindā gaidošā kļūda nostrādā — tukšumā. Node redz error notikumu, ko neviens neklausās, un dara vienīgo, ko atļauj tā līgums: pārmet to kā neuztvertu izņēmumu, kas avarē procesu. Lietotāja backend uz divām sekundēm iekšķuca, un viņu lietotne pazuda.

Labojums (v1.6.8) ir gandrīz vīlieniski vienkāršs. Uzreiz pēc klausītāju noņemšanas no jauna pievieno apzināti tukšu error apstrādātāju:

this.micWebSocket.removeAllListeners()
this.micWebSocket.on('error', () => {}) // absorb late error events

Šī tukšā funkcija ir visas jēgas kodols. Tā dod gaidošajai kļūdai vietu, kur nokrist — uztvērēju — tā ka Node ir klausītājs un tas nekad neko nepārmet. Tas pats commit arī sakārtoja pusatvērtas ligzdas, kad savienošanās mēģinājums neizdodas pusceļā, tā ka nevainīgi izgāzta savienošanās nevar atstāt dzīvu ligzdu karājamies ar kļūdu, kas gaida nostrādāt. Commit komentārs to saka tieši: "Bez šī gaidošās kļūdas kļūst par neuztvertiem izņēmumiem un avarē lietotni." Desmit rindas pilnu avāriju pārvērta par klusu, atgūstamu savienojuma zudumu.

Mācība ir vērtīgāka par pašu labojumu: ikreiz, kad izsauc removeAllListeners() uz ligzdas vai plūsmas, ko, iespējams, vēl joprojām nojauc, vispirms no jauna pievieno tukšu error uztvērēju. Neapstrādāts error notikums ir avārija, nevis žurnāla ieraksts.

Palikt pieteiktam: atjauno laikus un atjauno pēc atlēciena

Pusei "paliec savienots" bija dvīnis "paliec pieteicies". Pirms šīs kopas GeekBye autentifikācijas žetons vienkārši beidzās — pēc kāda laika tevi bez ceremonijām izmeta atpakaļ uz pieteikšanās ekrānu, darba plūsmas vidū, bez redzama iemesla.

v1.6.8 to novērsa no abiem virzieniem:

  • Proaktīvi: taimeris atjauno žetonu pirms tas beidzas, ieplānots derīguma beigu brīdim mīnus gudra rezerve (daļa no žetona dzīves ilguma, ar apakšējo un augšējo robežu). Tu esi atjaunināts, pirms to vispār pamanītu.
  • Reaktīvi: ja pieprasījums tomēr atgriežas ar 401 — pulksteņa nobīde, palaists garām taimeris, atdzisis klēpjdators — klients vienreiz atjauno un tieši vienreiz atkārto sākotnējo pieprasījumu. Viens sarga karodziņš ierobežo šo atkārtojumu, tā ka pastāvīgs 401 nekad nevar sagriezties bezgalīgā ciklā.

Smalkā, bet būtiskā daļa: abi ceļi ir aiz mutex. Ja desmit pieprasījumi tajā pašā mirklī saņem 401, tie neizraisa desmit atjaunošanas — tie visi gaida uz to vienu notiekošo atjaunošanu un tad atkārto. Un kad atjaunošanu patiešām nevar izglābt, lietotne izbeidz sesiju gludi — ar skaidru desmit sekunžu paziņojumu "tava sesija beidzās, lūdzu, piesakies vēlreiz" — nevis ar klusu izmešanu uz pieteikšanās ekrānu.

Atkārtota savienošanās: pievieno to klientā, tad pārcel uz backend

v1.6.9 padarīja pārtrauktu transkripcijas savienojumu atgūstamu, nevis fatālu. Kad backend ziņoja, ka augšuptecē esošais runas savienojums bija pārtrūcis, klients vairs neuzrādīja fatālu kļūdu, bet gan atkārtoti savienojās — ar apzināti mierīgu lietotāja pieredzi: viens dzintarkrāsas paziņojums "Atkārtoti savienojas…" (nevis viens uz katru mēģinājumu), zaļš paziņojums "Savienots atkārtoti", kad tas atgriezās, un galīgā "lūdzu, restartē" kļūda tikai pēc tam, kad bija izsmelts katrs mēģinājums.

Un tad kaut kas, kas man patīk šajā kopā: vienu izlaidumu vēlāk, v1.6.10, mēs izdzēsām to klienta puses atkārtotās savienošanās loģiku — apmēram 113 rindas — un pārcēlām atkārtoto savienošanos uz backend, kas tagad izdod klusus "atkārtoti savienojas" statusa ziņojumus, ko klients vienkārši atspoguļo. Mēs uzbūvējām atgūšanu klientā, izlēmām, ka backend ir pareizais īpašnieks, un pārcēlām to. Tā nav vilcināšanās; tas ir grūtas problēmas godīgs dzīves cikls. Uzticamības loģika, sadalīta starp abiem savienojuma galiem, ir slikta pazīme — izvēlies īpašnieku. (Backend galu galā kļuva par šī noteikto īpašnieku, un tas ir atkārtotās savienošanās stāsts, kas izstāstīts rakstā kāpēc tavs AI pierakstu rīks apstājas sliktā Wi-Fi.)

Tas pats izlaidums padarīja ieraksta laika ierobežojumus humānus: rupjas kļūdas vietā — brīdinājuma paziņojums, kas ļauj ierakstam turpināties, un skaidra ziņa "Sasniegts ieraksta ierobežojums", kad tas patiešām apstājas — draudzīgais teikums uzrādīts, iekšējais priedēklis noņemts.

Viens atkārtojums, kas valda pār visiem

Līdz v1.6.11 tā pati atkārtošanas-ar-atkāpi loģika bija nokopēta trīs dažādās vietās — autentifikācijā, atjauninājumos, sesijas nojaukšanā. Tāpēc to apvienoja vienā modulī ar vienu kopīgu noteikumu par to, ko vispār ir vērts atkārtot: pārejošas tīkla kļūmes (savienojuma atiestate, noildze, DNS ķeza, ligzdas pārrāvums) tiek atkārtotas ar eksponenciālu atkāpi; īsta servera kļūda izgāžas ātri, jo īsta 4xx atkārtošana vienkārši izšķiež lietotāja laiku. Viens klasifikators, daži nosaukti sagatavotie iestatījumi, trīs izsaucēji pārcelti uz to.

Ko šī kopa iemācīja

  1. Neapstrādāts error notikums ir avārija, nevis žurnāla ieraksts. Node pārmet error notikumus, kuriem nav klausītāja. Noņem klausītājus no ligzdas, ko, iespējams, vēl joprojām nojauc, un tev no jauna jāpievieno tukšs error uztvērējs — citādi viens pārtraukts savienojums paņem sev līdzi visu lietotni.
  2. Atjauno akreditācijas datus pirms derīguma beigām un reaktīvi pēc 401 — ar mutex uz abiem. Proaktīvais tevi patur pieteiktu; reaktīvais notver robežgadījumus; mutex nozīmē, ka N vienlaicīgi izsaucēji izraisa tieši vienu atjaunošanu, un atkārtojumu ierobežojums nozīmē, ka slikts žetons nevar cikloties mūžīgi.
  3. Atkārto tikai pārejošu kļūdu gadījumā un centralizē klasifikatoru. "Vai to ir vērts atkārtot?" ir viens lēmums, kam jābūt vienā vietā. Īstas kļūdas atkārtošana ir tikai lēnāka izgāšanās.
  4. Izlem, kam pieder atkārtotā savienošanās. Mēs to pievienojām klientā, tad pārcēlām uz backend. Atgūšanas loģika, kas dzīvo abos galos, ir kļūdu ģenerators; dod tai vienu māju.

Šī ir trešā nodaļa uzticamības stāstā, kas kļūst par GeekBye v2. Iepriekšējo nodaļu skaties rakstā mēs izdzēsām 5000 rindu audio koda, un transkripti sāka parādīties divreiz (v1.6.0); par to, kur atkārtotā savienošanās galu galā apmetās, kāpēc tavs AI pierakstu rīks apstājas sliktā Wi-Fi; un par visu loku kā izskatās programmatūras izlaišana līdz pilnībai.

Saistītie raksti

Drošības pārbaude, kas padarīja mūsu lietotni neiznīcināmu
Steven
Steven5 min lasīšana

Drošības pārbaude, kas padarīja mūsu lietotni neiznīcināmu

Automātiskā atjaunināšana bija grūtākā funkcija, ko jebkad izlaidām — seši laidieni četrās dienās, lai apturētu lietotnes sabrukšanu. Ļaunākā kļūda bija tā, ko ieviesām, cenšoties būt uzmanīgi: 500 milisekunžu "drošības" pārbaude, kas neizdevušos atjauninājumu pārvērta par procesu, ko tu burtiski nevarēji aizvērt.

Inženierija
Electron
Uzticamība
Viens CSS mainīgais, piecas pārskatīšanas kārtas un Swift rīkkopa, kas meloja
Steven
Steven6 min lasīšana

Viens CSS mainīgais, piecas pārskatīšanas kārtas un Swift rīkkopa, kas meloja

GeekBye v2.0.7 padarīja visu pārklājumu regulējami caurspīdīgu — kas izklausās pēc vienas rindas CSS izmaiņas, bet nekādā ziņā tāda nebija. Īstais stāsts ir par to, ko pamanīja koda pārskatīšana: divas virsmas, kas atteicās izbālēt, ierakstīšanas josla, kas pie tās pašas necaurspīdības izskatījās nepareizi, un kompilēts binārais fails, kas mainīja 672 baitus turp un atpakaļ, jo mūsu pašu dokumentācija pārskatītājam pateica nepareizo Swift versiju.

Izstrāde
Dizains
Būvēšana
Mierīga programmatūra: kā nogalināt mirgojošu slēdzi un iemācīt tērzēšanai pateikt, kādā režīmā tā ir
Steven
Steven5 min lasīšana

Mierīga programmatūra: kā nogalināt mirgojošu slēdzi un iemācīt tērzēšanai pateikt, kādā režīmā tā ir

Divi nelieli GeekBye laidieni bez lielām funkcijām — tikai iestatījumu slēdzis, kas beidza mirgot, un tērzēšanas asistents, kas beidzot pasaka, vai atbildēja sapulces režīmā vai kodēšanas režīmā. Lūk, cik patiesībā maksā "mierīga programmatūra", pa vienai detaļai.

Produkts
Dizains
Izstrāde