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
GeekBye laidieni
Drošības pārbaude, kas padarīja mūsu lietotni neiznīcināmu

Katrs darbvirsmas lietotņu izstrādātājs par zemu novērtē automātisko atjaunināšanu tieši vienu reizi. Tā izskatās pēc atrisinātas problēmas — bibliotēka lejupielādē jaunu versiju un pārstartē tavu lietotni. Tad tu to izlaid un uzzini, ka "pārstartē savu lietotni" ir viena no bīstamākajām lietām, ko programmai var likt darīt, jo tas notiek tieši tajā brīdī, kad tava lietotne sevi nojauc un tai ir vismazākā kļūdu rezerve.

GeekBye automātiskajai atjaunināšanai vajadzēja sešus laidienus četrās dienās — no v1.5.14 līdz v1.5.19 — lai stabilizētos. Šis ir stāsts par ļaunāko kļūdu šajā posmā, ko izraisījām paši, cenšoties būt uzmanīgi.

Seši laidieni un tas viens, kam bija nozīme

Loks sākās ikdienišķi. v1.5.14 izlaboja kaunpilnu drukas kļūdas tipa problēmu: atjaunināšanas plūsma norādīja uz GitHub repozitorija nosaukumu, kas neeksistēja, tāpēc atjauninātājs pārbaudīja 404. v1.5.15 pievienoja manuālu pogu "Check for Updates" un īstu kļūdas ziņojumu. Tad sākās quitAndInstall kļūdas, un laidieni nāca ātri — jo, kad tavs atjaunināšanas mehānisms ir salauzts, tu nevari izlaist tā labojumu caur atjaunināšanas mehānismu. Katra iterācija ir manuālas pārinstalēšanas azartspēle.

Tas, kam ir nozīme, ir v1.5.18. Tā viss saturs bija viens komits ar nosaukumu, no kura man joprojām sažņaudzas: restore original quitAndInstall behavior to prevent unkillable app.

Kā "uzmanīga rīcība" sabojāja lietotni

Lūk, situācija. Kad atjauninājums ir lejupielādēts, Electron quitAndInstall ir paredzēts, lai aizvērtu lietotni un ieliktu jauno versiju. Kādā agrākā laidienā kāds — pamatoti — uztraucās, ka aizvēršana beznosacījumu veidā ir riskanta. Ko darīt, ja instalēšanā rodas kļūda? Vai nebūtu drošāk aizvērt tikai tad, ja viss izskatās veselīgi?

Tāpēc kods ieguva sargu, kas izskatījās saprātīgs:

autoUpdater.quitAndInstall(false, true)
setTimeout(() => {
  if (this.updateDownloaded)
    app.quit() // only quit if the update is still "good"
  else console.log('Error detected — keeping app open')
}, 500)

Loģika: palaid instalēšanu, pagaidi pussekundi un piespiedu kārtā izsauc galīgo app.quit() tikai tad, ja karodziņš updateDownloaded joprojām ir patiess — pretējā gadījumā turi lietotni atvērtu, lai lietotājs nepaliktu iestrēdzis.

Slazds ir vienu rindu tālāk, kļūdu apstrādātājā. Šis apstrādātājs iestatīja this.updateDownloaded = false. Tāpēc iztēlojies neizdevušos instalēšanu: notikums error nostrādā un notīra karodziņu. Bet quitAndInstall jau bija sācis nojaukšanu — tas bija aizvēris logus un noņēmis lietotnes aizvēršanas klausītājus. Tad pamostas 500 ms taimeris, pārbauda tagad viltus karodziņu, izlemj "kļūda konstatēta, turi lietotni atvērtu" un izlaiž app.quit().

Tagad tev ir, tieši operētājsistēmā macOS, sliktākais iespējamais stāvoklis. macOS neaizver lietotni tikai tāpēc, ka aizvērts tās pēdējais logs — tā ir window-all-closed uzvedība, uz kuru paļaujas katra Mac lietotne. Tātad process joprojām ir dzīvs, bet tam nav loga, nav izvēlņu joslas ceļa, un tā aizvēršanas klausītāji ir izrauti. Nav uz ko klikšķināt. Cmd-Q nav ar ko sarunāties. Vienīgā izeja ir Force Quit no Activity Monitor. "Drošības" pārbaude bija pārvērtusi neizdevušos atjauninājumu — atkopjamu neērtību — par zombiju, ko nevarēji nogalināt.

Labojums: nojaukšanai jābūt beznosacījuma

