Theppitak's blog

My personal blog.

01 เมษายน 2552

libthai/libdatrie Migration in Debian

ระหว่างนี้ยังคงอยู่ในช่วงเคลียร์ TODO list ต่อไป โดยหลังจาก file bug เกี่ยวกับ X locale และ Pango ไปแล้ว ก็มาที่เรื่องของ libthai ซึ่งต้องทำหลายขั้นตอน ระหว่างที่ต้องหยุดรอกระบวนการ ก็สลับไปหยิบเรื่องการ update แพกเกจภาษาไทยใน Debian มาทำไปพลาง ๆ แล้วก็แวะมาที่เรื่อง ฟอนต์ นิดหน่อย ส่วนเรื่องบั๊กความกว้างของตัวเลขอารบิกในฟอนต์ที่ว่าจะทำนั้น ดูแล้ว เป็นปัญหาเกี่ยวกับ hinting ซึ่งจะกินเวลานานเกินไป เลยข้ามไปก่อน เมื่อรายการที่รอผ่านแล้ว ก็เลยกลับมาดูเรื่อง libthai ต่อ

เรื่องของ libthai ก็เริ่มจากการ ออก libdatrie 0.2.0 ที่ ทำค้างไว้ และก่อนที่จะออก libthai ตามมา ก็ upload libdatrie เข้า Debian experimental ก่อน เพื่อทดสอบความเรียบร้อยให้แน่ใจ แต่เนื่องจากรุ่นนี้มีการเปลี่ยน SONAME ของไลบรารี จาก libdatrie.so.0 เป็น libdatrie.so.1 ทำให้มีแพกเกจไบนารีตัวใหม่ คือ libdatrie1 และอื่น ๆ ก็เลยต้องไปผ่านการตรวจสอบใน NEW queue ของ Debian ก่อน เลยเป็นช่วงให้หยุดรอไปทำอย่างอื่นไปพลางอย่างที่ว่าไป

เมื่อผ่านแล้ว ก็มา ออก libthai 0.1.10 ที่ใช้ libdatrie ตัวใหม่ แล้วอัปโหลดเข้า Debian experimental ตามเข้าไป ซึ่งปรากฏว่าเจอตอเข้าให้ เกี่ยวกับเรื่องการ upgrade ไลบรารีข้ามรุ่น SONAME ใครที่เคยอ่านพบใน Debian New Maintainer's Guide แล้วไม่เข้าใจ ว่าทำไมเขาแนะนำ new maintainer ว่าไม่ควรเริ่มจากแพกเกจที่เป็นไลบรารี ก็ขอให้เชื่อเถอะ ว่าเป็นคำแนะนำที่ควรแก่การรับฟังอย่างยิ่ง

ถ้าผมสาธยายรายละเอียดปลีกย่อยเกี่ยวกับไลบรารีที่พบมาทั้งหมด blog ก็คงจะยาวจนไม่ได้เขียนบันทึกที่ตั้งใจจะเขียน ก็ขอข้ามมาที่ปัญหาที่พบในครั้งนี้เลยละกัน

ปัญหาที่พบในครั้งนี้ พอสรุปได้ 2 ประเด็นใหญ่ ๆ

  1. ปัญหาการจัดการการเปลี่ยนแปลง ABI ที่ไม่เข้ากับของเดิม
  2. ปัญหาของ transitive dependency

อาการเป็นดังนี้:

  • client ที่ลิงก์กับ libthai (เช่น Thai language engine ใน Pango) ไปลิงก์กับ libdatrie ไว้ด้วย ผ่านค่า "-lthai -ldatrie" ใน pkg-config ดังนั้น language engine นั้นจึงลิงก์กับ libdatrie 2 ชุด ลิงก์โดยตรงขณะคอมไพล์ชุดหนึ่ง และลิงก์ผ่าน libthai อีกชุดหนึ่ง
  • เมื่อ libthai มีการปรับรุ่น โดยรุ่นใหม่ไปลิงก์กับ libdatrie1 ในขณะที่ client ยังคงลิงก์กับ libdatrie0 อยู่ เมื่อโหลดขึ้นมาทำงาน จึงกลายเป็นโหลด libdatrie ขึ้นมา 2 รุ่นพร้อมกัน คือ libdatrie0 และ libdatrie1
  • เนื่องจากมีการเปลี่ยนแปลง ABI ที่ไม่เข้ากับของเดิม โดยที่ไม่ได้จัดการเรื่อง symbol ให้ดี ไลบรารีทั้งสองชุดจึงมี symbol ซ้ำกัน แต่ทำงานไม่เหมือนกัน จะเรียกได้ตัวเก่าหรือตัวใหม่ก็สุดแท้แต่ linker จะบังเอิญไปหยิบตัวไหนมาให้
  • ผลคือ โปรแกรมทำงานเพี้ยน หรือบางครั้งก็ segfault ไปเลย

