คู่มือการทำธุรกรรม

บทนำ

เมื่อใช้ไลบรารี C/C++ ที่ไม่ได้อยู่ในแซนด์บ็อกซ์ Linker จะตรวจสอบว่ามีฟังก์ชันที่จำเป็นทั้งหมด หลังจากการคอมไพล์ จึงไม่ต้องกังวลว่าการเรียก API อาจล้มเหลวในรันไทม์ หรือไม่

อย่างไรก็ตาม เมื่อใช้ไลบรารีที่อยู่ในแซนด์บ็อกซ์ การดำเนินการของไลบรารีจะอยู่ในกระบวนการแยกต่างหาก การเรียก API ที่ล้มเหลวต้องตรวจสอบปัญหาทุกประเภทที่เกี่ยวข้องกับการส่งการเรียกผ่านเลเยอร์ RPC บางครั้งข้อผิดพลาดของเลเยอร์ RPC อาจไม่เกี่ยวข้อง เช่น เมื่อทำการประมวลผลแบบเป็นกลุ่ม และเพิ่งรีสตาร์ทแซนด์บ็อกซ์

อย่างไรก็ตาม ด้วยเหตุผลที่กล่าวไว้ข้างต้น การขยายการตรวจสอบข้อผิดพลาดปกติของค่าที่ส่งคืนจากการเรียก API ใน Sandbox ให้รวมการตรวจสอบว่ามีการส่งคืนข้อผิดพลาดในเลเยอร์ RPC หรือไม่จึงเป็นสิ่งสำคัญ ด้วยเหตุนี้ ต้นแบบฟังก์ชันไลบรารีทั้งหมดจึงแสดงผล ::sapi::StatusOr<T> แทน T ในกรณีที่การเรียกใช้ฟังก์ชันไลบรารีล้มเหลว (เช่น เนื่องจากการละเมิดแซนด์บ็อกซ์) ค่าที่ส่งคืนจะมีรายละเอียดเกี่ยวกับข้อผิดพลาดที่เกิดขึ้น

การจัดการข้อผิดพลาดของเลเยอร์ RPC จะหมายความว่าการเรียกใช้ Sandboxed Library แต่ละครั้งจะตามด้วยการตรวจสอบเพิ่มเติมของเลเยอร์ RPC ของ SAPI SAPI มีโมดูลธุรกรรม SAPI (transaction.h) เพื่อรับมือกับสถานการณ์พิเศษเหล่านั้น โมดูลนี้มีคลาส ::sapi::Transaction และตรวจสอบว่าการเรียกฟังก์ชันทั้งหมดไปยังไลบรารีแซนด์บ็อกซ์เสร็จสมบูรณ์โดยไม่มีปัญหาที่ระดับ RPC หรือแสดงข้อผิดพลาดที่เกี่ยวข้อง

ธุรกรรม SAPI

SAPI จะแยกโค้ดโฮสต์ออกจากไลบรารีแซนด์บ็อกซ์ และให้ความสามารถแก่ผู้เรียกในการรีสตาร์ทหรือยกเลิกคำขอประมวลผลข้อมูลที่มีปัญหา SAPI Transaction จะก้าวไปอีกขั้นและทำกระบวนการที่ไม่สำเร็จซ้ำโดยอัตโนมัติ

ธุรกรรม SAPI สามารถใช้ได้ 2 วิธี ได้แก่ สืบทอดโดยตรงจาก ::sapi::Transaction หรือใช้ตัวชี้ฟังก์ชันที่ส่งไปยัง ::sapi::BasicTransaction

ธุรกรรม SAPI จะกำหนดโดยการลบล้างฟังก์ชัน 3 อย่างต่อไปนี้

วิธีการทำธุรกรรม SAPI
::sapi::Transaction::Init() ซึ่งคล้ายกับการเรียกใช้เมธอดการเริ่มต้นของไลบรารี C/C++ ทั่วไป ระบบจะเรียกใช้เมธอดนี้เพียงครั้งเดียวในระหว่างธุรกรรมแต่ละรายการไปยังไลบรารีที่แซนด์บ็อกซ์ เว้นแต่จะมีการรีสตาร์ทธุรกรรม ในกรณีที่มีการรีสตาร์ท ระบบจะเรียกใช้เมธอดอีกครั้ง ไม่ว่าก่อนหน้านี้จะมีการรีสตาร์ทกี่ครั้งก็ตาม
::sapi::Transaction::Main() ระบบจะเรียกใช้เมธอดสำหรับการเรียกใช้ ::sapi::Transaction::Run() แต่ละครั้ง
::sapi::Transaction::Finish() ซึ่งคล้ายกับการเรียกใช้เมธอดการล้างข้อมูลของไลบรารี C/C++ ทั่วไป ระบบจะเรียกใช้เมธอดนี้เพียงครั้งเดียวในระหว่างการทำลายออบเจ็กต์ธุรกรรม SAPI

