การอพิจารณาล่าสุดเกี่ยวกับการแก้ไขพฤติกรรม Ctrl+C ในแอปพลิเคชันเทอร์มินัล Rust ได้จุดประกายการถกเถียงที่กว้างขึ้นเกี่ยวกับการจัดการสัญญาณและการจัดการกระบวนการในชุมชน Rust ในขณะที่บทความต้นฉบับมุ่งเน้นไปที่การจัดการกระบวนการลูกและการล้างข้อมูลเทอร์มินัล นักพัฒนาได้ระบุปัญหาพื้นฐานที่สำคัญกว่าซึ่งส่งผลต่อเครื่องมือ command-line ของ Rust ในชีวิตประจำวัน
SIGPIPE: ปัญหาที่ซ่อนอยู่ซึ่งส่งผลต่อเครื่องมือ CLI ของ Rust
ความกังวลที่สำคัญที่สุดที่นักพัฒนายกขึ้นมาคือการจัดการ SIGPIPE ต่างจากภาษาโปรแกรมมิ่งอื่นๆ คอมไพเลอร์ของ Rust จะเพิ่ม signal handler โดยอัตโนมัติที่จะเพิกเฉยต่อสัญญาณ SIGPIPE ก่อนที่จะเรียกฟังก์ชัน main สิ่งนี้สร้างพฤติกรรมที่ไม่คาดคิดเมื่อโปรแกรม Rust ถูกใช้ใน Unix pipelines
เมื่อคุณ pipe เอาต์พุตของโปรแกรม Rust ไปยังคำสั่งเช่น head
หรือ grep
โปรแกรมที่รับอาจปิด pipe ก่อนเวลา ในโปรแกรม Unix แบบดั้งเดิม สิ่งนี้จะส่งสัญญาณ SIGPIPE ที่จะยุติผู้ส่งอย่างสะอาด อย่างไรก็ตาม โปรแกรม Rust จะได้รับ write error แทนและมักจะแสดงข้อความแสดงข้อผิดพลาดแทนที่จะออกอย่างเงียบๆ สิ่งนี้ทำลายพฤติกรรม Unix pipeline ที่คาดหวังซึ่งนักพัฒนาหลายคนพึ่งพา
ปัญหาจะซับซ้อนมากขึ้นเมื่อพิจารณาพฤติกรรมของ shell Shell มักจะตั้งค่า exit status ของโปรแกรมที่ถูก signal ฆ่าเป็น 128 บวกด้วยหมายเลขสัญญาณ ซึ่งจะเป็น 141 สำหรับ SIGPIPE โปรแกรม Rust ไม่สามารถจำลองพฤติกรรมนี้ได้อย่างสมบูรณ์ แม้เมื่อตรวจสอบ broken pipes ด้วยตนเองและตั้งค่า exit code ที่ถูกต้อง
หมายเหตุ: SIGPIPE เป็นสัญญาณ Unix ที่ส่งเมื่อโปรแกรมพยายามเขียนไปยัง pipe ที่ถูกปิดโดยโปรแกรมที่รับ
พฤติกรรมรหัสการออกของ SIGPIPE
- โปรแกรม Unix แบบดั้งเดิม: ออกด้วยสถานะ 141 (128 + 13) เมื่อถูกฆ่าโดย SIGPIPE
- โปรแกรม Rust : ได้รับข้อผิดพลาดการเขียนแทนที่จะเป็นสัญญาณ แสดงข้อความแสดงข้อผิดพลาด
- วิธีแก้ไขชั่วคราว: ตรวจสอบข้อผิดพลาด broken pipe ด้วยตนเองและออกด้วยสถานะ 141
แนวทางการจัดการกระบวนการจุดประกายการถกเถียงทางเทคนิค
สมาชิกในชุมชนยังตั้งคำถามเกี่ยวกับแนวทางที่แนะนำสำหรับการจัดการกระบวนการลูก นักพัฒนาหลายคนโต้แย้งว่าการ pipe เอาต์พุตของกระบวนการลูกเสมอไม่ใช่วิธีแก้ปัญหาที่ถูกต้อง โดยเฉพาะสำหรับโปรแกรมแบบโต้ตอบที่ต้องการเข้าถึงเทอร์มินัลหรือโปรแกรมที่ตรวจสอบว่าพวกมันกำลังทำงานในสภาพแวดล้อมเทอร์มินัลหรือไม่
กระบวนการบางอย่างต้องการ stdin (จะเป็นอย่างไรถ้ามันเป็น shell?) และกระบวนการบางอย่างจะตรวจสอบว่า stdout เป็น tty หรือไม่ สิ่งที่คุณควรทำ (และ Rust ไม่ได้ทำให้เรื่องนี้ง่าย) คือจัดสรร pty ใหม่สำหรับกระบวนการลูกของคุณหาก stdout ของคุณเองเป็น tty
แนวทางทางเลือกถูกเสนอแนะ รวมถึงการใช้ฟีเจอร์เฉพาะ Linux เช่น PR_SET_PDEATHSIG และ process namespaces หรือการใช้กลไกการ reap ลูกที่เหมาะสมคล้ายกับกระบวนการ init ของ Unix วิธีการเหล่านี้สามารถให้การล้างข้อมูลกระบวนการที่แข็งแกร่งกว่าโดยไม่ต้องรักษา global process registries ที่อาจพลาดกระบวนการที่เกิดขึ้นโดยโค้ดไลบรารี
หมายเหตุ: pty (pseudo-terminal) เป็นคู่ของอุปกรณ์เสมือนที่ให้อินเทอร์เฟซเทอร์มินัลสำหรับโปรแกรมที่ต้องการโต้ตอบกับเทอร์มินัล
แนวทางการจัดการ Child Process
- วิธี Registry: รักษารายการของ process ที่ถูกสร้างขึ้นเพื่อการทำความสะอาด
- เฉพาะ Linux: PR_SET_PDEATHSIG, PR_SET_CHILD_SUBREAPER, PID namespaces
- ข้ามแพลตฟอร์ม: การเก็บเกี่ยว process คล้ายกับระบบ init ของ Unix
- การจัดสรร PTY: สร้าง pseudo-terminal สำหรับ child process แบบ interactive
ความท้าทายข้ามแพลตฟอร์มยังคงไม่ได้รับการแก้ไข
นักพัฒนา Windows แสดงความผิดหวังที่โซลูชันมุ่งเน้นไปที่ระบบคล้าย Unix เป็นหลัก Windows ไม่ใช้สัญญาณ Unix และโดยทั่วไปจะให้เฉพาะสิ่งที่เทียบเท่ากับ SIGKILL แทนที่จะเป็นสัญญาณการยุติอย่างสุภาพเช่น SIGTERM สิ่งนี้ทำให้แอปพลิเคชันเทอร์มินัลข้ามแพลตฟอร์มมีความท้าทายเป็นพิเศษในการใช้งานให้ถูกต้อง
การอภิปรายเน้นย้ำว่าในขณะที่ปัญหาไม่ได้เป็นเฉพาะของ Rust ระบบนิเวศของภาษานี้จะได้ประโยชน์จากไลบรารีที่ทนต่อการใช้งานผิดมากขึ้นและพฤติกรรมเริ่มต้นที่ดีกว่าสำหรับแอปพลิเคชันเทอร์มินัล
ความแตกต่างของ Signal ระหว่างแพลตฟอร์ม
- Unix/Linux: SIGINT (Ctrl+C), SIGTERM (การปิดอย่างสุภาพ), SIGKILL (การบังคับปิด), SIGPIPE (pipe ที่ขาด)
- Windows: การรองรับ signal ที่จำกัด ส่วนใหญ่เป็นเทียบเท่า SIGKILL และการรองรับ SIGINT แบบเสริม
- Rust Default: ละเว้น SIGPIPE signals โดยอัตโนมัติผ่าน handler ที่เพิ่มโดย compiler
บทสรุป
สิ่งที่เริ่มต้นเป็นคำแนะนำสำหรับการจัดการ Ctrl+C ในแอปพลิเคชันเทอร์มินัล Rust ได้เปิดเผยปัญหาระบบที่ลึกซึ้งกว่าเกี่ยวกับการจัดการสัญญาณและการจัดการกระบวนการในระบบนิเวศ Rust ปัญหา SIGPIPE ส่งผลต่อเครื่องมือ command-line ของ Rust ที่มีอยู่หลายตัว ทำลายพฤติกรรม Unix pipeline ที่คาดหวัง แม้ว่าจะมีโซลูชันสำหรับแอปพลิเคชันแต่ละตัว แต่ชุมชนยังคงแสวงหาค่าเริ่มต้นที่ดีกว่าและแนวทางข้ามแพลตฟอร์มที่แข็งแกร่งกว่าซึ่งจะเป็นประโยชน์ต่อนักพัฒนา Rust ทุกคนที่สร้างแอปพลิเคชันเทอร์มินัล
อ้างอิง: Fixing Ctrl+C in Rust Terminal Apps: Child Process Management