-
การแครชที่เกิดขึ้นเฉพาะบน ARM64
- ระหว่างพอร์ตโค้ด network I/O ของ EdgeDB จาก Python ไปเป็น Rust พบปัญหาที่การทดสอบล้มเหลวเป็นครั้งคราวบน ARM64 CI runner
- ตอนแรกดูเหมือนจะเป็น deadlock แต่จริง ๆ แล้วโปรเซสแครช และตัวทดสอบไม่สามารถตรวจจับได้
-
ทฤษฎีเบื้องต้น
- เพื่อทำความเข้าใจว่าทำไมปัญหาจึงเกิดขึ้นเฉพาะบน ARM64 จึงพิจารณาความแตกต่างของ memory model
- memory model ของ Intel มีความเข้มงวดกว่า ขณะที่ ARM ใช้ memory model ที่อ่อนกว่า
-
การดีบักบนเครื่อง CI
- เชื่อมต่อเข้า ARM64 runner บน AWS โดยตรงเพื่อตรวจสอบปัญหา
- โปรเซสแครชและทิ้ง core dump ไว้ จึงใช้ข้อมูลนั้นสืบหาสาเหตุของปัญหา
-
สาเหตุจริง: setenv และ getenv
setenv ไม่ปลอดภัยในสภาพแวดล้อมแบบมัลติเธรด และอาจทำให้เกิดการแครชเมื่อทำงานร่วมกับ getenv
- พบว่าการจัดสรรค่าตัวแปรสภาพแวดล้อมใหม่เป็นสาเหตุของปัญหา
-
ความเชื่อมโยงกับ openssl_probe
- ปัญหาเกิดขึ้นขณะที่
openssl-probe ตั้งค่าตัวแปรสภาพแวดล้อม SSL_CERT_FILE และ SSL_CERT_DIR
- ระหว่างที่
rust-native-tls ของ Rust ตั้งค่าตัวแปรสภาพแวดล้อมเหล่านี้ ก็เกิดการแครชขึ้น
-
เหตุผลที่เกิดขึ้นเฉพาะบน ARM64 Linux
- การแครชจะเกิดขึ้นได้ก็ต่อเมื่อมีหลายเงื่อนไขมาประจวบกัน โดยจำนวนตัวแปรสภาพแวดล้อมและความล้มเหลวของ I/O เป็นหนึ่งในเงื่อนไขเหล่านั้น
-
วิธีแก้ไข
- ตัดสินใจเปลี่ยนจาก backend
rust-native-tls/openssl ของ reqwest ไปใช้ rustls
- โปรเจ็กต์ Rust มีแผนจะทำให้ฟังก์ชันตั้งค่าสภาพแวดล้อมเป็น unsafe และโปรเจ็กต์
glibc ก็กำลังปรับปรุง thread safety ของ getenv
4 ความคิดเห็น
setenvไม่เป็น thread-safe และ C ก็ไม่ต้องการแก้ไขเรื่องนี้ฟังก์ชัน
setenvก่อปัญหาอีกแล้วผมคิดว่าจะตั้งชื่อว่า "ความไม่ปลอดภัยด้านเธรดของ C stdlib แม้แต่ Rust ที่ว่าปลอดภัยก็ยังช่วยไม่ได้" ครับ :)
เข้าใจอย่างชัดเจนแล้ว
ความเห็นจาก Hacker News
Rust กำลังจะทำให้ตัวตั้งค่าสภาพแวดล้อมเป็น
unsafeใน edition ถัดไป ซึ่งอาจส่งผลต่อ crate ที่มีโค้ดขัดแย้งกันset_varและremove_varจะต้องใช้บล็อกunsafe {}ใน edition 2024มีแพตช์สำหรับ glibc ที่ทำให้
getenvปลอดภัยขึ้น แต่ C ก็ยังไม่ปลอดภัยอย่างสมบูรณ์ เพราะยังอนุญาตให้เข้าถึง environment ได้โดยตรงsetenvปลอดภัยกับมัลติเธรด แต่อย่างน้อยก็ควรนิยาม API แบบ thread-safe ตัวใหม่ขึ้นมาการเจอบั๊กเกี่ยวกับ environment บน Linux ดูเหมือนเป็นเหมือนพิธีผ่านด่านอย่างหนึ่ง
getenv_r()พร้อมซิงก์กับsetenv()และมีคำเตือนตอนคอมไพล์/ลิงก์ น่าจะช่วยแก้ปัญหานี้ได้การใช้ environment variable สำหรับตั้งค่าเป็นส่วนหนึ่งของแนวคิด "12-factor app" แต่มีความเห็นว่านี่เป็นวิธีที่ไม่ฉลาด
เครื่อง CI ที่รันบน Amazon AWS มีข้อดีตรงที่ให้สิทธิ์ root จริง
เป็นบทความยอดเยี่ยมที่ขุดลึกบั๊กแบบไม่เป็นไปตามสัญชาตญาณ
env::set_varตอนนี้กลายเป็นunsafeแล้วset_varหรือremove_varทำให้นึกถึงประสบการณ์ที่
setproctitleใช้งานไม่ได้ในโค้ดเบสบางตัวnumpyแล้วsetproctitleใช้งานไม่ได้ เพราะระหว่างการ initializanumpyมีการเรียกgetenvหรือsetenvจนทำให้ ที่อยู่ของ environ เปลี่ยนไป