Back home

L'affidabilità del runtime di Rust/Wasm richiede la gestione sia del ripristino da panico che da interruzione

Una volta che l'istanza Wasm condivisa inizia ad accettare chiamate per un lungo periodo, l'arresto anomalo si trasformerà da un singolo errore a un problema di ripristino dello stato e isolamento degli errori.

Inizialmente Wasm può essere facilmente considerato come un livello di porting: il codice può essere programmato, la pagina può essere eseguita, le prestazioni sono ok e le cose sembrano essere più o meno le stesse. Inizia davvero a diventare difficile, di solito dopo aver superato la demo. Una volta che moduli come editor, renderer e parser di documenti passeranno da esperimenti su singola pagina a runtime residenti a lungo termine, i modelli di errore cambieranno immediatamente.

In questo momento, il panico e l’interruzione non sono più rami eccezionali nello strato linguistico. Ciò che decidono è: se questa istanza può continuare a ricevere lavoro successivo, se lo stato nella memoria è contaminato, se il livello host deve eliminare immediatamente l’istanza e se il pool di istanze deve essere riempito. Quando il team mobile sposta sul Web un kernel che è stato eseguito per lungo tempo in contenitori nativi, è questo livello di cambiamento che viene più facilmente sottovalutato.

Dopo aver superato la demo, il modello di errore è appena iniziato.

Gli arresti anomali in una singola chiamata non sono difficili da comprendere. Un clic sul pulsante attiva una chiamata Wasm. Se fallisce, verrà segnalato un errore per l’operazione. Aggiorna la pagina e riprova. Il costo è ancora controllabile.

Il problema si verifica dopo che il runtime inizia a riutilizzare le istanze. Quando la stessa istanza Wasm apre continuamente più documenti, riceve più cicli di eventi di input e passa attraverso più chiamate bridge JS, l’ambito di influenza del panico e dell’interruzione non si ferma più all’azione corrente. Un fallimento incompleto può rallentare le richieste successive.

Tali rischi spesso non vengono esposti il ​​primo giorno. Nella prima fase, di solito si vedono solo segnalazioni di errori sparse: errori di rendering occasionali, una determinata esportazione è bloccata e un determinato documento si trova in uno stato errato dopo essere stato chiuso e riaperto. Se si controlla ulteriormente, gli indizi convergeranno gradualmente verso lo stesso fenomeno: sebbene il guasto sia avvenuto in una catena di chiamate, il danno è rimasto nell’istanza condivisa.

A questo punto, il focus della discussione non è più “se il codice Rust andrà nel panico”, ma “se questo runtime è qualificato per continuare a servire la chiamata successiva dopo il panico”.

Il panico può essere catturato, l’interruzione può solo modificare le istanze.

La cosa più importante da separare in Rust/Wasm sono le due semantiche del fallimento: panico e interruzione.

Il panico ha anche l’opportunità di rilassarsi lungo i confini stabiliti. Finché il livello di collegamento e il livello host concordano in anticipo sul metodo di ripristino, la chiamata corrente può fallire e anche altri stati nell’istanza possono essere mantenuti. l’interruzione non è affatto la strada da percorrere. Significa che l’esecuzione corrente ha raggiunto uno stato irrecuperabile. Se continui a utilizzare la stessa istanza per ricevere richieste, stai essenzialmente scommettendo che la memoria e le risorse non verranno danneggiate a metà.

Una volta che i due vengono mescolati insieme durante il runtime, si verificheranno sicuramente dei problemi nell’elaborazione successiva:

  • Considera l’interruzione come una normale eccezione e il pool di istanze continuerà a riutilizzare gli oggetti che hanno perso credibilità.
  • Trattare tutti i casi di panico come se l’istanza dovesse essere distrutta e il throughput verrà ridotto inutilmente
  • L’host JS sa solo che “la chiamata non è riuscita”, ma non sa se riprovare, perdere l’istanza o interrompere la sessione corrente

Questo è anche l’aspetto più realistico dell’affidabilità del runtime di Wasm: la semantica di ripristino deve essere definita prima di poter implementare l’isolamento e la pianificazione successivi.

Se il livello di associazione non fornisce la semantica di ripristino, il livello host assumerà lo stato non valido e continuerà ad accettare il lavoro.

Il luogo più pericoloso per questo tipo di problemi non è nel codice aziendale, ma nello strato vincolante che sembra essere “già risolto”. Il livello host spesso vede solo un oggetto errore generato e lo registra come un normale errore di chiamata. Il registro è presente e la pagina non si blocca immediatamente, ma il sistema potrebbe aver lasciato lo stato non valido all’interno dell’istanza.

