Zum Inhalt springen
Deutsch

InMemoryLease

InMemoryLease ist die Dev-/Test-Implementierung des Lease-Interfaces. Sie hält den Lease-State im Prozessspeicher — mehrere InMemoryLease-Instanzen, die denselben name teilen, schließen sich also korrekt aus, aber nur innerhalb eines Prozesses.

import { InMemoryLease, LeaseOptions } from 'actor-ts/coordination';
const leaseOptions = LeaseOptions.create()
.withName('my-singleton')
.withOwner('instance-1')
.withTtlMs(30_000);
const lease = new InMemoryLease(
leaseOptions,
);
await lease.acquire(); // → true (erstes Acquire)
  • Unit Tests für Actors, die einen Lease-Parameter bekommen — übergib InMemoryLease, um Lease-Verhalten ohne echtes Backend zu verifizieren.
  • Dev für Code, der einen Lease braucht, aber du willst Kubernetes oder etcd nicht lokal aufsetzen.
  • MultiNodeSpec-Tests — jeder “Node” läuft in einem Prozess, also schließt sich ein über die Test-Nodes geteiltes InMemoryLease korrekt aus.

Jedes InMemoryLease in einem Prozess konkurriert gegen einen modul-globalen Store (inMemoryLeaseStore) — es gibt kein Registry pro Instanz zu verdrahten. Zwei InMemoryLease-Instanzen, die denselben name teilen, schließen sich also automatisch aus:

const leaseOptions = LeaseOptions.create()
.withName('singleton-x')
.withOwner('node-a')
.withTtlMs(30_000);
const leaseA = new InMemoryLease(
leaseOptions,
);
const lease2Options = LeaseOptions.create()
.withName('singleton-x') // gleicher Name
.withOwner('node-b')
.withTtlMs(30_000);
const leaseB = new InMemoryLease(
lease2Options,
);
await leaseA.acquire(); // → true
await leaseB.acquire(); // → false (leaseA hält ihn)

Das ist das MultiNodeSpec-Muster — jeder simulierte Node bekommt ein InMemoryLease für denselben name, und sie kämpfen um den Lease, wie es echte verteilte Peers tun würden.

Da der Store ein einzelnes modul-globales Objekt ist, gilt der gegenseitige Ausschluss nur innerhalb eines Prozesses — separate Prozesse bekommen jeweils ihren eigenen Store und sehen die Leases der anderen nicht.

Die Implementierung:

  • acquire() CAS-t den Registry-Slot atomar. Gibt true zurück, wenn sie ihn beansprucht hat; false, wenn ein anderer Owner ihn hält.
  • TTL-Ablauf — wenn der Halter nicht innerhalb von ttlMs renewt, gibt das Registry automatisch frei (eine setTimeout-getriebene Bereinigung).
  • onLost feuert, wenn ein anderer Halter übernimmt (via TTL-Ablauf-Mechanismus) oder wenn das Registry forciert geleert wird.
  • Renewal läuft auf einem setInterval zum renewalIntervalMs (Default ttl / 3).

InMemoryLease nutzt echte Timer (setInterval) und bietet keinen Scheduler-Injektionspunkt — es gibt keinen ManualScheduler, den man einhängen könnte. Für deterministische Lease-Timing-Tests mocke Date.now(); um geteilten Lease-State zwischen Tests zurückzusetzen, leere das exportierte Singleton inMemoryLeaseStore direkt (inMemoryLeaseStore._clear()).

import { describe, it, expect } from 'bun:test';
import { TestKit } from 'actor-ts/testkit';
import { InMemoryLease, LeaseOptions } from 'actor-ts/coordination';
import { StartSingletonOptions } from 'actor-ts';
describe('SingletonManager mit Lease', () => {
it('nur ein Halter spawnt den Singleton', async () => {
const tk1 = TestKit.create('node-1');
const tk2 = TestKit.create('node-2');
const leaseOptions = LeaseOptions.create()
.withName('singleton')
.withOwner('n1')
.withTtlMs(30_000);
const lease1 = new InMemoryLease(
leaseOptions,
);
const lease2Options = LeaseOptions.create()
.withName('singleton')
.withOwner('n2')
.withTtlMs(30_000);
const lease2 = new InMemoryLease(
lease2Options,
);
// (Cluster-Verdrahtung + singletonActor ausgelassen)
const singleton1Options = StartSingletonOptions.create()
.withTypeName('s')
.withActor(singletonActor)
.withLease(lease1);
cluster1.singleton.start(singleton1Options);
const singleton2Options = StartSingletonOptions.create()
.withTypeName('s')
.withActor(singletonActor)
.withLease(lease2);
cluster2.singleton.start(singleton2Options);
// Nur einer sollte je das Singleton-Actor-Child haben.
// ... per Probes asserten ...
});
});

Diese Art Test verifiziert die Lease-Integration, ohne von einer echten K8s-API abzuhängen.