Back home

Die Laufzeitzuverlässigkeit von Rust/Wasm erfordert die Bewältigung von Panik- und Abbruchwiederherstellungen

Sobald die gemeinsam genutzte Wasm-Instanz über einen längeren Zeitraum anfängt, Anrufe anzunehmen, wird der Absturz von einem einzelnen Fehler zu einem Problem bei der Zustandswiederherstellung und der Fehlerisolierung eskalieren.

Wasm lässt sich zunächst gut als Portierungsschicht betrachten: Der Code ist programmierbar, die Seite lauffähig, die Performance ist in Ordnung und die Dinge scheinen in etwa gleich zu sein. Richtig schwierig wird es dann meist, wenn die Demo vorbei ist. Sobald Module wie Editoren, Renderer und Dokumentparser von Einzelseitenexperimenten zu langfristigen residenten Laufzeiten übergehen, werden sich die Fehlermodelle sofort ändern.

Zu diesem Zeitpunkt sind Panik und Abbruch keine Ausnahmezweige mehr in der Sprachebene. Sie entscheiden: ob diese Instanz weiterhin Folgearbeiten empfangen kann, ob der Zustand im Speicher kontaminiert ist, ob die Hostschicht die Instanz sofort verwerfen soll und ob der Instanzpool gefüllt werden muss. Wenn das mobile Team einen Kernel, der lange Zeit in nativen Containern ausgeführt wurde, ins Web verschiebt, wird diese Änderungsebene am häufigsten unterschätzt.

Nachdem die Demo bestanden wurde, hat das Fehlermodell gerade erst begonnen.

Abstürze bei einem einzelnen Anruf sind nicht schwer zu verstehen. Ein Tastenklick löst einen Wasm-Aufruf aus. Wenn dies fehlschlägt, wird ein Fehler für den Vorgang gemeldet. Aktualisieren Sie die Seite und versuchen Sie es erneut. Die Kosten sind noch kontrollierbar.

Das Problem tritt auf, nachdem die Laufzeit mit der Wiederverwendung von Instanzen beginnt. Wenn dieselbe Wasm-Instanz kontinuierlich mehrere Dokumente öffnet, mehrere Runden von Eingabeereignissen empfängt und mehrere JS-Bridge-Aufrufe durchläuft, endet der Einflussbereich von Panik und Abbruch nicht mehr bei der aktuellen Aktion. Ein unvollständiger Fehler kann nachfolgende Anfragen verzögern.

Solche Risiken werden oft nicht am ersten Tag erkannt. In der ersten Phase sehen Sie normalerweise nur vereinzelte Fehlerberichte: gelegentliche Rendering-Fehler, ein bestimmter Export bleibt hängen und ein bestimmtes Dokument befindet sich nach dem Schließen und erneuten Öffnen in einem falschen Zustand. Wenn man weiter nachschaut, werden die Hinweise nach und nach auf das gleiche Phänomen konvergieren: Obwohl der Fehler in einer Aufrufkette auftrat, blieb der Schaden in der gemeinsam genutzten Instanz bestehen.

An diesem Punkt liegt der Fokus der Diskussion nicht mehr auf „Ob der Rust-Code in Panik gerät“, sondern auf „Ob diese Laufzeit qualifiziert ist, um den nächsten Aufruf nach der Panik weiter zu bedienen“.

Panik kann abgefangen werden, Abbruch kann nur Instanzen ändern.

Das Wichtigste, was es in Rust/Wasm zu trennen gilt, sind die beiden Fehlersemantiken Panic und Abort.

Panik bietet auch die Möglichkeit, sich entlang etablierter Grenzen zu entspannen. Solange sich die Bindungsschicht und die Hostschicht im Voraus auf die Wiederherstellungsmethode einigen, kann der aktuelle Aufruf fehlschlagen und auch andere Zustände in der Instanz beibehalten werden. Abbruch ist überhaupt nicht der richtige Weg. Dies bedeutet, dass die aktuelle Ausführung einen nicht wiederherstellbaren Zustand erreicht hat. Wenn Sie weiterhin dieselbe Instanz zum Empfangen von Anfragen verwenden, gehen Sie im Wesentlichen davon aus, dass der Speicher und die Ressourcen nicht zur Hälfte beschädigt werden.

Sobald beides zur Laufzeit vermischt wird, kommt es in der Folgeverarbeitung auf jeden Fall zu Problemen:

  • Swallow-Abbruch als normale Ausnahme, und der Instanzpool wird weiterhin Objekte wiederverwenden, die ihre Glaubwürdigkeit verloren haben.
  • Behandeln Sie alle Paniken so, als müsste die Instanz zerstört werden, da sonst der Durchsatz unnötig reduziert wird – Der JS-Host weiß nur, dass der Aufruf fehlgeschlagen ist, weiß jedoch nicht, ob er es erneut versuchen, die Instanz verlieren oder die aktuelle Sitzung unterbrechen soll

Dies ist auch das Realistischste an der Wasm-Laufzeitzuverlässigkeit: Zuerst muss die Wiederherstellungssemantik definiert werden, bevor anschließende Isolierung und Planung implementiert werden können.

Wenn die Bindungsschicht keine Wiederherstellungssemantik bereitstellt, nimmt die Hostschicht den fehlerhaften Zustand an und akzeptiert weiterhin die Arbeit.

