Steven
Steven2 นาทีที่อ่าน

การหลุดของการเชื่อมต่อครั้งเดียวไม่ควรทำให้ทั้งแอปพัง — แต่ของเราพัง

เมื่อแบ็กเอนด์ของเราหลุดออฟไลน์กลางการประชุม มันไม่ได้แค่หยุดการถอดเสียง — มันทำให้ทั้งแอปพัง สาเหตุคือเหตุการณ์ที่ไม่ถูกจัดการเพียงตัวเดียว และการแก้ไขมีแค่สิบบรรทัด นี่คือชุดรีลีสที่ทำให้ GeekBye ยังคงล็อกอินอยู่และยังคงเชื่อมต่ออยู่ ผ่านสิ่งที่เคยฆ่ามันมาก่อน

วิศวกรรม
ความน่าเชื่อถือ
Electron
GeekBye รีลีส
การหลุดของการเชื่อมต่อครั้งเดียวไม่ควรทำให้ทั้งแอปพัง — แต่ของเราพัง

บั๊กบางตัวขัดจังหวะฟีเจอร์หนึ่ง ตัวที่แย่ที่สุดล้มทุกอย่างรอบ ๆ มันลง ในบิลด์ยุคแรกของ GeekBye ถ้าแบ็กเอนด์ของเราหลุดออฟไลน์ขณะที่คุณอยู่กลางการประชุม การถอดเสียงไม่ได้แค่หยุด — ทั้งแอปพัง การเชื่อมต่อหลุดครั้งเดียว แล้วทั้งหน้าต่างก็หายไป

ชุดรีลีสที่แก้เรื่องนี้ — v1.6.8 ถึง v1.6.11 — เป็นตำราชั้นครูของงานที่ไม่หวือหวาอย่าง "คงล็อกอิน คงการเชื่อมต่อ" การแก้ไขพาดหัวมีแค่สิบบรรทัด นี่คือสิ่งที่สิบบรรทัดนั้นเป็น และทุกอย่างที่ออกมาพร้อมกับมัน

สิบบรรทัดระหว่างการหลุดกับการพัง

การพังอาศัยอยู่ในโค้ดเสียง เมื่อเซสชันถอดเสียงรื้อ WebSocket ของมันลง มันเรียก removeAllListeners() บนพวกมัน — การเก็บกวาดที่สมเหตุสมผล แต่นี่คือรายละเอียดของ Node.js ที่เปลี่ยนการเก็บกวาดให้กลายเป็นหายนะ: เหตุการณ์ error ที่ไม่มีตัวรับฟังไม่ได้ถูกเพิกเฉย มันถูกโยนซ้ำเป็นข้อยกเว้นที่ไม่ถูกจับ

ลองนึกภาพลำดับเหตุการณ์เมื่อ แบ็กเอนด์หลุดออฟไลน์: ซ็อกเก็ตมีเหตุการณ์ error ต่อคิวรอ ที่จะยิงออกมา การเก็บกวาดรัน removeAllListeners() ถอดตัวจัดการที่ควรจะจับมันไว้ออกไป อีกชั่วอึดใจ error ที่ต่อคิวอยู่ก็ยิง — เข้าสู่ความว่างเปล่า Node เห็นเหตุการณ์ error ที่ไม่มีใครฟัง และทำสิ่งเดียวที่สัญญาของมันอนุญาต: มันโยนออกมาเป็นข้อยกเว้นที่ไม่ถูกจับ ซึ่งทำให้โปรเซสพัง แบ็กเอนด์ของผู้ใช้สะอึกไปสองวินาที แล้วแอปของพวกเขาก็หายวับ

การแก้ไข (v1.6.8) แทบจะน่าผิดหวังในความเรียบง่าย ทันทีหลังจากถอดตัวรับฟังออก ให้ผูก ตัวจัดการ error ที่ตั้งใจไม่ทำอะไร กลับเข้าไป:

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

ฟังก์ชันว่างเปล่านั้นคือประเด็นทั้งหมด มันให้ที่ลงเอยแก่ error ที่ค้างอยู่ — จุดรับ — เพื่อให้ Node มีตัวรับฟังและไม่มีวันโยนซ้ำ คอมมิตเดียวกันยังเก็บกวาดซ็อกเก็ตที่เปิดค้างครึ่งเดียวเมื่อความพยายามเชื่อมต่อล้มเหลวกลางคัน เพื่อไม่ให้การเชื่อมต่อที่พลาดทิ้งซ็อกเก็ตที่ยังมีชีวิตห้อยอยู่พร้อม error รอยิง ความคิดเห็นในคอมมิตพูดตรง ๆ ว่า: "ถ้าไม่มีสิ่งนี้ error ที่ค้างอยู่จะกลายเป็นข้อยกเว้นที่ไม่ถูกจับและทำให้แอปพัง" สิบบรรทัดเปลี่ยนการพังทั้งระบบให้กลายเป็นการหลุดที่เงียบและกู้คืนได้