ปัญหานี้ ถ้าสามารถโละทุกอย่างที่เกี่ยวข้องทิ้งแล้วคอมไพล์ใหม่หมดจาก libdatrie ตัวใหม่ ก็จะแก้ปัญหาได้ แต่คุณก็รู้ ว่ามันเป็นไปได้ยากสำหรับ distro ขนาดใหญ่อย่าง Debian โดยเฉพาะเมื่อการเปลี่ยนแปลงมาจากแพกเกจเล็ก ๆ ที่มีผลแค่กับภาษาไทยภาษาเดียว (หรือแม้แต่ถ้าคุณคอมไพล์ทุกอย่างจากซอร์สเอง การคอมไพล์ระบบใหม่หมดก็ไม่ใช่เรื่องสนุก) ดังนั้น การจัดการการปรับรุ่นของไลบรารีจึงมีความละเอียดอ่อนค่อนข้างมาก

ได้สรุปไปแล้วข้างต้น ว่าปัญหามีสองประเด็นหลัก ขอเล่าประเด็นหลังก่อน

Transitive Dependency เป็นปัญหาที่ไม่น่าจะเกิด สำหรับโปรแกรมที่ลิงก์กับ shared library ไม่ใช่ลิงก์แบบ static กล่าวคือ ถ้า client ของ libthai ไม่ได้เรียกใช้อะไรใน libdatrie เลย ก็ไม่ควรต้องลิงก์กับ libdatrie โดยตรง ปล่อยให้ libthai ลิงก์ไปคนเดียวก็พอแล้ว จากนั้น เมื่อมีการปรับรุ่น libthai ก็จะไม่กระทบกับ libdatrie อีก

เรื่องนี้เป็นความบกพร่องของผมเองที่ไม่ระวังเรื่อง link flag ของ libthai โดยแม้จะพยายามหลีกเลี่ยง libtool มาใช้ pkg-config แล้ว ก็ยังคงติดแฟล็ก '-ldatrie' มาในแฟ้ม pkg-config ของ libthai อยู่ กล่าวคือ ถ้าเป็นลิงก์โปรแกรมผ่าน *.la ที่สร้างโดย libtool นั้น มันจะไปดึงเอา dependency ของไลบรารีมาลิงก์ตรงหมด (เป็นสาเหตุหนึ่งที่ Debian และดิสโทรอื่นบางดิสโทรพยายามจะกำจัดแฟ้ม *.la ออกจากระบบให้หมด แต่ของ libthai ยังต้องคงไว้ เพื่อให้ kdelibs 3.x ใช้โดยเฉพาะ แต่ก็ไม่ได้มีผลอะไรกับระบบการลิงก์ เพราะ libthai ใช้ pkg-config เป็นหลัก) ในขณะที่การใช้ pkg-config จะสามารถแยก dependency ที่เป็น private ออกจาก public ได้ แต่พอมาใช้ pkg-config แล้ว ผมก็ยังเข้าใจผิดว่าการลิงก์นั้นยังต้องใช้ link flag เหมือน libtool ซึ่งไม่จำเป็นเลย

ดังนั้น ถ้าก่อนหน้านี้ ผมประกาศให้ libdatrie เป็นรายการ requires แบบ private ในแฟ้ม libthai.pc ก็จะไม่เกิดการลิงก์ client กับ libdatrie โดยตรงเป็นจุดที่สอง นอกเหนือจากผ่าน libthai และปัญหา ABI นี้ก็จะไม่เกิดขึ้นเลย

ปัญหานี้ Loic Minier ซึ่งเคยเป็น sponsor ให้กับแพกเกจ libthai ของผมได้ชี้ให้เห็น เขาไม่ได้พูดละเอียดขนาดนี้หรอก ผมเรียบเรียงและอธิบายเพิ่มเติมเพื่อให้เข้าใจง่ายเข้าน่ะ

แต่ผมกลับไปแก้ไขอดีตไม่ได้ ได้แต่กำจัด flag นั้นเพื่อลดรายการ dependency และการโหลดไลบรารีลงในอนาคต แล้วก็มาแก้ปัญหา ABI ที่พบอยู่ตรงหน้านี้

Incompatible ABI Change จาก libdatrie0 เป็น libdatrie1 มีการเปลี่ยนแปลง API โดยมีชื่อฟังก์ชันซ้ำกับของเดิมบางส่วน ดังนั้น เมื่อถูกโหลดขึ้นมาพร้อมกันโดยไม่มีการจัดการแยกแยะ symbol แบบนี้ loader จึง resolve symbol มั่ว แต่ปัญหานี้สามารถเลี่ยงได้ ด้วยการทำ symbol versioning ซึ่งพูดง่าย ๆ ก็เป็นเหมือนการใส่ namespace ให้กับแต่ละ symbol นั่นเอง โดยแยกแยะ ABI เป็นรุ่น ๆ ทับซ้อนกันได้ วิธีนี้มีข้อแม้ว่าไลบรารีจะต้องลิงก์ตรงกับ client เท่านั้น จะไม่มีผลต่อการโหลดผ่าน dlopen เพราะข้อมูล version ในการเรียก จะรู้ได้ในขณะลิงก์เท่านั้น ส่วนถ้าผ่าน dlopen การเรียกก็จะไม่มีการระบุ version

