Problem
TimerScheduler.startTimerWithFixedDelay is implemented as fixed-rate. It delegates to Scheduler.scheduleAtFixedRate, which is a raw setInterval.
The distinction is the entire reason the API exists. Fixed-delay measures the next interval from the end of the previous execution, specifically so that a handler slower than the interval cannot accumulate a backlog. Fixed-rate fires on wall-clock cadence regardless of whether the previous tick was handled. With the current implementation a slow actor's ticks pile into its mailbox — and with the default drop-head bounded mailbox those accumulated ticks then evict the actor's real work.
The call site reads correctly and the JSDoc does not warn, so this fails silently for exactly the workload the API was chosen to protect.
Evidence
src/internal/ActorCell.ts:1238-1249
startTimerWithFixedDelay(
key: string,
message: TMessage,
intervalMs: number,
initialDelayMs: number = intervalMs,
): void {
this.cancel(key);
const handle = this.cell.system.scheduler.scheduleAtFixedRate(
initialDelayMs, intervalMs, this.cell.self, message, null,
);
this.handles.set(key, handle);
}
and the target, which is interval-based:
src/Scheduler.ts:58-60
/** Deliver a message repeatedly at a fixed interval, after an initial delay. */
scheduleAtFixedRate<T>(
src/ActorContext.ts:264 documents the method without noting the discrepancy.
Proposal
Implement real fixed-delay: schedule the next tick from inside the completion of the previous one (scheduleOnce re-armed after the message is handled), rather than from a free-running interval. If that is not wanted, rename the method to match scheduleAtFixedRate — but shipping the fixed-delay name with fixed-rate behaviour will burn someone whose handler occasionally slows down, and the symptom (evicted work) will not point at the timer.
Acceptance sketch
Verification status
Found in the ten-lens production-readiness review of 2026-08-05 (v0.13.0) and re-verified before filing: confirmed by reading the delegation chain at the cited lines. Not reproduced at runtime because the mechanism is a direct call into setInterval.
Part of the production-readiness review batch — tracked in #913.
Problem
TimerScheduler.startTimerWithFixedDelayis implemented as fixed-rate. It delegates toScheduler.scheduleAtFixedRate, which is a rawsetInterval.The distinction is the entire reason the API exists. Fixed-delay measures the next interval from the end of the previous execution, specifically so that a handler slower than the interval cannot accumulate a backlog. Fixed-rate fires on wall-clock cadence regardless of whether the previous tick was handled. With the current implementation a slow actor's ticks pile into its mailbox — and with the default
drop-headbounded mailbox those accumulated ticks then evict the actor's real work.The call site reads correctly and the JSDoc does not warn, so this fails silently for exactly the workload the API was chosen to protect.
Evidence
and the target, which is interval-based:
src/ActorContext.ts:264documents the method without noting the discrepancy.Proposal
Implement real fixed-delay: schedule the next tick from inside the completion of the previous one (
scheduleOncere-armed after the message is handled), rather than from a free-running interval. If that is not wanted, rename the method to matchscheduleAtFixedRate— but shipping the fixed-delay name with fixed-rate behaviour will burn someone whose handler occasionally slows down, and the symptom (evicted work) will not point at the timer.Acceptance sketch
startTimerWithFixedDelayproduces one pending tick at a time, not a backlog.startTimerWithFixedRate(or the existing scheduler method) remains available for callers who genuinely want wall-clock cadence.Verification status
Found in the ten-lens production-readiness review of 2026-08-05 (
v0.13.0) and re-verified before filing: confirmed by reading the delegation chain at the cited lines. Not reproduced at runtime because the mechanism is a direct call intosetInterval.Part of the production-readiness review batch — tracked in #913.