Steven
Steven5 min čtení

Bezpečnostní kontrola, kvůli které nešla naše aplikace ukončit

Automatická aktualizace byla nejtěžší funkce, jakou jsme kdy vydali — šest vydání za čtyři dny, aby přestala odrovnávat aplikaci. Nejhorší chybu jsme si způsobili sami ve snaze být opatrní: 500milisekundová „bezpečnostní" kontrola, která z neúspěšné aktualizace udělala proces, který se doslova nedal ukončit.

Inženýrství
Electron
Spolehlivost
Vydání GeekBye
Bezpečnostní kontrola, kvůli které nešla naše aplikace ukončit

Každý vývojář desktopové aplikace podcení automatickou aktualizaci právě jednou. Vypadá to jako vyřešený problém — knihovna stáhne novou verzi a restartuje vaši aplikaci. Pak to vydáte a zjistíte, že „restartovat aplikaci" je jedna z nejnebezpečnějších věcí, o kterou lze program požádat, protože se to děje přesně ve chvíli, kdy se aplikace sama boří a má nejmenší prostor pro chybu.

Stabilizace automatické aktualizace v GeekBye zabrala šest vydání za čtyři dny — v1.5.14 až v1.5.19. Tohle je příběh nejhorší chyby v tomto úseku, kterou jsme si způsobili sami ve snaze být opatrní.

Šest vydání a to jediné, na kterém záleželo

Oblouk začal všedně. v1.5.14 opravila trapnou chybu třídy překlep: aktualizační kanál ukazoval na název GitHub repozitáře, který neexistoval, takže updater kontroloval 404. v1.5.15 přidala ruční tlačítko „Check for Updates" a skutečnou chybovou hlášku. Pak začaly chyby quitAndInstall a vydání přicházela rychle — protože když je váš aktualizační mechanismus rozbitý, nemůžete opravu poslat skrz aktualizační mechanismus. Každá iterace je sázka na ruční přeinstalaci.

Tím, na kterém záleží, je v1.5.18. Celý jeho obsah byl jediný commit s titulkem, ze kterého se mi pořád svírá žaludek: restore original quitAndInstall behavior to prevent unkillable app.

Jak „opatrnost" odrovnala aplikaci

Tady je zápletka. Když je aktualizace stažená, Electroní quitAndInstall má aplikaci zavřít a vyměnit ji za novou verzi. V dřívějším vydání někdo — rozumně — dostal obavu, že ukončit aplikaci bezpodmínečně je riskantní. Co když instalace skončí chybou? Nebylo by bezpečnější ukončit ji jen tehdy, když všechno vypadá v pořádku?

Tak kód narostl o pojistku, která vypadala rozumně:

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)

Logika: spustit instalaci, počkat půl sekundy a vynutit závěrečné app.quit() jen tehdy, pokud je příznak updateDownloaded stále true — jinak nechat aplikaci otevřenou, aby uživatel neuvízl.

Past je vzdálená jediný řádek, v obsluze chyby. Ta obsluha nastavovala this.updateDownloaded = false. Představte si tedy neúspěšnou instalaci: událost error se spustí a vymaže příznak. Jenže quitAndInstallzačal demontáž — zavřel okna a odebral posluchače ukončení aplikace. Pak se probudí 500ms časovač, zkontroluje nyní nepravdivý příznak, rozhodne se „error detected, keep the app open" a přeskočí app.quit().

A teď máte, konkrétně na macOS, nejhorší možný stav. macOS neukončí aplikaci jen proto, že se zavřelo její poslední okno — to je chování window-all-closed, na které spoléhá každá aplikace pro Mac. Takže proces je pořád naživu, ale nemá žádné okno, žádnou cestu přes lištu menu a jeho posluchače ukončení byly vytrhány. Není na co kliknout. Cmd-Q nemá s čím mluvit. Jediná cesta ven je Force Quit z Activity Monitor. „Bezpečnostní" kontrola přeměnila neúspěšnou aktualizaci — obtěžující, ale zotavitelnou — na zombie, kterou nešlo zabít.

Oprava: demontáž musí být bezpodmínečná

Náprava ve v1.5.18 je téměř agresivně nudná, což je celý smysl. Odstraňuje tu chytristiku:

  1. Odstranit posluchače window-all-closed a before-quit, kteří by mohli zasahovat.
  2. Zničit každé okno — window.destroy(), ne window.close(). Zavření může obsluha vetovat, zničení ne. Když se rozhodujete pro vypnutí, neptáte se zdvořile.
  3. Zavolat quitAndInstall.
  4. Zavolat app.quit() bezpodmínečně.

Žádný příznak, žádný časovač, žádné „nechme to otevřené pro jistotu". Protože pravda o vypínací cestě je taková, že napůl dokončené vypnutí je horší než kterýkoli z obou výsledků. Úplně ukončit je v pořádku. Úplně zůstat otevřený je v pořádku. Jediný stav, do kterého se nikdy nesmíte dostat, je zbouraný, ale stále běžící — a přesně do toho stavu vás podmíněné ukončení dokáže zaseknout.

