Hướng dẫn về giao dịch

Giới thiệu

Khi sử dụng thư viện C/C++ không có hộp cát, trình liên kết đảm bảo tất cả các hàm cần thiết đều sẵn sàng sau khi biên dịch, do đó bạn không cần phải lo lắng về việc lệnh gọi API có thể không thực hiện được trong thời gian chạy hay không.

Tuy nhiên, khi bạn sử dụng Thư viện hộp cát, quá trình thực thi thư viện này sẽ diễn ra trong một quy trình riêng. Lỗi trong lệnh gọi API yêu cầu kiểm tra tất cả các loại sự cố liên quan đến việc chuyển lệnh gọi qua lớp RPC. Đôi khi, bạn có thể không quan tâm đến lỗi lớp RPC, ví dụ: khi tiến hành xử lý hàng loạt và hộp cát vừa khởi động lại.

Tuy nhiên, vì những lý do nêu trên, bạn cần phải mở rộng hoạt động kiểm tra lỗi thường xuyên đối với giá trị trả về của lệnh gọi API trong hộp cát để bao gồm cả việc kiểm tra xem lỗi có được trả về trên lớp RPC hay không. Đây là lý do tại sao tất cả nguyên mẫu hàm của thư viện đều trả về ::sapi::StatusOr<T> thay vì T. Trong trường hợp lệnh gọi hàm thư viện không thành công (ví dụ: do vi phạm hộp cát), giá trị trả về sẽ chứa thông tin chi tiết về lỗi đã xảy ra.

Khi xử lý lỗi lớp RPC, mỗi lệnh gọi đến Thư viện hộp cát sẽ được thực hiện bằng một bước kiểm tra bổ sung lớp RPC của SAPI. Để xử lý các trường hợp đặc biệt đó, SAPI cung cấp mô-đun Giao dịch SAPI (transaction.h). Mô-đun này chứa lớp ::sapi::Transaction và đảm bảo rằng tất cả lệnh gọi hàm đến Thư viện hộp cát đều được hoàn tất mà không gặp bất kỳ sự cố nào ở cấp RPC hoặc trả về một lỗi có liên quan.

Giao dịch SAPI

SAPI tách Mã máy chủ khỏi Thư viện hộp cát và cho phép phương thức gọi có thể khởi động lại hoặc huỷ yêu cầu xử lý dữ liệu có vấn đề. Giao dịch SAPI tiến thêm một bước và tự động lặp lại các quy trình không thành công.

Có thể sử dụng Giao dịch SAPI theo 2 cách: kế thừa trực tiếp từ ::sapi::Transaction hoặc sử dụng con trỏ hàm được truyền đến ::sapi::BasicTransaction.

Giao dịch SAPI được xác định bằng cách ghi đè 3 hàm sau:

Phương thức giao dịch SAPI
::sapi::Transaction::Init() Điều này tương tự như việc gọi phương thức khởi chạy của thư viện C/C++ điển hình. Phương thức này chỉ được gọi một lần trong mỗi giao dịch đến Thư viện hộp cát, trừ khi giao dịch được khởi động lại. Trong trường hợp khởi động lại, phương thức này sẽ được gọi lại, bất kể số lần khởi động lại đã xảy ra trước đó.
::sapi::Transaction::Main() Phương thức này được gọi cho mỗi lệnh gọi đến ::sapi::Transaction::Run() .
::sapi::Transaction::Finish() Điều này tương tự như việc gọi phương thức dọn dẹp của thư viện C/C++ thông thường. Phương thức này chỉ được gọi một lần trong quá trình huỷ đối tượng Giao dịch SAPI.

Sử dụng thư viện thông thường

Trong một dự án không có thư viện hộp cát, mẫu thông thường khi xử lý thư viện sẽ có dạng như sau:

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

Thư viện được khởi chạy, sau đó các hàm được xuất của thư viện sẽ được sử dụng và cuối cùng là hàm kết thúc/đóng được gọi để dọn dẹp môi trường.

Sử dụng thư viện hộp cát

Trong một dự án có thư viện hộp cát, mã từ chế độ Sử dụng thư viện thông thường sẽ dịch thành đoạn mã sau đây khi sử dụng các giao dịch có lệnh gọi lại:

// 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);
    // ...
  }
  // ...
}

Lớp giao dịch đảm bảo khởi tạo lại thư viện trong trường hợp xảy ra lỗi trong lệnh gọi handle_data. Bạn có thể tìm hiểu thêm về vấn đề này trong phần sau.

Bắt đầu lại giao dịch

Nếu lệnh gọi API thư viện trong hộp cát xảy ra lỗi trong khi thực thi phương thức Giao dịch SAPI (xem bảng trên), thì giao dịch sẽ được bắt đầu lại. Số lần khởi động lại mặc định được kDefaultRetryCnt xác định trong transaction.h.

Sau đây là một số ví dụ về các lỗi lớn hơn sẽ kích hoạt việc khởi động lại:

  • Đã xảy ra lỗi vi phạm hộp cát
  • Quy trình hộp cát đã gặp sự cố
  • Một hàm trong hộp cát đã trả về mã lỗi do lỗi thư viện

Quy trình khởi động lại tuân theo luồng Init()Main() thông thường. Nếu các lệnh gọi lặp lại phương thức ::sapi::Transaction::Run() trả về lỗi, thì toàn bộ phương thức sẽ trả về lỗi cho phương thức gọi

Xử lý lỗi hộp cát hoặc RPC

Giao diện Thư viện hộp cát tạo tự động cố gắng càng giống với nguyên mẫu hàm thư viện C/C++ gốc càng tốt. Tuy nhiên, Thư viện hộp cát phải có khả năng báo hiệu mọi lỗi hộp cát hoặc RPC.

Điều này đạt được bằng cách sử dụng loại dữ liệu trả về ::sapi::StatusOr<T> (hoặc ::sapi::Status cho các hàm trả về void), thay vì trả về trực tiếp giá trị trả về của các hàm hộp cát.

SAPI cũng cung cấp một số macro thuận tiện để kiểm tra và phản ứng với đối tượng Trạng thái SAPI. Các macro này được xác định trong tệp tiêu đề status_macro.h.

Đoạn mã sau đây là phần trích dẫn từ ví dụ tổng và minh hoạ việc sử dụng Trạng thái SAPI và các macro:

// 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();
}

Khởi động lại Hộp cát

Nhiều thư viện hộp cát xử lý dữ liệu đầu vào nhạy cảm của người dùng. Nếu thư viện hộp cát bị hỏng tại một thời điểm nào đó và lưu trữ dữ liệu giữa các lần chạy, thì dữ liệu nhạy cảm này sẽ gặp rủi ro. Ví dụ: nếu phiên bản hộp cát của thư viện Imagemagick bắt đầu gửi ảnh của lần chạy trước.

Để tránh trường hợp như vậy, bạn không nên sử dụng lại hộp cát cho nhiều lần chạy. Để ngừng việc sử dụng lại hộp cát, Mã máy chủ có thể bắt đầu khởi động lại quy trình thư viện hộp cát bằng cách sử dụng ::sapi::Sandbox::Restart() hoặc ::sapi::Transaction::Restart() khi sử dụng Giao dịch SAPI.

Việc khởi động lại sẽ vô hiệu hoá mọi tham chiếu đến quy trình thư viện hộp cát. Điều này có nghĩa là chỉ số mô tả tệp đã chuyển hoặc bộ nhớ được phân bổ sẽ không còn tồn tại.