การใช้งานห้องสมุดตามปกติ

ในโปรเจ็กต์ที่ไม่มีไลบรารีแซนด์บ็อกซ์ รูปแบบปกติเมื่อต้องจัดการกับไลบรารีจะมีลักษณะดังนี้

LibInit();
while (data = NextDataToProcess()) {
  result += LibProcessData(data);
}
LibClose();

ระบบจะเริ่มต้นไลบรารี จากนั้นจะใช้ฟังก์ชันที่ส่งออกจากไลบรารี และ สุดท้ายจะเรียกฟังก์ชันสิ้นสุด/ปิดเพื่อล้างข้อมูลสภาพแวดล้อม

การใช้ไลบรารีแซนด์บ็อกซ์

ในโปรเจ็กต์ที่มีไลบรารีที่แซนด์บ็อกซ์ไว้ โค้ดจากNormal Library Use จะแปลเป็นข้อมูลโค้ดต่อไปนี้เมื่อใช้ ธุรกรรมที่มีการเรียกกลับ

// Overwrite the Init method, passed to BasicTransaction initialization
::absl::Status Init(::sapi::Sandbox* sandbox) {
  // Instantiate the SAPI Object
  LibraryAPI lib(sandbox);
  // Instantiate the Sandboxed Library
  SAPI_RETURN_IF_ERROR(lib.LibInit());
  return ::absl::OkStatus();
}

// Overwrite the Finish method, passed to BasicTransaction initialization
::absl::Status Finish(::sapi::Sandbox *sandbox) {
  // Instantiate the SAPI Object
  LibraryAPI lib(sandbox);
  // Clean-up sandboxed library instance
  SAPI_RETURN_IF_ERROR(lib.LibClose());
  return ::absl::OkStatus();
}

// Wrapper function to process data, passed to Run method call
::absl::Status HandleData(::sapi::Sandbox *sandbox, Data data_to_process,
                           Result *out) {
  // Instantiate the SAPI Object
  LibraryAPI lib(sandbox);
  // Call the sandboxed function LibProcessData
  SAPI_ASSIGN_OR_RETURN(*out, lib.LibProcessData(data_to_process));
  return ::absl::OkStatus();
}

void Handle() {
  // Use SAPI Transactions by passing function pointers to ::sapi::BasicTransaction
  ::sapi::BasicTransaction transaction(Init, Finish);
  while (data = NextDataToProcess()) {
    ::sandbox2::Result result;
    // call the ::sapi::Transaction::Run() method
    transaction.Run(HandleData, data, &result);
    // ...
  }
  // ...
}

คลาสธุรกรรมจะตรวจสอบว่าได้เริ่มต้นไลบรารีอีกครั้งในกรณีที่เกิดข้อผิดพลาด ระหว่างการเรียกใช้ handle_data ซึ่งจะกล่าวถึงเพิ่มเติมในส่วนต่อไปนี้

การรีสตาร์ทธุรกรรม

หากการเรียก API ของไลบรารีแซนด์บ็อกซ์ทำให้เกิดข้อผิดพลาดระหว่างการเรียกใช้เมธอด SAPI Transaction (ดูตารางด้านบน) ระบบจะรีสตาร์ทธุรกรรม จำนวนการรีสตาร์ทเริ่มต้นกำหนดโดย kDefaultRetryCnt ใน transaction.h

ตัวอย่างข้อผิดพลาดที่เกิดขึ้นซึ่งจะทําให้เกิดการรีสตาร์ทมีดังนี้

  • เกิดการละเมิดแซนด์บ็อกซ์
  • กระบวนการแซนด์บ็อกซ์ขัดข้อง
  • ฟังก์ชันแซนด์บ็อกซ์แสดงรหัสข้อผิดพลาดเนื่องจากข้อผิดพลาดของไลบรารี

