© Excellent backgrounds/Shutterstock.com
Teil 2: Fehlerbehandlung von Aktoren

Aktoren für Fortgeschrittene


Aufbauend auf dem Grundlagenartikel zu Akka und dem Aktorenmodell, geht es diesmal um weiterführende Konzepte. Wir schauen uns an, was passiert, wenn eine auf Aktoren setzende Anwendung mit der realen Welt konfrontiert wird – einer Welt, in der stets Fehler auftreten können und überall blockierende Schnittstellen lauern.

In komplexen Softwaresystemen, insbesondere wenn die Geschäftslogik über viele Komponenten oder gar Rechnergrenzen hinweg verteilt ist, kann immer etwas schiefgehen. Java setzt zur Behandlung von Fehlern auf Exceptions. Dieses Konzept lässt sich bei asynchroner Verarbeitung aber nur begrenzt nutzen. Bei Fehlern, die in einem nebenläufigen Prozess auftreten, ist es meist unklar, wie oder wann die Exception an den ursprünglichen Aufrufer weitergeleitet werden kann. Deshalb führt das Aktorenmodell sein eigenes Konzept zur Fehlerbehandlung ein.

Bevor wir darauf näher eingehen, lassen Sie uns ein kleines Experiment machen. Der vorherige Teil dieser Serie [1] enthielt eine Anwendung zum Lösen von Sudokus. Falls Sie diesen Teil verpasst haben, können Sie den Beispielcode unter [2] einsehen. Listing 1 zeigt den Aktor zum Prüfen einer Sudoku-Definition. Offenbar hat der Aktor ein Problem mit Nachrichten, die statt eines SudokuDef-Objekts eine Null-Referenz enthalten. In diesem Fall wird eine NullPointerException generiert. Was passiert nun, wenn das Testprogramm für den Sudoku-Service so modifiziert wird, dass es zu einem bestimmten Prozentsatz derartige Nachrichten produziert?

Listing 1: „SudokuCheckActor.scala“

class SudokuCheckActor extends Actor { override def receive: Receive = { case CheckSudoku(sudokuDef) => sender ! CheckSudokuResponse(sudokuDef, Sudokus.checkSudoku(sudokuDef)) } }

Ein solcher Programmablauf führt zunächst zu einer Reihe von Logausgaben für die aufgetretenen Exceptions. Danach treten Time-outs für Nachrichten auf, die nicht in der erwarteten Zeit beantwortet wurden. Eine nähere Analyse ergibt, dass die Time-outs mit den Exceptions korrelieren. Alle anderen Nachrichten wurden korrekt verarbeitet. Das ist im Grunde genommen ein annehmbares Ergebnis. Immerhin wurde das System durch die Exceptions – die ja letztendlich auf einen Programmierfehler zurückzuführen sind – nicht aus dem Tritt gebracht.

Die Erklärung für das beobachtete Verhalten hängt mit der speziellen Strategie zur Fehlerbehandlung von Aktoren zusammen. Hier gilt nämlich die Devise „Eltern haften für ihre Kinder“. Das Framework fängt nicht behandelte Exceptions ab, die in einem Aktor auftreten. Danach fragt es den Vateraktor, wie mit dieser Situation umzugehen ist. Zu diesem Zweck besitzt jeder Aktor eine sogenannte Supervisorstrategie [3]. Das ist eine Funktion, die als Eingabeparameter eine Exception erhält und als Ergebnis eine Konstante liefert, die über das Schicksal des betroffenen Kindaktors entscheidet. Die möglichen Optionen sind in Tabelle 1 zusammengefasst.

Option

Erläuterung

Anwendung

Resume

Die Instanz des Kindaktors bleibt unverändert. Die Verarbeitung wird mit der nächsten Nachricht aus der Mailbox fortgesetzt.

Sinnvoll bei Aktoren, die keinen eigenen Zustand haben.

Restart

Die alte Instanz des Kindaktors wird abgebaut und eine neue angelegt. Die Aktorreferenz bleibt weiterhin gültig. Auch der Inhalt der Mailbox bleibt erhalten – mit Ausnahme der Nachricht, die zu dem Fehler führte.

Durch die Exception kann der interne Zustand des Aktors korrumpiert sein. Nach dem Neustart kann er seine Verarbeitung wieder aufnehmen. Allerdings sind dadurch alle Daten verloren.

Stop

Der Kindaktor wird beendet.

Nach dem Fehler kann der Aktor nicht mehr sinnvoll genutzt werden oder die von ihm implementierte Aktion ist obsolet.

Escalate

Der Vateraktor kann keine Entscheidung treffen. Der Fehler wird an dessen Vater eskaliert und gegebenenfalls rekursiv weiter.

Auf diese Weise kann die Behandlung bestimmter Fehler auf verschiedene Ebenen der Aktorenhierarchie verteilt werden.

Tabelle 1: Rückgabewerte der Supervisorstrategie

Dieser Ansatz zur Fehlerbehandlung impliziert, dass jeder Aktor einen Vater und damit einen übergeordneten Supervisor besitzt. Das ist auch tatsächlich der Fall. Aktoren, die von außen – über die actorOf-Funktion des Aktorensystems – erzeugt wurden, sind einem speziellen Aktor, dem sogenannten Useraktor, zugeordnet. Dieser spannt die Hierarchie aller Aktoren der Clientanwendung auf. Weitere Ebenen ergeben sich durch die Vater-Kind-Beziehungen der Aktorimplementierungen. Abbildung 1 zeigt die Hierarchie der Aktoren für die Anwendung zum Lösen von Sudokus und damit gleichzeitig die Supervisor-Relationen. Die Darstellung ist etwas vereinfacht. Tatsächlich gibt es noch einen übergeordneten Root-Aktor und einen Systemaktor, die aber für unsere Betrachtung nicht relevant sind. Routeraktoren sind ein Sonderfall: Da sie nur stellvertretend für ihre Kindaktoren stehen, behandeln sie auftretende Fehler nicht selbst, sondern eskalieren immer nach oben.

heger_akka_1.tif_fmt1.jpgAbb. 1: Hierarchie der Aktoren in der Beispielanwendung

Wenn ein Aktor keine eigene Fehlerbehandlung definiert, ist der Standard ein Restart. Ein von einem Fehler betroffener Kindaktor wird mitsamt dessen Kindern – soweit vorhanden – neu gestartet. Der Neustart erfolgt dabei so, dass er von außen nicht sichtbar ist – sieht man einmal davon ab, dass die den Fehler verursachende Nachricht nicht beantwortet wird. Dieses Standardv...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang