Steven
Steven7 मिनट पढ़ें

एक कनेक्शन टूटने से पूरा ऐप क्रैश नहीं होना चाहिए — पर हमारा हुआ

जब हमारा बैकएंड मीटिंग के बीच में ऑफ़लाइन हुआ, तो उसने सिर्फ़ ट्रांसक्रिप्शन नहीं रोका — उसने पूरा ऐप क्रैश कर दिया। वजह थी एक अकेली अनहैंडल्ड इवेंट, और फ़िक्स दस लाइनों का था। यह उन रिलीज़ों का गुच्छा है जिसने GeekBye को उन हालात से गुज़रते हुए भी साइन-इन बनाए रखा और कनेक्टेड बनाए रखा, जो पहले उसे मार डालते थे।

इंजीनियरिंग
विश्वसनीयता
Electron
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 को फिर आज़माना बस उपयोगकर्ता का समय बर्बाद करता है। एक क्लासिफ़ायर, कुछ नामित प्रीसेट, तीन कॉलर उस पर स्थानांतरित।

इस गुच्छे ने क्या सिखाया

  1. एक अनहैंडल्ड error इवेंट एक क्रैश है, एक लॉग लाइन नहीं। Node बिना लिसनर वाली error इवेंट्स को फिर से थ्रो करता है। ऐसे सॉकेट पर लिसनर हटाओ जिसे तुम शायद अब भी तोड़ रहे हो, तो तुम्हें एक नो-ऑप error सिंक दोबारा जोड़ना ही होगा — वरना एक टूटा कनेक्शन पूरे ऐप को अपने साथ ले जाता है।
  2. साख को समाप्ति से पहले और 401 पर रिएक्टिव, दोनों तरह रिफ़्रेश करो — दोनों पर एक म्यूटेक्स के साथ। प्रोएक्टिव तुम्हें साइन-इन रखता है; रिएक्टिव किनारों वाले मामले पकड़ता है; म्यूटेक्स का मतलब है कि N एक-साथ कॉलर ठीक एक रिफ़्रेश कराते हैं, और एक रिट्राई सीमा का मतलब है कि एक ख़राब टोकन हमेशा के लिए लूप नहीं कर सकता।
  3. केवल क्षणिक त्रुटियों पर फिर आज़माओ, और क्लासिफ़ायर को केंद्रीकृत करो। "क्या इसे फिर आज़माना सार्थक है?" एक फ़ैसला है जो एक ही जगह का है। एक असली त्रुटि को फिर आज़माना बस एक धीमी विफलता है।
  4. तय करो कि पुनः-कनेक्शन का मालिक कौन है। हमने इसे क्लाइंट पर जोड़ा, फिर बैकएंड पर ले गए। रिकवरी लॉजिक का दोनों सिरों पर बसना एक बग जनरेटर है; उसे एक अकेला घर दो।

यह उस विश्वसनीयता कहानी का तीसरा अध्याय है जो GeekBye v2 बनती है। पिछले अध्याय के लिए, देखें हमने 5,000 लाइन ऑडियो कोड हटाया और ट्रांसक्रिप्ट दो-दो बार दिखने लगे (v1.6.0); पुनः-कनेक्शन आख़िरकार जहाँ जाकर टिका, उसके लिए आपका AI नोटटेकर खराब Wi-Fi पर क्यों रुक जाता है; और पूरे चाप के लिए, सॉफ़्टवेयर को पूर्णता तक पहुँचाने का शरीरशास्त्र

संबंधित लेख

वो सेफ्टी चेक जिसने हमारी ऐप को क्विट करना नामुमकिन बना दिया
Steven
Steven7 मिनट पढ़ें

वो सेफ्टी चेक जिसने हमारी ऐप को क्विट करना नामुमकिन बना दिया

ऑटो-अपडेट अब तक का सबसे मुश्किल फीचर था जो हमने शिप किया — चार दिन में छह रिलीज़ सिर्फ इसे ऐप को ब्रिक करने से रोकने के लिए। सबसे बुरा बग वो था जो हमने खुद सावधान रहने की कोशिश में पैदा किया: एक 500-मिलीसेकंड का "सेफ्टी" चेक जिसने एक फेल्ड अपडेट को ऐसे प्रोसेस में बदल दिया जिसे आप शाब्दिक रूप से क्विट ही नहीं कर सकते थे।

इंजीनियरिंग
Electron
विश्वसनीयता
ख़ामोशी भार-वहन कर रही थी
Steven
Steven9 मिनट पढ़ें

ख़ामोशी भार-वहन कर रही थी

GeekBye v1 की आख़िरी दो releases एक ही असहज सच्चाई के बारे में हैं: एक असली network पर real-time transcription लॉसलेस नहीं है, और ईमानदार चाल है यह दिखावा बंद करना कि वह है। v1.8.20 ने एक reconnect के दौरान हर audio chunk को गिराने से पहले उसकी एक प्रति disk पर रखी, और transcript के अंतरालों को ज़ोर से चिह्नित करना शुरू किया। v1.9.0 ने bandwidth बचाने के लिए silence भेजना बंद किया — और पाया कि वह silence ही वह ठीक-ठीक संकेत था जिससे transcriber जानता था कि एक वाक्य ख़त्म हो चुका है। चीज़ें फेंक देने की क़ीमत के बारे में दो releases।

इंजीनियरिंग
Audio
विश्वसनीयता
बिना किसी PDF library के एक meeting को PDF में print करना
Steven
Steven10 मिनट पढ़ें

बिना किसी PDF library के एक meeting को PDF में print करना

GeekBye एक meeting को PDF के रूप में export करती है, और code में कहीं भी कोई PDF library नहीं है। वह एक अदृश्य browser window में HTML render करती है और उसे print कर देती है। वही चुनाव पूरी कहानी है: उसने feature को बनाना आसान कर दिया और उसे हर वह ख़राबी दे दी जो एक असली browser में होती है — एक सफ़ेद flash, एक URL length limit, और एक page break जो screenshots को बीच से काट देता था। सबसे बदसूरत bug का fix CSS की एक लाइन थी।

इंजीनियरिंग
Electron
डेस्कटॉप