amqplib 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +1 -1
- package/.github/workflows/test.yml +1 -1
- package/CHANGELOG.md +8 -0
- package/Makefile +4 -4
- package/lib/callback_model.js +2 -1
- package/lib/channel.js +5 -4
- package/lib/channel_model.js +9 -8
- package/lib/connection.js +10 -0
- package/package.json +5 -3
- package/test/callback_api.test.js +11 -0
- package/test/channel.test.js +20 -0
- package/test/channel_api.test.js +11 -0
- package/test/connection.test.js +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Change log for amqplib
|
|
2
2
|
|
|
3
|
+
## v1.0.5
|
|
4
|
+
- Fix ConfirmChannel callbacks silently dropped on channel close when some publishes had no callback (fixes #191)
|
|
5
|
+
|
|
6
|
+
## v1.0.4
|
|
7
|
+
- Updated build to use RabbitMQ 4.2
|
|
8
|
+
- Fix memory leak in ConfirmChannel.publish when channel is already closed (fixes #842)
|
|
9
|
+
- Close connection immediately when close() is called while the connection is blocked (fixes #744)
|
|
10
|
+
|
|
3
11
|
## v1.0.3
|
|
4
12
|
- Fix AssertionError crash when backpressure occurs while draining newStreams in Mux (fixes #841)
|
|
5
13
|
|
package/Makefile
CHANGED
|
@@ -2,7 +2,7 @@ RABBITMQ_SRC_VERSION=v3.12.13
|
|
|
2
2
|
JSON=amqp-rabbitmq-0.9.1.json
|
|
3
3
|
AMQP_JSON=https://raw.githubusercontent.com/rabbitmq/rabbitmq-server/$(RABBITMQ_SRC_VERSION)/deps/rabbitmq_codegen/$(JSON)
|
|
4
4
|
|
|
5
|
-
NODEJS_VERSIONS='18.
|
|
5
|
+
NODEJS_VERSIONS='18.13.0' '20.10.0' '22.14.0' '24.7.0'
|
|
6
6
|
|
|
7
7
|
UGLIFY=./node_modules/.bin/uglifyjs
|
|
8
8
|
|
|
@@ -13,16 +13,16 @@ error:
|
|
|
13
13
|
@exit 1
|
|
14
14
|
|
|
15
15
|
test:
|
|
16
|
-
node --test
|
|
16
|
+
node --test --test-reporter=spec
|
|
17
17
|
|
|
18
18
|
test-all-nodejs:
|
|
19
19
|
for v in $(NODEJS_VERSIONS); \
|
|
20
20
|
do echo "-- Node version $$v --"; \
|
|
21
|
-
nave use $$v node --test; \
|
|
21
|
+
nave use $$v node --test --test-reporter=spec; \
|
|
22
22
|
done
|
|
23
23
|
|
|
24
24
|
coverage:
|
|
25
|
-
node --test --test-coverage
|
|
25
|
+
node --test --test-coverage --test-reporter=spec
|
|
26
26
|
|
|
27
27
|
lib/defs.js: clean bin/generate-defs test
|
|
28
28
|
|
package/lib/callback_model.js
CHANGED
|
@@ -246,8 +246,9 @@ function callbackWrapper(_ch, cb) {
|
|
|
246
246
|
|
|
247
247
|
class ConfirmChannel extends Channel {
|
|
248
248
|
publish(exchange, routingKey, content, options, cb) {
|
|
249
|
+
const result = Channel.prototype.publish.call(this, exchange, routingKey, content, options);
|
|
249
250
|
this.pushConfirmCallback(cb);
|
|
250
|
-
return
|
|
251
|
+
return result;
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
sendToQueue(queue, content, options, cb) {
|
package/lib/channel.js
CHANGED
|
@@ -33,11 +33,12 @@ class Channel extends EventEmitter {
|
|
|
33
33
|
}),
|
|
34
34
|
);
|
|
35
35
|
this.on('close', function () {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
cb
|
|
36
|
+
const err = new Error('channel closed');
|
|
37
|
+
for (let i = 0; i < this.unconfirmed.length; i++) {
|
|
38
|
+
const cb = this.unconfirmed[i];
|
|
39
|
+
if (cb) cb(err);
|
|
40
40
|
}
|
|
41
|
+
this.unconfirmed = this.unconfirmed.filter((cb) => cb === false);
|
|
41
42
|
});
|
|
42
43
|
// message frame state machine
|
|
43
44
|
this.handleMessage = acceptDeliveryOrReturn;
|
package/lib/channel_model.js
CHANGED
|
@@ -224,8 +224,9 @@ Channel.prototype.prefetch = Channel.prototype.qos;
|
|
|
224
224
|
|
|
225
225
|
class ConfirmChannel extends Channel {
|
|
226
226
|
publish(exchange, routingKey, content, options, cb) {
|
|
227
|
+
const result = super.publish(exchange, routingKey, content, options);
|
|
227
228
|
this.pushConfirmCallback(cb);
|
|
228
|
-
return
|
|
229
|
+
return result;
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
sendToQueue(queue, content, options, cb) {
|
|
@@ -235,11 +236,11 @@ class ConfirmChannel extends Channel {
|
|
|
235
236
|
waitForConfirms() {
|
|
236
237
|
const awaiting = [];
|
|
237
238
|
const unconfirmed = this.unconfirmed;
|
|
238
|
-
unconfirmed.forEach((
|
|
239
|
-
if (
|
|
239
|
+
unconfirmed.forEach((cb, index) => {
|
|
240
|
+
if (cb !== null) {
|
|
240
241
|
const confirmed = new Promise((resolve, reject) => {
|
|
241
242
|
unconfirmed[index] = (err) => {
|
|
242
|
-
if (
|
|
243
|
+
if (cb) cb(err);
|
|
243
244
|
if (err === null) resolve();
|
|
244
245
|
else reject(err);
|
|
245
246
|
};
|
|
@@ -249,10 +250,10 @@ class ConfirmChannel extends Channel {
|
|
|
249
250
|
});
|
|
250
251
|
// Channel closed
|
|
251
252
|
if (!this.pending) {
|
|
252
|
-
|
|
253
|
-
while (
|
|
254
|
-
|
|
255
|
-
cb
|
|
253
|
+
const err = new Error('channel closed');
|
|
254
|
+
while (unconfirmed.length > 0) {
|
|
255
|
+
const cb = unconfirmed.shift();
|
|
256
|
+
if (cb) cb(err);
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
return Promise.all(awaiting);
|
package/lib/connection.js
CHANGED
|
@@ -41,6 +41,7 @@ class Connection extends EventEmitter {
|
|
|
41
41
|
this.recvSinceLastCheck = false;
|
|
42
42
|
|
|
43
43
|
this.expectSocketClose = false;
|
|
44
|
+
this.blocked = false;
|
|
44
45
|
this.freeChannels = new BitSet();
|
|
45
46
|
this.channels = [
|
|
46
47
|
{
|
|
@@ -301,6 +302,13 @@ class Connection extends EventEmitter {
|
|
|
301
302
|
toClosing(capturedStack, k) {
|
|
302
303
|
const send = this.sendMethod.bind(this);
|
|
303
304
|
|
|
305
|
+
if (this.blocked) {
|
|
306
|
+
if (k) k();
|
|
307
|
+
const s = stackCapture('Connection blocked: closing immediately');
|
|
308
|
+
this.toClosed(s, undefined);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
304
312
|
this.accept = function (f) {
|
|
305
313
|
if (f.id === defs.ConnectionCloseOk) {
|
|
306
314
|
if (k) k();
|
|
@@ -581,8 +589,10 @@ function channel0(connection) {
|
|
|
581
589
|
}
|
|
582
590
|
connection.toClosed(s, e);
|
|
583
591
|
} else if (f.id === defs.ConnectionBlocked) {
|
|
592
|
+
connection.blocked = true;
|
|
584
593
|
connection.emit('blocked', f.fields.reason);
|
|
585
594
|
} else if (f.id === defs.ConnectionUnblocked) {
|
|
595
|
+
connection.blocked = false;
|
|
586
596
|
connection.emit('unblocked');
|
|
587
597
|
} else if (f.id === defs.ConnectionUpdateSecretOk) {
|
|
588
598
|
connection.emit('update-secret-ok');
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "amqplib",
|
|
3
3
|
"homepage": "http://amqp-node.github.io/amqplib/",
|
|
4
4
|
"main": "./channel_api.js",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.5",
|
|
6
6
|
"description": "An AMQP 0-9-1 (e.g., RabbitMQ) library and client.",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -21,8 +21,10 @@
|
|
|
21
21
|
"uglify-js": "2.8.x"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"test": "
|
|
25
|
-
"test:only": "node --test --test-only",
|
|
24
|
+
"test": "node --test --test-timeout=1000",
|
|
25
|
+
"test:only": "node --test --test-only --test-timeout=1000",
|
|
26
|
+
"rabbitmq-3": "docker run -d --rm --name rabbitmq -p 5672:5672 rabbitmq:3.12-alpine",
|
|
27
|
+
"rabbitmq-4": "docker run -d --rm --name rabbitmq -p 5672:5672 rabbitmq:4.2-alpine",
|
|
26
28
|
"format": "biome format . --write",
|
|
27
29
|
"lint": "biome lint --max-diagnostics=1000 ."
|
|
28
30
|
},
|
|
@@ -305,6 +305,17 @@ describe('Callback API', () => {
|
|
|
305
305
|
done();
|
|
306
306
|
});
|
|
307
307
|
});
|
|
308
|
+
|
|
309
|
+
confirm_channel_test('publish on closed channel does not leak callbacks', (_ch, done) => {
|
|
310
|
+
ch = _ch;
|
|
311
|
+
ch.close(() => {
|
|
312
|
+
for (let i = 0; i < 10; i++) {
|
|
313
|
+
try { ch.publish('', '', Buffer.from('x'), {}, () => {}); } catch (_) {}
|
|
314
|
+
}
|
|
315
|
+
assert.strictEqual(ch.unconfirmed.length, 0);
|
|
316
|
+
done();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
308
319
|
});
|
|
309
320
|
|
|
310
321
|
describe('Error handling', () => {
|
package/test/channel.test.js
CHANGED
|
@@ -588,5 +588,25 @@ describe('Channel', () => {
|
|
|
588
588
|
send(defs.BasicAck, { deliveryTag: 1, multiple: false }, ch);
|
|
589
589
|
}, cb);
|
|
590
590
|
}));
|
|
591
|
+
|
|
592
|
+
// Regression test for https://github.com/amqp-node/amqplib/issues/191
|
|
593
|
+
// pushConfirmCallback stores `false` for publishes with no callback.
|
|
594
|
+
// The close drain used `while (cb)` which stops at the first `false`,
|
|
595
|
+
// silently dropping all callbacks that follow it.
|
|
596
|
+
it('invokes all pending callbacks on close even when some publishes had no callback', channelTest((ch, cb) => {
|
|
597
|
+
let called = 0;
|
|
598
|
+
const countErr = (err) => { if (err) called++; };
|
|
599
|
+
ch.pushConfirmCallback(countErr); // tag = 1 — has callback
|
|
600
|
+
ch.pushConfirmCallback(null); // tag = 2 — no callback, stored as `false`
|
|
601
|
+
ch.pushConfirmCallback(countErr); // tag = 3 — has callback, dropped by while(cb) bug
|
|
602
|
+
open(ch).then(() => {
|
|
603
|
+
ch.toClosed('test close');
|
|
604
|
+
completes(() => {
|
|
605
|
+
assert.equal(2, called);
|
|
606
|
+
}, cb);
|
|
607
|
+
}, cb);
|
|
608
|
+
}, (_send, _wait, cb) => {
|
|
609
|
+
cb();
|
|
610
|
+
}));
|
|
591
611
|
});
|
|
592
612
|
});
|
package/test/channel_api.test.js
CHANGED
|
@@ -499,6 +499,17 @@ describe('confirms', () => {
|
|
|
499
499
|
}));
|
|
500
500
|
});
|
|
501
501
|
});
|
|
502
|
+
|
|
503
|
+
it('publish on closed channel does not leak callbacks', () => {
|
|
504
|
+
return withConfirmChannel((ch) => {
|
|
505
|
+
return ch.close().then(() => {
|
|
506
|
+
for (let i = 0; i < 10; i++) {
|
|
507
|
+
try { ch.publish('', '', Buffer.from('x'), {}, () => {}); } catch (_) {}
|
|
508
|
+
}
|
|
509
|
+
assert.strictEqual(ch.unconfirmed.length, 0);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
});
|
|
502
513
|
});
|
|
503
514
|
|
|
504
515
|
function connect() {
|
package/test/connection.test.js
CHANGED
|
@@ -278,6 +278,26 @@ describe('Connection', () => {
|
|
|
278
278
|
.then(() => send(defs.ConnectionCloseOk, {}))
|
|
279
279
|
.then(cb, cb);
|
|
280
280
|
}));
|
|
281
|
+
|
|
282
|
+
it('close while blocked closes immediately', connectionTest((c, cb) => {
|
|
283
|
+
const decrementLatch = latch(2, cb);
|
|
284
|
+
c.on('close', () => decrementLatch());
|
|
285
|
+
c.open(OPEN_OPTS, (err) => {
|
|
286
|
+
assert.ifError(err);
|
|
287
|
+
c.once('blocked', () => {
|
|
288
|
+
c.close((err) => {
|
|
289
|
+
assert.ifError(err);
|
|
290
|
+
decrementLatch();
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}, (send, wait, cb) => {
|
|
295
|
+
handshake(send, wait)
|
|
296
|
+
.then(() => send(defs.ConnectionBlocked, { reason: 'memory' }, 0))
|
|
297
|
+
.then(wait(defs.ConnectionClose))
|
|
298
|
+
// deliberately do NOT send ConnectionCloseOk — client must close anyway
|
|
299
|
+
.then(cb, cb);
|
|
300
|
+
}));
|
|
281
301
|
});
|
|
282
302
|
|
|
283
303
|
describe('heartbeats', () => {
|