-
Notifications
You must be signed in to change notification settings - Fork 10
Description
Problem
InMemoryOrchestrationBackend.purge() deletes the orchestration instance from the store but does not cancel pending setTimeout handles created by processCreateTimerAction(). This causes timer handles to leak, keeping the Node.js event loop alive unnecessarily.
File: packages/durabletask-js/src/testing/in-memory-backend.ts
Lines: purge() (lines 208-221) and processCreateTimerAction() (lines 526-555)
When an orchestration creates a durable timer (e.g., ctx.createTimer(3600)), the backend schedules a setTimeout and tracks the handle in a global pendingTimers set. When the orchestration is later terminated and purged, purge() removes the instance and its state waiters but does not cancel the pending timer handles.
The timer callback does check whether the instance still exists and skips action if it does not, so this is not a correctness bug — but the timer handles remain in the Node.js event loop until they fire, which can be hours or days for long-delay timers.
Note that reset() correctly cancels all timers, but purge() does not.
Root Cause
The pendingTimers set tracks timer handles globally with no per-instance mapping. There is no way for purge() to identify which timers belong to the purged instance, so it cannot cancel them.
Proposed Fix
Add a per-instance timer tracking map (instanceTimers: Map<string, Set<ReturnType<typeof setTimeout>>>) that associates timer handles with their orchestration instance. When purge() is called, cancel all timers for that instance and remove them from both the instance-level and global tracking sets.
Impact
- Severity: Medium — resource leak (timer handles keep event loop alive), not a correctness issue
- Affected scenarios: Any test that creates long-delay timers and then purges the orchestration. In test suites, this can cause Jest to hang with "Jest did not exit" warnings. In long-running test processes, leaked timers accumulate memory.