บทเรียนมีค่ามากกว่าตัวการแก้ไข: ทุกครั้งที่คุณเรียก removeAllListeners() บนซ็อกเก็ตหรือสตรีมที่คุณอาจยังรื้อไม่เสร็จ ให้ผูกจุดรับ error ที่ไม่ทำอะไรกลับเข้าไปก่อน เหตุการณ์ error ที่ไม่ถูกจัดการคือการพัง ไม่ใช่บรรทัดล็อก

การคงล็อกอิน: รีเฟรชล่วงหน้า และรีเฟรชตอนเด้งกลับ

ครึ่งที่ว่า "คงการเชื่อมต่อ" มีฝาแฝดที่ว่า "คงล็อกอิน" ก่อนชุดนี้ โทเคนยืนยันตัวตนของ GeekBye ก็แค่หมดอายุไปเฉย ๆ — สักพักคุณก็ถูกโยนกลับไปหน้าล็อกอินอย่างไม่ไยดี กลางเวิร์กโฟลว์ โดยไม่มีเหตุผลที่คุณมองเห็นได้

v1.6.8 แก้เรื่องนี้จากทั้งสองทิศทาง:

  • เชิงรุก: ตัวจับเวลารีเฟรชโทเคน ก่อน ที่มันจะหมดอายุ กำหนดไว้ที่เวลาหมดอายุลบด้วยบัฟเฟอร์อันชาญฉลาด (เศษเสี้ยวของอายุโทเคน มีขั้นต่ำและเพดานกำหนดไว้) คุณถูกรีเฟรชก่อนที่คุณจะทันสังเกตด้วยซ้ำ
  • เชิงรับ: ถ้าคำขอกลับมาเป็น 401 อยู่ดี — นาฬิกาคลาดเคลื่อน ตัวจับเวลาที่พลาดไป แล็ปท็อปที่เพิ่งตื่น — ไคลเอนต์รีเฟรชหนึ่งครั้งและลองส่งคำขอเดิมใหม่เพียงครั้งเดียวเป๊ะ แฟล็กป้องกันเพียงตัวเดียวจำกัดการลองใหม่นั้น เพื่อให้ 401 ที่ดื้อรั้นไม่มีวันหมุนเป็นลูปไม่รู้จบ

ส่วนที่ละเอียดอ่อนแต่สำคัญยิ่ง: เส้นทางทั้งสองอยู่หลัง มิวเท็กซ์ ถ้าสิบคำขอชน 401 ในเสี้ยววินาทีเดียวกัน พวกมันไม่ยิงสิบการรีเฟรช — พวกมันทั้งหมดรอการรีเฟรช ครั้งเดียว ที่กำลังดำเนินอยู่แล้วจึงลองใหม่ และเมื่อการรีเฟรชกู้ไม่ได้จริง ๆ แอปทำให้เซสชันหมดอายุ อย่างนุ่มนวล — ทอสต์ชัดเจนสิบวินาทีว่า "เซสชันของคุณหมดอายุแล้ว โปรดล็อกอินอีกครั้ง" — แทนที่จะเป็นการเตะกลับไปหน้าล็อกอินอย่างเงียบ ๆ

การเชื่อมต่อใหม่: เพิ่มที่ไคลเอนต์ แล้วย้ายไปแบ็กเอนด์

v1.6.9 ทำให้การเชื่อมต่อถอดเสียงที่หลุดกลายเป็นสิ่งที่ กู้คืนได้ แทนที่จะเป็นความตาย เมื่อแบ็กเอนด์รายงานว่าการเชื่อมต่อเสียงต้นทางหลุด ไคลเอนต์เลิกแสดง error ร้ายแรงและเชื่อมต่อใหม่แทน — ด้วยประสบการณ์ที่จงใจให้สงบ: ทอสต์สีเหลืองอำพันอันเดียวว่า "กำลังเชื่อมต่อใหม่…" (ไม่ใช่อันละหนึ่งต่อความพยายาม) ทอสต์สีเขียว "เชื่อมต่อใหม่แล้ว" เมื่อมันกลับมา และ error ปลายทางว่า "โปรดเริ่มใหม่" จะปรากฏ ก็ต่อเมื่อ ทุกความพยายามถูกใช้จนหมด แล้วเท่านั้น

