Skip to content

Commit 4dc8950

Browse files
evantahlerclaude
andcommitted
Add tests for fire-and-forget error handling during shutdown
Verify that Worker and Scheduler emit errors via the "error" event instead of causing unhandled promise rejections when Redis commands fail during ping/poll timer callbacks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7776183 commit 4dc8950

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

__tests__/core/scheduler.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,36 @@ describe("scheduler", () => {
8787
await queue.end();
8888
});
8989

90+
test("emits error instead of unhandled rejection when poll fails", async () => {
91+
const testScheduler = new Scheduler({
92+
connection: specHelper.cleanConnectionDetails(),
93+
timeout: specHelper.timeout,
94+
});
95+
await testScheduler.connect();
96+
await testScheduler.start();
97+
98+
const errorPromise = new Promise<Error>((resolve) => {
99+
testScheduler.on("error", (err) => resolve(err));
100+
});
101+
102+
// stub redis.set to simulate a closed connection during tryForLeader
103+
const originalSet = testScheduler.connection.redis.set.bind(
104+
testScheduler.connection.redis,
105+
);
106+
const redisError = new Error("Connection is closed");
107+
testScheduler.connection.redis.set = async () => {
108+
throw redisError;
109+
};
110+
111+
// wait for pollAgainLater to fire poll() which will fail
112+
const emittedError = await errorPromise;
113+
expect(emittedError).toBe(redisError);
114+
115+
// restore and clean up
116+
testScheduler.connection.redis.set = originalSet;
117+
await testScheduler.end();
118+
});
119+
90120
test("queues can see who the leader is", async () => {
91121
await scheduler.poll();
92122
const leader = await queue.leader();

__tests__/core/worker.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,3 +358,39 @@ describe("worker", () => {
358358
});
359359
});
360360
});
361+
362+
describe("worker error handling during shutdown", () => {
363+
test("emits error instead of unhandled rejection when ping fails", async () => {
364+
const testWorker = new Worker(
365+
{
366+
connection: specHelper.cleanConnectionDetails(),
367+
timeout: specHelper.timeout,
368+
queues: ["shutdown_test_queue"],
369+
},
370+
jobs,
371+
);
372+
await testWorker.connect();
373+
await testWorker.start();
374+
375+
const errorPromise = new Promise<Error>((resolve) => {
376+
testWorker.on("error", (err) => resolve(err));
377+
});
378+
379+
// stub redis.set to simulate a closed connection during ping
380+
const originalSet = testWorker.connection.redis.set.bind(
381+
testWorker.connection.redis,
382+
);
383+
const redisError = new Error("Connection is closed");
384+
testWorker.connection.redis.set = async () => {
385+
throw redisError;
386+
};
387+
388+
// wait for the ping interval to fire and fail
389+
const emittedError = await errorPromise;
390+
expect(emittedError).toBe(redisError);
391+
392+
// restore and clean up
393+
testWorker.connection.redis.set = originalSet;
394+
await testWorker.end();
395+
});
396+
});

0 commit comments

Comments
 (0)