Một cuộc thảo luận gần đây về việc khắc phục hành vi Ctrl+C trong các ứng dụng terminal Rust đã châm ngòi cho một cuộc tranh luận rộng hơn về xử lý tín hiệu và quản lý tiến trình trong cộng đồng Rust . Trong khi bài viết gốc tập trung vào việc quản lý các tiến trình con và dọn dẹp terminal, các nhà phát triển nhanh chóng xác định được những vấn đề cơ bản hơn ảnh hưởng đến các công cụ dòng lệnh Rust hàng ngày.
SIGPIPE: Vấn đề ẩn giấu ảnh hưởng đến các công cụ CLI Rust
Mối quan tâm quan trọng nhất được các nhà phát triển đưa ra tập trung vào việc xử lý SIGPIPE . Khác với các ngôn ngữ lập trình khác, trình biên dịch Rust tự động thêm một trình xử lý tín hiệu bỏ qua các tín hiệu SIGPIPE trước khi gọi hàm main. Điều này tạo ra hành vi bất ngờ khi các chương trình Rust được sử dụng trong các pipeline Unix .
Khi bạn chuyển đầu ra của một chương trình Rust đến các lệnh như head
hoặc grep
, chương trình nhận có thể đóng pipe sớm. Trong các chương trình Unix truyền thống, điều này sẽ gửi một tín hiệu SIGPIPE để kết thúc một cách sạch sẽ chương trình gửi. Tuy nhiên, các chương trình Rust thay vào đó nhận được lỗi ghi và thường hiển thị thông báo lỗi thay vì thoát một cách im lặng. Điều này phá vỡ hành vi pipeline Unix mong đợi mà nhiều nhà phát triển dựa vào.
Vấn đề trở nên phức tạp hơn khi xem xét hành vi của shell. Các shell thường đặt trạng thái thoát của các chương trình bị giết bởi tín hiệu thành 128 cộng với số tín hiệu, sẽ là 141 đối với SIGPIPE . Các chương trình Rust không thể tái tạo hoàn toàn hành vi này, ngay cả khi kiểm tra thủ công các pipe bị hỏng và đặt mã thoát chính xác.
Lưu ý: SIGPIPE là một tín hiệu Unix được gửi khi một chương trình cố gắng ghi vào một pipe đã được đóng bởi chương trình nhận.
Hành vi mã thoát SIGPIPE
- Các chương trình Unix truyền thống: Thoát với trạng thái 141 (128 + 13) khi bị giết bởi SIGPIPE
- Các chương trình Rust: Nhận lỗi ghi thay vì tín hiệu, hiển thị thông báo lỗi
- Cách khắc phục: Kiểm tra thủ công các lỗi broken pipe và thoát với trạng thái 141
Các phương pháp quản lý tiến trình châm ngòi tranh luận kỹ thuật
Các thành viên cộng đồng cũng đặt câu hỏi về một số phương pháp được khuyến nghị cho việc quản lý tiến trình con. Một số nhà phát triển cho rằng việc luôn chuyển đầu ra của tiến trình con qua pipe không phải là giải pháp đúng, đặc biệt đối với các chương trình tương tác cần truy cập terminal hoặc các chương trình kiểm tra xem chúng có đang chạy trong môi trường terminal hay không.
Một số tiến trình cần stdin (nếu đó là một shell thì sao?) và một số tiến trình sẽ kiểm tra xem stdout có phải là tty hay không. Điều bạn nên làm (và Rust không làm cho điều này dễ dàng) là phân bổ một pty mới cho các tiến trình con của bạn nếu stdout của chính bạn là một tty.
Các phương pháp thay thế đã được đề xuất, bao gồm sử dụng các tính năng đặc thù của Linux như PR_SET_PDEATHSIG và process namespaces, hoặc triển khai các cơ chế thu hoạch tiến trình con thích hợp tương tự như các tiến trình init Unix . Những phương pháp này có thể cung cấp việc dọn dẹp tiến trình mạnh mẽ hơn mà không cần duy trì các registry tiến trình toàn cục có thể bỏ lỡ các tiến trình được tạo ra bởi mã thư viện.
Lưu ý: Một pty (pseudo-terminal) là một cặp thiết bị ảo cung cấp giao diện terminal cho các chương trình cần tương tác với terminal.
Các Phương Pháp Quản Lý Tiến Trình Con
- Phương Pháp Registry: Duy trì danh sách các tiến trình đã sinh ra để dọn dẹp
- Đặc thù Linux: PR_SET_PDEATHSIG, PR_SET_CHILD_SUBREAPER, không gian tên PID
- Đa nền tảng: Thu hoạch tiến trình tương tự như hệ thống init của Unix
- Phân Bổ PTY: Tạo các pseudo-terminal cho các tiến trình con tương tác
Thách thức đa nền tảng vẫn chưa được giải quyết
Các nhà phát triển Windows bày tỏ sự thất vọng rằng các giải pháp chủ yếu tập trung vào các hệ thống giống Unix . Windows không sử dụng tín hiệu Unix và thường chỉ cung cấp tương đương với SIGKILL thay vì các tín hiệu kết thúc nhẹ nhàng như SIGTERM . Điều này làm cho các ứng dụng terminal đa nền tảng đặc biệt khó triển khai một cách chính xác.
Cuộc thảo luận nhấn mạnh rằng mặc dù các vấn đề không phải là duy nhất đối với Rust , hệ sinh thái của ngôn ngữ này có thể được hưởng lợi từ các thư viện chống lạm dụng tốt hơn và các hành vi mặc định tốt hơn cho các ứng dụng terminal.
Sự Khác Biệt Tín Hiệu Giữa Các Nền Tảng
- Unix / Linux: SIGINT (Ctrl+C), SIGTERM (dừng nhẹ nhàng), SIGKILL (dừng cưỡng bức), SIGPIPE (đường ống bị hỏng)
- Windows: Hỗ trợ tín hiệu hạn chế, chủ yếu tương đương SIGKILL, hỗ trợ SIGINT tùy chọn
- Rust Mặc định: Tự động bỏ qua tín hiệu SIGPIPE thông qua trình xử lý được thêm bởi compiler
Kết luận
Điều bắt đầu như hướng dẫn xử lý Ctrl+C trong các ứng dụng terminal Rust đã tiết lộ những vấn đề hệ thống sâu hơn với việc xử lý tín hiệu và quản lý tiến trình trong hệ sinh thái Rust . Vấn đề SIGPIPE ảnh hưởng đến nhiều công cụ dòng lệnh Rust hiện có, phá vỡ hành vi pipeline Unix mong đợi. Mặc dù các giải pháp tồn tại cho từng ứng dụng riêng lẻ, cộng đồng tiếp tục tìm kiếm các mặc định tốt hơn và các phương pháp đa nền tảng mạnh mẽ hơn sẽ có lợi cho tất cả các nhà phát triển Rust xây dựng ứng dụng terminal.
Tham khảo: Fixing Ctrl+C in Rust Terminal Apps: Child Process Management