
एक कनेक्शन टूटने से पूरा ऐप क्रैश नहीं होना चाहिए — पर हमारा हुआ
जब हमारा बैकएंड मीटिंग के बीच में ऑफ़लाइन हुआ, तो उसने सिर्फ़ ट्रांसक्रिप्शन नहीं रोका — उसने पूरा ऐप क्रैश कर दिया। वजह थी एक अकेली अनहैंडल्ड इवेंट, और फ़िक्स दस लाइनों का था। यह उन रिलीज़ों का गुच्छा है जिसने GeekBye को उन हालात से गुज़रते हुए भी साइन-इन बनाए रखा और कनेक्टेड बनाए रखा, जो पहले उसे मार डालते थे।
कुछ बग एक फ़ीचर को बाधित करते हैं। सबसे बुरे उसके इर्द-गिर्द की हर चीज़ को गिरा देते हैं। GeekBye के एक शुरुआती बिल्ड में, अगर आप मीटिंग के बीचोंबीच थे और हमारा बैकएंड ऑफ़लाइन हो जाता, तो ट्रांसक्रिप्शन सिर्फ़ रुकता नहीं — पूरा ऐप क्रैश हो जाता। एक कनेक्शन टूटा, और पूरी विंडो गायब।
जिस रिलीज़ गुच्छे ने इसे ठीक किया — v1.6.8 से v1.6.11 तक — वह "साइन-इन बनाए रखो, कनेक्टेड बनाए रखो" के उस बिना-चमक वाले काम का उस्ताद-दर्जे का उदाहरण है। मुख्य फ़िक्स दस लाइनों का था। यहाँ बताया है कि वे दस लाइनें क्या थीं, और उनके साथ जो कुछ और आया।
एक डिस्कनेक्ट और एक क्रैश के बीच की दस लाइनें
क्रैश ऑडियो कोड में बसा था। जब एक ट्रांसक्रिप्शन सेशन अपने WebSocket को तोड़ता था, तो वह उन पर removeAllListeners() कॉल करता था — वाजिब सफ़ाई। पर यहाँ वह Node.js बारीकी है जो सफ़ाई को आपदा में बदल देती है: बिना लिसनर वाली एक error इवेंट को नज़रअंदाज़ नहीं किया जाता। उसे एक अनकॉट एक्सेप्शन के रूप में फिर से थ्रो कर दिया जाता है।
तो बैकएंड के ऑफ़लाइन होने पर के क्रम की कल्पना कीजिए: सॉकेट के पास फ़ायर होने के लिए एक error इवेंट कतार में लगी है। सफ़ाई removeAllListeners() चलाती है, उस हैंडलर को हटा देती है जो उसे पकड़ लेता। एक पल बाद कतार में लगी वह त्रुटि फ़ायर होती है — शून्य में। Node एक ऐसी error इवेंट देखता है जिसे कोई नहीं सुन रहा, और वही करता है जो उसका अनुबंध अनुमति देता है: उसे एक अनकॉट एक्सेप्शन के रूप में थ्रो कर देता है, जो प्रोसेस को क्रैश कर देता है। उपयोगकर्ता के बैकएंड ने दो सेकंड की हिचकी ली और उनका ऐप गायब हो गया।
फ़िक्स (v1.6.8) लगभग बेमज़ा हद तक सीधा है। लिसनर हटाने के ठीक बाद, एक जानबूझकर बनाया नो-ऑप error हैंडलर दोबारा जोड़ें:
this.micWebSocket.removeAllListeners()
this.micWebSocket.on('error', () => {}) // absorb late error events
वह खाली फ़ंक्शन ही सारा मतलब है। यह लंबित त्रुटि को उतरने की एक जगह देता है — एक सिंक — ताकि Node के पास एक लिसनर हो और वह कभी फिर से थ्रो न करे। उसी कमिट ने कनेक्शन प्रयास के बीच में विफल होने पर आधे-खुले सॉकेट भी साफ़ किए, ताकि एक बिगड़ा कनेक्ट फ़ायर होने को तैयार त्रुटि के साथ एक ज़िंदा सॉकेट लटकता न छोड़ जाए। कमिट की टिप्पणी साफ़ कहती है: "इसके बिना, लंबित त्रुटियाँ अनकॉट एक्सेप्शन बन जाती हैं और ऐप को क्रैश कर देती हैं।" दस लाइनों ने एक पूरे क्रैश को एक शांत, रिकवर होने लायक डिस्कनेक्ट में बदल दिया।
सबक फ़िक्स से भी ज़्यादा कीमती है: जब भी आप ऐसे सॉकेट या स्ट्रीम पर removeAllListeners() कॉल करें जिसे आप शायद अब भी तोड़ रहे हों, तो पहले एक नो-ऑप error सिंक दोबारा जोड़ें। एक अनहैंडल्ड error इवेंट एक क्रैश है, एक लॉग लाइन नहीं।
साइन-इन बनाए रखना: पहले से रिफ़्रेश करो, और उछाल पर भी रिफ़्रेश करो
"कनेक्टेड बनाए रखो" के आधे हिस्से का एक जुड़वाँ था "साइन-इन बनाए रखो"। इस गुच्छे से पहले, GeekBye का ऑथ टोकन बस समाप्त हो जाता था — कुछ देर बाद आपको वर्कफ़्लो के बीचोंबीच, बिना किसी दिखने वाली वजह के, बेरुख़ी से लॉगिन स्क्रीन पर फेंक दिया जाता।
v1.6.8 ने इसे दोनों दिशाओं से ठीक किया:
- प्रोएक्टिव: एक टाइमर टोकन को उसके समाप्त होने से पहले रिफ़्रेश करता है, जो समाप्ति समय में से एक समझदार बफ़र (टोकन के जीवनकाल का एक अंश, जिसकी न्यूनतम और अधिकतम सीमा तय है) घटाकर शेड्यूल होता है। आपको पता चलने से पहले ही रिफ़्रेश हो जाता है।
- रिएक्टिव: अगर फिर भी कोई अनुरोध
401के साथ लौटता है — घड़ी का अंतर, कोई छूटा टाइमर, एक ठंडा लैपटॉप — तो क्लाइंट एक बार रिफ़्रेश करता है और मूल अनुरोध को ठीक एक बार फिर से आज़माता है। एक अकेला गार्ड फ़्लैग उस रिट्राई की सीमा बाँध देता है, ताकि एक ज़िद्दी 401 कभी अनंत लूप में न घूम सके।
बारीक पर बेहद अहम हिस्सा: दोनों रास्ते एक म्यूटेक्स के पीछे हैं। अगर दस अनुरोध एक ही पल में 401 से टकराएँ, तो वे दस रिफ़्रेश फ़ायर नहीं करते — वे सभी उस एक चालू रिफ़्रेश का इंतज़ार करते हैं और फिर आज़माते हैं। और जब कोई रिफ़्रेश सचमुच बचाया नहीं जा सकता, तो ऐप सेशन को सुगमता से समाप्त करता है — एक साफ़ दस-सेकंड का "आपका सेशन समाप्त हो गया, कृपया दोबारा लॉग इन करें" टोस्ट — न कि लॉगिन स्क्रीन पर एक खामोश धक्का।
पुनः-कनेक्शन: क्लाइंट पर जोड़ो, फिर बैकएंड पर ले जाओ
v1.6.9 ने एक टूटे ट्रांसक्रिप्शन कनेक्शन को घातक के बजाय रिकवर होने लायक बनाया। जब बैकएंड ने बताया कि अपस्ट्रीम स्पीच कनेक्शन टूट गया है, तो क्लाइंट ने एक घातक त्रुटि दिखाना बंद कर दिया और इसके बजाय फिर से कनेक्ट हुआ — जानबूझकर शांत अनुभव के साथ: एक अकेला अंबर "फिर से कनेक्ट हो रहा है…" टोस्ट (हर प्रयास पर एक नहीं), वापसी पर एक हरा "फिर से कनेक्ट हुआ" टोस्ट, और आख़िरी "कृपया रीस्टार्ट करें" त्रुटि केवल हर प्रयास खत्म हो जाने के बाद।
फिर इस गुच्छे की एक बात जो मुझे पसंद है: एक रिलीज़ बाद, v1.6.10 में, हमने वह क्लाइंट-साइड पुनः-कनेक्शन लॉजिक हटा दिया — करीब 113 लाइनें — और पुनः-कनेक्शन को बैकएंड पर ले गए, जो अब शांत "फिर से कनेक्ट हो रहा है" स्टेटस संदेश भेजता है जिन्हें क्लाइंट बस दर्शा देता है। हमने रिकवरी क्लाइंट पर बनाई, तय किया कि बैकएंड ही सही मालिक है, और उसे वहाँ ले गए। यह डगमगाना नहीं है; यह एक कठिन समस्या का ईमानदार जीवनचक्र है। विश्वसनीयता लॉजिक का एक कनेक्शन के दोनों सिरों पर बँटा होना एक बुरी गंध है — एक मालिक चुनो। (आख़िरकार बैकएंड ही इसका निश्चित मालिक बना, जो आपका AI नोटटेकर खराब Wi-Fi पर क्यों रुक जाता है में कही गई पुनः-कनेक्शन की कहानी है।)
इसी रिलीज़ ने रिकॉर्डिंग-समय की सीमाओं को इंसानी बनाया: एक कच्ची त्रुटि के बजाय, एक चेतावनी टोस्ट जो रिकॉर्डिंग को जारी रहने देता है, और जब यह सचमुच रुकती है तो एक साफ़ "रिकॉर्डिंग सीमा पर पहुँच गए" संदेश — दोस्ताना वाक्य सामने लाया गया, भीतरी उपसर्ग हटा दिया गया।
एक रिट्राई सब पर राज करने के लिए
v1.6.11 तक, वही बैकऑफ़-वाली रिट्राई लॉजिक तीन अलग-अलग जगहों पर कॉपी-पेस्ट हो चुकी थी — ऑथ, अपडेट, सेशन टियरडाउन। तो इसे एक अकेले मॉड्यूल में समेट दिया गया, आख़िर किस चीज़ को आज़माना भी सार्थक है के बारे में एक साझा नियम के साथ: क्षणिक नेटवर्क विफलताएँ (कनेक्शन रीसेट, टाइमआउट, DNS हिचकी, सॉकेट हैंग-अप) एक्सपोनेंशियल बैकऑफ़ के साथ फिर आज़माई जाती हैं; एक असली सर्वर त्रुटि तुरंत विफल होती है, क्योंकि एक सच्चे 4xx को फिर आज़माना बस उपयोगकर्ता का समय बर्बाद करता है। एक क्लासिफ़ायर, कुछ नामित प्रीसेट, तीन कॉलर उस पर स्थानांतरित।
इस गुच्छे ने क्या सिखाया
- एक अनहैंडल्ड
errorइवेंट एक क्रैश है, एक लॉग लाइन नहीं। Node बिना लिसनर वाली error इवेंट्स को फिर से थ्रो करता है। ऐसे सॉकेट पर लिसनर हटाओ जिसे तुम शायद अब भी तोड़ रहे हो, तो तुम्हें एक नो-ऑप error सिंक दोबारा जोड़ना ही होगा — वरना एक टूटा कनेक्शन पूरे ऐप को अपने साथ ले जाता है। - साख को समाप्ति से पहले और 401 पर रिएक्टिव, दोनों तरह रिफ़्रेश करो — दोनों पर एक म्यूटेक्स के साथ। प्रोएक्टिव तुम्हें साइन-इन रखता है; रिएक्टिव किनारों वाले मामले पकड़ता है; म्यूटेक्स का मतलब है कि N एक-साथ कॉलर ठीक एक रिफ़्रेश कराते हैं, और एक रिट्राई सीमा का मतलब है कि एक ख़राब टोकन हमेशा के लिए लूप नहीं कर सकता।
- केवल क्षणिक त्रुटियों पर फिर आज़माओ, और क्लासिफ़ायर को केंद्रीकृत करो। "क्या इसे फिर आज़माना सार्थक है?" एक फ़ैसला है जो एक ही जगह का है। एक असली त्रुटि को फिर आज़माना बस एक धीमी विफलता है।
- तय करो कि पुनः-कनेक्शन का मालिक कौन है। हमने इसे क्लाइंट पर जोड़ा, फिर बैकएंड पर ले गए। रिकवरी लॉजिक का दोनों सिरों पर बसना एक बग जनरेटर है; उसे एक अकेला घर दो।
यह उस विश्वसनीयता कहानी का तीसरा अध्याय है जो GeekBye v2 बनती है। पिछले अध्याय के लिए, देखें हमने 5,000 लाइन ऑडियो कोड हटाया और ट्रांसक्रिप्ट दो-दो बार दिखने लगे (v1.6.0); पुनः-कनेक्शन आख़िरकार जहाँ जाकर टिका, उसके लिए आपका AI नोटटेकर खराब Wi-Fi पर क्यों रुक जाता है; और पूरे चाप के लिए, सॉफ़्टवेयर को पूर्णता तक पहुँचाने का शरीरशास्त्र।