
वो सेफ्टी चेक जिसने हमारी ऐप को क्विट करना नामुमकिन बना दिया
ऑटो-अपडेट अब तक का सबसे मुश्किल फीचर था जो हमने शिप किया — चार दिन में छह रिलीज़ सिर्फ इसे ऐप को ब्रिक करने से रोकने के लिए। सबसे बुरा बग वो था जो हमने खुद सावधान रहने की कोशिश में पैदा किया: एक 500-मिलीसेकंड का "सेफ्टी" चेक जिसने एक फेल्ड अपडेट को ऐसे प्रोसेस में बदल दिया जिसे आप शाब्दिक रूप से क्विट ही नहीं कर सकते थे।
हर डेस्कटॉप ऐप डेवलपर ऑटो-अपडेट को ठीक एक बार कम आँकता है। यह एक सुलझी हुई समस्या जैसी दिखती है — एक लाइब्रेरी नया वर्ज़न डाउनलोड करती है और आपकी ऐप को रीस्टार्ट कर देती है। फिर आप इसे शिप करते हैं, और तब सीखते हैं कि "आपकी ऐप को रीस्टार्ट करना" उन सबसे खतरनाक कामों में से एक है जो किसी प्रोग्राम से करवाया जा सकता है, क्योंकि यह ठीक उसी पल होता है जब आपकी ऐप खुद को गिरा रही होती है और गलती के लिए उसके पास सबसे कम गुंजाइश होती है।
GeekBye के ऑटो-अपडेट को स्थिर होने में चार दिन में छह रिलीज़ लगीं — v1.5.14 से v1.5.19 तक। यह उस दौर के सबसे बुरे बग की कहानी है, जो हमने सावधान रहने की कोशिश में खुद पैदा किया।
छह रिलीज़, और वो एक जो मायने रखती थी
अर्क की शुरुआत मामूली थी। v1.5.14 ने एक शर्मनाक टाइपो-क्लास बग ठीक किया: अपडेट फ़ीड एक ऐसे GitHub रेपो नाम की ओर इशारा कर रहा था जो अस्तित्व में ही नहीं था, इसलिए अपडेटर एक 404 चेक कर रहा था। v1.5.15 ने एक मैनुअल "Check for Updates" बटन और एक असली एरर मैसेज जोड़ा। फिर quitAndInstall के बग शुरू हुए, और रिलीज़ें तेज़ी से आने लगीं — क्योंकि जब आपका अपडेट मैकेनिज़्म टूटा हुआ हो, तो आप उसका फिक्स उसी अपडेट मैकेनिज़्म के जरिए शिप नहीं कर सकते। हर इटरेशन एक मैनुअल-रीइंस्टॉल जुआ है।
जो मायने रखती है वो है v1.5.18। इसका पूरा कंटेंट एक ही कमिट था जिसका टाइटल आज भी मुझे झिझका देता है: restore original quitAndInstall behavior to prevent unkillable app.
कैसे "सावधान रहने" ने ऐप को ब्रिक कर दिया
यहाँ सेटअप है। जब कोई अपडेट डाउनलोड होता है, तो Electron का quitAndInstall ऐप को बंद करके नए वर्ज़न को स्वैप-इन करने वाला होता है। एक पहले की रिलीज़ में, किसी ने — वाजिब तौर पर — चिंता की कि अनकंडीशनली क्विट करना जोखिम भरा है। अगर इंस्टॉल में एरर आ गया तो? क्या सिर्फ तभी क्विट करना ज़्यादा सुरक्षित नहीं होगा जब सब कुछ ठीक दिखे?
तो कोड में एक गार्ड आ गया जो समझदार दिखता था:
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)
तर्क यह था: इंस्टॉल फायर करो, आधा सेकंड रुको, और फाइनल app.quit() को तभी फोर्स करो जब updateDownloaded फ्लैग अब भी true हो — वरना ऐप को खुला रखो ताकि यूज़र फँसा न रहे।
जाल एक ही लाइन दूर है, error हैंडलर में। वो हैंडलर this.updateDownloaded = false सेट कर देता था। तो एक फेल्ड इंस्टॉल की कल्पना करें: error इवेंट फायर होता है और फ्लैग क्लियर कर देता है। लेकिन quitAndInstall पहले ही टियरडाउन शुरू कर चुका था — वो विंडोज़ बंद कर चुका था और ऐप के क्विट लिसनर्स हटा चुका था। फिर 500ms टाइमर जागता है, अब-false फ्लैग चेक करता है, तय करता है कि "error detected, keep the app open," और app.quit() को स्किप कर देता है।
अब आपके पास, ख़ास तौर पर macOS पर, सबसे बुरी संभव स्थिति है। macOS किसी ऐप को सिर्फ इसलिए क्विट नहीं करता क्योंकि उसकी आख़िरी विंडो बंद हो गई — यही window-all-closed बिहेवियर है जिस पर हर Mac ऐप निर्भर करती है। तो प्रोसेस अब भी ज़िंदा है, लेकिन उसके पास न कोई विंडो है, न मेन्यू बार का रास्ता, और उसके क्विट लिसनर्स फाड़ कर निकाले जा चुके हैं। क्लिक करने के लिए कुछ नहीं है। Cmd-Q के पास बात करने के लिए कुछ नहीं है। बाहर निकलने का एकमात्र रास्ता Activity Monitor से Force Quit है। "सेफ्टी" चेक ने एक फेल्ड अपडेट — एक रिकवरेबल झुँझलाहट — को एक ऐसे ज़ॉम्बी में बदल दिया था जिसे आप मार नहीं सकते थे।
फिक्स: टियरडाउन अनकंडीशनल होना ही चाहिए
v1.5.18 में सुधार लगभग आक्रामक रूप से उबाऊ है, और यही तो बात है। यह होशियारी को हटा देता है:
- वे
window-all-closedऔरbefore-quitलिसनर्स हटाओ जो दख़ल दे सकते हैं। - हर विंडो को destroy करो —
window.destroy(), न किwindow.close()। Close को एक हैंडलर वीटो कर सकता है; destroy को नहीं। जब आप शटडाउन करने को ठान चुके हों, तो आप विनम्रता से नहीं पूछते। quitAndInstallकॉल करो।app.quit()को अनकंडीशनली कॉल करो।
कोई फ्लैग नहीं, कोई टाइमर नहीं, कोई "just in case खुला रखो" नहीं। क्योंकि शटडाउन पाथ के बारे में सच यह है कि एक आधा-अधूरा शटडाउन दोनों में से किसी भी नतीजे से बुरा होता है। पूरी तरह क्विट होना ठीक है। पूरी तरह खुला रहना ठीक है। जिस एक स्थिति तक आपको कभी नहीं पहुँचना है वो है टूटा हुआ लेकिन फिर भी चल रहा — और यही ठीक वो स्थिति है जिसमें एक कंडीशनल क्विट आपको फँसा सकता है।
उसी हफ़्ते ने सिखाए दो और सबक
अनकिलेबल बग सुर्खी है, लेकिन छह-रिलीज़ वाली आपाधापी ने दो और आदतें मज़बूत कीं जिन्हें चुरा लेना फ़ायदेमंद है।
अपनी क्रैश टेलीमेट्री को व्यापक कीवर्ड्स से नहीं, बल्कि exact signatures से फ़िल्टर करो। आपाधापी के बीच हमने पाया कि हमारी एरर रिपोर्टिंग को ऐसे सेट किया गया था कि वो permission, token, या microphone जैसे शब्द वाली कोई भी चीज़ ड्रॉप कर दे — नॉइज़ काटने की एक कोशिश जो चुपके से उन असली क्रैशों को निगल रही थी जिनमें संयोग से वे शब्द आ जाते थे। हमने वे कंबल-फ़िल्टर उखाड़ फेंके और उनकी जगह exact denial strings (वो ख़ास मैसेज जो macOS तब निकालता है जब कोई permission अस्वीकार हो) और ERR_NETWORK_CHANGED जैसे ख़ास ट्रांज़िएंट नेटवर्क कोड डाल दिए। नॉइज़ रिडक्शन और बग-छिपाना एक ही नॉब हैं जो उलटी दिशाओं में घुमाया जाता है; अगर आप वाइब से फ़िल्टर करेंगे, तो वही चीज़ फ़िल्टर कर देंगे जिसे देखना ज़रूरी था।
हर ऑटोमैटिक पाथ को एक मैनुअल एस्केप हैच चाहिए। ऑटो-अपडेट स्वभाव से ही बेस्ट-एफर्ट होता है — नेटवर्क लड़खड़ाते हैं, इंस्टॉल फेल होते हैं। तो हर फेलियर मोड को एक ह्यूमन फॉलबैक मिला: मैनुअल "Check for Updates" बटन, exponential-backoff रीट्राईज़, एक री-चेक टाइमर, और — ख़ास तौर पर उस केस के लिए रिज़र्व्ड जहाँ यूज़र की मैनुअल कोशिश फेल हुई — एक सीधी-सादी "ऐप डिलीट करो और वेबसाइट से दोबारा इंस्टॉल करो" वाला मैसेज। ऑटोमैटिक पाथ एक सुविधा है; मैनुअल पाथ एक गारंटी है।
निचोड़
- एक इर्रिवर्सिबल एक्शन के इर्द-गिर्द लगा गार्ड, ख़ुद उस एक्शन से ज़्यादा खतरनाक होता है। कंडीशनल क्विट ने एक ख़राब अपडेट को ऐप क्विट करने से रोकने की कोशिश की, और इसके बजाय क्विट होने या न होने दोनों से बुरी स्थिति बना दी। शटडाउन और इंस्टॉल पाथ अनकंडीशनल और आइडेम्पोटेंट होने चाहिए — कभी भी किसी म्यूटेबल फ्लैग पर गेटेड नहीं, जिसे कोई दूसरा हैंडलर आपके नीचे से पलट सकता है।
- macOS पर, "कोई विंडो नहीं" का मतलब "कोई ऐप नहीं" नहीं है। किसी भी टियरडाउन लॉजिक को उस प्लेटफ़ॉर्म का हिसाब रखना होगा जहाँ एक विंडोलेस प्रोसेस चलती रहती है। फेलियर पाथ को टेस्ट करो, असली OS पर, सिर्फ हैप्पी पाथ नहीं।
- जो फीचर आप अपडेट सिस्टम के जरिए शिप करते हैं, उसे अपडेट सिस्टम के जरिए टेस्ट नहीं किया जा सकता। यही असममितता है जिसकी वजह से ऑटो-अपडेट पैरानॉयड, अनकंडीशनल, भारी-मैनुअली-वेरिफाइड कोड का हक़दार है। इसे आसान तरीके से ठीक करने का मौका आपको तभी मिलता है जब यह पहले से काम कर रहा हो।
यह उस विश्वसनीयता वाले काम का सबसे शुरुआती अध्याय है जो आख़िरकार GeekBye v2 बना। वो रास्ता कहाँ ले गया, यह देखने के लिए पढ़ें what a version 2 actually takes (v2.0.0) और पूरा अर्क the anatomy of shipping software to perfection में।