- Khi thực hiện các lần thử kết nối lặp lại để kiểm tra trạng thái máy chủ web trong script Bash, có thể phát sinh vấn đề máy chủ bất ngờ rơi vào vòng lặp vô hạn
- Công cụ để giải quyết việc này là
timeout, cho phép đặt giới hạn thời gian thực thi cho lệnh và khi vượt quá sẽ gửi tín hiệu để cố gắng kết thúc tiến trình
- Không thể áp dụng trực tiếp cho các shell built-in như
until, nên có thể xử lý bằng cách bọc trong tiến trình bash hoặc tách thành script riêng
Chờ máy chủ web trong script Bash và vấn đề vòng lặp vô hạn
- Trong công việc thực tế, script Bash được dùng để thiết lập máy chủ web và kiểm tra trạng thái
- Cấu trúc này sẽ tạm hoãn bước tiếp theo cho đến khi máy chủ khởi động xong, và về cơ bản hoạt động bình thường
- Tuy nhiên, nếu máy chủ bị crash trong lúc khởi động thì script sẽ rơi vào vòng lặp vô hạn, nên cần có cách xử lý
Ví dụ dùng until và giới hạn của nó
Đưa utility timeout vào sử dụng
- Lệnh
timeout sẽ gửi tín hiệu (SIGTERM, v.v.) để kết thúc nếu lệnh không hoàn thành trong thời gian đã chỉ định
- Ví dụ: với
timeout 1s sleep 5, sau 1 giây sẽ cố gắng kết thúc tiến trình sleep
- Khi kết thúc, nó sẽ trả về mã thoát bất thường (ví dụ: 124)
Thử kết hợp timeout với until và vấn đề phát sinh
Cách giải quyết: bọc trong tiến trình Bash hoặc dùng script bên ngoài
1 bình luận
Ý kiến trên Hacker News
Mẹo ít người biết mà tôi thích nhất là dùng strace fault injection để kiểm tra các trường hợp lỗi của nhiều system call khác nhau
Liên kết liên quan giải thích chi tiết hơn
Chia sẻ rằng tính năng này thực sự đáng kinh ngạc và ước gì đã biết sớm hơn
Trước đây vì không có cách kiểm tra nhánh thất bại nên thường phải tạm thời thay thế một phần của hàm bằng mã thử nghiệm, nhưng mẹo này mở ra khả năng tiếp cận gọn gàng hơn
Ý kiến cho rằng cách này trông rất hữu ích
Đồng thời tò mò không biết trên Windows có tính năng tương tự hay không
Đề xuất rằng cách tối ưu cho service health check là đặt cả thời gian timeout tối đa lẫn số lần retry tối đa
Thông thường sẽ thử retry tối đa X lần và coi là thất bại trong tối đa Y thời gian
Nhấn mạnh cần quyết định thất bại càng sớm càng tốt thay vì chờ quá lâu
Với các service tiêu chuẩn, chỉ nên bắt đầu health check sau khi dependency của container đã được đảm bảo đầy đủ và sẵn sàng hoạt động
Trên Kubernetes có Init Container, trên AWS ECS có dependsOn, trong Docker Compose có thiết lập depends_on
Có đưa ra ví dụ shell script POSIX
Tuy nhiên cũng nhắc rằng curl đã tích hợp sẵn tính năng này nên có thể dùng như sau mà không cần script riêng
Chia sẻ kinh nghiệm từng thử nhiều cách để tự triển khai timeout chỉ bằng bash builtins vì trên Mac lệnh timeout không được cung cấp sẵn mặc định
Giải thích rằng lệnh sleep là tiêu chuẩn trong POSIX nên có thể sử dụng
Đưa ra ví dụ triển khai chức năng timeout như dưới đây
Dùng hàm times_up để xử lý timeout
Có ví dụ kiểm tra bằng vòng lặp for chạy 20 lần với timeout 10 giây
Chia sẻ rằng 12 năm trước đã triển khai cách tương tự theo lời khuyên trên Stack Overflow
Có thể xem chi tiết tại liên kết tham khảo
Nhấn mạnh rằng chỉ dùng shell builtins và sleep, và đoạn mã đó bắt buộc phải tương thích POSIX
Lưu ý rằng cú pháp
{1..20}của bash trong ví dụ không phải POSIXĐiểm cải tiến của tôi là nếu không bị timeout thì trả về true, còn nếu timeout xảy ra thì trả về false để có thể xử lý lỗi trong script đơn giản hơn
Chia sẻ một cách cực kỳ đơn giản như dưới đây: chạy lệnh và sleep song song, rồi sau thời gian chỉ định thì dùng signal để dừng lệnh
Chia sẻ ví dụ script từ 13 năm trước từng dùng
read -tđể triển khai timeoutLiên kết
Thông báo rằng curl đã có sẵn cờ
--retry-connrefused, nên có thể tận dụng ngay tính năng này mà không cần vòng lặp shellNếu cần truyền biến khi dùng bash -c, khuyến nghị thêm đối số như sau
Giải thích lý do dùng
"--"và vai trò của argv[0]Cũng có thể dùng
printf %q, nhưng có nói rằng vẫn thích cách tương thích Bourne hơnGiải thích rằng
"--"có ý nghĩa rất rõ ràng là dấu kết thúc tùy chọn trong bash và hầu hết CLI Unix/LinuxTham khảo liên quan
Chia sẻ rằng Busybox quyết định chương trình sẽ chạy dựa trên giá trị của argv[0], nên có thể gán thành các lệnh mong muốn như
ls,mv,cpv.v.Khi cần logic thử lại nhiều lần, đây là cách tôi thường dùng
Không quá bóng bẩy nhưng nhìn chung khá chính xác, và ở mức nâng cao hơn có thể áp dụng exponential backoff
Cũng có lợi thế về khả năng mở rộng
shellcheck khuyến nghị xử lý vấn đề này bằng cách dùng biến
_Liên kết tham khảo
Nhấn mạnh rằng hàm eventually_succeeds tùy tình huống có thể vẫn cần timeout hoặc mã phòng thủ bổ sung
Nhắc lại rằng với POSIX/process/IO luôn cần viết mã theo hướng phòng thủ
Chia sẻ rằng trước đây khi con cái còn nhỏ, đã dùng lệnh dưới đây như một dạng công cụ kiểm soát của phụ huynh để chỉ cho xem một chương trình trong 30 phút
Đánh giá rằng ý tưởng này đã được áp dụng rất hữu ích
Nói rằng bản thân không thích dùng command inline hay file script tạm khi cần gửi signal tới subprocess
Cách tôi thích là viết logic phức tạp mong muốn thành một hàm, export nó rồi bọc bằng timeout bash -c
Liên quan tới cách xử lý truyền đối số an toàn mà aidenn0 đã nhắc tới
"$@"Nếu không, các đối số có chứa khoảng trắng sẽ không được truyền đúng
Có chia sẻ ví dụ long_fn để kiểm chứng điểm này
Nhắc lại một bài blog trước đây từng đề cập đến timeout
Nếu tò mò hơn về ngôn ngữ lập trình thông thường hoặc cơ chế hoạt động bên trong thay vì shell, có thể tham khảo blog liên quan
Chia sẻ kinh nghiệm từng thêm timeout cho lệnh trong môi trường Kubernetes
Các shell script POSIX như await-cmd.sh, await-http.sh, await-tcp.sh đã khá hoàn thiện và có thể rất hữu ích trong một số tình huống nhất định
Liên kết dự án liên quan