Der gefährlichste Ort für diese Art von Problem liegt nicht im Geschäftscode, sondern in der Bindungsschicht, die anscheinend „bereits behoben“ wurde. Die Hostschicht sieht häufig nur ein ausgelöstes Fehlerobjekt und zeichnet es als normalen Anruffehler auf. Das Protokoll ist vorhanden und die Seite stürzt nicht sofort ab, aber das System hat den fehlerhaften Zustand möglicherweise innerhalb der Instanz belassen.

Was wirklich behoben werden muss, ist nicht nur Try/Catch, sondern die Handhabungsaktionen nach einem Fehler. Eine Logik ähnlich der folgenden hat gerade erst begonnen, in das Zuverlässigkeitsdesign Einzug zu halten:

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

Der Schwerpunkt dieses Codes liegt nicht auf der Syntax, sondern auf einer einfachen Beurteilung: ob der aktuelle Fehler diese Instanz als nicht vertrauenswürdiges Objekt markiert hat. Wenn die Antwort „Ja“ lautet, sollte die Wiederherstellungsaktion nicht beim Auslösen von Fehlern aufhören, sondern bis zur Instanzbeseitigung, Ressourcenrekonstruktion und Anforderungsflussunterbrechung fortfahren.

Solange diese Ebene nicht klar definiert ist, wird es so aussehen, als würde das System Fehler verarbeiten, in Wirklichkeit aber wird eine möglicherweise beschädigte Laufzeit wieder in den Produktionspfad zurückgeführt.

Gemeinsam genutzte Instanzen verstärken das Wiederherstellungsproblem zu einem Problem der Pooling-Strategie

Nachdem Wasm in echte Produkte integriert wurde, gibt es selten nur „eine Instanz, bis die Seite geschlossen wird“. Häufiger sind Instanzpools, Worker-Pools oder Vordergrunddokumente und Hintergrundaufgaben, die sich eine Reihe von Laufzeitressourcen teilen. In dieser Phase werden die Wiederherstellungskosten von Panik und Abbruch die Pooling-Strategie direkt neu schreiben.

Wenn die Instanzinitialisierung teuer ist, tendiert das System natürlich dazu, sie so oft wie möglich wiederzuverwenden. Aber sobald die Wiederverwendung etabliert ist, muss gleichzeitig die Fehlerisolierung verbessert werden:

– Welche Zustände können nur in einem einzigen Anruf aufgehängt werden und werden nach einem Fehlschlag mit dem Anruf verworfen – Welche Caches dürfen über Aufrufe hinweg beibehalten werden und welche Caches müssen vollständig ungültig gemacht werden, sobald ein Abbruch auftritt – Wie werden die Aufgaben in der Warteschlange nach dem Ersetzen der Instanz migriert? Verursacht ein erneuter Versuch zweimal Nebenwirkungen?

Dies sind keine Antworten, die die Sprachebene automatisch sendet. Es handelt sich um Laufzeitdesigns.

Aus diesem Grund ist es leicht, das Problem zu unterschätzen, wenn die Diskussion über die Rust/Wasm-Zuverlässigkeit nur bei der Frage „Kann Panik abgefangen werden?“ aufhört. Was die Wartungskostenlücke wirklich vergrößert, ist die Frage, ob der Instanzpool nach einem Ausfall eine klare Vertrauensgrenze aufrechterhalten kann.

Die anwendbare Grenze hängt stark vom Lebenszyklus ab

Dieser Satz an Restaurierungsentwürfen ist nicht für jedes Wasm-Projekt erforderlich.

Wenn es sich bei dem Modul nur um ein einmaliges Offline-Tool handelt oder die gesamte Instanz bei der Zerstörung der Seite wiederverwendet wird, bleibt der Unterschied zwischen Panik und Abbruch bestehen, der Wiederherstellungsvorteil ist jedoch viel geringer. Oft reicht es aus, die Seite direkt zu aktualisieren und die Aufgabe direkt erneut auszuführen.

Sobald das System die folgenden Merkmale aufweist, ändert sich die Wiederherstellungssemantik schnell von einem „Optimierungselement“ zu einem „Infrastrukturelement“:

  • Die Instanz bleibt für eine lange Zeit bestehen und wird nicht zusammen mit einem einzigen Seitenlebenszyklus zerstört
  • Dieselbe Instanz führt kontinuierlich mehrere Aufrufrunden durch – Die Hosting-Ebene muss Pooling im Austausch für Startzeit und Durchsatz verwenden – Schützen Sie den Sitzungsstatus, den Cache-Status und Aufgaben in der Warteschlange nach einem Fehler

Wenn mobile Teams native Funktionen ins Web verlagern, ist es am wahrscheinlichsten, dass diese Grenze erreicht wird. Die ursprünglich standardmäßig im App-Prozess eingerichtete Isolationsbeziehung musste nach Erreichen der JS/Wasm-Hostgrenze oft erneut ausgefüllt werden.

Wasm erleichtert die Eingabe von nativem Code in den Browser, bringt jedoch keine Laufzeitwiederherstellungssemantik mit sich. Sobald das System beginnt, Instanzen zu teilen, den Status wiederzuverwenden und langfristige Anrufe zu akzeptieren, müssen Panik und Abbruch als zwei verschiedene Laufzeitereignisse behandelt werden. Ersteres kümmert sich darum, wie der aktuelle Anruf beendet wird, und letzteres kümmert sich darum, ob diese Instanz weiterhin im Pool leben kann. Wenn diese Beurteilung nicht zuerst getroffen wird, wird es umso schwieriger, mit nachfolgenden Fehlern umzugehen, je erfolgreicher die Code-Transplantation ist.