Ciò che realmente deve essere risolto non è solo try/catch, ma le azioni di gestione dopo il fallimento. Una logica simile alla seguente ha appena iniziato a entrare nella progettazione dell’affidabilità:

async function runWithRecovery(instance, input) {
  try {
    return await instance.exports.handle(input)
  } catch (error) {
    if (isAbort(error)) {
      pool.replace(instance.id)
    }
    throw error
  }
}

Il focus di questo codice non è sulla sintassi, ma su un semplice giudizio: se l’errore corrente ha contrassegnato questa istanza come oggetto non attendibile. Se la risposta è sì, l’azione di ripristino non dovrebbe fermarsi alla generazione di errori, ma dovrebbe continuare fino all’eliminazione dell’istanza, alla ricostruzione delle risorse e al taglio del flusso delle richieste.

Finché questo livello non è chiaramente definito, sembrerà che il sistema gestisca gli errori, ma ciò che in realtà sta facendo è reinserire un runtime potenzialmente danneggiato nel percorso di produzione.

Le istanze condivise amplificheranno il problema del ripristino trasformandolo in un problema di strategia di pooling

Dopo che Wasm è stato inserito nei prodotti reali, raramente c’è solo “un’istanza fino alla chiusura della pagina”. Più comuni sono i pool di istanze, i pool di lavoro o i documenti in primo piano e le attività in background che condividono un set di risorse di runtime. In questa fase, i costi di recupero dovuti al panico e all’aborto riscriveranno direttamente la strategia di pooling.

Se l’inizializzazione dell’istanza è costosa, il sistema tenderà naturalmente a riutilizzarla il più possibile. Ma una volta stabilito il riutilizzo, l’isolamento dei guasti deve essere aggiornato contemporaneamente:

  • Quali stati possono essere bloccati solo in una singola chiamata e verranno scartati con la chiamata in caso di fallimento
  • Quali cache possono essere conservate tra le chiamate e quali cache devono essere completamente invalidate una volta riscontrata un’interruzione
  • Dopo la sostituzione dell’istanza, come verranno trasferite le attività in coda? Riprovare causerà effetti collaterali due volte?

Queste non sono risposte che il livello linguistico invierà automaticamente. Sono progetti di runtime.

Per questo motivo, se la discussione sull’affidabilità di Rust/Wasm si ferma solo a “è possibile catturare il panico?”, è facile sottovalutare il problema. Ciò che amplia realmente il divario nei costi di manutenzione è se il pool di istanze è in grado di mantenere un chiaro confine di attendibilità dopo un guasto.

Il confine applicabile è fortemente correlato al ciclo di vita

Questa serie di progetti di restauro non è necessaria per ogni progetto Wasm.

Se il modulo è solo uno strumento offline utilizzabile una sola volta o se l’intera istanza viene riciclata quando la pagina viene distrutta, la differenza tra panico e interruzione esisterà ancora, ma il vantaggio in termini di ripristino sarà molto inferiore. Spesso è sufficiente aggiornare direttamente la pagina ed eseguire nuovamente l’attività direttamente.

Una volta che il sistema avrà le seguenti caratteristiche, la semantica del ripristino cambierà rapidamente da un “elemento di ottimizzazione” a un “elemento di infrastruttura”:

  • L’istanza risiede a lungo e non viene distrutta insieme al ciclo di vita di una singola pagina
  • La stessa istanza effettua continuamente più cicli di chiamate
  • Il livello di hosting deve utilizzare il pooling in cambio di tempo di avvio e velocità effettiva
  • Proteggi lo stato della sessione, lo stato della cache e le attività in coda dopo un errore

Quando i team mobili spostano le funzionalità native sul Web, è più probabile che si incontri questo limite. La relazione di isolamento originariamente stabilita per impostazione predefinita nel processo dell’app spesso doveva essere compilata nuovamente dopo aver raggiunto il limite dell’host JS/Wasm.

Wasm semplifica l’ingresso del codice nativo nel browser, ma non porta con sé la semantica di ripristino del runtime. Non appena il sistema inizia a condividere istanze, riutilizzare lo stato e accettare chiamate a lungo termine, il panico e l’interruzione devono essere trattati come due diversi eventi di runtime. Il primo si preoccupa di come terminare la chiamata corrente e il secondo si preoccupa se questa istanza può continuare a vivere nel pool. Se questo giudizio non viene espresso prima, quanto più successo avrà il trapianto di codice, tanto più difficile sarà affrontare i successivi fallimenti.