Resource Management & Lifecycle
Because Ticker execution loops are driven by asynchronous background timers (setTimeout / setImmediate), proper resource management is essential for preventing memory leaks, test suite hangs, and unhandled timer handles.
1. Explicit Resource Management (using & await using)
Tempo Ticker implements the standard TypeScript 5.2+ / ECMAScript Explicit Resource Management protocol (Symbol.dispose and Symbol.asyncDispose).
Synchronous Disposer (using)
Ideal for callback-driven Tickers. The timer is automatically cleared the moment execution exits the block:
import { Tempo } from '@magmacomputing/tempo';
import '@magmacomputing/tempo-plugin-ticker/install';
{
using ticker = Tempo.ticker(1, (t) => {
updateUI(t);
});
// Perform operations within block...
} // ⚡ Ticker is automatically stopped and timers cleared hereAsynchronous Disposer (await using)
Ideal for for await streaming loops:
{
await using stream = Tempo.ticker({ seconds: 1 });
for await (const t of stream) {
console.log('Pulse:', t.format('{hh}:{mi}:{ss}'));
if (shouldStop) break; // Exiting loop automatically disposes the ticker
}
} // ⚡ Generator finalized and timer unreferenced2. Async Generator Streaming (for await ... of)
Every Ticker instance implements Symbol.asyncIterator, allowing you to consume pulses as a first-class async stream:
const ticker = Tempo.ticker({ seconds: 2 });
async function processStream() {
for await (const t of ticker) {
console.log(`Stream tick: ${t.iso}`);
}
}When you break from a for await loop or call ticker.stop(), pending promises are immediately resolved, allowing the loop to exit cleanly without dangling promises.
3. Programmatic Lifecycle Controls
If you are managing long-lived tickers across components, you can use the object's control methods:
const ticker = Tempo.ticker({ seconds: 1 });
// 1. Check live status and telemetry
console.log(ticker.info);
// { next: Tempo, ticks: 0, limit: undefined, interval: { seconds: 1 }, stopped: false }
// 2. Manually advance or trigger a pulse (e.g., from a user UI event)
ticker.pulse();
// 3. Stop execution and tear down timers
ticker.stop();
console.log(ticker.info.stopped); // true4. Bounded Termination Conditions
Prevent runaway processes by defining built-in stopping criteria:
A. Pulse Count Limit (limit)
Stops automatically after emitting
// Stops after exactly 5 pulses
using ticker = Tempo.ticker({ seconds: 1, limit: 5 }, (t) => {
console.log(`Pulse #${t.info?.ticks}`);
});
// Immediate zero-execution limit (strictly honored without pulsing)
using zeroTicker = Tempo.ticker({ limit: 0 }, (t) => console.log(t));B. Stop Boundary (until)
Stops when simulated or wall-clock time reaches an upper boundary (inclusive):
using boundedTicker = Tempo.ticker({
seconds: 10,
seed: '2026-12-25T10:00:00Z',
until: '2026-12-25T12:00:00Z' // Ceases when reaching 12:00:00
}, (t) => {
console.log(t.format('{hh}:{mi}'));
});5. Dual-Layer Lifecycle Architecture & GC Safety-Net
Tempo Ticker features an advanced Dual-Layer Lifecycle Model ensuring both deterministic resource teardown and automatic background cleanup:
┌────────────────────────────────────────────────────────────────────────┐
│ Tempo Ticker Lifecycle │
├───────────────────────────────────┬────────────────────────────────────┤
│ 1. Deterministic Layer (Explicit) │ 2. Safety-Net Layer (Automatic) │
│ • `using` / `await using` │ • `FinalizationRegistry` hooks │
│ • `ticker.stop()` │ • `WeakRef` Active Registry │
│ • Immediate microsecond cleanup │ • GC-driven background reclaim │
└───────────────────────────────────┴────────────────────────────────────┘Layer 1: Deterministic Cleanup (using & .stop())
In unit tests and latency-sensitive code paths, explicit disposal guarantees immediate teardown before subsequent assertions run:
// ✅ RECOMMENDED: Immediate, deterministic cleanup
{
using ticker = Tempo.ticker(1, (t) => { ... });
}
// ✅ EXPLICIT: Try...finally for cross-scope handles
let ticker;
try {
ticker = Tempo.ticker(1, (t) => { ... });
// assertions...
} finally {
ticker?.stop();
}Layer 2: Best-Effort GC Finalization (FinalizationRegistry)
While explicit teardown with .stop() or using is the required approach for deterministic resource management, Tempo provides a best-effort GC finalizer as a background safety net:
- Safety-Net Teardown: Active
setTimeout/setIntervaltimer loops are scheduled for cleanup if and when the JavaScript engine reclaims an unreferenced Ticker handle. - Weak Registry:
Tempo.tickersqueries active handles viaWeakRef—unreferenced tickers are not kept alive in memory solely by the registry. - Leak Mitigation: Provides defense-in-depth against orphaned timer handles in long-running processes when handles are dropped inadvertently.
NOTE
Garbage Collection timing is non-deterministic and never guaranteed by ECMAScript runtimes. Always call .stop(), using, or await using to ensure prompt, deterministic release of timers and event listeners.