Leitfaden zu Transaktionen

Einführung

Wenn Sie eine C/C++-Bibliothek ohne Sandbox verwenden, sorgt die Verknüpfung dafür, dass alle erforderlichen Funktionen nach der Kompilierung verfügbar sind, sodass Sie sich keine Sorgen machen müssen, ob ein API-Aufruf zur Laufzeit fehlschlägt.

Bei Verwendung einer Sandbox-Bibliothek wird die Ausführung der Bibliothek jedoch in einem separaten Prozess ausgeführt. Ein Fehler in einem API-Aufruf erfordert eine Prüfung auf alle Arten von Problemen, die mit der Übergabe des Aufrufs über die RPC-Ebene zusammenhängen. Manchmal sind die Fehler auf der RPC-Ebene nicht von Interesse, z. B. wenn die Bulk-Verarbeitung durchgeführt und die Sandbox gerade neu gestartet wurde.

Dennoch ist es aus den oben genannten Gründen wichtig, die regelmäßige Fehlerprüfung des Rückgabewerts des in der Sandbox ausgeführten API-Aufrufs um die Prüfung zu erweitern, ob auf der RPC-Ebene ein Fehler zurückgegeben wurde. Aus diesem Grund geben alle Prototypen von Bibliotheksfunktionen ::sapi::StatusOr<T> anstelle von T zurück. Falls der Aufruf der Bibliotheksfunktion fehlschlägt (z.B. aufgrund eines Sandbox-Verstoßes), enthält der Rückgabewert Details zum aufgetretenen Fehler.

Die Behandlung von RPC-Ebenenfehlern würde bedeuten, dass auf jeden Aufruf einer Sandbox-Bibliothek eine zusätzliche Prüfung der RPC-Ebene der SAPI folgt. Um diesen Ausnahmesituationen gerecht zu werden, stellt SAPI das SAPI-Transaktionsmodul (transaction.h) bereit. Dieses Modul enthält die Klasse ::sapi::Transaction und sorgt dafür, dass alle Funktionsaufrufe an die Bibliothek mit Sandbox ohne Probleme auf RPC-Ebene ausgeführt wurden oder einen entsprechenden Fehler zurückgeben.

SAPI-Transaktion

SAPI isoliert den Hostcode von der Sandbox-Bibliothek und gibt dem Aufrufer die Möglichkeit, die problematische Anfrage zur Datenverarbeitung neu zu starten oder abzubrechen. Die SAPI-Transaktion geht noch einen Schritt weiter und wiederholt fehlgeschlagene Prozesse automatisch.

SAPI-Transaktionen können auf zwei verschiedene Arten verwendet werden: entweder durch direkte Übernahme von ::sapi::Transaction oder mithilfe von Funktionszeigern, die an ::sapi::BasicTransaction übergeben werden.

SAPI-Transaktionen werden durch Überschreiben der folgenden drei Funktionen definiert:

SAPI-Transaktionsmethoden
::sapi::Transaction::Init() Dies ähnelt dem Aufruf einer Initialisierungsmethode einer typischen C/C++-Bibliothek. Die Methode wird während jeder Transaktion an die Sandbox-Bibliothek nur einmal aufgerufen, es sei denn, die Transaktion wird neu gestartet. Im Fall eines Neustarts wird die Methode noch einmal aufgerufen, unabhängig davon, wie viele Neustarts zuvor erfolgt sind.
::sapi::Transaction::Main() Die Methode wird für jeden Aufruf von ::sapi::Transaction::Run() aufgerufen.
::sapi::Transaction::Finish() Dies entspricht dem Aufruf einer Bereinigungsmethode für eine typische C/C++-Bibliothek. Die Methode wird während des Löschens des SAPI-Transaktionsobjekts nur einmal aufgerufen.

Normale Verwendung der Bibliothek

In einem Projekt ohne Bibliotheken, die in einer Sandbox ausgeführt werden, sieht das übliche Muster für den Umgang mit Bibliotheken in etwa so aus:

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

