Skip to content

Commit e0dee3f

Browse files
PROD-77638 - Prevent double-stringify of DLQ messages on catch path (#75)
* Prevent double-stringify of DLQ messages
1 parent f81db3a commit e0dee3f

4 files changed

Lines changed: 35 additions & 15 deletions

File tree

test/base-queue-handler.test.ts

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

186-
it('should add string to dlq because afterDlq throws error', async function () {
187-
sandbox.useFakeTimers();
186+
it('should add raw buffer to dlq when afterDlq throws to prevent double-encoding', async function () {
188187
const handler = new DemoHandler(this.name, rabbit, {
189188
retries: 2,
190-
retryDelay: 100
189+
retryDelay: 10
191190
});
191+
await handler.created;
192192
handler.handle = sandbox.spy(() => {
193193
throw new Error('test error');
194194
});
@@ -198,18 +198,28 @@ describe('Test baseQueueHandler', function () {
198198
throw new Error('test error');
199199
}
200200
}));
201+
202+
const dlqMessages: any[] = [];
203+
await rabbit.subscribe(this.name + '_dlq', (msg, ack) => {
204+
dlqMessages.push({ event: JSON.parse(msg.content.toString()), content: msg.content });
205+
ack(null);
206+
});
207+
201208
await rabbit.publish(this.name, { test: 'data' }, { correlationId: '3' });
202-
const publish = (handler.rabbit.publish = sandbox.spy(handler.rabbit, 'publish'));
203-
sandbox.clock.tick(100);
204-
await rabbit.connected;
205-
sandbox.clock.tick(100);
206-
await rabbit.connected;
207-
sandbox.clock.tick(100);
208-
sandbox.clock.restore();
209-
await new Promise(resolve => setTimeout(resolve, 400));
209+
const publish = sandbox.spy(handler.rabbit, 'publish');
210+
await new Promise(resolve => setTimeout(resolve, 300));
211+
210212
afterDlq.calledOnce.should.be.true();
211213
publish.calledTwice.should.be.true();
212-
publish.args[publish.callCount - 1][1].should.eql('{"test":"data"}');
214+
const fallbackPayload = publish.args[publish.callCount - 1][1];
215+
Buffer.isBuffer(fallbackPayload).should.be.true();
216+
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);
221+
dlqMessages[0].event.should.eql({ test: 'data' });
222+
dlqMessages[1].event.should.eql({ test: 'data' });
213223
});
214224

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

test/encode-decode.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ describe('encode-decode', function() {
1414
it('supports text', function() {
1515
encode('foo', 'application/text').should.eql(Buffer.from('foo'));
1616
});
17+
18+
it('returns Buffer unchanged without re-encoding as JSON', function() {
19+
const buf = Buffer.from('{"foo":"bar"}');
20+
const result = encode(buf);
21+
result.should.equal(buf);
22+
result.toString().should.equal('{"foo":"bar"}');
23+
});
1724
});
1825

1926
describe('decode', function() {

ts/base-queue-handler.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,16 @@ abstract class BaseQueueHandler {
182182
}
183183

184184
async addToDLQ(retries, msg: amqp.Message, ack) {
185+
const correlationId = this.getCorrelationId(msg);
185186
try {
186-
const correlationId = this.getCorrelationId(msg);
187187
const event = decode(msg);
188188
this.logger.warn(`[${correlationId}] Adding to dlq: ${this.dlqName} after ${retries} retries`);
189189
await this.rabbit.publish(this.dlqName, event, msg.properties);
190190
const response = await this.afterDlq({ msg, event });
191191
ack(msg.properties.headers.errors.message, response);
192192
} catch (err) {
193-
this.logger.error(err);
194-
await this.rabbit.publish(this.dlqName, msg.content.toString(), msg.properties);
193+
this.logger.error(`[${correlationId}] Failed to add to dlq: ${this.dlqName}`, err);
194+
await this.rabbit.publish(this.dlqName, msg.content, msg.properties);
195195
ack(err.message, null);
196196
}
197197
}

ts/encode-decode.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
export function encode(message: Buffer | string | Object = '', contentType = 'application/json') {
2+
if (Buffer.isBuffer(message)) {
3+
return message;
4+
}
25
if (contentType === 'application/json') {
36
return Buffer.from(JSON.stringify(message));
47
}

0 commit comments

Comments
 (0)