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

מבוא

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

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

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

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

עסקת SAPI

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

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

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

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

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

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

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

הספרייה מופעלת, לאחר מכן נעשה שימוש בפונקציות מיוצאות של הספרייה, ולבסוף מופעלת פונקציית סיום/סגירה כדי לנקות את הסביבה.

שימוש בספרייה בארגז חול

בפרויקט עם ספריות Sandbox, הקוד משימוש בספרייה רגילה מתורגם לקטע הקוד הבא כאשר משתמשים בטרנזקציות עם קריאה חוזרת (callback):

// 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.

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

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

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

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

הממשק של ספריית Sandboxed שנוצר באופן אוטומטי מנסה להיות קרוב ככל האפשר לאב-טיפוס המקורי של הפונקציה של ספריית C/C++. עם זאת, הספרייה Sandboxed צריכה להיות מסוגלת לאותת לשגיאות Sandbox או 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();
}

הפעלות מחדש של Sandbox

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

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

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