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

เกริ่นนำ

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

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

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

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

ธุรกรรม SAPI

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

คุณใช้ธุรกรรม 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();

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

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

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

// 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 (ดูตารางด้านบน) ธุรกรรมจะรีสตาร์ท จำนวนเริ่มต้นของการรีสตาร์ทจะกำหนดโดย kDefaultRetryCnt ใน transaction.h

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

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

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

การจัดการข้อผิดพลาดเกี่ยวกับแซนด์บ็อกซ์หรือ RPC

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

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

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

ข้อมูลโค้ดต่อไปนี้เป็นบางส่วนจากตัวอย่างผลรวมและสาธิตการใช้สถานะ 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

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