
Uygulamamızı Kapatmayı İmkânsız Hâle Getiren Güvenlik Kontrolü
Otomatik güncelleme, şimdiye kadar yayınladığımız en zor özellikti — uygulamayı bozmasını engellemek için dört günde altı sürüm. En kötü hata, dikkatli olmaya çalışırken kendi elimizle soktuğumuz bir hataydı: başarısız bir güncellemeyi kelimenin tam anlamıyla kapatamayacağınız bir sürece dönüştüren 500 milisaniyelik bir "güvenlik" kontrolü.
Her masaüstü uygulama geliştiricisi otomatik güncellemeyi tam olarak bir kez hafife alır. Çözülmüş bir problem gibi görünür — bir kütüphane yeni bir sürüm indirir ve uygulamanızı yeniden başlatır. Sonra onu yayınlarsınız ve "uygulamanızı yeniden başlatmanın" bir programa istenebilecek en tehlikeli şeylerden biri olduğunu öğrenirsiniz, çünkü tam olarak uygulamanızın kendini yıktığı ve hata payının en az olduğu anda gerçekleşir.
GeekBye''ın otomatik güncellemesinin oturması dört günde altı sürüm aldı — v1.5.14''ten v1.5.19''a. Bu, o dönemdeki en kötü hatanın hikâyesi; dikkatli olmaya çalışırken kendimizin sebep olduğu bir hata.
Altı sürüm ve önemli olan tek sürüm
Süreç sıradan başladı. v1.5.14 utanç verici, yazım hatası sınıfından bir hatayı düzeltti: güncelleme akışı var olmayan bir GitHub depo adına işaret ediyordu, bu yüzden güncelleyici bir 404''ü kontrol ediyordu. v1.5.15 manuel bir "Check for Updates" düğmesi ve gerçek bir hata mesajı ekledi. Sonra quitAndInstall hataları başladı ve sürümler hızlıca geldi — çünkü güncelleme mekanizmanız bozukken, onun düzeltmesini güncelleme mekanizması üzerinden yayınlayamazsınız. Her yineleme manuel yeniden kurulum kumarıdır.
Önemli olan v1.5.18. Tüm içeriği, hâlâ yüzümü buruşturan bir başlığa sahip tek bir commit''ti: restore original quitAndInstall behavior to prevent unkillable app.
"Dikkatli olmak" uygulamayı nasıl bozdu
İşte kurgu. Bir güncelleme indirildiğinde, Electron''un quitAndInstall fonksiyonu uygulamayı kapatıp yeni sürümü yerine koymalıdır. Daha önceki bir sürümde birisi — haklı olarak — koşulsuz çıkmanın riskli olduğundan endişelendi. Ya kurulum hata verirse? Yalnızca her şey sağlıklı görünürse çıkmak daha güvenli olmaz mıydı?
Bu yüzden kod mantıklı görünen bir koruma geliştirdi:
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)
Mantık şu: kurulumu ateşle, yarım saniye bekle ve son app.quit()''i yalnızca updateDownloaded bayrağı hâlâ doğruysa zorla — aksi hâlde kullanıcı ortada kalmasın diye uygulamayı açık tut.
Tuzak bir satır ötede, hata işleyicisinde. O işleyici this.updateDownloaded = false ayarlıyordu. Başarısız bir kurulumu düşünün: error olayı ateşlenir ve bayrağı temizler. Ama quitAndInstall yıkımı çoktan başlatmıştı — pencereleri kapatmış ve uygulamanın kapatma dinleyicilerini kaldırmıştı. Sonra 500 ms''lik zamanlayıcı uyanır, artık yanlış olan bayrağı kontrol eder, "hata algılandı, uygulamayı açık tut" diye karar verir ve app.quit()''i atlar.
Şimdi, özellikle macOS''ta, olası en kötü duruma sahipsiniz. macOS bir uygulamayı yalnızca son penceresi kapandı diye kapatmaz — bu, her Mac uygulamasının güvendiği window-all-closed davranışıdır. Yani süreç hâlâ canlı, ama hiçbir penceresi, menü çubuğu yolu yok ve kapatma dinleyicileri söküldü. Tıklanacak hiçbir şey yok. Cmd-Q''nun konuşacağı hiçbir şey yok. Tek çıkış yolu Activity Monitor''dan Force Quit. "Güvenlik" kontrolü başarısız bir güncellemeyi — kurtarılabilir bir sıkıntıyı — öldüremeyeceğiniz bir zombiye dönüştürmüştü.
Çözüm: yıkım koşulsuz olmalı
v1.5.18''deki düzeltme neredeyse saldırgan derecede sıkıcı, ki asıl mesele de bu. Zekiliği kaldırır:
- Karışabilecek
window-all-closedvebefore-quitdinleyicilerini kaldır. - Her pencereyi yok et —
window.destroy(),window.close()değil. Kapatma bir işleyici tarafından veto edilebilir; yok etme edilemez. Kapatmaya karar verdiğinizde kibarca sormazsınız. quitAndInstall''ı çağır.app.quit()''i koşulsuz olarak çağır.
Bayrak yok, zamanlayıcı yok, "ne olur ne olmaz açık tut" yok. Çünkü bir kapatma yolu hakkındaki gerçek şudur: yarım kalmış bir kapatma, iki sonuçtan da kötüdür. Tamamen çıkmak sorun değil. Tamamen açık kalmak sorun değil. Asla ulaşmamanız gereken tek durum yıkılmış ama hâlâ çalışıyor — ve koşullu bir çıkışın sizi ortada bırakabileceği durum tam olarak budur.
Aynı haftanın öğrettiği iki ders daha
Kapatılamayan hata manşet, ama altı sürümlük telaş çalmaya değer iki alışkanlığı daha sağlamlaştırdı.
Çökme telemetrinizi geniş anahtar kelimelerle değil, tam imzalarla filtreleyin. Telaşın ortasında hata raporlamamızın permission, token veya microphone gibi kelimeler içeren her şeyi atmak üzere yapılandırıldığını keşfettik — bu, gürültüyü kesme girişimiydi ama sessizce, o kelimeleri tesadüfen anan gerçek çökmeleri yutuyordu. Battaniye gibi filtreleri söktük ve bunları tam ret dizileriyle (bir izin reddedildiğinde macOS''un yaydığı belirli mesaj) ve ERR_NETWORK_CHANGED gibi belirli geçici ağ kodlarıyla değiştirdik. Gürültü azaltma ile hata gizleme aynı düğmenin ters yönlere çevrilmiş hâlidir; sezgiyle filtrelerseniz, tam da görmeniz gereken şeyi filtreleyip atarsınız.
Her otomatik yolun manuel bir çıkış kapısına ihtiyacı vardır. Otomatik güncelleme doğası gereği en iyi çabaya dayanır — ağlar takılır, kurulumlar başarısız olur. Bu yüzden her başarısızlık moduna insani bir yedek eklendi: manuel "Check for Updates" düğmesi, üstel geri çekilmeli yeniden denemeler, bir yeniden kontrol zamanlayıcısı ve — özellikle bir kullanıcının manuel denemesinin başarısız olduğu durum için ayrılmış — sade bir dille "uygulamayı sil ve web sitesinden yeniden kur" mesajı. Otomatik yol bir kolaylıktır; manuel yol güvencedir.
Çıkarım
- Geri döndürülemez bir eylemin etrafındaki bir koruma, eylemin kendisinden daha tehlikelidir. Koşullu çıkış, kötü bir güncellemenin uygulamayı kapatmasını önlemeye çalıştı ve bunun yerine hem çıkmaktan hem de çıkmamaktan daha kötü bir durum yarattı. Kapatma ve kurulum yolları koşulsuz ve idempotent olmalı — asla başka bir işleyicinin ayağınızın altından çevirebileceği değişken bir bayrağa bağlı olmamalı.
- macOS''ta "pencere yok" "uygulama yok" demek değildir. Herhangi bir yıkım mantığı, penceresiz bir sürecin çalışmaya devam ettiği platformu hesaba katmalıdır. Başarısızlık yolunu gerçek işletim sisteminde test edin, yalnızca mutlu yolu değil.
- Güncelleme sistemi üzerinden yayınladığınız özellik, güncelleme sistemi üzerinden test edilemez. Bu asimetri, otomatik güncellemenin neden paranoyak, koşulsuz ve yoğun biçimde elle doğrulanmış kod hak ettiğinin sebebidir. Onu kolay yoldan ancak zaten çalıştıktan sonra düzeltebilirsiniz.
Bu, sonunda GeekBye v2 hâline gelen güvenilirlik çalışmasının en erken bölümü. O yolun nereye vardığını görmek için, bir versiyon 2''nin gerçekte neye mal olduğu (v2.0.0) ve tüm sürecin anlatıldığı yazılımı kusursuzluğa kadar yayınlamanın anatomisi yazılarına bakın.