Steven
Steven6 minit bacaan

Sambungan yang Terputus Tidak Sepatutnya Merosakkan Seluruh Aplikasi Anda — Tetapi Aplikasi Kami Ya

Apabila backend kami luar talian di tengah-tengah mesyuarat, ia bukan sekadar menjeda transkripsi — ia merosakkan seluruh aplikasi. Puncanya ialah satu peristiwa yang tidak dikendalikan, dan pembaikannya hanya sepuluh baris. Inilah kelompok keluaran yang membuatkan GeekBye kekal log masuk dan kekal bersambung menerusi perkara-perkara yang dahulunya membunuhnya.

Kejuruteraan
Kebolehpercayaan
Electron
GeekBye Releases
Sambungan yang Terputus Tidak Sepatutnya Merosakkan Seluruh Aplikasi Anda — Tetapi Aplikasi Kami Ya

Sesetengah pepijat mengganggu satu ciri. Yang paling teruk meruntuhkan segala-galanya di sekelilingnya. Dalam binaan GeekBye yang awal, jika backend kami luar talian semasa anda di tengah-tengah mesyuarat, transkripsi bukan sekadar berhenti — seluruh aplikasi crash. Satu sambungan terputus, dan seluruh tetingkap hilang.

Kelompok keluaran yang membaiki ini — v1.6.8 hingga v1.6.11 — ialah satu kelas induk tentang kerja tidak glamor bernama "kekal log masuk, kekal bersambung". Pembaikan utamanya sepuluh baris. Inilah apa isi sepuluh baris itu, dan segala yang lain yang dikeluarkan bersamanya.

Sepuluh baris antara satu sambungan terputus dan satu crash

Crash itu tinggal dalam kod audio. Apabila satu sesi transkripsi merobohkan WebSocket-nya, ia memanggil removeAllListeners() pada soket tersebut — pembersihan yang munasabah. Tetapi inilah butiran Node.js yang mengubah pembersihan menjadi malapetaka: satu peristiwa error tanpa pendengar tidak diabaikan. Ia dilontar semula sebagai pengecualian yang tidak ditangkap.

Jadi bayangkan urutannya apabila backend luar talian: soket mempunyai satu peristiwa error yang beratur untuk dicetuskan. Pembersihan menjalankan removeAllListeners(), menanggalkan pengendali yang sepatutnya menangkapnya. Sesaat kemudian error yang beratur itu tercetus — ke dalam kekosongan. Node nampak satu peristiwa error yang tiada sesiapa mendengar, dan melakukan satu-satunya perkara yang dibenarkan kontraknya: ia melontarnya sebagai pengecualian yang tidak ditangkap, yang membuatkan proses crash. Backend pengguna tersekat dua saat dan aplikasi mereka lenyap.

Pembaikannya (v1.6.8) hampir antiklimaks. Tepat selepas menanggalkan pendengar, pasang semula satu pengendali error no-op secara sengaja:

this.micWebSocket.removeAllListeners()
this.micWebSocket.on('error', () => {}) // absorb late error events

Fungsi kosong itulah intinya. Ia memberi error yang tertangguh itu tempat untuk mendarat — satu penyerap — supaya Node mempunyai pendengar dan tidak pernah melontar semula. Commit yang sama juga membersihkan soket separuh terbuka apabila satu percubaan sambungan gagal separuh jalan, supaya sambungan yang tergendala tidak boleh meninggalkan soket hidup yang tergantung dengan error yang menunggu untuk tercetus. Komen commit itu berkata dengan terang: "Tanpa ini, error yang tertangguh menjadi pengecualian yang tidak ditangkap dan membuatkan aplikasi crash." Sepuluh baris mengubah satu crash penuh menjadi satu sambungan terputus yang senyap dan boleh dipulihkan.

Pengajarannya lebih bernilai daripada pembaikannya: setiap kali anda memanggil removeAllListeners() pada satu soket atau strim yang mungkin masih anda robohkan, pasang semula dahulu satu penyerap error no-op. Satu peristiwa error yang tidak dikendalikan ialah satu crash, bukan satu baris log.

Kekal log masuk: refresh awal, dan refresh ketika melantun

Separuh "kekal bersambung" mempunyai kembar "kekal log masuk". Sebelum kelompok ini, token pengesahan GeekBye sekadar tamat tempoh — selepas seketika anda dibuang tanpa upacara kembali ke skrin log masuk, di tengah aliran kerja, tanpa sebab yang boleh anda lihat.

v1.6.8 membaikinya dari kedua-dua arah:

  • Proaktif: satu pemasa me-refresh token sebelum ia tamat tempoh, dijadualkan pada masa tamat tempoh tolak satu penimbal pintar (sebahagian kecil daripada jangka hayat token, dengan had bawah dan had atas). Anda sudah di-refresh sebelum anda sempat menyedarinya.
  • Reaktif: jika satu permintaan tetap kembali sebagai 401 — anjakan jam, satu pemasa yang terlepas, satu komputer riba yang sejuk — klien me-refresh sekali dan mencuba semula permintaan asal tepat sekali. Satu bendera pengawal mengehadkan cubaan semula itu supaya 401 yang berterusan tidak boleh berpusing menjadi gelung tak terhingga.

