Skip to content

Commit a00722e

Browse files
prevent duplicate dlq messages when afterDlq throws after successful publish (#76)
1 parent e0dee3f commit a00722e

2 files changed

Lines changed: 48 additions & 11 deletions

File tree

test/base-queue-handler.test.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ describe('Test baseQueueHandler', function () {
183183
DemoHandler.prototype.afterDlq = originalAfterDlq;
184184
});
185185

186-
it('should add raw buffer to dlq when afterDlq throws to prevent double-encoding', async function () {
186+
it('should not duplicate dlq message when afterDlq throws after successful publish', async function () {
187187
const handler = new DemoHandler(this.name, rabbit, {
188-
retries: 2,
188+
retries: 1,
189189
retryDelay: 10
190190
});
191191
await handler.created;
@@ -207,19 +207,49 @@ describe('Test baseQueueHandler', function () {
207207

208208
await rabbit.publish(this.name, { test: 'data' }, { correlationId: '3' });
209209
const publish = sandbox.spy(handler.rabbit, 'publish');
210-
await new Promise(resolve => setTimeout(resolve, 300));
210+
await new Promise(resolve => setTimeout(resolve, 100));
211211

212212
afterDlq.calledOnce.should.be.true();
213+
publish.calledOnce.should.be.true();
214+
dlqMessages.length.should.equal(1);
215+
dlqMessages[0].event.should.eql({ test: 'data' });
216+
});
217+
218+
it('should publish raw buffer to dlq when first publish fails', async function () {
219+
const handler = new DemoHandler(this.name, rabbit, {
220+
retries: 1,
221+
retryDelay: 10
222+
});
223+
await handler.created;
224+
handler.handle = sandbox.spy(() => {
225+
throw new Error('test error');
226+
});
227+
const afterDlq = (handler.afterDlq = sandbox.spy());
228+
229+
const dlqMessages: any[] = [];
230+
await rabbit.subscribe(this.name + '_dlq', (msg, ack) => {
231+
dlqMessages.push({ event: JSON.parse(msg.content.toString()), content: msg.content });
232+
ack(null);
233+
});
234+
235+
await rabbit.publish(this.name, { test: 'data' }, { correlationId: '5' });
236+
const originalPublish = handler.rabbit.publish.bind(handler.rabbit);
237+
let publishCallCount = 0;
238+
const publish = sandbox.stub(handler.rabbit, 'publish').callsFake((...args) => {
239+
publishCallCount++;
240+
if (publishCallCount === 1) {
241+
return Promise.reject(new Error('broker rejected'));
242+
}
243+
return originalPublish(...args);
244+
});
245+
await new Promise(resolve => setTimeout(resolve, 100));
246+
213247
publish.calledTwice.should.be.true();
214-
const fallbackPayload = publish.args[publish.callCount - 1][1];
248+
const fallbackPayload = publish.args[1][1];
215249
Buffer.isBuffer(fallbackPayload).should.be.true();
216250
fallbackPayload.toString().should.eql('{"test":"data"}');
217-
218-
// TODO: This flow also produces duplicate messages on the DLQ,
219-
// to be investigated and handled on the next PR
220-
dlqMessages.length.should.equal(2);
251+
dlqMessages.length.should.equal(1);
221252
dlqMessages[0].event.should.eql({ test: 'data' });
222-
dlqMessages[1].event.should.eql({ test: 'data' });
223253
});
224254

225255
it('should add to dlq after x retries and get error response even if afterDlq throws error', async function () {

ts/base-queue-handler.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,15 +183,22 @@ abstract class BaseQueueHandler {
183183

184184
async addToDLQ(retries, msg: amqp.Message, ack) {
185185
const correlationId = this.getCorrelationId(msg);
186+
let dlqPublished = false;
187+
186188
try {
187189
const event = decode(msg);
188190
this.logger.warn(`[${correlationId}] Adding to dlq: ${this.dlqName} after ${retries} retries`);
189191
await this.rabbit.publish(this.dlqName, event, msg.properties);
192+
dlqPublished = true;
190193
const response = await this.afterDlq({ msg, event });
191194
ack(msg.properties.headers.errors.message, response);
192195
} catch (err) {
193-
this.logger.error(`[${correlationId}] Failed to add to dlq: ${this.dlqName}`, err);
194-
await this.rabbit.publish(this.dlqName, msg.content, msg.properties);
196+
if (dlqPublished) {
197+
this.logger.error(`[${correlationId}] afterDlq failed after publishing to dlq: ${this.dlqName}`, err);
198+
} else {
199+
this.logger.error(`[${correlationId}] Failed to add to dlq: ${this.dlqName}`, err);
200+
await this.rabbit.publish(this.dlqName, msg.content, msg.properties);
201+
}
195202
ack(err.message, null);
196203
}
197204
}

0 commit comments

Comments
 (0)