@droidsolutions-oss/amqp-ts 3.0.4 → 4.0.0

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.
Files changed (48) hide show
  1. package/README.md +186 -45
  2. package/lib/AmqpProperties.js +1 -2
  3. package/lib/Binding.d.ts +36 -6
  4. package/lib/Binding.js +244 -75
  5. package/lib/Binding.js.map +1 -1
  6. package/lib/Connection/Connection.d.ts +87 -46
  7. package/lib/Connection/Connection.js +360 -181
  8. package/lib/Connection/Connection.js.map +1 -1
  9. package/lib/Connection/ConnectionMetrics.js +1 -2
  10. package/lib/Connection/RecoveryOptions.d.ts +6 -0
  11. package/lib/Connection/RecoveryOptions.js +2 -0
  12. package/lib/Connection/RecoveryOptions.js.map +1 -0
  13. package/lib/Connection/Topology.d.ts +1 -1
  14. package/lib/Connection/Topology.js +1 -2
  15. package/lib/Deferred.d.ts +18 -0
  16. package/lib/Deferred.js +33 -0
  17. package/lib/Deferred.js.map +1 -0
  18. package/lib/Exchange/DeclarationOptions.js +1 -2
  19. package/lib/Exchange/Exchange.d.ts +38 -17
  20. package/lib/Exchange/Exchange.js +204 -159
  21. package/lib/Exchange/Exchange.js.map +1 -1
  22. package/lib/Exchange/ExchangeType.js +1 -2
  23. package/lib/Exchange/InitializeResult.js +1 -2
  24. package/lib/LoggerFactory.js +1 -5
  25. package/lib/LoggerFactory.js.map +1 -1
  26. package/lib/Message.d.ts +11 -10
  27. package/lib/Message.js +66 -37
  28. package/lib/Message.js.map +1 -1
  29. package/lib/Queue/DeclarationOptions.js +1 -2
  30. package/lib/Queue/DeleteResult.js +1 -2
  31. package/lib/Queue/InitializeResult.js +1 -2
  32. package/lib/Queue/Queue.d.ts +87 -26
  33. package/lib/Queue/Queue.js +440 -207
  34. package/lib/Queue/Queue.js.map +1 -1
  35. package/lib/Queue/StartConsumerResult.js +1 -2
  36. package/lib/amqp-ts.d.ts +0 -1
  37. package/lib/amqp-ts.js +2 -7
  38. package/lib/amqp-ts.js.map +1 -1
  39. package/lib/errorMessage.d.ts +1 -0
  40. package/lib/errorMessage.js +18 -0
  41. package/lib/errorMessage.js.map +1 -0
  42. package/lib/index.d.ts +19 -14
  43. package/lib/index.js +7 -13
  44. package/lib/index.js.map +1 -1
  45. package/package.json +38 -60
  46. package/lib/Connection/ReconnectStrategy.d.ts +0 -4
  47. package/lib/Connection/ReconnectStrategy.js +0 -3
  48. package/lib/Connection/ReconnectStrategy.js.map +0 -1
@@ -1,11 +1,70 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Queue = void 0;
4
- const amqp_ts_1 = require("../amqp-ts");
5
- const Binding_1 = require("../Binding");
6
- const Message_1 = require("../Message");
1
+ import { Binding } from "../Binding.js";
2
+ import { Deferred } from "../Deferred.js";
3
+ import { Message } from "../Message.js";
7
4
  /** Represents an AQMP queue. */