แต่กรณีของเรา libthai ก็ลิงก์ตรงกับ libdatrie อยู่แล้ว สามารถนำมาใช้แก้ปัญหาได้ โดยผมต้องกลับไปใส่ symbol version ใน libdatrie ตัวเก่า แล้ว ออก libdatrie 0.1.4 เพื่อ upload เข้า unstable รอไว้ก่อน พร้อมกันนั้นก็ใส่ symbol version ให้กับ libdatrie ใน experimental ไว้ด้วย เมื่อ libthai ตัวใหม่ย้ายจาก experimental เข้า unstable จึงจะไม่เกิดปัญหาชื่อชนกัน ซึ่งกว่าผมจะย้ายมาได้ ก็ต้องรอให้ libdatrie 0.1.4 ย้ายเข้าไปถึง testing เสียก่อน (ใช้เวลาประมาณ 10 วัน) เพื่อให้ผู้ใช้ testing สามารถ upgrade ได้โดยไม่สะดุดเหมือนกัน

เรื่องการทำ symbol versioning นี้ Josselin Mouette เป็นคนชี้ให้เห็น แต่ความรู้พื้นฐานเกี่ยวกับ symbol versioning ที่ทำให้เข้าใจในสิ่งที่เขาบอก ก็ต้องขอบคุณ Martin Wuertele ซึ่งเป็น Application Manager ในกระบวนการสมัครเป็น Debian Developer ของผม เพราะข้อสอบภาคทฤษฎีของเขา ทำให้ผมไปค้นคว้าจนได้ความรู้ประดับสมองรอไว้ ตอนนี้ก็ได้เวลางัดออกมาใช้ในภาคสนามละ

จากเงื่อนไขต่าง ๆ ที่เล่ามา แผนการโดยสรุปของผมจึงเป็นขั้น ๆ ดังนี้:

  1. update libthai ใน unstable (0.1.9-5) เพื่อตัด link flag '-ldatrie' ออก รอไว้ ในระหว่างนี้ ถ้าแพกเกจไหนที่ลิงก์กับ libthai มีการ build เกิดขึ้น ก็จะตัดลิงก์ตรงกับ libdatrie0 ออกไป เลี่ยงปัญหาการพังไปโสดหนึ่ง หรืออย่างน้อยก็ตัดการโหลดไลบรารีที่ไม่จำเป็นออกไปหนึ่งรายการ แต่ถ้ายังมีใครไม่ build ก็ไม่เป็นไร ยังมีก๊อกสอง
  2. update libdatrie0 ใน unstable (0.1.4-1) ซึ่งเพิ่ม symbol version รอไว้ เพื่อที่เมื่อ libthai ตัวใหม่ย้ายเข้า unstable แล้ว จะไม่เกิดการชนของ symbol และต้องรอให้แพกเกจนี้ย้ายเข้าไปถึง testing เสียก่อนด้วย จึงจะดำเนินการขั้นต่อไปได้
  3. ออก libdatrie1 ตัวใหม่ที่มี symbol version และ upload เข้า experimental
  4. update libthai ใน experimental อีกครั้ง โดยลิงก์กับ libdatrie1 ที่มี symbol version พร้อมกับตัด link flag เหมือนที่ทำใน unstable ด้วย นอกจากนี้ จากที่ Josselin ได้ท้วงว่าอาจมีปัญหากับการ upgrade เพียงบางส่วนจาก lenny ที่ยังใช้ libdatrie0 ตัวเก่า จึงควรเพิ่ม versioned conflict กับ libdatrie0 (<< 0.1.4) ด้วย
  5. เมื่อถึงเวลาอันควร (ซึ่ง Josselin บอกว่า น่าจะเป็นหลัง GNOME 2.26 migration ล็อตใหญ่ที่กำลังจะเริ่ม) ก็ติดต่อกับ release team เพื่อย้าย libthai เข้า unstable โดยขอ binNMU สำหรับทุกแพกเกจที่ลิงก์กับ libthai ซึ่งน่าจะทำให้ libdatrie0 รุ่นเก่าถูกปลดระวางไปอย่างสมบูรณ์

สำหรับ swath ตัวใหม่ที่ลิงก์กับ libdatrie1 แล้วนั้น คงจะออกเมื่อ libdatrie1 ตัวใหม่อยู่ตัวแล้ว อาจจะระหว่างอยู่ใน experimental หรือหลังจากย้ายเข้า unstable ก็แล้วแต่ความเหมาะสม

ป้ายกำกับ: , , ,

2 ความเห็น:

  • 7 เมษายน 2552 12:46 , Blogger bact' แถลง…

    สุดยอดเลยพี่ กระบวนการ

    ยิ่งอ่านยิ่งรู้สึกว่า Debian นี่มันไม่ใช่ distro ที่เป็น product แต่เป็นเรื่องของ process มากกว่า เป็น 'วิถี' อะไรประมาณนั้น

     
  • 12 กรกฎาคม 2552 18:42 , OpenID preor แถลง…

    libthai บนแมคเหรอ

     

แสดงความเห็น (มีการกลั่นกรองสำหรับ blog ที่เก่ากว่า 14 วัน)

<< กลับหน้าแรก

hacker emblem