Steven
Steven5 min. skaitymo

Saugumo patikra, dėl kurios mūsų programos nebuvo įmanoma uždaryti

Automatinis atnaujinimas buvo sunkiausia funkcija, kokią kada nors išleidome — šeši leidimai per keturias dienas, kad jis nustotų gadinti programą. Blogiausią klaidą įvėlėme patys, stengdamiesi būti atsargūs: 500 milisekundžių „saugumo" patikra, kuri nepavykusį atnaujinimą paversdavo procesu, kurio tiesiogine prasme nebuvo įmanoma uždaryti.

Inžinerija
Electron
Patikimumas
GeekBye leidimai
Saugumo patikra, dėl kurios mūsų programos nebuvo įmanoma uždaryti

Kiekvienas darbalaukio programų kūrėjas tiksliai vieną kartą neįvertina automatinio atnaujinimo. Jis atrodo kaip išspręsta problema — biblioteka atsisiunčia naują versiją ir paleidžia programą iš naujo. Paskui tai išleidi ir sužinai, kad „paleisk programą iš naujo" yra vienas pavojingiausių dalykų, kurių galima paprašyti programos, nes tai nutinka lygiai tuo momentu, kai programa ardo pati save ir turi mažiausią atsargą klaidai.

GeekBye automatiniam atnaujinimui prireikė šešių leidimų per keturias dienas — nuo v1.5.14 iki v1.5.19 — kol jis stabilizavosi. Tai istorija apie blogiausią to atkarpos klaidą, kurią patys ir sukėlėme stengdamiesi būti atsargūs.

Šeši leidimai ir tas vienas, kuris turėjo reikšmės

Lankas prasidėjo kasdieniškai. v1.5.14 ištaisė gėdingą korektūros lygio klaidą: atnaujinimų srautas rodė į GitHub saugyklos pavadinimą, kurio nebuvo, tad atnaujintuvas tikrino 404. v1.5.15 pridėjo rankinį mygtuką „Check for Updates" ir tikrą klaidos pranešimą. Paskui prasidėjo quitAndInstall klaidos, ir leidimai ėjo greitai — nes kai tavo atnaujinimo mechanizmas sugedęs, jo pataisos negali išleisti per atnaujinimo mechanizmą. Kiekviena iteracija — tai lošimas dėl rankinio įdiegimo iš naujo.

Tas, kuris turi reikšmės, yra v1.5.18. Visą jo turinį sudarė vienas commitas su pavadinimu, nuo kurio iki šiol susiraukiu: restore original quitAndInstall behavior to prevent unkillable app.

Kaip „atsargumas" sugadino programą

Štai padėtis. Kai atnaujinimas atsisiųstas, Electron quitAndInstall turi uždaryti programą ir pakeisti ją nauja versija. Ankstesniame leidime kažkas — visai pagrįstai — sunerimo, kad uždaryti besąlygiškai rizikinga. O jei diegimas suklys? Ar nebūtų saugiau uždaryti tik tada, kai viskas atrodo sveika?

Tad kodas apaugo apsauga, kuri atrodė protinga:

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: paleisk diegimą, palauk pusę sekundės ir priversk galutinį app.quit() tik tada, kai vėliavėlė updateDownloaded vis dar teisinga — kitaip palik programą atvirą, kad naudotojas neliktų nieko.

Spąstai yra viena eilute toliau, klaidos tvarkyklėje. Ta tvarkyklė nustatydavo this.updateDownloaded = false. Tad įsivaizduok nepavykusį diegimą: error įvykis įsijungia ir išvalo vėliavėlę. Bet quitAndInstall jau buvo pradėjęs ardymą — jis uždarė langus ir pašalino programos uždarymo klausytojus. Paskui pabunda 500 ms laikmatis, patikrina dabar jau klaidingą vėliavėlę, nusprendžia „aptikta klaida, palik programą atvirą" ir praleidžia app.quit().

Dabar turi, konkrečiai macOS aplinkoje, blogiausią galimą būseną. macOS neuždaro programos vien todėl, kad užsidarė paskutinis jos langas — tai window-all-closed elgsena, kuria remiasi kiekviena Mac programa. Tad procesas vis dar gyvas, bet neturi jokio lango, jokio kelio per meniu juostą, o jo uždarymo klausytojai išrauti. Nėra į ką spustelėti. Cmd-Q neturi su kuo kalbėtis. Vienintelė išeitis — Force Quit iš Activity Monitor. „Saugumo" patikra pavertė nepavykusį atnaujinimą — pataisomą nemalonumą — zombiu, kurio nebuvo įmanoma nužudyti.

Sprendimas: ardymas turi būti besąlygiškas

Pataisa v1.5.18 yra beveik agresyviai nuobodi, ir čia visa esmė. Ji pašalina gudrumą:

  1. Pašalink window-all-closed ir before-quit klausytojus, kurie galėtų trukdyti.
  2. Sunaikink kiekvieną langą — window.destroy(), o ne window.close(). Uždarymą tvarkyklė gali vetuoti; sunaikinimo — ne. Kai įsipareigoji išsijungti, mandagiai neklausi.
  3. Iškviesk quitAndInstall.
  4. Iškviesk app.quit() besąlygiškai.

Jokios vėliavėlės, jokio laikmačio, jokio „palik atvirą dėl visa ko". Nes tiesa apie išjungimo šaką yra tokia: pusiau užbaigtas išjungimas yra blogiau nei bet kuri iš baigčių. Visiškai užsidaryti — gerai. Visiškai likti atviram — gerai. Vienintelė būsena, kurios niekada negalima pasiekti, yra išardytas, bet vis dar veikiantis — ir tai lygiai ta būsena, kurioje sąlyginis uždarymas gali tave įstrigdyti.

