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 } from 'actor-ts/coordination';
const lease = new InMemoryLease({
name: 'my-singleton',
owner: 'instance-1',
ttlMs: 30_000,
});
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.

Um denselben Lease über mehrere InMemoryLease-Instanzen innerhalb eines Prozesses zu nutzen, müssen sie das Registry teilen:

import { InMemoryLeaseRegistry } from 'actor-ts/coordination';
const registry = new InMemoryLeaseRegistry();
const leaseA = new InMemoryLease({
name: 'singleton-x',
owner: 'node-a',
ttlMs: 30_000,
registry,
});
const leaseB = new InMemoryLease({
name: 'singleton-x', // gleicher Name
owner: 'node-b',
ttlMs: 30_000,
registry, // gleiches Registry → schließen sich aus
});
await leaseA.acquire(); // → true
await leaseB.acquire(); // → false (leaseA hält ihn)

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

Ohne explizites registry bekommt jedes InMemoryLease sein eigenes privates Registry — effektiv kein gegenseitiger Ausschluss.

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).

Für deterministische Tests willst du vielleicht einen ManualScheduler in das Registry injizieren — das InMemoryLease des Frameworks nutzt aber aktuell echte Timer. Für sehr-deterministische Lease-Timing-Tests mocke Date.now() und manipuliere das Registry direkt.

import { describe, it, expect } from 'bun:test';
import { TestKit, InMemoryLease, InMemoryLeaseRegistry } from 'actor-ts/testkit';
import { ClusterSingletonManager, Props } 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 registry = new InMemoryLeaseRegistry();
const lease1 = new InMemoryLease({
name: 'singleton', owner: 'n1', ttlMs: 30_000, registry,
});
const lease2 = new InMemoryLease({
name: 'singleton', owner: 'n2', ttlMs: 30_000, registry,
});
// (Cluster-Verdrahtung ausgelassen)
tk1.system.spawn(
ClusterSingletonManager.props({
cluster: cluster1, typeName: 's', singletonProps: ..., lease: lease1,
}),
'singleton-manager-s',
);
tk2.system.spawn(
ClusterSingletonManager.props({
cluster: cluster2, typeName: 's', singletonProps: ..., lease: lease2,
}),
'singleton-manager-s',
);
// 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.