Summary
While investigating gradual worker heap growth, I found a potential memory retention issue in PostgresMessageQueue.listen() under delayed retry traffic.
Why this may matter
If delayed messages are frequent, retained timeout handles can accumulate in memory and contribute to gradual heap growth in long-running workers.
Suspected code path
In packages/postgres/src/mq.ts, delayed notifications call timeouts.add(setTimeout(serializedPoll, durationMs)), but the handle is not removed from timeouts after the callback runs.
const timeouts = new Set<ReturnType<typeof setTimeout>>();
const listen = await this.#sql.listen(
this.#channelName,
async (delay) => {
const durationMs = Temporal.Duration.from(delay).total("millisecond");
if (durationMs < 1) await serializedPoll();
else timeouts.add(setTimeout(serializedPoll, durationMs));
},
serializedPoll,
);
signal?.addEventListener("abort", () => {
listen.unlisten();
for (const timeout of timeouts) clearTimeout(timeout);
});
Expected behavior
Executed timeout handles should be removed from the tracking set so the set size does not grow monotonically during normal operation.
Suggested fix
Delete each handle when it fires and optionally clear the set during abort cleanup.
const timeout = setTimeout(() => {
timeouts.delete(timeout);
void serializedPoll();
}, durationMs);
timeouts.add(timeout);
Additional context
This was observed while investigating production-like worker memory growth in fedify-dev/hollo#365, so that issue is a related report rather than direct proof of root cause.
Summary
While investigating gradual worker heap growth, I found a potential memory retention issue in
PostgresMessageQueue.listen()under delayed retry traffic.Why this may matter
If delayed messages are frequent, retained timeout handles can accumulate in memory and contribute to gradual heap growth in long-running workers.
Suspected code path
In
packages/postgres/src/mq.ts, delayed notifications calltimeouts.add(setTimeout(serializedPoll, durationMs)), but the handle is not removed fromtimeoutsafter the callback runs.Expected behavior
Executed timeout handles should be removed from the tracking set so the set size does not grow monotonically during normal operation.
Suggested fix
Delete each handle when it fires and optionally clear the set during abort cleanup.
Additional context
This was observed while investigating production-like worker memory growth in fedify-dev/hollo#365, so that issue is a related report rather than direct proof of root cause.