แล้วสิ่งที่ผมชอบเกี่ยวกับชุดนี้: อีกหนึ่งรีลีสถัดมา ใน v1.6.10 เราลบตรรกะการเชื่อมต่อใหม่ฝั่งไคลเอนต์นั้นทิ้ง — ราว 113 บรรทัด — และย้ายการเชื่อมต่อใหม่ไปที่แบ็กเอนด์ ซึ่งตอนนี้ส่งข้อความสถานะ "กำลังเชื่อมต่อใหม่" อย่างเงียบ ๆ ให้ไคลเอนต์แค่สะท้อนออกมา เราสร้างการกู้คืนที่ไคลเอนต์ ตัดสินใจว่าแบ็กเอนด์คือเจ้าของที่ถูกต้อง แล้วย้ายมัน นี่ไม่ใช่การโลเล นี่คือวงจรชีวิตอันซื่อตรงของปัญหาที่ยาก ตรรกะความน่าเชื่อถือที่แยกกันอยู่คนละปลายของการเชื่อมต่อเป็นกลิ่นที่ไม่ดี — เลือกเจ้าของสักหนึ่ง (ท้ายที่สุดแบ็กเอนด์กลายเป็นเจ้าของที่แน่นอนของเรื่องนี้ ซึ่งคือเรื่องราวการเชื่อมต่อใหม่ที่เล่าไว้ใน ทำไมโปรแกรมจดโน้ต AI ของคุณถึงหยุดเมื่อ Wi-Fi แย่)

รีลีสเดียวกันทำให้ขีดจำกัดเวลาบันทึกเสียงมีความเป็นมนุษย์: แทนที่จะเป็น error ดิบ ๆ กลับเป็นทอสต์เตือนที่ยอมให้บันทึกต่อไปได้ และข้อความชัดเจนว่า "ถึงขีดจำกัดการบันทึกแล้ว" เมื่อมันหยุดจริง — โผล่ประโยคที่เป็นมิตรออกมา ถอดคำนำหน้าภายในทิ้งไป

การลองใหม่หนึ่งเดียวที่ปกครองทั้งหมด

พอถึง v1.6.11 ตรรกะลองใหม่พร้อมแบ็กออฟชุดเดียวกันถูกก็อปวางไปในสามที่ต่างกัน — การยืนยันตัวตน การอัปเดต การรื้อเซสชัน มันจึงถูกรวมเข้าเป็นโมดูลเดียวพร้อมกฎร่วมข้อหนึ่งว่าด้วย อะไรกันแน่ที่คุ้มจะลองใหม่: ความล้มเหลวของเครือข่ายชั่วคราว (การเชื่อมต่อรีเซ็ต ไทม์เอาต์ DNS สะอึก ซ็อกเก็ตหลุด) จะถูกลองใหม่ด้วยแบ็กออฟแบบเอ็กซ์โพเนนเชียล ส่วน error ของเซิร์ฟเวอร์จริง ๆ จะล้มเหลวทันที เพราะการลองใหม่กับ 4xx ของแท้ก็แค่เปลืองเวลาของผู้ใช้ ตัวแยกประเภทหนึ่งตัว พรีเซ็ตที่มีชื่อไม่กี่ตัว และผู้เรียกสามรายย้ายมาใช้มัน

สิ่งที่ชุดนี้สอน

  1. เหตุการณ์ error ที่ไม่ถูกจัดการคือการพัง ไม่ใช่บรรทัดล็อก Node โยนเหตุการณ์ error ที่ไม่มีตัวรับฟังซ้ำ ถ้าคุณถอดตัวรับฟังบนซ็อกเก็ตที่คุณอาจยังรื้อไม่เสร็จ คุณต้องผูกจุดรับ error ที่ไม่ทำอะไรกลับเข้าไป — ไม่อย่างนั้นการหลุดครั้งเดียวจะพาทั้งแอปไปด้วย
  2. รีเฟรชข้อมูลรับรองทั้งก่อนหมดอายุ และ เชิงรับเมื่อเจอ 401 — พร้อมมิวเท็กซ์ทั้งคู่ เชิงรุกทำให้คุณคงล็อกอิน เชิงรับจับกรณีขอบ ๆ มิวเท็กซ์หมายความว่าผู้เรียกพร้อมกัน N รายก่อให้เกิดการรีเฟรชเพียงครั้งเดียวเป๊ะ และเพดานการลองใหม่หมายความว่าโทเคนเสียจะวนไปตลอดกาลไม่ได้
  3. ลองใหม่เฉพาะกับ error ชั่วคราว และรวมศูนย์ตัวแยกประเภท "อันนี้คุ้มจะลองใหม่ไหม?" เป็นการตัดสินใจหนึ่งเดียวที่ควรอยู่ที่เดียว การลองใหม่กับ error ของจริงก็แค่การล้มเหลวที่ช้ากว่า
  4. ตัดสินใจว่าใครเป็นเจ้าของการเชื่อมต่อใหม่ เราเพิ่มมันที่ไคลเอนต์ แล้วย้ายไปแบ็กเอนด์ ตรรกะการกู้คืนที่อยู่ทั้งสองปลายคือเครื่องผลิตบั๊ก ให้บ้านเดียวแก่มัน