กระบวนการรีสตาร์ทจะสังเกตโฟลว์ปกติของ Init() และ Main() และหากการเรียกใช้เมธอด ::sapi::Transaction::Run() ซ้ำๆ แสดงข้อผิดพลาด เมธอดทั้งหมดจะแสดงข้อผิดพลาดต่อผู้เรียก

การจัดการข้อผิดพลาดในแซนด์บ็อกซ์หรือ RPC

อินเทอร์เฟซไลบรารี Sandbox ที่สร้างขึ้นโดยอัตโนมัติพยายาม ให้ใกล้เคียงกับต้นแบบฟังก์ชันไลบรารี C/C++ เดิมมากที่สุด อย่างไรก็ตาม ไลบรารีที่อยู่ในแซนด์บ็อกซ์ต้องสามารถส่งสัญญาณข้อผิดพลาดของแซนด์บ็อกซ์หรือ RPC ได้

ซึ่งทำได้โดยใช้ประเภทการแสดงผล ::sapi::StatusOr<T> (หรือ ::sapi::Status สำหรับฟังก์ชันที่แสดงผล void) แทนที่จะแสดงผลค่าการแสดงผลของฟังก์ชันแซนด์บ็อกซ์โดยตรง

นอกจากนี้ SAPI ยังมีมาโครที่สะดวกบางอย่างเพื่อตรวจสอบและตอบสนองต่อออบเจ็กต์สถานะ SAPI มาโครเหล่านี้กำหนดไว้ในไฟล์ส่วนหัว status_macro.h

ข้อมูลโค้ดต่อไปนี้เป็นข้อความที่ตัดตอนมาจากตัวอย่าง sum และแสดงให้เห็นการใช้ สถานะ SAPI และมาโคร

// Instead of void, use ::sapi::Status
::sapi::Status SumTransaction::Main() {
  // Instantiate the SAPI Object
  SumApi f(sandbox());

  // ::sapi::StatusOr<int> sum(int a, int b)
  SAPI_ASSIGN_OR_RETURN(int v, f.sum(1000, 337));
  // ...

  // ::sapi::Status sums(sapi::v::Ptr* params)
  SumParams params;
  params.mutable_data()->a = 1111;
  params.mutable_data()->b = 222;
  params.mutable_data()->ret = 0;
  SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
  // ...
  // Gets symbol address and prints its value
  int *ssaddr;
  SAPI_RETURN_IF_ERROR(sandbox()->Symbol(
      "sumsymbol", reinterpret_cast<void**>(&ssaddr)));
  ::sapi::v::Int sumsymbol;
  sumsymbol.SetRemote(ssaddr);
  SAPI_RETURN_IF_ERROR(sandbox()->TransferFromSandboxee(&sumsymbol));
  // ...
  return ::sapi::OkStatus();
}

การรีสตาร์ทแซนด์บ็อกซ์

ไลบรารีที่อยู่ในแซนด์บ็อกซ์จำนวนมากจัดการอินพุตของผู้ใช้ที่มีความละเอียดอ่อน หากไลบรารีแซนด์บ็อกซ์ เสียหายในบางจุดและจัดเก็บข้อมูลระหว่างการเรียกใช้ ข้อมูลที่ละเอียดอ่อนนี้จะมีความเสี่ยง เช่น หากเวอร์ชันแซนด์บ็อกซ์ของไลบรารี Imagemagick เริ่ม ส่งรูปภาพของการเรียกใช้ก่อนหน้า

เพื่อหลีกเลี่ยงสถานการณ์ดังกล่าว คุณไม่ควรนำแซนด์บ็อกซ์มาใช้ซ้ำหลายครั้ง หากต้องการ หยุดการนำแซนด์บ็อกซ์กลับมาใช้ใหม่ โค้ดโฮสต์จะเริ่มการรีสตาร์ทกระบวนการไลบรารีแซนด์บ็อกซ์ได้โดยใช้ ::sapi::Sandbox::Restart() หรือ ::sapi::Transaction::Restart() เมื่อใช้ธุรกรรม SAPI

การรีสตาร์ทจะทำให้การอ้างอิงกระบวนการไลบรารีแซนด์บ็อกซ์ทั้งหมดไม่ถูกต้อง ซึ่ง หมายความว่าตัวอธิบายไฟล์ที่ส่งผ่านหรือหน่วยความจำที่จัดสรรจะไม่มีอยู่อีกต่อไป