Dvě další lekce, které nás naučil stejný týden

Neukončitelná chyba je hlavní titulek, ale ten spěch šesti vydání zocelil dva další návyky, které stojí za ukradení.

Filtrujte telemetrii pádů podle přesných signatur, ne širokých klíčových slov. Uprostřed shonu jsme zjistili, že naše hlášení chyb bylo nastavené tak, aby zahazovalo cokoli, co obsahovalo slova jako permission, token nebo microphone — pokus omezit šum, který ale tiše polykal skutečné pády, jež ta slova náhodou zmiňovaly. Plošné filtry jsme vytrhli a nahradili je přesnými řetězci zamítnutí (konkrétní hláškou, kterou macOS vydá při odepření oprávnění) a konkrétními přechodnými síťovými kódy jako ERR_NETWORK_CHANGED. Redukce šumu a skrývání chyb jsou tentýž knoflík otočený opačným směrem; když filtrujete podle dojmu, vyfiltrujete přesně to, co jste potřebovali vidět.

Každá automatická cesta potřebuje ruční záchrannou páku. Automatická aktualizace je ze své podstaty snaha o co nejlepší výsledek — sítě selhávají, instalace se nezdaří. Takže každý způsob selhání dostal lidskou záložní variantu: ruční tlačítko „Check for Updates", opakování s exponenciálním odstupem, časovač pro opětovnou kontrolu a — vyhrazenou konkrétně pro případ, kdy selhal ruční pokus uživatele — srozumitelnou hlášku „smažte aplikaci a přeinstalujte ji z webu". Automatická cesta je pohodlí; ruční cesta je záruka.

Ponaučení

  1. Pojistka kolem nevratné akce je nebezpečnější než ta akce samotná. Podmíněné ukončení se snažilo zabránit tomu, aby špatná aktualizace ukončila aplikaci, a místo toho vytvořilo stav horší, než je ukončení i neukončení. Cesty vypnutí a instalace by měly být bezpodmínečné a idempotentní — nikdy podmíněné mutovatelným příznakem, který vám jiná obsluha může podtrhnout pod nohama.
  2. Na macOS „žádná okna" neznamená „žádná aplikace". Jakákoli logika demontáže musí počítat s platformou, kde proces bez oken běží dál. Otestujte cestu selhání, na skutečném OS, ne jen šťastnou cestu.
  3. Funkci, kterou vydáváte přes aktualizační systém, nelze přes aktualizační systém otestovat. Právě tato asymetrie je důvod, proč si automatická aktualizace zaslouží paranoidní, bezpodmínečný a důkladně ručně ověřený kód. Opravit ji tou snadnou cestou dostanete šanci až potom, co už funguje.

Tohle je nejranější kapitola práce na spolehlivosti, ze které se nakonec stal GeekBye v2. Kam ta cesta vedla, se dočtete v what a version 2 actually takes (v2.0.0) a celý oblouk v the anatomy of shipping software to perfection.

Související články

Přerušené spojení by nemělo shodit celou aplikaci — ale ta naše to dělala
Steven
Steven6 min čtení

Přerušené spojení by nemělo shodit celou aplikaci — ale ta naše to dělala

Když nám backend spadl uprostřed schůzky, nezastavil jen přepis — shodil celou aplikaci. Příčinou byla jediná neošetřená událost a oprava měla deset řádků. Tohle je shluk vydání, díky kterému GeekBye zůstává přihlášený a připojený i skrz věci, které ho dřív zabíjely.

Inženýrství
Spolehlivost
Electron
Ticho bylo nosné
Steven
Steven7 min čtení

Ticho bylo nosné

Poslední dvě vydání GeekBye v1 jsou o téže nepříjemné pravdě: přepis v reálném čase přes skutečnou síť není bezeztrátový a poctivý krok je přestat předstírat, že je. v1.8.20 uchovávalo kopii každého audio chunku na disku, než ho během reconnectu zahodilo, a začalo mezery v přepisu označovat nahlas. v1.9.0 přestalo posílat ticho kvůli úspoře šířky pásma — a zjistilo, že ticho bylo přesně ten signál, podle kterého přepisovač poznal, že věta skončila. Dvě vydání o ceně zahazování věcí.

Inženýrství
Audio
Spolehlivost
Tisk schůzky do PDF bez PDF knihovny
Steven
Steven8 min čtení

Tisk schůzky do PDF bez PDF knihovny

GeekBye exportuje schůzku jako PDF a nikde v kódu není žádná PDF knihovna. Renderuje HTML v neviditelném okně prohlížeče a vytiskne ho. Ta volba je celý příběh: udělala funkci snadnou na postavení a dala jí každé selhání, které má skutečný prohlížeč — bílý záblesk, limit délky URL a zalomení stránky, které rozřízlo screenshoty napůl. Oprava toho nejošklivějšího bugu byla jeden řádek CSS.

Inženýrství
Electron
Desktop