@droidsolutions-oss/amqp-ts 3.0.3 → 4.0.0-develop.1
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/README.md +186 -45
- package/lib/AmqpProperties.js +1 -2
- package/lib/Binding.d.ts +36 -6
- package/lib/Binding.js +244 -75
- package/lib/Binding.js.map +1 -1
- package/lib/Connection/Connection.d.ts +87 -46
- package/lib/Connection/Connection.js +360 -181
- package/lib/Connection/Connection.js.map +1 -1
- package/lib/Connection/ConnectionMetrics.js +1 -2
- package/lib/Connection/RecoveryOptions.d.ts +6 -0
- package/lib/Connection/RecoveryOptions.js +2 -0
- package/lib/Connection/RecoveryOptions.js.map +1 -0
- package/lib/Connection/Topology.d.ts +1 -1
- package/lib/Connection/Topology.js +1 -2
- package/lib/Deferred.d.ts +18 -0
- package/lib/Deferred.js +33 -0
- package/lib/Deferred.js.map +1 -0
- package/lib/Exchange/DeclarationOptions.js +1 -2
- package/lib/Exchange/Exchange.d.ts +38 -17
- package/lib/Exchange/Exchange.js +204 -159
- package/lib/Exchange/Exchange.js.map +1 -1
- package/lib/Exchange/ExchangeType.js +1 -2
- package/lib/Exchange/InitializeResult.js +1 -2
- package/lib/LoggerFactory.js +1 -5
- package/lib/LoggerFactory.js.map +1 -1
- package/lib/Message.d.ts +11 -10
- package/lib/Message.js +66 -37
- package/lib/Message.js.map +1 -1
- package/lib/Queue/DeclarationOptions.js +1 -2
- package/lib/Queue/DeleteResult.js +1 -2
- package/lib/Queue/InitializeResult.js +1 -2
- package/lib/Queue/Queue.d.ts +87 -26
- package/lib/Queue/Queue.js +440 -207
- package/lib/Queue/Queue.js.map +1 -1
- package/lib/Queue/StartConsumerResult.js +1 -2
- package/lib/amqp-ts.d.ts +0 -1
- package/lib/amqp-ts.js +2 -7
- package/lib/amqp-ts.js.map +1 -1
- package/lib/errorMessage.d.ts +1 -0
- package/lib/errorMessage.js +18 -0
- package/lib/errorMessage.js.map +1 -0
- package/lib/index.d.ts +19 -14
- package/lib/index.js +7 -13
- package/lib/index.js.map +1 -1
- package/package.json +38 -60
- package/lib/Connection/ReconnectStrategy.d.ts +0 -4
- package/lib/Connection/ReconnectStrategy.js +0 -3
- package/lib/Connection/ReconnectStrategy.js.map +0 -1
package/lib/Queue/Queue.js
CHANGED
|
@@ -1,11 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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.
|
|
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
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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.
|
|
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
|
-
*
|
|
157
|
-
* is
|
|
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
|
-
|
|
160
|
-
|
|
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
|
|
409
|
+
const message = new Message(msg.content, msg.properties);
|
|
163
410
|
message.fields = msg.fields;
|
|
164
411
|
message._message = msg;
|
|
165
|
-
message._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
|
|
174
|
-
resultValue = new
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
475
|
+
return;
|
|
211
476
|
}
|
|
212
477
|
this._consumerStopping = true;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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 =
|
|
237
|
-
this.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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 =
|
|
282
|
-
this.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
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
|
-
|
|
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
|