Receive-Timeout
Ein Receive-Timeout ist das actor-gebundene Äquivalent zu “dieses
Ding war zu lange ruhig — mach etwas.” Du setzt eine Dauer; wenn
innerhalb dieses Fensters keine User-Nachricht ankommt, liefert das
Framework eine ReceiveTimeout-System-Nachricht an den Actor.
import { Actor, ReceiveTimeout } from 'actor-ts';
type Msg = { kind: 'activity' } | ReceiveTimeout;
class Session extends Actor<Msg> { override preStart(): void { // 30 s idle → feuere einen ReceiveTimeout. this.context.setReceiveTimeout(30_000); }
override onReceive(msg: Msg): void { if (msg === ReceiveTimeout.instance) { this.log.info('session idle — stopping'); this.context.stopSelf(); return; } // Jede User-Nachricht setzt den Idle-Timer zurück. this.log.info('activity'); }}Drei Dinge, die auffallen:
setReceiveTimeout(ms)armiert den Timer. Nachmsohne User-Nachrichten landetReceiveTimeout.instancein der Mailbox.- Jede User-Nachricht setzt den Timer zurück. Die Uhr beginnt
bei jedem
onReceive-Aufruf von vorne. Aktivität = der Actor ist lebendig; Stille = der Timer zählt herunter. - Der Timeout feuert wiederholt — sobald geliefert, wird der
Timer neu armiert. Ein Actor, der den
ReceiveTimeoutnicht verarbeitet, bekommt sie weiter allems. VerwendecancelReceiveTimeout()odersetReceiveTimeout(0)zum Stoppen.
Die API
Abschnitt betitelt „Die API“interface ActorContext<TMsg> { setReceiveTimeout(ms: number): void; cancelReceiveTimeout(): void;}Zwei Methoden. setReceiveTimeout(0) entspricht
cancelReceiveTimeout() — der Timer ist deaktiviert. Ein positiver
Wert (re-)armiert ihn.
Häufige Muster
Abschnitt betitelt „Häufige Muster“Session-Ablauf
Abschnitt betitelt „Session-Ablauf“class Session extends Actor<SessionMsg | ReceiveTimeout> { constructor(private readonly userId: string) { super(); }
override preStart(): void { this.context.setReceiveTimeout(15 * 60_000); // 15 Minuten }
override onReceive(msg: SessionMsg | ReceiveTimeout): void { if (msg === ReceiveTimeout.instance) { this.log.info(`session ${this.userId} expired`); this.context.stopSelf(); return; } // Verarbeite die Session-Nachricht... }}Ein Per-User-Session-Actor, der sich nach 15 Minuten Inaktivität automatisch stoppt. Wenn der User zurückkommt, spawnt ein frischer Actor; der alte ist weg. Das ist der Brot-und-Butter-Einsatz von Receive-Timeouts.
Passivierung in sharded Entities
Abschnitt betitelt „Passivierung in sharded Entities“class Entity extends Actor<EntityMsg | ReceiveTimeout> { override preStart(): void { this.context.setReceiveTimeout(2 * 60_000); // 2 Minuten idle }
override onReceive(msg: EntityMsg | ReceiveTimeout): void { if (msg === ReceiveTimeout.instance) { // Sag der Shard-Region, dass wir fertig sind — sie stoppt uns sauber. this.context.parent.forEach((p) => p.tell({ kind: 'passivate', entityId: this.id })); return; } // ... }}Cluster-Sharding paart sich natürlich mit Receive-Timeouts: eine Entität, die einige Zeit untätig war, signalisiert ihrer Region, sie zu passivieren, was Memory freigibt. Wenn eine neue Nachricht für dieselbe Entity-ID ankommt, spawnt die Region die Entität aus ihrem persistierten Zustand erneut.
Siehe Sharding für das volle Passivierungs-Protokoll.
Watchdog für Downstream-Stille
Abschnitt betitelt „Watchdog für Downstream-Stille“class Heartbeat extends Actor<...> { override preStart(): void { // Wir erwarten, dass das Upstream mindestens alle 30 s pusht. this.context.setReceiveTimeout(30_000); this.upstream.tell({ kind: 'subscribe', subscriber: this.self }); }
override onReceive(msg): void { if (msg === ReceiveTimeout.instance) { this.log.warn('upstream silent — re-subscribing'); this.upstream.tell({ kind: 'subscribe', subscriber: this.self }); // Nicht stoppen — weiter versuchen. return; } // Verarbeite den Heartbeat... }}Erkennen, dass “das Upstream hat aufgehört, Events zu senden” ohne
expliziten ping von dieser Seite. Der Receive-Timeout ist die
Abwesenheit von Traffic — er feuert genau dann, wenn es sonst
nichts gibt, worauf reagiert werden müsste.
Interaktion mit system-Nachrichten
Abschnitt betitelt „Interaktion mit system-Nachrichten“Der Timer zählt nur User-Nachrichten. System-Nachrichten
(PoisonPill, Kill, Supervisor-Signale, interne Events) setzen
ihn nicht zurück. Das ist wichtig, weil:
- Ein Actor mit einem langlaufenden System-Nachrichten-Stream (z.B.
Terminatedvon vielen beobachteten Kindern empfangen) löst immer noch Receive-Timeouts aus, wenn keine User-Nachrichten ankommen. - Ebenso bekommt ein ruhiger Actor, der von außen einen
PoisonPillempfängt, keinen “letzten Receive-Timeout” vor dem Stoppen — der PoisonPill triggert den normalen Shutdown, der Receive-Timeout-Zustand wird aufgeräumt.
Interaktion mit become
Abschnitt betitelt „Interaktion mit become“Verhaltenswechsel mit context.become(...) betrifft den
Receive-Timeout nicht. Das gleiche ms-Fenster gilt für
welchen Handler auch immer gerade aktiv ist. Zwei Phasen eines
Actors teilen sich einen Timeout — armiere explizit in become neu,
wenn du phasen-spezifische Dauern willst:
private idle = (msg) => { if (msg.kind === 'start') { this.context.become(this.working); this.context.setReceiveTimeout(60_000); // 1 min idle im Working-Mode }};
private working = (msg) => { if (msg === ReceiveTimeout.instance) { this.context.become(this.idle); this.context.setReceiveTimeout(0); // kein Timeout im Idle-Mode }};ReceiveTimeout-Nachrichten und die Mailbox
Abschnitt betitelt „ReceiveTimeout-Nachrichten und die Mailbox“ReceiveTimeout.instance wird über denselben Pfad wie jede andere
User-Nachricht ausgeliefert — sie wird enqueued, dispatched, an
onReceive übergeben. Drei Nebeneffekte:
- Eine gestaute Mailbox verzögert Receive-Timeouts. Wenn der
Actor 100 User-Nachrichten in der Queue hat und sie langsam
verarbeitet, kann der
ReceiveTimeoutaus einer früheren ruhigen Phase nach einigen dieser Nachrichten landen — wodurch der Actor nicht mehr untätig ist. Das ist meist okay (ein beschäftigter Actor ist per Definition nicht untätig), aber das Timing ist nicht garantiert. ReceiveTimeout.instanceist ein Singleton, Identitäts-Vergleich (msg === ReceiveTimeout.instance) ist also zuverlässig.- Restart setzt den Timer zurück. Bei Restart startet die neue
Instanz ohne konfigurierten Receive-Timeout; rufe
setReceiveTimeoutinpreStart(oderpostRestart) auf, um neu zu armieren.
Wie es weitergeht
Abschnitt betitelt „Wie es weitergeht“- Timer und Scheduling — für geplante Feuerungen; Receive-Timeout ist für Idle-Detection.
- Sharding — Remember Entities — Receive-Timeout-getriebene Passivierung in Cluster-Setups.
- Actor — die Basisklasse,
deren
context.setReceiveTimeoutdu aufrufst. - Mailboxes — wo ReceiveTimeout-Nachrichten landen.
Die ActorContext.setReceiveTimeout-
und ReceiveTimeout-API-Referenzen
decken die vollen Signaturen ab.