นี่คือบทที่สามของเรื่องราวความน่าเชื่อถือที่กลายมาเป็น GeekBye v2 สำหรับบทก่อนหน้า ดู เราลบโค้ดเสียง 5,000 บรรทัด แล้วการถอดเสียงเริ่มโผล่มาสองครั้ง (v1.6.0); สำหรับที่ที่การเชื่อมต่อใหม่ลงเอยในที่สุด ดู ทำไมโปรแกรมจดโน้ต AI ของคุณถึงหยุดเมื่อ Wi-Fi แย่; และสำหรับเส้นโค้งทั้งเส้น ดู กายวิภาคของการส่งมอบซอฟต์แวร์สู่ความสมบูรณ์แบบ

บทความที่เกี่ยวข้อง

ความเงียบคือสิ่งที่รับน้ำหนัก
Steven
Steven3 นาทีที่อ่าน

ความเงียบคือสิ่งที่รับน้ำหนัก

สอง release สุดท้ายของ GeekBye v1 ว่าด้วยความจริงอันน่าอึดอัดข้อเดียวกัน: การถอดเสียงแบบเรียลไทม์บนเครือข่ายจริงไม่ได้ไร้การสูญเสีย และการเดินหมากที่ซื่อสัตย์คือเลิกแกล้งทำเป็นว่ามันไร้การสูญเสีย v1.8.20 เก็บสำเนาของ audio chunk ทุกก้อนไว้บนดิสก์ก่อนจะทิ้งมันระหว่างการเชื่อมต่อใหม่ และเริ่มทำเครื่องหมายช่องว่างใน transcript ออกมาดัง ๆ v1.9.0 เลิกส่งความเงียบเพื่อประหยัด bandwidth — แล้วก็ค้นพบว่าความเงียบนั่นแหละคือสัญญาณเป๊ะ ๆ ที่เครื่องถอดเสียงใช้รู้ว่าประโยคจบแล้ว สอง release ว่าด้วยราคาของการโยนของทิ้ง

วิศวกรรม
Audio
ความน่าเชื่อถือ
พิมพ์การประชุมเป็น PDF โดยไม่มี PDF library
Steven
Steven5 นาทีที่อ่าน

พิมพ์การประชุมเป็น PDF โดยไม่มี PDF library

GeekBye export การประชุมเป็น PDF ทั้งที่ในโค้ดไม่มี PDF library อยู่ที่ไหนเลย มัน render HTML ในหน้าต่าง browser ที่มองไม่เห็นแล้วพิมพ์มันออกมา ทางเลือกนั้นแหละคือทั้งเรื่อง: มันทำให้ฟีเจอร์นี้สร้างง่าย และมอบทุกความล้มเหลวที่ browser จริง ๆ มีให้มัน — แฟลชขาว, ลิมิตความยาว URL, และ page break ที่ตัด screenshot ขาดครึ่ง ทางแก้ของ bug ที่น่าเกลียดที่สุดคือ CSS หนึ่งบรรทัด

วิศวกรรม
Electron
Desktop
สองโหมดความล้มเหลวของ overlay แบบคลิกทะลุ
Steven
Steven4 นาทีที่อ่าน

สองโหมดความล้มเหลวของ overlay แบบคลิกทะลุ

หน้าต่างของ GeekBye ลอยอยู่เหนือทุกอย่าง และปล่อยให้คลิกของคุณผ่านทะลุมันไป — ยกเว้นตรงที่มันมีปุ่ม นั่นคือสัญญาสองด้าน และ v1.8.5 กับ v1.8.14 คือหน้าตาของตอนที่แต่ละด้านพัง: รีลีสหนึ่ง overlay กลืน dialog ของระบบไป, อีกรีลีสหนึ่งมันขโมยการกดปุ่มของคุณ ทางแก้ที่ชนะสำหรับอันที่สองคือการลบโค้ดทิ้ง

วิศวกรรม
Electron
Desktop