Bahagian yang halus tetapi kritikal: kedua-dua laluan berada di sebalik satu mutex. Jika sepuluh permintaan terkena 401 pada saat yang sama, ia tidak mencetuskan sepuluh refresh — semuanya menunggu satu refresh yang sedang berjalan kemudian mencuba semula. Dan apabila satu refresh benar-benar tidak dapat diselamatkan, aplikasi menamatkan sesi secara lancar — satu toast sepuluh saat yang jelas "sesi anda telah tamat tempoh, sila log masuk semula" — bukannya satu tendangan senyap ke skrin log masuk.

Sambung semula: tambah pada klien, kemudian pindah ke backend

v1.6.9 menjadikan satu sambungan transkripsi yang terputus boleh dipulihkan dan bukannya maut. Apabila backend melaporkan sambungan pertuturan di hulu telah terputus, klien berhenti memaparkan error maut dan sebaliknya menyambung semula — dengan UX yang sengaja dibuat tenang: satu toast ambar "Menyambung semula…" (bukan satu setiap cubaan), satu toast hijau "Tersambung semula" apabila ia kembali, dan error terminal "sila mula semula" hanya selepas setiap cubaan habis.

Kemudian ada sesuatu yang saya suka tentang kelompok ini: satu keluaran kemudian, dalam v1.6.10, kami memadamkan logik sambung semula sebelah klien itu — kira-kira 113 baris — dan memindahkan sambung semula ke backend, yang kini memancarkan mesej status "menyambung semula" yang tenang yang tinggal dicerminkan oleh klien. Kami membina pemulihan pada klien, memutuskan bahawa backend ialah pemilik yang betul, dan memindahkannya. Itu bukan berdolak-dalih; itu kitaran hayat yang jujur bagi satu masalah sukar. Logik kebolehpercayaan yang berpecah di kedua-dua hujung satu sambungan ialah tanda tidak elok — pilih satu pemilik. (Backend akhirnya menjadi pemilik muktamad bagi perkara ini, iaitu kisah sambung semula yang diceritakan dalam mengapa pencatat nota AI anda berhenti pada Wi-Fi yang teruk.)

Keluaran yang sama menjadikan had masa rakaman lebih berperikemanusiaan: bukannya satu error mentah, satu toast amaran yang membenarkan rakaman diteruskan, dan satu mesej jelas "Had Rakaman Dicapai" apabila ia benar-benar berhenti — ayat mesra dizahirkan, awalan dalaman ditanggalkan.

Satu cubaan semula untuk memerintah semuanya

Menjelang v1.6.11, logik retry-dengan-backoff yang sama telah disalin-tampal ke tiga tempat berbeza — pengesahan, kemas kini, perobohan sesi. Maka ia disatukan menjadi satu modul dengan satu peraturan dikongsi tentang apa yang berbaloi untuk dicuba semula: kegagalan rangkaian sementara (sambungan diset semula, tamat masa, DNS tersekat, soket terputus) dicuba semula dengan backoff eksponen; satu error pelayan yang sebenar gagal cepat, kerana mencuba semula 4xx yang tulen hanya membazir masa pengguna. Satu pengelas, beberapa pratetap bernama, tiga pemanggil dipindahkan ke atasnya.

Apa yang diajar oleh kelompok ini

  1. Satu peristiwa error yang tidak dikendalikan ialah satu crash, bukan satu baris log. Node melontar semula peristiwa error yang tiada pendengar. Tanggalkan pendengar pada soket yang mungkin masih anda robohkan, dan anda mesti memasang semula satu penyerap error no-op — atau satu sambungan terputus membawa seluruh aplikasi bersamanya.
  2. Refresh kelayakan sebelum tamat tempoh dan secara reaktif apabila 401 — dengan satu mutex pada kedua-duanya. Yang proaktif memastikan anda kekal log masuk; yang reaktif menangkap kes tepi; mutex bermakna N pemanggil serentak menyebabkan tepat satu refresh, dan had cubaan semula bermakna token yang buruk tidak boleh bergelung selama-lamanya.
  3. Cuba semula hanya pada error sementara, dan pusatkan pengelas. "Adakah ini berbaloi untuk dicuba semula?" ialah satu keputusan yang milik satu tempat. Mencuba semula satu error sebenar hanyalah satu kegagalan yang lebih perlahan.
  4. Putuskan siapa pemilik sambung semula. Kami menambahnya pada klien, kemudian memindahkannya ke backend. Logik pemulihan yang hidup di kedua-dua hujung ialah penjana pepijat; berikan ia satu rumah.

Ini ialah bab ketiga daripada kisah kebolehpercayaan yang menjadi GeekBye v2. Untuk bab sebelumnya, lihat kami memadamkan 5,000 baris kod audio dan transkrip mula muncul dua kali (v1.6.0); untuk tempat sambung semula akhirnya menetap, mengapa pencatat nota AI anda berhenti pada Wi-Fi yang teruk; dan untuk keseluruhan lengkoknya, anatomi mengeluarkan perisian sehingga sempurna.