Korekcija v1.5.18 ir gandrīz agresīvi garlaicīga, un tieši tā ir doma. Tā noņem gudrības:

  1. Noņem window-all-closed un before-quit klausītājus, kas varētu iejaukties.
  2. Iznīcini katru logu — window.destroy(), nevis window.close(). Aizvēršanu var vetot apstrādātājs; iznīcināšanu nevar. Kad tu apņemies izslēgties, tu nelūdz pieklājīgi.
  3. Izsauc quitAndInstall.
  4. Izsauc app.quit() beznosacījumu veidā.

Nekāda karodziņa, nekāda taimera, nekāda "turi to atvērtu katram gadījumam". Jo patiesība par izslēgšanās ceļu ir tāda, ka pusizdarīta izslēgšanās ir sliktāka par jebkuru no iznākumiem. Pilnīga aizvēršana ir labi. Pilnīga palikšana atvērtai ir labi. Vienīgais stāvoklis, kuru tu nekad nedrīksti sasniegt, ir nojaukts, bet joprojām darbojas — un tieši tas ir stāvoklis, kurā tevi var iestrēdzināt nosacīta aizvēršana.

Vēl divas mācības, ko iemācīja tā pati nedēļa

Neiznīcināmā kļūda ir virsraksts, bet sešu laidienu ķepurošanās nostiprināja divus citus ieradumus, ko vērts nozagt.

Filtrē savu avāriju telemetriju pēc precīziem parakstiem, nevis plašiem atslēgvārdiem. Ķepurošanās vidū atklājām, ka mūsu kļūdu ziņošana bija konfigurēta atmest visu, kas satur vārdus kā permission, token vai microphone — mēģinājums samazināt troksni, kas klusi norija īstas avārijas, kurās nejauši tika pieminēti šie vārdi. Mēs izrāvām laukā vispārīgos filtrus un aizstājām tos ar precīzām noraidījuma virknēm (konkrēto ziņojumu, ko macOS izdod, kad atļauja tiek noraidīta) un konkrētiem īslaicīgiem tīkla kodiem kā ERR_NETWORK_CHANGED. Trokšņa samazināšana un kļūdu slēpšana ir viens un tas pats slēdzis, pagriezts pretējos virzienos; ja tu filtrē pēc noskaņas, tu izfiltrēsi tieši to, kas tev bija jāredz.

Katram automātiskajam ceļam ir vajadzīga manuāla izeja. Automātiskā atjaunināšana pēc būtības ir cenšanās pēc labākā — tīkli lēkā, instalēšana neizdodas. Tāpēc katrs kļūdas režīms ieguva cilvēcisku rezerves variantu: manuālo pogu "Check for Updates", eksponenciālas atkāpes atkārtojumus, atkārtotas pārbaudes taimeri un — rezervētu tieši gadījumam, kad lietotāja manuālais mēģinājums neizdevās — vienkāršā valodā ziņojumu "izdzēs lietotni un pārinstalē no vietnes". Automātiskais ceļš ir ērtība; manuālais ceļš ir garantija.

Galvenā doma

  1. Sargs ap neatgriezenisku darbību ir bīstamāks par pašu darbību. Nosacītā aizvēršana mēģināja novērst, ka slikts atjauninājums aizver lietotni, un tā vietā radīja stāvokli, kas ir sliktāks par aizvēršanu vai neaizvēršanu. Izslēgšanās un instalēšanas ceļiem jābūt beznosacījuma un idempotentiem — nekad nesaistītiem ar mainīgu karodziņu, ko cits apstrādātājs var izsist tev no rokām.
  2. Operētājsistēmā macOS "nav logu" nav "nav lietotnes". Jebkurai nojaukšanas loģikai jāņem vērā platforma, kur process bez logiem turpina darboties. Testē kļūdas ceļu īstajā operētājsistēmā, nevis tikai laimīgo ceļu.
  3. Funkciju, ko izlaid caur atjaunināšanas sistēmu, nevar testēt caur atjaunināšanas sistēmu. Šī asimetrija ir iemesls, kāpēc automātiskā atjaunināšana pelna paranoisku, beznosacījuma, smagi manuāli pārbaudītu kodu. Tu vari to salabot vieglo ceļu tikai pēc tam, kad tā jau darbojas.

Šī ir agrākā nodaļa uzticamības darbā, kas galu galā kļuva par GeekBye v2. Par to, kurp šis ceļš aizveda, skati ko patiešām prasa versija 2 (v2.0.0) un visu loku sadaļā programmatūras izlaišanas anatomija līdz pilnībai.