
Pemeriksaan Keamanan yang Membuat Aplikasi Kami Mustahil Ditutup
Pembaruan otomatis adalah fitur tersulit yang pernah kami rilis — enam rilis dalam empat hari untuk mencegahnya merusak aplikasi. Bug terburuk justru yang kami buat sendiri saat mencoba berhati-hati: pemeriksaan "keamanan" 500 milidetik yang mengubah pembaruan gagal menjadi proses yang benar-benar tidak bisa Anda tutup.
Setiap pengembang aplikasi desktop meremehkan pembaruan otomatis tepat satu kali. Kelihatannya seperti masalah yang sudah terpecahkan — sebuah pustaka mengunduh versi baru dan memulai ulang aplikasi Anda. Lalu Anda merilisnya, dan Anda belajar bahwa "memulai ulang aplikasi Anda" adalah salah satu hal paling berbahaya yang bisa diminta dari sebuah program, karena itu terjadi tepat pada saat aplikasi Anda sedang merobohkan dirinya sendiri dan memiliki margin kesalahan paling kecil.
Pembaruan otomatis GeekBye butuh enam rilis dalam empat hari — v1.5.14 hingga v1.5.19 — untuk stabil. Ini adalah kisah bug terburuk dalam rentetan itu, yang kami sebabkan sendiri karena mencoba berhati-hati.
Enam rilis, dan satu yang penting
Rentetan ini dimulai dengan hal yang biasa. v1.5.14 memperbaiki bug memalukan sekelas salah ketik: umpan pembaruan mengarah ke nama repo GitHub yang tidak ada, jadi pembaru memeriksa sebuah 404. v1.5.15 menambahkan tombol manual "Check for Updates" dan pesan error yang sesungguhnya. Lalu bug quitAndInstall mulai muncul, dan rilis-rilis datang dengan cepat — karena ketika mekanisme pembaruan Anda rusak, Anda tidak bisa merilis perbaikannya melalui mekanisme pembaruan itu. Setiap iterasi adalah pertaruhan pemasangan ulang manual.
Yang penting adalah v1.5.18. Seluruh isinya adalah satu commit dengan judul yang masih membuat saya meringis: restore original quitAndInstall behavior to prevent unkillable app.
Bagaimana "berhati-hati" merusak aplikasi
Ini penataannya. Ketika sebuah pembaruan telah diunduh, quitAndInstall dari Electron seharusnya menutup aplikasi dan menukarnya dengan versi baru. Pada rilis sebelumnya, seseorang — dengan masuk akal — khawatir bahwa keluar tanpa syarat itu berisiko. Bagaimana jika pemasangan error? Bukankah lebih aman untuk keluar hanya jika semuanya tampak sehat?
Maka kode itu menumbuhkan penjaga yang tampak masuk akal:
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)
Logikanya: picu pemasangan, tunggu setengah detik, dan paksa app.quit() terakhir hanya jika flag updateDownloaded masih bernilai benar — jika tidak, biarkan aplikasi tetap terbuka agar pengguna tidak terlantar.
Jebakannya hanya berjarak satu baris, di penangan error. Penangan itu mengeset this.updateDownloaded = false. Jadi bayangkan pemasangan yang gagal: event error terpicu dan menghapus flag. Tetapi quitAndInstall sudah mulai membongkar — ia telah menutup jendela dan menghapus listener penutup aplikasi. Lalu timer 500 ms terbangun, memeriksa flag yang kini bernilai salah, memutuskan "error terdeteksi, biarkan aplikasi terbuka," dan melewati app.quit().
Sekarang Anda punya, khususnya di macOS, keadaan terburuk yang mungkin. macOS tidak menutup aplikasi hanya karena jendela terakhirnya tertutup — itulah perilaku window-all-closed yang diandalkan setiap aplikasi Mac. Jadi proses itu masih hidup, tetapi ia tidak punya jendela, tidak punya jalur bilah menu, dan listener penutupnya telah dicabut. Tidak ada yang bisa diklik. Cmd-Q tidak punya apa pun untuk diajak bicara. Satu-satunya jalan keluar adalah Force Quit dari Activity Monitor. Pemeriksaan "keamanan" itu telah mengubah pembaruan yang gagal — gangguan yang bisa dipulihkan — menjadi zombi yang tidak bisa Anda matikan.
Perbaikannya: pembongkaran harus tanpa syarat
Koreksi di v1.5.18 nyaris membosankan secara agresif, dan itulah intinya. Ia menghapus kepintaran itu:
- Hapus listener
window-all-closeddanbefore-quityang bisa mengganggu. - Hancurkan setiap jendela —
window.destroy(), bukanwindow.close(). Menutup bisa diveto oleh sebuah handler; menghancurkan tidak bisa. Ketika Anda berkomitmen untuk mematikan, Anda tidak meminta dengan sopan. - Panggil
quitAndInstall. - Panggil
app.quit()tanpa syarat.
Tanpa flag, tanpa timer, tanpa "biarkan terbuka untuk berjaga-jaga." Karena kebenaran tentang jalur penutupan adalah bahwa penutupan yang setengah jadi lebih buruk daripada kedua hasilnya. Keluar sepenuhnya tidak apa-apa. Tetap terbuka sepenuhnya tidak apa-apa. Satu-satunya keadaan yang tidak boleh Anda capai adalah dirobohkan tapi masih berjalan — dan itulah tepatnya keadaan tempat penutupan bersyarat bisa membuat Anda terlantar.
Dua pelajaran lagi yang diajarkan minggu yang sama
Bug yang tak bisa dimatikan adalah berita utamanya, tetapi kekalutan enam rilis itu mengeraskan dua kebiasaan lain yang patut dicontek.
Saring telemetri crash Anda berdasarkan tanda tangan yang tepat, bukan kata kunci yang luas. Di tengah kekalutan, kami menemukan bahwa pelaporan error kami dikonfigurasi untuk membuang apa pun yang mengandung kata seperti permission, token, atau microphone — upaya untuk memangkas gangguan yang diam-diam menelan crash asli yang kebetulan menyebut kata-kata itu. Kami mencabut filter selimut itu dan menggantinya dengan string penolakan yang tepat (pesan spesifik yang dikeluarkan macOS ketika sebuah izin ditolak) dan kode jaringan sementara yang spesifik seperti ERR_NETWORK_CHANGED. Pengurangan gangguan dan penyembunyian bug adalah tombol yang sama yang diputar ke arah berlawanan; jika Anda menyaring berdasarkan firasat, Anda akan menyaring keluar hal yang justru perlu Anda lihat.
Setiap jalur otomatis butuh jalan keluar manual. Pembaruan otomatis pada dasarnya adalah usaha terbaik — jaringan tersendat, pemasangan gagal. Jadi setiap mode kegagalan mendapat cadangan manusiawi: tombol manual "Check for Updates", percobaan ulang dengan backoff eksponensial, timer pemeriksaan ulang, dan — dicadangkan khusus untuk kasus di mana upaya manual seorang pengguna gagal — pesan berbahasa sederhana "hapus aplikasi dan pasang ulang dari situs web". Jalur otomatis adalah kenyamanan; jalur manual adalah jaminannya.
Kesimpulannya
- Sebuah penjaga di sekitar tindakan yang tak bisa dibatalkan lebih berbahaya daripada tindakan itu sendiri. Penutupan bersyarat itu mencoba mencegah pembaruan buruk menutup aplikasi, dan malah menciptakan keadaan yang lebih buruk daripada keluar maupun tidak keluar. Jalur penutupan dan pemasangan harus tanpa syarat dan idempoten — tidak pernah dikunci oleh flag yang bisa berubah yang bisa dibalik oleh handler lain di luar kendali Anda.
- Di macOS, "tidak ada jendela" bukan berarti "tidak ada aplikasi." Logika pembongkaran apa pun harus memperhitungkan platform di mana proses tanpa jendela tetap berjalan. Uji jalur kegagalan, pada OS yang sebenarnya, bukan hanya jalur yang mulus.
- Fitur yang Anda rilis melalui sistem pembaruan tidak bisa diuji melalui sistem pembaruan. Asimetri itulah alasan mengapa pembaruan otomatis layak mendapat kode yang paranoid, tanpa syarat, dan diverifikasi secara manual dengan berat. Anda baru bisa memperbaikinya dengan cara mudah setelah ia sudah bekerja.
Ini adalah bab paling awal dari pekerjaan keandalan yang akhirnya menjadi GeekBye v2. Untuk melihat ke mana jalan itu mengarah, lihat apa yang sebenarnya dibutuhkan untuk sebuah versi 2 (v2.0.0) dan seluruh rentetannya di anatomi merilis perangkat lunak hingga kesempurnaan.