מדריך לעסקאות

מבוא

כשמשתמשים בספריית C/C++ לא מוגבלת, ה-linker מוודא שכל הפונקציות הדרושות זמינות אחרי ההידור, ולכן אין צורך לדאוג שאולי קריאה ל-API תיכשל בזמן הריצה.

עם זאת, כשמשתמשים בספרייה מוגבלת, ההרצה של הספרייה מתבצעת בתהליך נפרד. אם קריאת API נכשלת, צריך לבדוק את כל סוגי הבעיות שקשורות להעברת הקריאה בשכבת ה-RPC. לפעמים, שגיאות בשכבת ה-RPC לא רלוונטיות, למשל כשמבצעים עיבוד בכמות גדולה וארגז החול פשוט הופעל מחדש.

עם זאת, מהסיבות שצוינו למעלה, חשוב להרחיב את בדיקת השגיאות הרגילה של ערך ההחזרה של קריאת ה-API בארגז החול, כך שתכלול בדיקה אם הוחזרה שגיאה בשכבת ה-RPC. לכן כל אבות הטיפוס של פונקציות הספרייה מחזירים ::sapi::StatusOr<T> במקום T. אם הפעלת פונקציית הספרייה נכשלת (למשל, בגלל הפרה של כללי ארגז החול), ערך ההחזרה יכלול פרטים על השגיאה שהתרחשה.

טיפול בשגיאות בשכבת ה-RPC יתבצע כך שכל קריאה לספרייה ב-Sandbox תלווה בבדיקה נוספת של שכבת ה-RPC של SAPI. כדי להתמודד עם המצבים החריגים האלה, SAPI מספק את מודול SAPI Transaction ‏(transaction.h). המודול הזה מכיל את המחלקה ::sapi::Transaction ומוודא שכל הקריאות לפונקציות בספריית ארגז החול הושלמו ללא בעיות ברמת ה-RPC, או מחזיר שגיאה רלוונטית.

עסקה ב-SAPI

ממשק SAPI מבודד את קוד המארח מהספרייה בארגז החול, ומאפשר למי שקורא לו להפעיל מחדש או לבטל את בקשת עיבוד הנתונים הבעייתית. ‫SAPI Transaction עושה צעד נוסף וחוזרת אוטומטית על תהליכים שנכשלו.

אפשר להשתמש ב-SAPI Transactions בשתי דרכים שונות: או בירושה ישירה מ-::sapi::Transaction, או באמצעות מצביעי פונקציות שמועברים ל-::sapi::BasicTransaction.

כדי להגדיר עסקאות SAPI, צריך לבטל את שלוש הפונקציות הבאות:

שיטות של עסקאות ב-SAPI
::sapi::Transaction::Init() זה דומה לקריאה לשיטת אתחול של ספריית C/C++‎ טיפוסית. השיטה נקראת רק פעם אחת במהלך כל עסקה בספרייה המוגנת בארגז חול, אלא אם העסקה מופעלת מחדש. במקרה של הפעלה מחדש, ה-method מופעלת שוב, בלי קשר למספר הפעמים שהייתה הפעלה מחדש לפני כן.
::sapi::Transaction::Main() השיטה מופעלת בכל קריאה ל- ::sapi::Transaction::Run() .
::sapi::Transaction::Finish() זה דומה לקריאה לשיטת ניקוי של ספריית C/C++ רגילה. המערכת קוראת לשיטה הזאת רק פעם אחת במהלך השמדה של אובייקט SAPI Transaction.

שימוש רגיל בספרייה

בפרויקט בלי ספריות בסביבת ארגז חול, התבנית הרגילה כשעובדים עם ספריות נראית בערך כך:

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 Transaction (ראו את הטבלה שלמעלה), העסקה תופעל מחדש. מספר ההפעלה מחדש שמוגדר כברירת מחדל מוגדר על ידי kDefaultRetryCnt ב-transaction.h.

דוגמאות לשגיאות שגורמות להפעלה מחדש:

  • הייתה הפרה של ארגז החול
  • תהליך ארגז החול קרס
  • פונקציה בתוך ארגז חול החזירה קוד שגיאה בגלל שגיאה בספרייה

תהליך ההפעלה מחדש מתבצע לפי התהליך הרגיל של Init() ו-Main(), ואם קריאות חוזרות לשיטה ::sapi::Transaction::Run() מחזירות שגיאות, השיטה כולה מחזירה שגיאה לקורא שלה.

טיפול בשגיאות ב-Sandbox או ב-RPC

ממשק Sandboxed Library שנוצר אוטומטית מנסה להיות כמה שיותר דומה לאב טיפוס של פונקציית הספרייה המקורית של C/C++. עם זאת, Sandboxed Library צריך להיות מסוגל לסמן שגיאות של Sandbox או RPC.

הדבר נעשה באמצעות שימוש בסוגי החזרה של ::sapi::StatusOr<T> (או ::sapi::Status עבור פונקציות שמחזירות void), במקום להחזיר ישירות את ערך ההחזרה של הפונקציות בסביבת ארגז החול.

בנוסף, SAPI מספק כמה פקודות מאקרו נוחות לבדיקה של אובייקט SAPI Status ולתגובה אליו. הפקודות המאקרו האלה מוגדרות בקובץ הכותרת 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.

הפעלה מחדש תבטל כל הפניה לתהליך של ספריית ארגז החול. המשמעות היא שמתארי קבצים שעברו או זיכרון שהוקצה לא יתקיימו יותר.