- เมื่อสคริปต์ Bash พยายามเชื่อมต่อซ้ำเพื่อตรวจสอบ สถานะของเว็บเซิร์ฟเวอร์ อาจเกิดปัญหาที่เซิร์ฟเวอร์เข้าสู่ ลูปไม่สิ้นสุด โดยไม่คาดคิด
timeout ซึ่งเป็นเครื่องมือสำหรับแก้ปัญหานี้ จะกำหนดเวลาจำกัดในการรันคำสั่ง และเมื่อเกินเวลาจะส่งสัญญาณเพื่อพยายาม ยุติโพรเซส
- ไม่สามารถใช้กับ shell built-in อย่าง
until ได้โดยตรง จึงต้องแก้ด้วยการ ห่อด้วยโพรเซส bash หรือแยกเป็นสคริปต์ต่างหาก
การรอเว็บเซิร์ฟเวอร์และปัญหาลูปไม่สิ้นสุดในสคริปต์ Bash
- ในการทำงานจริง มีการใช้สคริปต์ Bash เพื่อทำ การตั้งค่าเว็บเซิร์ฟเวอร์และตรวจสอบสถานะ
- โครงสร้างจะพักงานถัดไปไว้ก่อนระหว่างที่เซิร์ฟเวอร์กำลังเริ่มทำงาน ซึ่งโดยปกติจะทำงานได้ไม่มีปัญหา
- แต่หากเกิด crash ระหว่างการเริ่มต้นเซิร์ฟเวอร์ ก็จะเข้าสู่ลูปไม่สิ้นสุด ทำให้ต้องหาวิธีแก้ไข
ตัวอย่างการใช้ until และข้อจำกัด
การนำยูทิลิตี timeout มาใช้
- คำสั่ง
timeout จะยุติคำสั่งโดยส่ง สัญญาณ (เช่น SIGTERM) หากคำสั่งไม่เสร็จภายในเวลาที่กำหนด
- ตัวอย่าง: ในกรณี
timeout 1s sleep 5 เมื่อผ่านไป 1 วินาที จะพยายาม ยุติโพรเซส sleep
- เมื่อยุติ จะคืนค่าเป็น รหัสออกแบบผิดปกติ (เช่น 124)
การพยายามใช้ timeout ร่วมกับ until และปัญหาที่พบ
วิธีแก้: ห่อด้วยโพรเซส Bash หรือใช้สคริปต์ภายนอก
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ทริกที่ฉันชอบมากที่สุดอย่างหนึ่งซึ่งไม่ค่อยมีใครรู้จัก คือการแนะนำวิธีทดสอบความล้มเหลวของ system call แบบต่าง ๆ ด้วย strace fault injection
มีคำอธิบายรายละเอียดเพิ่มเติมในลิงก์ที่เกี่ยวข้อง
รู้สึกว่าฟีเจอร์นี้น่าทึ่งมาก และแชร์ประสบการณ์ว่าถ้ารู้เรื่องนี้ตั้งแต่ก่อนก็คงดี
เพราะไม่มีวิธีทดสอบเส้นทางเมื่อเกิดความล้มเหลว จึงเคยต้องแทนที่บางส่วนของฟังก์ชันด้วยโค้ดชั่วคราว แต่ทริกนี้ทำให้มีแนวทางที่กระชับกว่านั้น
มีความเห็นว่าวิธีนี้ดูมีประโยชน์มาก
และสงสัยว่าบน Windows มีฟังก์ชันคล้าย ๆ กันหรือไม่
มีข้อเสนอว่าวิธีที่ดีที่สุดสำหรับ service health check คือกำหนดทั้งเวลา timeout สูงสุดและจำนวนครั้ง retry สูงสุด
โดยทั่วไปจะลอง retry ได้ถึง X ครั้ง และตัดสินว่า failed ภายในเวลาไม่เกิน Y
เน้นว่าควรตัดสินให้ล้มเหลวให้เร็วที่สุด แทนที่จะรอนานเกินไป
ในบริการมาตรฐาน มักเริ่ม health check เฉพาะหลังจากรับประกัน dependency ของคอนเทนเนอร์ครบถ้วนและพร้อมทำงานแล้ว
ใน Kubernetes ให้ดู Init Container, ใน AWS ECS ให้ดู dependsOn, และใน Docker Compose ให้ดูการตั้งค่า depends_on
มีการยกตัวอย่าง POSIX shell script
แต่ก็มีการกล่าวว่า curl เองมีฟังก์ชันนี้ในตัวอยู่แล้ว จึงสามารถใช้แบบด้านล่างได้โดยไม่ต้องมีสคริปต์แยก
มีการแชร์ประสบการณ์ว่าใน Mac ไม่มีคำสั่ง timeout มาให้โดยปริยาย จึงเคยลองหลายวิธีเพื่อทำ timeout ด้วย bash builtin เพียงอย่างเดียว
พร้อมอธิบายว่าคำสั่ง sleep เป็นมาตรฐานใน POSIX จึงสามารถใช้ได้
และยกตัวอย่างการทำฟังก์ชัน timeout ดังนี้
ใช้ฟังก์ชันชื่อ times_up เพื่อจัดการ timeout
และมีตัวอย่างทดสอบด้วยการวน for 20 ครั้งพร้อม timeout 10 วินาที
มีการแชร์ว่าตนเคยทำวิธีคล้ายกันตามคำแนะนำใน Stack Overflow เมื่อ 12 ปีก่อน
สามารถดูรายละเอียดได้ในลิงก์อ้างอิง
เน้นว่าใช้แค่ shell builtins กับ sleep และโค้ดนั้นจำเป็นต้องเข้ากันได้กับ POSIX
พร้อมเตือนว่าไวยากรณ์
{1..20}ของ bash ในตัวอย่างไม่ใช่ POSIXส่วนที่ตนปรับปรุงคือ ถ้าไม่เกิด timeout ให้คืนค่า true แต่ถ้าเกิด timeout ให้คืนค่า false เพื่อให้จัดการ error ในสคริปต์ได้ง่ายขึ้น
มีการแชร์วิธีที่ง่ายมาก โดยรันคำสั่งกับ sleep แบบขนานกัน แล้วส่งสัญญาณเพื่อจบคำสั่งเมื่อครบเวลาที่กำหนด
มีการแชร์ตัวอย่างสคริปต์เมื่อ 13 ปีก่อนที่ใช้
read -tเพื่อทำ timeoutลิงก์
มีการแนะนำว่า curl มีแฟล็ก
--retry-connrefusedอยู่แล้ว จึงสามารถใช้ความสามารถนี้ได้ทันทีโดยไม่ต้องเขียน shell loopหากต้องส่งตัวแปรตอนใช้ bash -c ก็แนะนำให้เพิ่มอาร์กิวเมนต์ดังนี้
พร้อมอธิบายเหตุผลที่ใช้
"--"และบทบาทของ argv[0]แม้จะใช้ printf %q ได้เช่นกัน แต่มีการระบุว่าชอบวิธีที่เข้ากันได้กับ Bourne มากกว่า
อธิบายว่า
"--"มีความหมายชัดเจนมากในฐานะสัญญาณสิ้นสุดออปชันทั้งใน bash และ CLI ของ Unix/Linux ส่วนใหญ่อ้างอิงที่เกี่ยวข้อง
มีการแชร์ว่า Busybox ใช้ค่า argv[0] เพื่อตัดสินว่าจะรันโปรแกรมใด จึงสามารถกำหนดเป็น
"ls","mv","cp"หรือคำสั่งอื่นที่ต้องการได้เวลาต้องมีตรรกะ retry วิธีที่ฉันมักใช้มีดังนี้
แม้จะไม่หรูหรานัก แต่โดยทั่วไปค่อนข้างแม่นยำ และในขั้นที่ซับซ้อนขึ้นก็สามารถใช้ exponential backoff ได้
อีกทั้งยังมีข้อดีในด้านการขยายต่อยอด
shellcheck แนะนำให้จัดการกรณีนี้โดยใช้ตัวแปร
_ลิงก์อ้างอิง
มีการเน้นว่าฟังก์ชัน eventually_succeeds อาจต้องมี timeout หรือ defensive coding เพิ่มเติมตามสถานการณ์
พร้อมย้ำว่าควรเขียนโค้ดเชิงป้องกันเสมอในเรื่อง POSIX/process/IO
มีการแชร์ประสบการณ์ว่าเมื่อก่อนตอนลูก ๆ ยังเล็ก เคยใช้คำสั่งด้านล่างเป็นเหมือนเครื่องมือ parental control เพื่อให้ดูรายการได้แค่ 30 นาที
พร้อมบอกว่าเป็นไอเดียที่นำไปใช้ประโยชน์ได้ดีมาก
มีการระบุว่าไม่ค่อยชอบใช้คำสั่งแบบ inline หรือไฟล์สคริปต์ชั่วคราว เพราะต้องส่งสัญญาณไปยัง subprocess
วิธีที่ตนชอบคือทำตรรกะที่ซับซ้อนตามต้องการเป็นฟังก์ชัน แล้ว export ออกมา จากนั้นห่อด้วย timeout bash -c
เกี่ยวข้องกับวิธีส่งอาร์กิวเมนต์อย่างปลอดภัยที่ aidenn0 กล่าวไว้
"$@"เสมอไม่เช่นนั้นจะเกิดปัญหาส่งอาร์กิวเมนต์ที่มีช่องว่างได้ไม่ถูกต้อง
พร้อมแชร์ตัวอย่าง long_fn ที่ใช้ตรวจสอบจุดนี้ได้
มีการรำลึกถึงบทความบล็อกเก่าที่เคยพูดถึง timeout
หากสนใจมากขึ้นในแง่ภาษาโปรแกรมทั่วไปหรือกลไกภายในที่ไม่ใช่ shell ก็แนะนำให้อ่านบล็อกที่เกี่ยวข้อง
มีการแชร์ประสบการณ์ว่าเคยเพิ่ม command timeout ในการตั้งค่า Kubernetes
และระบุว่าสคริปต์ POSIX shell อย่าง await-cmd.sh, await-http.sh, await-tcp.sh มีความสมบูรณ์ดีและใช้งานได้ค่อนข้างมีประโยชน์ในบางสถานการณ์
ลิงก์โปรเจกต์ที่เกี่ยวข้อง