Dar dvi tos pačios savaitės pamokos

Neuždaromos programos klaida yra antraštė, bet šešių leidimų lenktynės užgrūdino dar du įpročius, kuriuos verta pasisavinti.

Filtruok lūžių telemetriją pagal tikslias signatūras, o ne plačius raktažodžius. Lenktynių įkarštyje aptikome, kad mūsų klaidų ataskaitos buvo sukonfigūruotos atmesti viską, kas turi žodžių kaip permission, token ar microphone — bandymas nupjauti triukšmą, kuris tyliai rijo tikrus lūžius, atsitiktinai paminėjusius tuos žodžius. Išrovėme antklodinius filtrus ir pakeitėme juos tiksliomis atmetimo eilutėmis (konkrečiu pranešimu, kurį macOS pateikia atmetus leidimą) ir konkrečiais laikinaisiais tinklo kodais kaip ERR_NETWORK_CHANGED. Triukšmo mažinimas ir klaidų slėpimas yra ta pati rankenėlė, pasukta priešingomis kryptimis; jei filtruoji pagal nuojautą, atfiltruosi būtent tai, ką reikėjo pamatyti.

Kiekvienai automatinei šakai reikia rankinio avarinio išėjimo. Automatinis atnaujinimas savo prigimtimi yra geriausios pastangos — tinklai strigsta, diegimai žlunga. Tad kiekvienas gedimo režimas gavo žmogišką atsarginį variantą: rankinį mygtuką „Check for Updates", pakartojimus su eksponentiniu atidėjimu, pakartotinės patikros laikmatį ir — skirtą būtent atvejui, kai rankinis naudotojo bandymas žlugo — paprastais žodžiais suformuluotą pranešimą „ištrink programą ir įdiek iš naujo iš svetainės". Automatinė šaka yra patogumas; rankinė šaka yra garantija.

Išvada

  1. Apsauga aplink negrįžtamą veiksmą yra pavojingesnė nei pats veiksmas. Sąlyginis uždarymas mėgino neleisti blogam atnaujinimui uždaryti programos, o vietoj to sukūrė būseną, blogesnę nei uždarymas ar neuždarymas. Išjungimo ir diegimo šakos turi būti besąlygiškos ir idempotentiškos — niekada nesusietos su kintama vėliavėle, kurią kita tvarkyklė gali ištraukti tau iš po kojų.
  2. macOS aplinkoje „nėra langų" nėra „nėra programos". Bet kokia ardymo logika turi atsižvelgti į platformą, kur procesas be langų veikia toliau. Testuok gedimo šaką tikroje OS, o ne tik laimingą šaką.
  3. Funkcijos, kurią išleidi per atnaujinimo sistemą, negalima ištestuoti per atnaujinimo sistemą. Būtent ši asimetrija ir yra priežastis, kodėl automatinis atnaujinimas nusipelno paranojiško, besąlygiško, kruopščiai rankiniu būdu patikrinto kodo. Lengvą taisymo būdą gauni tik po to, kai jis jau veikia.

Tai ankstyviausias patikimumo darbo skyrius, kuris galiausiai tapo GeekBye v2. Kur nuvedė tas kelias, skaityk ko iš tiesų kainuoja antra versija (v2.0.0) ir visą lanką programinės įrangos tobulinimo iki tobulybės anatomijoje.

Susiję straipsniai

Nutrūkęs ryšys neturėtų nulaužti visos programos — bet mūsiškė nulauždavo
Steven
Steven5 min. skaitymo

Nutrūkęs ryšys neturėtų nulaužti visos programos — bet mūsiškė nulauždavo

Kai mūsų serverio dalis nulūžo pačiame susitikimo įkarštyje, ji ne tik sustabdė transkripciją — ji nulaužė visą programą. Priežastis buvo vienas neapdorotas įvykis, o pataisymas tilpo į dešimt eilučių. Tai leidimų grupė, dėl kurios GeekBye lieka prisijungęs ir susietas per tuos dalykus, kurie anksčiau jį žudydavo.

Inžinerija
Patikimumas
Electron
Tyla buvo laikančioji
Steven
Steven6 min. skaitymo

Tyla buvo laikančioji

Du paskutiniai GeekBye v1 leidimai — apie tą pačią nemalonią tiesą: transkripcija realiu laiku per tikrą tinklą nėra be nuostolių, o sąžiningas žingsnis — liautis apsimetinėti, kad taip yra. v1.8.20 laikė kiekvieno garso fragmento kopiją diske, prieš atmesdamas jį pakartotinio prisijungimo metu, ir ėmė garsiai žymėti spragas transkripcijoje. v1.9.0 nustojo siųsti tylą, kad sutaupytų pralaidumą — ir atrado, kad tyla buvo būtent tas signalas, pagal kurį transkribuotojas suprato, jog sakinys baigėsi. Du leidimai apie daiktų išmetimo kainą.

Inžinerija
Audio
Patikimumas
Susitikimo spausdinimas į PDF be PDF bibliotekos
Steven
Steven8 min. skaitymo

Susitikimo spausdinimas į PDF be PDF bibliotekos

GeekBye eksportuoja susitikimą kaip PDF, o niekur kode nėra PDF bibliotekos. Jis atvaizduoja HTML nematomame naršyklės lange ir jį spausdina. Tas pasirinkimas — visa istorija: jis padarė funkciją lengvai sukuriamą ir suteikė jai kiekvieną gedimą, kokį turi tikra naršyklė — baltą blyksnį, URL ilgio ribą ir puslapio lūžį, kuris perkirsdavo ekrano nuotraukas pusiau. Vaistas nuo bjauriausio bago buvo viena CSS eilutė.

Inžinerija
Electron
Desktop