
Turvatarkistus, joka teki sovelluksestamme mahdottoman sulkea
Automaattinen päivitys oli vaikein ominaisuus, jonka olemme koskaan julkaisseet — kuusi julkaisua neljässä päivässä, jotta se lakkaisi rikkomasta sovellusta. Pahin vika oli se, jonka toimme mukaan yrittäessämme olla varovaisia: 500 millisekunnin "turva"-tarkistus, joka muutti epäonnistuneen päivityksen prosessiksi, jota et kirjaimellisesti voinut sulkea.
Jokainen työpöytäsovelluskehittäjä aliarvioi automaattisen päivityksen tasan kerran. Se näyttää ratkaistulta ongelmalta — kirjasto lataa uuden version ja käynnistää sovelluksesi uudelleen. Sitten julkaiset sen ja opit, että "käynnistä sovelluksesi uudelleen" on yksi vaarallisimmista asioista, joita ohjelmaa voi pyytää tekemään, koska se tapahtuu juuri sillä hetkellä, kun sovelluksesi purkaa itseään ja sillä on pienin mahdollinen virhemarginaali.
GeekByn automaattinen päivitys vaati vakautuakseen kuusi julkaisua neljässä päivässä — v1.5.14 versioon v1.5.19. Tämä on tarina tuon jakson pahimmasta viasta, jonka aiheutimme itse yrittäessämme olla varovaisia.
Kuusi julkaisua ja se yksi, jolla oli merkitystä
Kaari alkoi arkisesti. v1.5.14 korjasi nolon kirjoitusvirhetyyppisen vian: päivitysvirta osoitti GitHub-repositorion nimeen, jota ei ollut olemassa, joten päivittäjä tarkisti 404:ää. v1.5.15 lisäsi manuaalisen "Check for Updates" -painikkeen ja oikean virheilmoituksen. Sitten alkoivat quitAndInstall-viat, ja julkaisut tulivat nopeasti — koska kun päivitysmekanismisi on rikki, et voi julkaista sen korjausta päivitysmekanismin kautta. Jokainen iteraatio on manuaalisen uudelleenasennuksen uhkapeli.
Se, jolla on merkitystä, on v1.5.18. Sen koko sisältö oli yksi commit otsikolla, joka saa minut yhä irvistämään: restore original quitAndInstall behavior to prevent unkillable app.
Kuinka "varovaisuus" rikkoi sovelluksen
Tässä on asetelma. Kun päivitys on ladattu, Electronin quitAndInstallin on tarkoitus sulkea sovellus ja vaihtaa tilalle uusi versio. Aiemmassa julkaisussa joku — perustellusti — huolestui siitä, että sulkeminen ehdoitta oli riskialtista. Entä jos asennus antaa virheen? Eikö olisi turvallisempaa sulkea vain, jos kaikki näyttää terveeltä?
Niin koodiin kasvoi vartija, joka näytti järkevältä:
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)
Logiikka: käynnistä asennus, odota puoli sekuntia ja pakota lopullinen app.quit() vain, jos lippu updateDownloaded on yhä tosi — muuten pidä sovellus auki, jottei käyttäjä jää pulaan.
Ansa on yhden rivin päässä, virhekäsittelijässä. Kyseinen käsittelijä asetti this.updateDownloaded = false. Kuvittele siis epäonnistunut asennus: tapahtuma error laukeaa ja tyhjentää lipun. Mutta quitAndInstall oli jo aloittanut purkamisen — se oli sulkenut ikkunat ja poistanut sovelluksen sulkemiskuuntelijat. Sitten 500 ms:n ajastin herää, tarkistaa nyt epätoden lipun, päättää "virhe havaittu, pidä sovellus auki" ja ohittaa app.quit():n.
Nyt sinulla on, nimenomaan macOS:ssä, pahin mahdollinen tila. macOS ei sulje sovellusta vain siksi, että sen viimeinen ikkuna sulkeutui — se on window-all-closed-käyttäytyminen, johon jokainen Mac-sovellus luottaa. Prosessi on siis yhä hengissä, mutta sillä ei ole ikkunaa, ei valikkorivipolkua, ja sen sulkemiskuuntelijat on revitty irti. Ei ole mitään mitä klikata. Cmd-Q:lla ei ole mitään, jolle puhua. Ainoa ulospääsy on Force Quit Activity Monitorista. "Turva"-tarkistus oli muuttanut epäonnistuneen päivityksen — palautuvan harmituksen — zombiksi, jota et voinut tappaa.
Korjaus: purkamisen on oltava ehdotonta
Korjaus versiossa v1.5.18 on lähes aggressiivisen tylsä, ja siinä on koko pointti. Se poistaa nokkeluuden:
- Poista
window-all-closed- jabefore-quit-kuuntelijat, jotka voisivat häiritä. - Tuhoa jokainen ikkuna —
window.destroy(), eiwindow.close(). Sulkemisen voi käsittelijä estää vetolla; tuhoamista ei voi. Kun sitoudut sammuttamaan, et pyydä kohteliaasti. - Kutsu
quitAndInstall. - Kutsu
app.quit()ehdoitta.
Ei lippua, ei ajastinta, ei "pidetään se auki varmuuden vuoksi". Sillä totuus sammutuspolusta on, että puolivalmis sammutus on pahempi kuin kumpikaan lopputulos. Täysi sulkeutuminen on kunnossa. Täysin auki jääminen on kunnossa. Ainoa tila, jota et koskaan saa saavuttaa, on purettu mutta yhä käynnissä — ja juuri siihen tilaan ehdollinen sulkeminen voi sinut jättää jumiin.
Kaksi muuta oppituntia, jotka sama viikko opetti
Suljettamaton vika on otsikko, mutta kuuden julkaisun rähmiminen karkaisi kaksi muuta tapaa, jotka kannattaa varastaa.
Suodata kaatumistelemetriasi tarkkojen allekirjoitusten, ei laajojen avainsanojen mukaan. Kesken rähminnän huomasimme, että virheraportointimme oli määritetty pudottamaan kaikki, mikä sisälsi sanoja kuten permission, token tai microphone — yritys karsia melua, joka hiljaa nielaisi todellisia kaatumisia, jotka sattuivat mainitsemaan noita sanoja. Repäisimme laajat suodattimet irti ja korvasimme ne tarkoilla epäysmerkkijonoilla (tietyllä viestillä, jonka macOS antaa, kun lupa evätään) ja tietyillä ohimenevillä verkkokoodeilla kuten ERR_NETWORK_CHANGED. Melun vähentäminen ja vikojen piilottaminen ovat sama nuppi käännettynä vastakkaisiin suuntiin; jos suodatat fiiliksen mukaan, suodatat pois juuri sen, mikä sinun piti nähdä.
Jokainen automaattinen polku tarvitsee manuaalisen pakotien. Automaattinen päivitys on luonteeltaan parhaansa yrittävä — verkot pätkivät, asennukset epäonnistuvat. Niinpä jokainen virhetila sai inhimillisen varasuunnitelman: manuaalisen "Check for Updates" -painikkeen, eksponentiaalisen perääntymisen uusintayritykset, uudelleentarkistusajastimen ja — varattuna nimenomaan tapaukseen, jossa käyttäjän manuaalinen yritys epäonnistui — selkokielisen viestin "poista sovellus ja asenna uudelleen verkkosivustolta". Automaattinen polku on mukavuus; manuaalinen polku on takuu.
Ydinajatus
- Vartija peruuttamattoman toiminnon ympärillä on vaarallisempi kuin itse toiminto. Ehdollinen sulkeminen yritti estää huonoa päivitystä sulkemasta sovellusta ja loi sen sijaan tilan, joka on pahempi kuin sulkeminen tai sulkematta jättäminen. Sammutus- ja asennuspolkujen tulisi olla ehdottomia ja idempotentteja — ei koskaan ehdollisia muuttuvaan lippuun, jonka toinen käsittelijä voi napsauttaa altasi pois.
- macOS:ssä "ei ikkunoita" ei ole "ei sovellusta". Kaiken purkamislogiikan on huomioitava alusta, jolla ikkunaton prosessi jää käyntiin. Testaa virhepolku oikeassa käyttöjärjestelmässä, älä pelkkää onnellista polkua.
- Ominaisuutta, jonka julkaiset päivitysjärjestelmän kautta, ei voi testata päivitysjärjestelmän kautta. Tämä epäsymmetria on syy, miksi automaattinen päivitys ansaitsee paranoidin, ehdottoman, raskaasti manuaalisesti varmennetun koodin. Voit korjata sen helpolla tavalla vasta sen jälkeen, kun se jo toimii.
Tämä on luotettavuustyön varhaisin luku, josta lopulta tuli GeekBye v2. Siitä, minne tuo tie johti, katso mitä versio 2 todella vaatii (v2.0.0) ja koko kaari artikkelissa ohjelmiston täydellisyyteen julkaisemisen anatomia.