TypedActor
TypedActor<T> ist der Runtime-Host, der einen Behavior<T>-Wert
nimmt und ausführt. Intern ist es eine Actor<T>-Subklasse —
gleiche Mailbox, gleicher Dispatcher, gleicher Supervisionsbaum —
aber sein onReceive delegiert an das gerade aktive Behavior.
Du konstruierst TypedActor nicht direkt. Die Spawn-Methoden
(system.spawnTyped / ctx.spawnTyped und
typedProps) wickeln es für dich. Aber
zu wissen, wie es funktioniert, klärt die typed-DSL-Semantik —
besonders, was “die Runtime interpretiert das Behavior” tatsächlich
bedeutet.
Wie ein Behavior zu einem Actor wird
Abschnitt betitelt „Wie ein Behavior zu einem Actor wird“import { ActorSystem, Behaviors } from 'actor-ts';
const myBehavior = Behaviors.receive<Msg>((ctx, msg) => { // ... msg behandeln return Behaviors.same;});
const system = ActorSystem.create('demo');const ref = system.spawnTypedAnonymous(myBehavior);// Unter der Haube: system.spawnAnonymous(Props.create(() => new TypedActor(myBehavior)))Das Framework:
- Konstruiert ein
TypedActor<T>mitmyBehaviorals initialem Wert. - In
preStartresolved es das initiale Behavior — läuft durch allesetup/withTimers/withStash/supervise-Wrapper, bis es bei einem Blatt landet (einemreceiveoder einem Sentinel). - Bei jeder Nachricht ruft es den resolved Handler auf. Der Return-Wert wird zum neuen “aktuellen Behavior”.
- Wenn der Return
sameist, ändert sich nichts. Wenn es ein frisches Behavior ist, resolved das Framework das und nimmt es als das neue aktuelle an.
Die Resolution-Loop
Abschnitt betitelt „Die Resolution-Loop“Komponierte Behaviors werden Schicht für Schicht ausgepackt. Gegeben:
const b = Behaviors.supervise( Behaviors.withTimers((timers) => Behaviors.setup((ctx) => Behaviors.receive((ctx, msg) => Behaviors.same)))) .onFailure(strategy);Der Resolver läuft:
supervise(...) ← Supervise-Strategie einfangen, in Kind rekursieren withTimers(...) ← Timer-Scheduler einfangen, in Factory-Return rekursieren setup(...) ← factory(ctx) laufen lassen, in Return rekursieren receive(...) ← Blatt — als `current` speichernJeder Wrapper hat seinen Seiteneffekt einmal (Supervisor
installieren, Timer einfangen, Setup-Factory ausführen) und
verschwindet. Die finale Form ist ein einfacher receive-Wert.
Das Framework merkt sich die Supervise-Strategie und den
Timer-Scheduler über die Lebenszeit des Actors hinweg; die
setup-Factory läuft nur beim ersten Mal und bei Restart.
Ein Wrapper-Zyklus (ein setup, das sich selbst zurückgibt, oder
ein supervise, das rekursiert) ist auf 64 Hops gebunden — danach
wirft der Resolver, was die Fehlkonfiguration laut surfacet, statt
ewig zu drehen.
Übergänge
Abschnitt betitelt „Übergänge“Behaviors.receive<Msg>((ctx, msg) => { if (msg.kind === 'start') return runningBehavior; if (msg.kind === 'stop') return Behaviors.stopped; return Behaviors.same;});Drei Ergebnisse:
Behaviors.same→ derselbe Closure handhabt die nächste Nachricht. Die Runtime hältcurrentunverändert.- Ein anderer
Behavior<T>-Wert → die Runtime resolved ihn und tauscht ihn ein. Wrapper führen ihre Seiteneffekte wieder aus (ein frischessetupläuft, frische Timer werden eingefangen, wennwithTimerserneut eingeführt wird — wichtig für “Phase 1 hatte einen Timer, Phase 2 nicht”-Patterns). Behaviors.stopped→ die Runtime stoppt den Actor.Behaviors.unhandled→ die Nachricht geht zu Dead Letters; das aktuelle Behavior bleibt.
Lifecycle-Signale
Abschnitt betitelt „Lifecycle-Signale“Behaviors.receiveWithSignal<Msg>( (ctx, msg) => Behaviors.same, (ctx, signal) => { if (signal.kind === 'post-stop') ctx.log.info('cleaning up'); if (signal.kind === 'pre-restart') ctx.log.warn(`restarting: ${signal.reason}`); if (signal.kind === 'terminated') ctx.log.info(`${signal.ref.path} stopped`); return Behaviors.same; },);Der Signal-Handler wird aus den postStop /
preRestart-Hooks/Terminated-Message-Delivery-Hooks des Frameworks
aufgerufen. Drei Signale:
| Kind | Ausgelöst durch |
|---|---|
post-stop | Der Actor stoppt (aus irgendeinem Grund: PoisonPill, Behaviors.stopped, Supervisor-Stop). |
pre-restart | Ein Fehler wird gleich neu gestartet. signal.reason ist der Fehler. |
terminated | Ein beobachteter Actor (ctx.watch(ref)) hat gestoppt. signal.ref ist seine Ref. |
Der Return-Wert funktioniert wie beim Receive-Handler — same, um
das aktuelle Behavior zu behalten, ein neues Behavior, um zu
tauschen.
Wo die Nähte sichtbar werden
Abschnitt betitelt „Wo die Nähte sichtbar werden“TypedActor ist die Brücke — und an der Naht zwischen typed und
untyped tauchen ein paar Details auf:
- Fehler in einem typed-Handler erreichen den typed-Supervisor
zuerst. Wenn der Handler in
Behaviors.supervise(...).onFailure(strategy)gewickelt ist, behandelt diese Strategie den Fehler. Wenn nicht, propagiert er an den Supervisor des Parents — wie bei untyped. ctx.spawn(behavior)gibt eine voll typisierteActorRef<U>zurück. Die Runtime wickelt das Kind in einen weiterenTypedActor<U>. Das ist das cast-freie Child-Spawning, das die typed-Form bietet.- Watching ist einseitig. Ein typed-Actor kann eine untyped-Ref
ctx.watch(ref)en oder umgekehrt. Das Terminated-Signal kommt auf der typed-Seite über dasterminated-Signal; auf der untyped-Seite über eineTerminated-Nachricht.
Initial-Behavior-Fallstricke
Abschnitt betitelt „Initial-Behavior-Fallstricke“Das Behavior, das du an system.spawnTypedAnonymous(behavior) übergibst,
wird zum initialen aktuellen. Zwei Grenzfälle:
Behaviors.sameals Initial ist sinnlos — es gibt nichts zu behalten. Die Runtime behandelt es alsBehaviors.empty(stilles No-Op), der Actor existiert also, verwirft aber alle Nachrichten. Das deutet meist auf einen Logik-Bug irgendwo hin.Behaviors.stoppedals Initial stoppt den Actor inpreStart. Die Ref des Actors wird zurückgegeben, aber er terminiert bereits. Nützlich, wenn “soll dieser Actor existieren?” durch eine externe Prüfung zur Spawn-Zeit bestimmt wird.
Wie es weitergeht
Abschnitt betitelt „Wie es weitergeht“- Behaviors — das DSL, das die
Werte produziert, die
TypedActorinterpretiert. - Spawn Typed — die drei
Helfer, die
TypedActorfür den normalen Gebrauch wickeln. - Actor (untyped) — die
Eltern-Klasse, die
TypedActorerweitert. - Supervision — wohin
die
Behaviors.supervise(...).onFailure(...)-Strategie routet.