Guida alle transazioni

Introduzione

Quando utilizzi una libreria C/C++ senza sandbox, il linker garantisce che tutte le funzioni necessarie siano disponibili dopo la compilazione, quindi non devi preoccuparti se una chiamata API può avere esito negativo in fase di runtime.

Quando invece si utilizza una libreria con sandbox, l'esecuzione della libreria avviene in un processo separato. Un errore in una chiamata API richiede il controllo di tutti i tipi di problemi relativi al passaggio della chiamata al livello RPC. A volte, gli errori del livello RPC potrebbero non essere di interesse, ad esempio quando si esegue l'elaborazione collettiva e la sandbox è appena stata riavviata.

Tuttavia, per i motivi sopra menzionati, è importante estendere il controllo regolare degli errori del valore restituito della chiamata API con sandbox per includere il controllo se è stato restituito un errore nel livello RPC. Questo è il motivo per cui tutti i prototipi di funzioni di libreria restituiscono ::sapi::StatusOr<T> anziché T. Nel caso in cui la chiamata della funzione di libreria non vada a buon fine (ad esempio a causa di una violazione della sandbox), il valore restituito conterrà i dettagli dell'errore che si è verificato.

Se gestisci gli errori del livello RPC, ogni chiamata a una libreria con sandbox è seguita da un controllo aggiuntivo del livello RPC di SAPI. Per far fronte a queste situazioni eccezionali, SAPI fornisce il modulo Transazione SAPI (transaction.h). Questo modulo contiene la classe ::sapi::Transaction e garantisce che tutte le chiamate di funzione alla libreria con sandbox siano state completate senza problemi a livello di RPC oppure che restituiscano un errore pertinente.

Transazione SAPI

SAPI isola il codice host dalla libreria con sandbox e offre al chiamante la possibilità di riavviare o interrompere la richiesta di elaborazione dati problematica. La transazione SAPI fa un passo in più e ripete automaticamente i processi non riusciti.

Le transazioni SAPI possono essere utilizzate in due modi diversi: ereditando direttamente da ::sapi::Transaction o utilizzando puntatori alle funzioni passati a ::sapi::BasicTransaction.

Le transazioni SAPI vengono definite sostituendo le tre funzioni seguenti:

Metodi di transazione SAPI
::sapi::Transaction::Init() Questa operazione è simile alla chiamata di un metodo di inizializzazione di una tipica libreria C/C++. Il metodo viene chiamato solo una volta durante ogni transazione nella Libreria con sandbox, a meno che la transazione non venga riavviata. In caso di riavvio, il metodo viene richiamato, indipendentemente dal numero di riavvii eseguiti in precedenza.
::sapi::Transaction::Main() Il metodo viene richiamato per ogni chiamata a ::sapi::Transaction::Run() .
::sapi::Transaction::Finish() Questa operazione è simile alla chiamata di un metodo di pulizia di una tipica libreria C/C++. Il metodo viene richiamato solo una volta durante l'eliminazione dell'oggetto Transaction SAPI.

Normale utilizzo della libreria

In un progetto senza librerie con sandbox, lo schema tipico per la gestione delle librerie è simile al seguente:

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

La libreria viene inizializzata, quindi vengono utilizzate le funzioni della libreria esportate e infine viene chiamata una funzione di fine/chiusura per ripulire l'ambiente.

Utilizzo della libreria con sandbox

In un progetto con librerie con sandbox, il codice della funzionalità Uso normale della libreria viene convertito nel seguente snippet di codice quando vengono utilizzate le transazioni con 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);
    // ...
  }
  // ...
}

La classe Transaction si assicura di reinizializzare la libreria nel caso in cui si verifichi un errore durante la chiamata a handle_data. Ne parleremo nella sezione seguente.

Riavvii delle transazioni

Se una chiamata all'API della libreria con sandbox genera un errore durante l'esecuzione dei metodi Transaction SAPI (vedi la tabella precedente), la transazione verrà riavviata. Il numero predefinito di riavvii è definito da kDefaultRetryCnt in transaction.h.

Ecco alcuni esempi di errori visualizzati che attivano un riavvio:

  • Si è verificata una violazione della sandbox
  • Si è verificato un arresto anomalo del processo sandbox
  • Una funzione con sandbox ha restituito un codice di errore a causa di un errore della libreria

La procedura di riavvio osserva il normale flusso Init() e Main() e, se le chiamate ripetute al metodo ::sapi::Transaction::Run() restituiscono errori, l'intero metodo restituisce un errore al chiamante.

Gestione degli errori di sandbox o RPC

L'interfaccia della libreria con sandbox generata automaticamente tenta di avvicinarsi il più possibile al prototipo di funzione della libreria C/C++ originale. Tuttavia, la libreria con sandbox deve essere in grado di segnalare eventuali errori di sandbox o RPC.

Questo si ottiene utilizzando i tipi restituiti ::sapi::StatusOr<T> (o ::sapi::Status per le funzioni che restituiscono void), invece di restituire direttamente il valore restituito dalle funzioni con sandbox.

SAPI fornisce inoltre alcune utili macro per controllare e reagire a un oggetto Stato SAPI. Queste macro sono definite nel file di intestazione status_macro.h.

Il seguente snippet di codice è un estratto dell'esempio di somma e illustra l'utilizzo dello stato SAPI e delle 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();
}

Riavvii della sandbox

Molte librerie con sandbox gestiscono l'input utente sensibile. Se la libreria con sandbox si danneggia in un determinato momento e archivia i dati tra le esecuzioni, questi dati sensibili sono a rischio. Ad esempio, se una versione con sandbox della libreria Imagemagick inizia a inviare immagini dell'esecuzione precedente.

Per evitare questo scenario, la sandbox non deve essere riutilizzata per più esecuzioni. Per interrompere il riutilizzo delle sandbox, l'host Code può avviare un riavvio del processo della libreria con sandbox utilizzando ::sapi::Sandbox::Restart() o ::sapi::Transaction::Restart() quando si utilizzano le transazioni SAPI.

Il riavvio renderà non valido alcun riferimento al processo della libreria con sandbox. Ciò significa che i descrittori dei file passati o la memoria allocata non esisteranno più.