8
- class Queue {
5
+ export class Queue {
6
+ connection;
7
+ options;
8
+ /** A broker-side consumer cancel this soon after the consumer was registered again does not re-initialize. */
9
+ static CancelLoopWindowMs = 1000;
10
+ /**
11
+ * A promise that resolves once the connection is initialized and the queue is declared on its channel. It is
12
+ * replaced by a pending promise while the queue is re-initialized after a lost connection or a closed channel. It
13
+ * rejects when the queue fails on its own, or when the connection is closed or recovery gives up while it is pending.
14
+ */
15
+ initialized;
16
+ /**
17
+ * The AMQP channel.
18
+ * @internal
19
+ */
20
+ _channel;
21
+ /**
22
+ * A promise that resolves when a consumer handler is registered and initialized.
23
+ * @internal
24
+ */
25
+ _consumerInitialized;
26
+ _name;
27
+ /** @internal */
28
+ _consumer;
29
+ _consumerOptions;
30
+ _consumerTag;
31
+ _consumerStopping;
32
+ _deleting;
33
+ _closing;
34
+ log;
35
+ _deferred;
36
+ _consumerDeferred;
37
+ /** The generation of the last initialization, and that initialization. */
38
+ _generation;
39
+ _initializing = Promise.resolve();
40
+ /** Changes with every initialization, so a superseded one can tell. */
41
+ _attempt = 0;
42
+ /** The channel the queue was successfully declared on, and its model. */
43
+ _readyChannel;
44
+ _readyModel;
45
+ /** The channel the consumer is registered on, or being registered on. */
46
+ _consumerChannel;
47
+ _consuming = Promise.resolve();
48
+ /** The generation in which a broker-side consumer cancel made the queue re-initialize, until it re-consumed. */
49
+ _cancelReinitGeneration;
50
+ /**
51
+ * When the current consumer was registered by a re-initialization after a broker-side cancel. Another cancel within
52
+ * {@link Queue.CancelLoopWindowMs} of that does not re-initialize again, which stops a cancel loop.
53
+ */
54
+ _cancelRecoveredAt;
55
+ /**
56
+ * The generation in which the broker refused the consumer. Registering it again on the same connection would be
57
+ * refused again, so it is only registered again after the next reconnect.
58
+ */
59
+ _consumerRefusedIn;
60
+ /** Why the queue failed on its own, after which it is no longer declared. */
61
+ _failure;
62
+ /** Settles {@link _initializedChanged} for those waiting on it. */
63
+ _changed;
64
+ /** Counts deliveries; bound once so it can be removed again. */
65
+ _onDelivery = () => {
66
+ this.connection._increaseCounter("receivedMessages");
67
+ };
9
68
  /** Returns the name of the queue. */
10
69
  get name() {
11
70
  return this._name;
@@ -25,50 +84,249 @@ class Queue {
25
84
  this.options = options;
26
85
  this.log = this.connection.loggerFactory(this.constructor, { queue: name });
27
86
  this._name = name;
87
+ this._deferred = this.connection._createDeferred();
88
+ this.initialized = this._deferred.promise;
28
89
  this.connection._queues[this._name] = this;
29
- this._initialize();
90
+ this.connection._forgetFailure(this);
91
+ // while disconnected the next connect initializes it
92
+ if (this.connection._model) {
93
+ this._initialize(this.connection._model, this.connection._generation).catch(() => { });
94
+ }
30
95
  }
31
- /** Waits until the connection is initialized then creates a channel and declares the queue on it. */
32
- _initialize() {
33
- this.initialized = new Promise((resolve, reject) => {
34
- this.connection.initialized
35
- .then(() => {
36
- this.connection.connection.createChannel((createChannelError, channel) => {
37
- /* istanbul ignore if */
38
- if (createChannelError) {
39
- reject(createChannelError);
40
- }
41
- else {
42
- this._channel = channel;
43
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
44
- this._channel.on("delivery", this.handleReceivedMessageEvent.bind(this));
45
- const callback = (err, ok) => {
46
- /* istanbul ignore if */
47
- if (err) {
48
- this.log.error({ error: err }, "Failed to create queue '%s'.", this._name);
49
- delete this.connection._queues[this._name];
50
- reject(err);
51
- }
52
- else {
53
- if (this.options.prefetch) {
54
- this._channel.prefetch(this.options.prefetch);
55
- }
56
- resolve(ok);
57
- }
58
- };
59
- if (this.options.noCreate) {
60
- this._channel.checkQueue(this._name, callback);
61
- }
62
- else {
63
- this._channel.assertQueue(this._name, this.options, callback);
64
- }
65
- }
66
- });
67
- })
68
- .catch((err) => {
69
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
70
- this.log.warn(err, "Channel failure, error caused during connection!");
96
+ /**
97
+ * Creates a channel on the given model and declares the queue on it, then settles {@link initialized}. Does nothing
98
+ * if the queue was already initialized for the given generation. The consumer is started by
99
+ * {@link _initializeConsumer}.
100
+ * @internal
101
+ */
102
+ _initialize(model, generation) {
103
+ if (this.connection._queues[this._name] !== this) {
104
+ // failed, closed or deleted: only a new declaration brings it back
105
+ return Promise.reject(this._failure ?? new Error(`Queue '${this._name}' is no longer declared`));
106
+ }
107
+ if (this._generation !== generation) {
108
+ this._generation = generation;
109
+ this._initializing = this._declare(model, ++this._attempt);
110
+ }
111
+ return this._initializing;
112
+ }
113
+ /**
114
+ * Replaces a settled {@link initialized} (and consumer promise) with a pending one, until the queue is initialized
115
+ * again. When only the channel closed (`connectionLost` false), a consumer the broker refused on this connection
116
+ * keeps its rejected promise: it is not registered again before the next reconnect, so a pending one would never
117
+ * settle until then.
118
+ * @internal
119
+ */
120
+ _invalidate(connectionLost = true) {
121
+ this._readyChannel = undefined;
122
+ this._pendInitialized();
123
+ const refused = this._consumerRefusedIn !== undefined && this._consumerRefusedIn === this._generation;
124
+ if (this._consumerDeferred?.settled && (connectionLost || !refused)) {
125
+ this._consumerDeferred = this.connection._createDeferred();
126
+ this._consumerInitialized = this._consumerDeferred.promise;
127
+ }
128
+ }
129
+ /** Replaces a settled {@link initialized} with a pending one. */
130
+ _pendInitialized() {
131
+ if (this._deferred.settled) {
132
+ this._deferred = this.connection._createDeferred();
133
+ this.initialized = this._deferred.promise;
134
+ this._notifyChanged();
135
+ }
136
+ }
137
+ /**
138
+ * Whether the queue or its connection is closed or deleted on purpose, so nothing initializes it again.
139
+ * @internal
140
+ */
141
+ get _isClosedOnPurpose() {
142
+ return this._closing !== undefined || this._deleting !== undefined || this.connection._isStopped;
143
+ }
144
+ /**
145
+ * Resolves once {@link initialized} is no longer the given promise, or once nothing will replace it any more
146
+ * because the queue or its connection is closed or deleted. Used by a send whose channel turned out to be closing.
147
+ * @internal
148
+ */
149
+ _initializedChanged(ready) {
150
+ if (this.initialized !== ready || this._isClosedOnPurpose) {
151
+ return Promise.resolve();
152
+ }
153
+ this._changed ??= new Deferred();
154
+ return this._changed.promise;
155
+ }
156
+ /**
157
+ * Wakes up whoever waits in {@link _initializedChanged}.
158
+ * @internal
159
+ */
160
+ _notifyChanged() {
161
+ const changed = this._changed;
162
+ this._changed = undefined;
163
+ changed?.resolve();
164
+ }
165
+ /**
166
+ * Rejects {@link initialized} and the consumer promise if they are still pending. Used when the connection is closed
167
+ * or recovery gave up.
168
+ * @internal
169
+ */
170
+ _rejectPending(err) {
171
+ if (!this._deferred.settled) {
172
+ this._deferred.reject(err);
173
+ }
174
+ if (this._consumerDeferred?.settled === false) {
175
+ this._consumerDeferred.reject(err);
176
+ }
177
+ }
178
+ /**
179
+ * Registers the consumer, if there is one, on the channel the queue is declared on, unless it already is
180
+ * registered there or the broker refused it on this connection. The consumer promise is pending until the
181
+ * registration is done.
182
+ * @internal
183
+ */
184
+ _initializeConsumer() {
185
+ const channel = this._readyChannel;
186
+ if (this._consumer === undefined ||
187
+ channel === undefined ||
188
+ channel === this._consumerChannel ||
189
+ this._consumerRefusedIn === this._generation) {
190
+ return this._consuming;
191
+ }
192
+ if (this._consumerDeferred.settled) {
193
+ this._consumerDeferred = this.connection._createDeferred();
194
+ this._consumerInitialized = this._consumerDeferred.promise;
195
+ }
196
+ this._consumerChannel = channel;
197
+ this._consuming = this._consume(channel, this._readyModel);
198
+ return this._consuming;
199
+ }
200
+ /**
201
+ * Re-creates the channel after it closed while the connection stayed up, declares the queue again and restores its
202
+ * bindings, which a broker-side delete removes as well. Only then {@link initialized} resolves. The caller registers
203
+ * the consumer again.
204
+ */
205
+ _reinitialize(model, generation) {
206
+ this._generation = generation;
207
+ this._initializing = this._declare(model, ++this._attempt, true);
208
+ return this._initializing;
209
+ }
210
+ async _declare(model, attempt, rebind = false) {
211
+ try {
212
+ const channel = await model.createChannel();
213
+ channel.on("error", (err) => {
214
+ this.log.warn({ err }, "Channel of queue '%s' failed: %s", this._name, err.message);
71
215
  });
216
+ channel.on("close", () => this._onChannelClose(channel, model));
217
+ if (attempt !== this._attempt) {
218
+ await channel.close().catch(() => { });
219
+ return;
220
+ }
221
+ this._channel = channel;
222
+ channel.on("delivery", this._onDelivery);
223
+ const ok = this.options.noCreate
224
+ ? await channel.checkQueue(this._name)
225
+ : await channel.assertQueue(this._name, this.options);
226
+ if (this.options.prefetch) {
227
+ await channel.prefetch(this.options.prefetch);
228
+ }
229
+ if (attempt !== this._attempt) {
230
+ await channel.close().catch(() => { });
231
+ return;
232
+ }
233
+ this._readyChannel = channel;
234
+ this._readyModel = model;
235
+ if (rebind) {
236
+ await Binding._rebindContaining(this, model);
237
+ if (attempt !== this._attempt) {
238
+ return;
239
+ }
240
+ }
241
+ // a reply and the connection's close can arrive together: never resolve on a closed connection
242
+ if (this.connection._isModelClosed(model)) {
243
+ throw new Error("Connection closed while the queue was initialized");
244
+ }
245
+ this._deferred.resolve(ok);
246
+ }
247
+ catch (err) {
248
+ if (attempt !== this._attempt) {
249
+ return;
250
+ }
251
+ if (this.connection._isModelClosed(model)) {
252
+ // the next connect initializes it again, until then initialized stays pending
253
+ this.log.debug({ err }, "Connection lost while initializing queue '%s'.", this._name);
254
+ throw err;
255
+ }
256
+ this.log.error({ err }, "Failed to create queue '%s'.", this._name);
257
+ delete this.connection._queues[this._name];
258
+ this._failure = err;
259
+ // the consumer cannot be registered on a queue that failed
260
+ this._rejectPending(err);
261
+ this.connection._onEntityFailed(this, err);
262
+ throw err;
263
+ }
264
+ }
265
+ /**
266
+ * Registers the consumer on the given channel. A refusal (for example 403 because another connection holds an
267
+ * exclusive consumer) is also reported through `error_connection`, since during recovery the consumer would
268
+ * otherwise stop silently.
269
+ */
270
+ async _consume(channel, model) {
271
+ const options = this._consumerOptions;
272
+ try {
273
+ const ok = await channel.consume(this._name, this._createConsumerWrapper(channel, options?.noAck === true), options);
274
+ if (channel !== this._consumerChannel) {
275
+ // stopped or registered elsewhere meanwhile, but the broker registered it all the same
276
+ this._cancelSuperseded(channel, ok.consumerTag);
277
+ return;
278
+ }
279
+ if (this.connection._isModelClosed(model)) {
280
+ throw new Error("Connection closed while the consumer was registered");
281
+ }
282
+ this._consumerTag = ok.consumerTag;
283
+ const recovered = this._cancelReinitGeneration !== undefined && this._cancelReinitGeneration === this._generation;
284
+ this._cancelRecoveredAt = recovered ? Date.now() : undefined;
285
+ this._cancelReinitGeneration = undefined;
286
+ this._consumerDeferred.resolve(ok);
287
+ }
288
+ catch (err) {
289
+ if (channel !== this._consumerChannel || this.connection._isModelClosed(model)) {
290
+ // superseded, or the next connect registers it again
291
+ throw err;
292
+ }
293
+ this.log.error({ err }, "Failed to start the consumer of queue '%s'.", this._name);
294
+ this._consumerRefusedIn = this._generation;
295
+ this._consumerDeferred.reject(err);
296
+ this.connection._reportFailure(err);
297
+ throw err;
298
+ }
299
+ }
300
+ /** Cancels a consumer registration that completed after it was superseded, so it does not keep receiving messages. */
301
+ _cancelSuperseded(channel, consumerTag) {
302
+ Promise.resolve()
303
+ .then(() => channel.cancel(consumerTag))
304
+ .catch((err) => {
305
+ this.log.debug({ err }, "Cancelling a superseded consumer of queue '%s' failed: %s", this._name, err.message);
306
+ });
307
+ }
308
+ /**
309
+ * A channel that closes while the connection stays up (for example after a double ack) is re-created, the queue
310
+ * declared again and its consumer registered again. Closes during a failed declaration or consumer registration, on
311
+ * a lost connection or caused by {@link close} or {@link delete} are left alone.
312
+ */
313
+ _onChannelClose(channel, model) {
314
+ if (channel !== this._readyChannel || this._closing !== undefined || this._deleting !== undefined) {
315
+ return;
316
+ }
317
+ // a consumer registration that the broker refused closes the channel as well; retrying it would loop
318
+ const consumeFailed = channel === this._consumerChannel && this._consumerDeferred?.settled === false;
319
+ const generation = this._generation;
320
+ this._invalidate(false);
321
+ // the connection emits its own close right after closing its channels, so look at it once that happened
322
+ queueMicrotask(() => {
323
+ if (!this.connection._isModelUsable(model) || this._generation !== generation) {
324
+ return;
325
+ }
326
+ this.log.warn("Channel of queue '%s' closed, re-initializing it.", this._name);
327
+ this._reinitialize(model, generation)
328
+ .then(() => (consumeFailed ? undefined : this._initializeConsumer()))
329
+ .catch(() => { });
72
330
  });
73
331
  }
74
332
  /**
@@ -79,33 +337,6 @@ class Queue {
79
337
  send(message) {
80
338
  message.sendTo(this);
81
339
  }
82
- rpc(requestParameters) {
83
- return new Promise((resolve, reject) => {
84
- const processRpc = () => {
85
- let consumerTag;
86
- this._channel.consume(amqp_ts_1.DIRECT_REPLY_TO_QUEUE, (resultMsg) => {
87
- this._channel.cancel(consumerTag);
88
- const result = new Message_1.Message(resultMsg.content, resultMsg.properties);
89
- result.fields = resultMsg.fields;
90
- resolve(result);
91
- }, { noAck: true }, (err, ok) => {
92
- /* istanbul ignore if */
93
- if (err) {
94
- reject(new Error("amqp-ts: Queue.rpc error: " + err.message));
95
- }
96
- else {
97
- // send the rpc request
98
- consumerTag = ok.consumerTag;
99
- const message = new Message_1.Message(requestParameters, {
100
- replyTo: amqp_ts_1.DIRECT_REPLY_TO_QUEUE,
101
- });
102
- message.sendTo(this);
103
- }
104
- });
105
- };
106
- this.initialized.then(processRpc);
107
- });
108
- }
109
340
  /**
110
341
  * Sets the prefetch of the current quere. This limits the amount of unacknowledged messages on a channel. If you
111
342
  * already specified this in the options in the constructor you don't need to set it here again.
@@ -115,23 +346,17 @@ class Queue {
115
346
  */
116
347
  prefetch(count) {
117
348
  this.initialized.then(() => {
118
- this._channel.prefetch(count);
349
+ this._channel.prefetch(count).catch((err) => {
350
+ this.log.warn({ err }, "Setting the prefetch of queue '%s' failed.", this._name);
351
+ });
119
352
  this.options.prefetch = count;
353
+ }, (err) => {
354
+ this.log.warn({ err }, "Cannot set the prefetch of queue '%s', it failed to initialize.", this._name);
120
355
  });
121
356
  }
122
- recover() {
123
- return new Promise((resolve, reject) => {
124
- this.initialized.then(() => {
125
- this._channel.recover((err, _ok) => {
126
- if (err) {
127
- reject(err);
128
- }
129
- else {
130
- resolve(null);
131
- }
132
- });
133
- });
134
- });
357
+ async recover() {
358
+ await this.initialized;
359
+ await this._channel.recover();
135
360
  }
136
361
  /**
137
362
  * Actives a consumer by binding the given handler to this queue.
@@ -149,33 +374,54 @@ class Queue {
149
374
  }
150
375
  this._consumerOptions = options;
151
376
  this._consumer = onMessage;
152
- this._initializeConsumer();
377
+ delete this._consumerRefusedIn;
378
+ this._consumerDeferred = this.connection._createDeferred();
379
+ this._consumerInitialized = this._consumerDeferred.promise;
380
+ // registers right away if the queue is ready, otherwise once it is; a reconnect registers it as well
381
+ this.initialized.then(() => this._initializeConsumer().catch(() => { }), (err) => this._consumerDeferred?.reject(err));
153
382
  return this._consumerInitialized;
154
383
  }
155
384
  /**
156
- * Initializes the consumer with a handler function. Resolves the @see _consumerInitialized promise once the handler
157
- * is bound.
385
+ * Wraps the consumer handler for messages delivered on the given channel. A message that arrives after the consumer
386
+ * was stopped, before the broker confirmed the cancel, is given back to the queue for another consumer; one that
387
+ * needs no acknowledgement cannot be given back.
158
388
  */
159
- _initializeConsumer() {
160
- const activateConsumerWrapper = (msg) => {
389
+ _createConsumerWrapper(channel, noAck) {
390
+ return (msg) => {
391
+ if (msg === null) {
392
+ this._onConsumerCancelled(channel);
393
+ return;
394
+ }
395
+ if (this._consumer === undefined) {
396
+ this.log.debug("Message for the stopped consumer of queue '%s', requeuing it.", this._name);
397
+ if (!noAck) {
398
+ try {
399
+ channel.nack(msg, false, true);
400
+ }
401
+ catch (err) {
402
+ // the channel closed meanwhile, which requeues it as well
403
+ this.log.debug({ err }, "Requeuing a message of queue '%s' failed.", this._name);
404
+ }
405
+ }
406
+ return;
407
+ }
161
408
  try {
162
- const message = new Message_1.Message(msg.content, msg.properties);
409
+ const message = new Message(msg.content, msg.properties);
163
410
  message.fields = msg.fields;
164
411
  message._message = msg;
165
- message._channel = this._channel;
412
+ message._channel = channel;
166
413
  const result = this._consumer(message);
167
414
  // convert the result to a promise if it isn't one already
168
415
  Promise.resolve(result)
169
416
  .then((resultValue) => {
170
- var _a;
171
417
  // check if there is a reply-to
172
418
  if (msg.properties.replyTo && resultValue) {
173
- if (!(resultValue instanceof Message_1.Message)) {
174
- resultValue = new Message_1.Message(resultValue, (_a = this._consumerOptions.properties) !== null && _a !== void 0 ? _a : {});
419
+ if (!(resultValue instanceof Message)) {
420
+ resultValue = new Message(resultValue, this._consumerOptions.properties ?? {});
175
421
  }
176
422
  resultValue.properties.correlationId = msg.properties.correlationId;
177
423
  this.log.debug("Replying directly to queue %s", msg.properties.replyTo);
178
- this._channel.sendToQueue(msg.properties.replyTo, resultValue.content, resultValue.properties);
424
+ channel.sendToQueue(msg.properties.replyTo, resultValue.content, resultValue.properties);
179
425
  }
180
426
  })
181
427
  .catch((err) => {
@@ -187,89 +433,98 @@ class Queue {
187
433
  this.log.error({ err, correlationId: msg.properties.correlationId, consumerTag: msg.fields.consumerTag }, "Consuming the message %s returned error: %s", msg.properties.correlationId, err.message);
188
434
  }
189
435
  };
190
- this._consumerInitialized = new Promise((resolve, reject) => {
191
- this.initialized.then(() => {
192
- this._channel.consume(this._name, activateConsumerWrapper, this._consumerOptions, (err, ok) => {
193
- /* istanbul ignore if */
194
- if (err) {
195
- reject(err);
196
- }
197
- else {
198
- this._consumerTag = ok.consumerTag;
199
- resolve(ok);
200
- }
201
- });
202
- });
203
- });
204
436
  }
205
437
  /**
206
- * Completely removes the consumer handler and cancels the channel.
438
+ * The broker cancelled the consumer, for example because the queue was deleted. The channel stays open, so nothing
439
+ * else would notice: close it, which re-declares the queue, restores its bindings and registers the consumer again
440
+ * through the channel close handling. A `noCreate` queue that is gone then fails like any failed declaration.
441
+ */
442
+ _onConsumerCancelled(channel) {
443
+ if (channel !== this._consumerChannel ||
444
+ channel !== this._readyChannel ||
445
+ this._closing !== undefined ||
446
+ this._deleting !== undefined) {
447
+ return;
448
+ }
449
+ if (this._cancelRecoveredAt !== undefined && Date.now() - this._cancelRecoveredAt < Queue.CancelLoopWindowMs) {
450
+ const err = new Error(`The consumer of queue '${this._name}' was cancelled by the broker again right after it was registered ` +
451
+ "again; it is registered again on the next reconnect.");
452
+ this.log.error({ err }, "%s", err.message);
453
+ // the consumer is gone until then, like a refused one, and nobody would notice otherwise
454
+ this._consumerRefusedIn = this._generation;
455
+ this._consumerDeferred = this.connection._createDeferred();
456
+ this._consumerDeferred.reject(err);
457
+ this._consumerInitialized = this._consumerDeferred.promise;
458
+ this.connection._reportFailure(err);
459
+ return;
460
+ }
461
+ this.log.warn("The consumer of queue '%s' was cancelled by the broker, re-initializing the queue.", this._name);
462
+ this._cancelReinitGeneration = this._generation;
463
+ // a publish fails until the broker confirmed the close, so sends must wait for the re-initialization instead of
464
+ // taking the channel for closed on purpose; the close handling still needs the ready channel
465
+ this._pendInitialized();
466
+ channel.close().catch(() => { });
467
+ }
468
+ /**
469
+ * Completely removes the consumer handler and cancels the consumer on the broker. A consumer the broker refused has
470
+ * nothing to cancel, it is only removed. A consumer whose channel closes meanwhile is registered again on a new one,
471
+ * so that registration is waited for and cancelled instead.
207
472
  */
208
- stopConsumer() {
473
+ async stopConsumer() {
209
474
  if (this._consumerStopping || this._consumerInitialized == undefined) {
210
- return Promise.resolve();
475
+ return;
211
476
  }
212
477
  this._consumerStopping = true;
213
- return new Promise((resolve, reject) => {
214
- this._consumerInitialized.then(() => {
215
- this._channel.cancel(this._consumerTag, (err, _ok) => {
216
- /* istanbul ignore if */
217
- if (err) {
218
- reject(err);
219
- }
220
- else {
221
- delete this._consumerInitialized;
222
- delete this._consumer;
223
- delete this._consumerOptions;
224
- delete this._consumerStopping;
225
- resolve();
478
+ try {
479
+ for (;;) {
480
+ const consumer = this._consumerInitialized;
481
+ const registered = await consumer.then(() => true, () => false);
482
+ if (this._consumerInitialized !== consumer) {
483
+ // invalidated meanwhile, a registration is under way again
484
+ continue;
485
+ }
486
+ if (!registered) {
487
+ break;
488
+ }
489
+ try {
490
+ await this._consumerChannel.cancel(this._consumerTag);
491
+ break;
492
+ }
493
+ catch (err) {
494
+ if (this._consumerInitialized === consumer) {
495
+ throw err;
226
496
  }
227
- });
228
- });
229
- });
497
+ // the channel closed while cancelling and the consumer is being registered again
498
+ }
499
+ }
500
+ }
501
+ finally {
502
+ // a failed cancel can be retried
503
+ delete this._consumerStopping;
504
+ }
505
+ delete this._consumerInitialized;
506
+ delete this._consumerDeferred;
507
+ delete this._consumer;
508
+ delete this._consumerOptions;
509
+ delete this._consumerChannel;
510
+ delete this._consumerRefusedIn;
230
511
  }
231
512
  /**
232
513
  * Removes any bindings of this queue, the consumer handler and then deletes the queue.
233
514
  */
234
515
  delete() {
235
516
  if (this._deleting === undefined) {
236
- this._deleting = new Promise((resolve, reject) => {
237
- this.initialized
238
- .then(() => {
239
- return Binding_1.Binding.removeBindingsContaining(this);
240
- })
241
- .then(() => {
242
- return this.stopConsumer();
243
- })
244
- .then(() => {
245
- return this._channel.deleteQueue(this._name, {}, (deleteQueueError, ok) => {
246
- /* istanbul ignore if */
247
- if (deleteQueueError) {
248
- reject(deleteQueueError);
249
- }
250
- else {
251
- delete this.initialized; // invalidate queue
252
- delete this.connection._queues[this._name]; // remove the queue from our administration
253
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
254
- this._channel.removeListener("delivery", this.handleReceivedMessageEvent.bind(this));
255
- this._channel.close((channelCloseError) => {
256
- /* istanbul ignore if */
257
- if (channelCloseError) {
258
- reject(channelCloseError);
259
- }
260
- else {
261
- delete this._channel;
262
- delete this.connection;
263
- resolve(ok);
264
- }
265
- });
266
- }
267
- });
268
- })
269
- .catch((err) => {
270
- reject(err);
271
- });
272
- });
517
+ this._deleting = (async () => {
518
+ this._notifyChanged();
519
+ await this.initialized;
520
+ await Binding.removeBindingsContaining(this);
521
+ await this.stopConsumer();
522
+ const ok = await this._channel.deleteQueue(this._name, {});
523
+ delete this.connection._queues[this._name]; // remove the queue from our administration
524
+ this._channel.removeListener("delivery", this._onDelivery);
525
+ await this._channel.close();
526
+ return ok;
527
+ })();
273
528
  }
274
529
  return this._deleting;
275
530
  }
@@ -278,35 +533,15 @@ class Queue {
278
533
  */
279
534
  close() {
280
535
  if (this._closing === undefined) {
281
- this._closing = new Promise((resolve, reject) => {
282
- this.initialized
283
- .then(() => {
284
- return Binding_1.Binding.removeBindingsContaining(this);
285
- })
286
- .then(() => {
287
- return this.stopConsumer();
288
- })
289
- .then(() => {
290
- delete this.initialized; // invalidate queue
291
- delete this.connection._queues[this._name]; // remove the queue from our administration
292
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
293
- this._channel.removeListener("delivery", this.handleReceivedMessageEvent.bind(this));
294
- this._channel.close((err) => {
295
- /* istanbul ignore if */
296
- if (err) {
297
- reject(err);
298
- }
299
- else {
300
- delete this._channel;
301
- delete this.connection;
302
- resolve(null);
303
- }
304
- });
305
- })
306
- .catch((err) => {
307
- reject(err);
308
- });
309
- });
536
+ this._closing = (async () => {
537
+ this._notifyChanged();
538
+ await this.initialized;
539
+ await Binding.removeBindingsContaining(this);
540
+ await this.stopConsumer();
541
+ delete this.connection._queues[this._name]; // remove the queue from our administration
542
+ this._channel.removeListener("delivery", this._onDelivery);
543
+ await this._channel.close();
544
+ })();
310
545
  }
311
546
  return this._closing;
312
547
  }
@@ -318,22 +553,20 @@ class Queue {
318
553
  * @param args Any arguments.
319
554
  */
320
555
  bind(source, pattern = "", args = {}) {
321
- const binding = new Binding_1.Binding(this, source, pattern, args);
556
+ const binding = new Binding(this, source, pattern, args);
322
557
  return binding.initialized;
323
558
  }
324
559
  /**
325
- * Unbinds the queue from the given exchange by deleting the binding.
560
+ * Unbinds the queue from the given exchange by deleting the binding. Resolves right away if there is no such
561
+ * binding, for example because it was unbound before.
326
562
  *
327
563
  * @param source The exchange to unbind from.
328
564
  * @param pattern The pattern the queue was bind with.
329
565
  * @param _args Any additional arguments.
330
566
  */
331
- unbind(source, pattern = "", _args = {}) {
332
- return this.connection._bindings[Binding_1.Binding.id(this, source, pattern)].delete();
333
- }
334
- handleReceivedMessageEvent(_args) {
335
- this.connection._increaseCounter("receivedMessages");
567
+ async unbind(source, pattern = "", _args = {}) {
568
+ // a binding that does not exist (any more) has nothing to delete
569
+ await this.connection._bindings[Binding.id(this, source, pattern)]?.delete();
336
570
  }
337
571
  }
338
- exports.Queue = Queue;
339
572
  //# sourceMappingURL=Queue.js.map