Die Bibliothek wird initialisiert, dann werden die aus der Bibliothek exportierten Funktionen verwendet und schließlich wird eine end/close-Funktion aufgerufen, um die Umgebung zu bereinigen.

Verwendung der Mediathek in Sandbox

In einem Projekt mit Bibliotheken, die in einer Sandbox ausgeführt werden, wird der Code aus der Normalen Bibliotheksnutzung bei der Verwendung von Transaktionen mit Callbacks in das folgende Code-Snippet umgewandelt:

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

Die Transaktionsklasse sorgt dafür, dass die Bibliothek neu initialisiert wird, falls beim handle_data-Aufruf ein Fehler aufgetreten ist. Weitere Informationen hierzu finden Sie im folgenden Abschnitt.

Neustarts von Transaktionen

Löst ein in einer Sandbox ausgeführter Bibliotheks-API-Aufruf während der Ausführung der SAPI-Transaktionsmethoden (siehe Tabelle oben) ein Fehler aus, wird die Transaktion neu gestartet. Die Standardanzahl der Neustarts wird durch kDefaultRetryCnt in transaction.h definiert.

Beispiele für gemeldete Fehler, die einen Neustart auslösen:

  • Ein Sandbox-Verstoß ist aufgetreten
  • Der Sandbox-Prozess ist abgestürzt.
  • Eine Sandbox-Funktion hat aufgrund eines Bibliotheksfehlers einen Fehlercode zurückgegeben.

Das Neustartverfahren beobachtet den normalen Init()- und Main()-Ablauf. Wenn wiederholte Aufrufe der Methode ::sapi::Transaction::Run() Fehler zurückgeben, gibt die gesamte Methode einen Fehler an ihren Aufrufer zurück.

Sandbox- oder RPC-Fehlerbehandlung

Die automatisch generierte Benutzeroberfläche der Sandbox versucht, dem ursprünglichen Prototyp der C/C++-Bibliotheksfunktion so nahe wie möglich zu sein. Die Sandbox-Bibliothek muss jedoch in der Lage sein, Sandbox- oder RPC-Fehler zu signalisieren.

Dazu werden ::sapi::StatusOr<T>-Rückgabetypen (oder ::sapi::Status für Funktionen, die void zurückgeben) verwendet, anstatt den Rückgabewert der in der Sandbox ausgeführten Funktionen direkt zurückzugeben.

SAPI bietet außerdem einige praktische Makros für die Prüfung und Reaktion auf ein SAPI-Statusobjekt. Diese Makros sind in der Header-Datei status_macro.h definiert.

Das folgende Code-Snippet ist ein Auszug aus dem Summenbeispiel und veranschaulicht die Verwendung des SAPI-Status und der Makros:

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

Viele in einer Sandbox ausgeführte Bibliotheken verarbeiten sensible Nutzereingaben. Wenn die in der Sandbox ausgeführte Bibliothek irgendwann beschädigt ist und Daten zwischen Ausführungen speichert, sind diese sensiblen Daten gefährdet. Beispiel: Eine Sandbox-Version der Imagemagick-Bibliothek beginnt, Bilder der vorherigen Ausführung zu senden.

Um dies zu vermeiden, sollte die Sandbox nicht für mehrere Ausführungen wiederverwendet werden. Um die Wiederverwendung von Sandboxes zu verhindern, kann der Hostcode einen Neustart des Bibliotheksprozesses in einer Sandbox mit ::sapi::Sandbox::Restart() oder ::sapi::Transaction::Restart() initiieren, wenn SAPI-Transaktionen verwendet werden.

Durch einen Neustart werden alle Verweise auf den Bibliotheksprozess in einer Sandbox ungültig. Das bedeutet, dass übergebene Dateideskriptoren oder der zugewiesene Arbeitsspeicher nicht mehr vorhanden sind.