rascal 13.1.3 → 14.2.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 (69) hide show
  1. package/.prettierrc.json +4 -1
  2. package/CHANGELOG.md +23 -0
  3. package/README.md +200 -209
  4. package/examples/advanced/cluster.js +4 -4
  5. package/examples/advanced/config.js +45 -50
  6. package/examples/advanced/handlers/deleteUser.js +9 -16
  7. package/examples/advanced/handlers/saveUser.js +11 -18
  8. package/examples/advanced/index.js +81 -103
  9. package/examples/busy-publisher/config.json +40 -0
  10. package/examples/busy-publisher/index.js +14 -20
  11. package/examples/default-exchange/config.json +25 -0
  12. package/examples/default-exchange/index.js +10 -15
  13. package/examples/mocha/config.json +21 -0
  14. package/examples/mocha/test.js +16 -24
  15. package/examples/promises/config.json +27 -0
  16. package/examples/promises/index.js +9 -14
  17. package/examples/simple/config.json +37 -0
  18. package/examples/simple/index.js +11 -15
  19. package/index.js +6 -6
  20. package/lib/amqp/Broker.js +65 -95
  21. package/lib/amqp/BrokerAsPromised.js +7 -16
  22. package/lib/amqp/Publication.js +72 -212
  23. package/lib/amqp/PublicationSession.js +8 -8
  24. package/lib/amqp/SubscriberError.js +107 -233
  25. package/lib/amqp/SubscriberSession.js +56 -76
  26. package/lib/amqp/SubscriberSessionAsPromised.js +3 -3
  27. package/lib/amqp/Subscription.js +96 -313
  28. package/lib/amqp/Vhost.js +120 -265
  29. package/lib/amqp/tasks/applyBindings.js +12 -42
  30. package/lib/amqp/tasks/assertExchanges.js +6 -11
  31. package/lib/amqp/tasks/assertQueues.js +4 -4
  32. package/lib/amqp/tasks/assertVhost.js +6 -4
  33. package/lib/amqp/tasks/checkExchanges.js +4 -4
  34. package/lib/amqp/tasks/checkQueues.js +4 -4
  35. package/lib/amqp/tasks/checkVhost.js +6 -4
  36. package/lib/amqp/tasks/closeChannel.js +3 -3
  37. package/lib/amqp/tasks/closeConnection.js +3 -3
  38. package/lib/amqp/tasks/createChannel.js +3 -3
  39. package/lib/amqp/tasks/createConnection.js +38 -53
  40. package/lib/amqp/tasks/deleteExchanges.js +5 -5
  41. package/lib/amqp/tasks/deleteQueues.js +4 -4
  42. package/lib/amqp/tasks/deleteVhost.js +12 -16
  43. package/lib/amqp/tasks/index.js +25 -25
  44. package/lib/amqp/tasks/initCounters.js +5 -6
  45. package/lib/amqp/tasks/initPublications.js +4 -4
  46. package/lib/amqp/tasks/initShovels.js +15 -20
  47. package/lib/amqp/tasks/initSubscriptions.js +5 -11
  48. package/lib/amqp/tasks/initVhosts.js +8 -8
  49. package/lib/amqp/tasks/purgeQueues.js +4 -4
  50. package/lib/backoff/exponential.js +6 -8
  51. package/lib/backoff/index.js +2 -2
  52. package/lib/backoff/linear.js +3 -3
  53. package/lib/config/baseline.js +15 -16
  54. package/lib/config/configure.js +68 -193
  55. package/lib/config/fqn.js +3 -3
  56. package/lib/config/schema.json +686 -0
  57. package/lib/config/tests.js +3 -3
  58. package/lib/config/validate.js +87 -458
  59. package/lib/counters/inMemory.js +3 -3
  60. package/lib/counters/inMemoryCluster.js +16 -23
  61. package/lib/counters/index.js +3 -3
  62. package/lib/management/Client.js +55 -0
  63. package/package.json +2 -1
  64. package/examples/busy-publisher/config.js +0 -39
  65. package/examples/default-exchange/config.js +0 -24
  66. package/examples/mocha/config.js +0 -20
  67. package/examples/promises/config.js +0 -26
  68. package/examples/simple/config.js +0 -36
  69. package/lib/management/client.js +0 -90
@@ -1,55 +1,34 @@
1
- const debug = require("debug")("rascal:SubscriberError");
2
- const format = require("util").format;
3
- const _ = require("lodash");
4
- const async = require("async");
5
- const setTimeoutUnref = require("../utils/setTimeoutUnref");
1
+ const debug = require('debug')('rascal:SubscriberError');
2
+ const format = require('util').format;
3
+ const _ = require('lodash');
4
+ const async = require('async');
5
+ const setTimeoutUnref = require('../utils/setTimeoutUnref');
6
6
 
7
7
  module.exports = function SubscriptionRecovery(broker, vhost) {
8
8
  this.handle = function (session, message, err, recoveryProcess, next) {
9
- debug(
10
- "Handling subscriber error for message: %s",
11
- message.properties.messageId
12
- );
9
+ debug('Handling subscriber error for message: %s', message.properties.messageId);
13
10
 
14
11
  async.eachSeries(
15
- [].concat(recoveryProcess || []).concat({ strategy: "fallback-nack" }),
12
+ [].concat(recoveryProcess || []).concat({ strategy: 'fallback-nack' }),
16
13
  (recoveryConfig, cb) => {
17
- debug(
18
- "Attempting to recover message: %s using strategy: %s",
19
- message.properties.messageId,
20
- recoveryConfig.strategy
21
- );
14
+ debug('Attempting to recover message: %s using strategy: %s', message.properties.messageId, recoveryConfig.strategy);
22
15
 
23
16
  const once = _.once(cb);
24
17
 
25
18
  setTimeoutUnref(() => {
26
- getStrategy(recoveryConfig).execute(
27
- session,
28
- message,
29
- err,
30
- _.omit(recoveryConfig, "defer"),
31
- (err, handled) => {
32
- if (err) {
33
- debug(
34
- "Message: %s failed to be recovered using stragegy: %s",
35
- message.properties.messageId,
36
- recoveryConfig.strategy
37
- );
38
- setImmediate(() => next(err));
39
- return once(false);
40
- }
41
- if (handled) {
42
- debug(
43
- "Message: %s was recovered using stragegy: %s",
44
- message.properties.messageId,
45
- recoveryConfig.strategy
46
- );
47
- setImmediate(next);
48
- return once(false);
49
- }
50
- once();
19
+ getStrategy(recoveryConfig).execute(session, message, err, _.omit(recoveryConfig, 'defer'), (err, handled) => {
20
+ if (err) {
21
+ debug('Message: %s failed to be recovered using stragegy: %s', message.properties.messageId, recoveryConfig.strategy);
22
+ setImmediate(() => next(err));
23
+ return once(false);
24
+ }
25
+ if (handled) {
26
+ debug('Message: %s was recovered using stragegy: %s', message.properties.messageId, recoveryConfig.strategy);
27
+ setImmediate(next);
28
+ return once(false);
51
29
  }
52
- );
30
+ once();
31
+ });
53
32
  }, recoveryConfig.defer);
54
33
  },
55
34
  next
@@ -59,7 +38,7 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
59
38
  const recoveryStrategies = _.keyBy(
60
39
  [
61
40
  {
62
- name: "ack",
41
+ name: 'ack',
63
42
  execute(session, message, err, strategyConfig, next) {
64
43
  session._ack(message, (err) => {
65
44
  next(err, true);
@@ -67,7 +46,7 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
67
46
  },
68
47
  },
69
48
  {
70
- name: "nack",
49
+ name: 'nack',
71
50
  execute(session, message, err, strategyConfig, next) {
72
51
  session._nack(message, { requeue: strategyConfig.requeue }, (err) => {
73
52
  next(err, true);
@@ -75,7 +54,7 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
75
54
  },
76
55
  },
77
56
  {
78
- name: "fallback-nack",
57
+ name: 'fallback-nack',
79
58
  execute(session, message, err, strategyConfig, next) {
80
59
  session._nack(message, { requeue: strategyConfig.requeue }, (err) => {
81
60
  next(err, true);
@@ -83,229 +62,124 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
83
62
  },
84
63
  },
85
64
  {
86
- name: "republish",
65
+ name: 'republish',
87
66
  execute(session, message, err, strategyConfig, next) {
88
- debug("Republishing message: %s", message.properties.messageId);
67
+ debug('Republishing message: %s', message.properties.messageId);
89
68
 
90
- const originalQueue = _.get(
91
- message,
92
- "properties.headers.rascal.originalQueue"
93
- );
94
- const republished = _.get(
95
- message,
96
- [
97
- "properties",
98
- "headers",
99
- "rascal",
100
- "recovery",
101
- originalQueue,
102
- "republished",
103
- ],
104
- 0
105
- );
69
+ const once = _.once(next);
70
+ const originalQueue = _.get(message, 'properties.headers.rascal.originalQueue');
71
+ const republished = _.get(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'republished'], 0);
106
72
 
107
- if (
108
- strategyConfig.attempts &&
109
- strategyConfig.attempts <= republished
110
- ) {
111
- debug(
112
- "Skipping recovery - message: %s has already been republished %d times.",
113
- message.properties.messageId,
114
- republished
115
- );
116
- return next(null, false);
73
+ if (strategyConfig.attempts && strategyConfig.attempts <= republished) {
74
+ debug('Skipping recovery - message: %s has already been republished %d times.', message.properties.messageId, republished);
75
+ return once(null, false);
117
76
  }
118
77
 
119
78
  const publishOptions = _.cloneDeep(message.properties);
120
- _.set(
121
- publishOptions,
122
- ["headers", "rascal", "recovery", originalQueue, "republished"],
123
- republished + 1
124
- );
125
- _.set(
126
- publishOptions,
127
- "headers.rascal.originalExchange",
128
- message.fields.exchange
129
- );
130
- _.set(
131
- publishOptions,
132
- "headers.rascal.originalRoutingKey",
133
- message.fields.routingKey
134
- );
135
- _.set(
136
- publishOptions,
137
- "headers.rascal.error.message",
138
- _.truncate(err.message, { length: 1024 })
139
- );
140
- _.set(publishOptions, "headers.rascal.error.code", err.code);
141
- _.set(
142
- publishOptions,
143
- "headers.rascal.restoreRoutingHeaders",
144
- _.has(strategyConfig, "restoreRoutingHeaders")
145
- ? strategyConfig.restoreRoutingHeaders
146
- : true
147
- );
79
+ _.set(publishOptions, ['headers', 'rascal', 'recovery', originalQueue, 'republished'], republished + 1);
80
+ _.set(publishOptions, 'headers.rascal.originalExchange', message.fields.exchange);
81
+ _.set(publishOptions, 'headers.rascal.originalRoutingKey', message.fields.routingKey);
82
+ _.set(publishOptions, 'headers.rascal.error.message', _.truncate(err.message, { length: 1024 }));
83
+ _.set(publishOptions, 'headers.rascal.error.code', err.code);
84
+ _.set(publishOptions, 'headers.rascal.restoreRoutingHeaders', _.has(strategyConfig, 'restoreRoutingHeaders') ? strategyConfig.restoreRoutingHeaders : true);
148
85
 
149
- if (strategyConfig.immediateNack)
150
- _.set(
151
- publishOptions,
152
- ["headers", "rascal", "recovery", originalQueue, "immediateNack"],
153
- true
154
- );
86
+ if (strategyConfig.immediateNack) _.set(publishOptions, ['headers', 'rascal', 'recovery', originalQueue, 'immediateNack'], true);
155
87
 
156
88
  vhost.getConfirmChannel((err, publisherChannel) => {
157
- if (err) return next(err);
158
- if (!publisherChannel)
159
- return next(
160
- new Error(
161
- "Unable to handle subscriber error by republishing. The VHost is shutting down"
162
- )
163
- );
89
+ const nackMessage = (err) => {
90
+ session._nack(message, (nackErr) => {
91
+ // nackError just means the channel was already closed meaning the original message would have been rolled back
92
+ once(err);
93
+ });
94
+ };
164
95
 
165
- publisherChannel.on("error", next);
96
+ if (err) return nackMessage(err);
166
97
 
167
- publisherChannel.publish(
168
- undefined,
169
- originalQueue,
170
- message.content,
171
- publishOptions,
172
- (err) => {
173
- if (err) return next(err); // Channel will already be closed, reclosing will trigger an error
174
- publisherChannel.close();
175
- debug(
176
- "Message: %s was republished to queue: %s %d times",
177
- message.properties.messageId,
178
- originalQueue,
179
- republished + 1
180
- );
181
- session._ack(message, (err) => {
182
- next(err, true);
183
- });
184
- }
185
- );
98
+ if (!publisherChannel) return nackMessage(new Error('Unable to handle subscriber error by republishing. The VHost is shutting down'));
99
+
100
+ publisherChannel.on('error', (err) => {
101
+ nackMessage(err);
102
+ });
103
+ publisherChannel.on('return', () => {
104
+ nackMessage(new Error(format('Message: %s was republished to queue: %s, but was returned', message.properties.messageId, originalQueue)));
105
+ });
106
+
107
+ publisherChannel.publish(undefined, originalQueue, message.content, publishOptions, (err) => {
108
+ if (err) return nackMessage(err); // Channel will already be closed, reclosing will trigger an error
109
+
110
+ publisherChannel.close();
111
+ debug('Message: %s was republished to queue: %s %d times', message.properties.messageId, originalQueue, republished + 1);
112
+ session._ack(message, (err) => {
113
+ once(err, true);
114
+ });
115
+ });
186
116
  });
187
117
  },
188
118
  },
189
119
  {
190
- name: "forward",
120
+ name: 'forward',
191
121
  execute(session, message, err, strategyConfig, next) {
192
- debug("Forwarding message: %s", message.properties.messageId);
122
+ debug('Forwarding message: %s to publication: %s', message.properties.messageId, strategyConfig.publication);
193
123
 
194
- const originalQueue = _.get(
195
- message,
196
- "properties.headers.rascal.originalQueue"
197
- );
198
- const forwarded = _.get(
199
- message,
200
- [
201
- "properties",
202
- "headers",
203
- "rascal",
204
- "recovery",
205
- originalQueue,
206
- "forwarded",
207
- ],
208
- 0
209
- );
124
+ const once = _.once(next);
125
+ const originalQueue = _.get(message, 'properties.headers.rascal.originalQueue');
126
+ const forwarded = _.get(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'forwarded'], 0);
210
127
 
211
128
  if (strategyConfig.attempts && strategyConfig.attempts <= forwarded) {
212
- debug(
213
- "Skipping recovery - message: %s has already been forwarded %d times.",
214
- message.properties.messageId,
215
- forwarded
216
- );
217
- return next(null, false);
129
+ debug('Skipping recovery - message: %s has already been forwarded %d times.', message.properties.messageId, forwarded);
130
+ return once(null, false);
218
131
  }
219
132
 
220
133
  // See https://github.com/rabbitmq/rabbitmq-server/issues/161
221
- if (strategyConfig.xDeathFix)
222
- delete message.properties.headers["x-death"];
134
+ if (strategyConfig.xDeathFix) delete message.properties.headers['x-death'];
223
135
 
224
136
  const forwardOverrides = _.cloneDeep(strategyConfig.options) || {};
225
- _.set(
226
- forwardOverrides,
227
- "restoreRoutingHeaders",
228
- _.has(strategyConfig, "restoreRoutingHeaders")
229
- ? strategyConfig.restoreRoutingHeaders
230
- : true
231
- );
232
- _.set(
233
- forwardOverrides,
234
- [
235
- "options",
236
- "headers",
237
- "rascal",
238
- "recovery",
239
- originalQueue,
240
- "forwarded",
241
- ],
242
- forwarded + 1
243
- );
244
- _.set(
245
- forwardOverrides,
246
- "options.headers.rascal.error.message",
247
- _.truncate(err.message, { length: 1024 })
248
- );
249
- _.set(
250
- forwardOverrides,
251
- "options.headers.rascal.error.code",
252
- err.code
253
- );
254
-
255
- broker.forward(
256
- strategyConfig.publication,
257
- message,
258
- forwardOverrides,
259
- (err, publication) => {
260
- if (err) return next(err);
261
- publication.on("success", () => {
262
- debug(
263
- "Message: %s was forwarded to publication: %s %d times",
264
- message.properties.messageId,
265
- strategyConfig.publication,
266
- forwarded + 1
267
- );
268
- session._ack(message, (err) => {
269
- next(err, true);
270
- });
271
- });
272
- publication.on("error", next);
273
- publication.on("return", () => {
274
- next(
275
- new Error(
276
- format(
277
- "Message: %s was forwared to publication: %s, but was returned",
278
- message.properties.messageId,
279
- strategyConfig.publication
280
- )
281
- )
282
- );
137
+ _.set(forwardOverrides, 'restoreRoutingHeaders', _.has(strategyConfig, 'restoreRoutingHeaders') ? strategyConfig.restoreRoutingHeaders : true);
138
+ _.set(forwardOverrides, ['options', 'headers', 'rascal', 'recovery', originalQueue, 'forwarded'], forwarded + 1);
139
+ _.set(forwardOverrides, 'options.headers.rascal.error.message', _.truncate(err.message, { length: 1024 }));
140
+ _.set(forwardOverrides, 'options.headers.rascal.error.code', err.code);
141
+
142
+ const nackMessage = (err) => {
143
+ session._nack(message, (nackErr) => {
144
+ // nackError just means the channel was already closed meaning the original message would have been rolled back
145
+ once(err);
146
+ });
147
+ };
148
+
149
+ broker.forward(strategyConfig.publication, message, forwardOverrides, (err, publication) => {
150
+ if (err) return nackMessage(err);
151
+
152
+ publication.on('success', () => {
153
+ debug('Message: %s was forwarded to publication: %s %d times', message.properties.messageId, strategyConfig.publication, forwarded + 1);
154
+ session._ack(message, (ackErr) => {
155
+ once(ackErr, true);
283
156
  });
284
- }
285
- );
157
+ });
158
+
159
+ publication.on('error', (err) => {
160
+ nackMessage(err);
161
+ });
162
+
163
+ publication.on('return', () => {
164
+ publication.removeAllListeners('success');
165
+ nackMessage(new Error(format('Message: %s was forwarded to publication: %s, but was returned', message.properties.messageId, strategyConfig.publication)));
166
+ });
167
+ });
286
168
  },
287
169
  },
288
170
  {
289
- name: "unknown",
171
+ name: 'unknown',
290
172
  execute(session, message, err, strategyConfig, next) {
291
- next(
292
- new Error(
293
- format(
294
- "Error recovering message: %s. No such strategy: %s.",
295
- message.properties.messageId,
296
- strategyConfig.strategy
297
- )
298
- )
299
- );
173
+ session._nack(message, () => {
174
+ next(new Error(format('Error recovering message: %s. No such strategy: %s.', message.properties.messageId, strategyConfig.strategy)));
175
+ });
300
176
  },
301
177
  },
302
178
  ],
303
- "name"
179
+ 'name'
304
180
  );
305
181
 
306
182
  function getStrategy(recoveryConfig) {
307
- return (
308
- recoveryStrategies[recoveryConfig.strategy] || recoveryStrategies.unknown
309
- );
183
+ return recoveryStrategies[recoveryConfig.strategy] || recoveryStrategies.unknown;
310
184
  }
311
185
  };
@@ -1,8 +1,9 @@
1
- const debug = require("debug")("rascal:SubscriberSession");
2
- const EventEmitter = require("events").EventEmitter;
3
- const inherits = require("util").inherits;
4
- const _ = require("lodash");
5
- const setTimeoutUnref = require("../utils/setTimeoutUnref");
1
+ const debug = require('debug')('rascal:SubscriberSession');
2
+ const EventEmitter = require('events').EventEmitter;
3
+ const inherits = require('util').inherits;
4
+ const _ = require('lodash');
5
+ const async = require('async');
6
+ const setTimeoutUnref = require('../utils/setTimeoutUnref');
6
7
 
7
8
  module.exports = SubscriberSession;
8
9
 
@@ -23,15 +24,11 @@ function SubscriberSession(sequentialChannelOperations, config) {
23
24
  };
24
25
 
25
26
  this._open = function (channel, consumerTag, next) {
26
- if (cancelled) return next(new Error("Subscriber has been cancelled"));
27
- debug(
28
- "Opening subscriber session: %s on channel: %s",
29
- consumerTag,
30
- channel._rascal_id
31
- );
32
- channels[consumerTag] = { index: index++, channel, consumerTag };
33
- channel.once("close", unref.bind(null, consumerTag));
34
- channel.once("error", unref.bind(null, consumerTag));
27
+ if (cancelled) return next(new Error('Subscriber has been cancelled'));
28
+ debug('Opening subscriber session: %s on channel: %s', consumerTag, channel._rascal_id);
29
+ channels[consumerTag] = { index: index++, channel, consumerTag, unacknowledgedMessages: 0 };
30
+ channel.once('close', unref.bind(null, consumerTag));
31
+ channel.once('error', unref.bind(null, consumerTag));
35
32
  next();
36
33
  };
37
34
 
@@ -51,20 +48,22 @@ function SubscriberSession(sequentialChannelOperations, config) {
51
48
 
52
49
  this._unsafeClose = function (next) {
53
50
  withCurrentChannel(
54
- (channel, consumerTag) => {
55
- debug(
56
- "Cancelling subscriber session: %s on channel: %s",
57
- consumerTag,
58
- channel._rascal_id
59
- );
51
+ (channel, consumerTag, entry) => {
52
+ entry.doomed = true;
53
+ debug('Cancelling subscriber session: %s on channel: %s', consumerTag, channel._rascal_id);
60
54
  channel.cancel(consumerTag, (err) => {
61
55
  if (err) return next(err);
62
- doom(consumerTag);
63
- next();
56
+ const waitOrTimeout = config.closeTimeout ? async.timeout(waitForUnacknowledgedMessages, config.closeTimeout) : waitForUnacknowledgedMessages;
57
+ waitOrTimeout(entry, null, (err) => {
58
+ channel.close(() => {
59
+ debug('Channel: %s was closed', entry.channel._rascal_id);
60
+ next(err);
61
+ });
62
+ });
64
63
  });
65
64
  },
66
65
  () => {
67
- debug("No current subscriber session");
66
+ debug('No current subscriber session');
68
67
  next();
69
68
  }
70
69
  );
@@ -74,10 +73,6 @@ function SubscriberSession(sequentialChannelOperations, config) {
74
73
  timeout = setTimeoutUnref(fn, delay);
75
74
  };
76
75
 
77
- this._maxDeferCloseChannel = function (other) {
78
- return Math.max(config.deferCloseChannel, other);
79
- };
80
-
81
76
  this._getRascalChannelId = function () {
82
77
  let rascalChannelId = null;
83
78
  withCurrentChannel((channel) => {
@@ -86,46 +81,50 @@ function SubscriberSession(sequentialChannelOperations, config) {
86
81
  return rascalChannelId;
87
82
  };
88
83
 
84
+ this._incrementUnacknowledgeMessageCount = function (consumerTag) {
85
+ if (config.options.noAck) return;
86
+ withConsumerChannel(consumerTag, (channel, __, entry) => {
87
+ debug('Channel: %s has %s unacknowledged messages', channel._rascal_id, ++entry.unacknowledgedMessages);
88
+ });
89
+ };
90
+
91
+ this._decrementUnacknowledgeMessageCount = function (consumerTag) {
92
+ if (config.options.noAck) return;
93
+ withConsumerChannel(consumerTag, (channel, __, entry) => {
94
+ debug('Channel: %s has %s unacknowledged messages', channel._rascal_id, --entry.unacknowledgedMessages);
95
+ });
96
+ };
97
+
89
98
  this._ack = function (message, next) {
90
99
  withConsumerChannel(
91
100
  message.fields.consumerTag,
92
101
  (channel) => {
93
- debug(
94
- "Acknowledging message: %s on channel: %s",
95
- message.properties.messageId,
96
- channel._rascal_id
97
- );
102
+ debug('Acknowledging message: %s on channel: %s', message.properties.messageId, channel._rascal_id);
98
103
  channel.ack(message);
104
+ self._decrementUnacknowledgeMessageCount(message.fields.consumerTag);
99
105
  setImmediate(next);
100
106
  },
101
107
  () => {
102
108
  setImmediate(() => {
103
- next(new Error("The channel has been closed. Unable to ack message"));
109
+ next(new Error('The channel has been closed. Unable to ack message'));
104
110
  });
105
111
  }
106
112
  );
107
113
  };
108
114
 
109
115
  this._nack = function (message, options, next) {
110
- if (arguments.length === 2)
111
- return self._nack(arguments[0], {}, arguments[1]);
116
+ if (arguments.length === 2) return self._nack(arguments[0], {}, arguments[1]);
112
117
  withConsumerChannel(
113
118
  message.fields.consumerTag,
114
119
  (channel) => {
115
- debug(
116
- "Not acknowledging message: %s with requeue: %s on channel: %s",
117
- message.properties.messageId,
118
- !!options.requeue,
119
- channel._rascal_id
120
- );
120
+ debug('Not acknowledging message: %s with requeue: %s on channel: %s', message.properties.messageId, !!options.requeue, channel._rascal_id);
121
121
  channel.nack(message, false, !!options.requeue);
122
+ self._decrementUnacknowledgeMessageCount(message.fields.consumerTag);
122
123
  setImmediate(next);
123
124
  },
124
125
  () => {
125
126
  setImmediate(() => {
126
- next(
127
- new Error("The channel has been closed. Unable to nack message")
128
- );
127
+ next(new Error('The channel has been closed. Unable to nack message'));
129
128
  });
130
129
  }
131
130
  );
@@ -134,10 +133,8 @@ function SubscriberSession(sequentialChannelOperations, config) {
134
133
  function withCurrentChannel(fn, altFn) {
135
134
  const entry = _.chain(channels)
136
135
  .values()
137
- .filter((channel) => {
138
- return !channel.doomed;
139
- })
140
- .sortBy("index")
136
+ .filter((entry) => !entry.doomed)
137
+ .sortBy('index')
141
138
  .last()
142
139
  .value();
143
140
  if (entry) return fn(entry.channel, entry.consumerTag, entry);
@@ -152,37 +149,20 @@ function SubscriberSession(sequentialChannelOperations, config) {
152
149
 
153
150
  function unref(consumerTag) {
154
151
  withConsumerChannel(consumerTag, (channel) => {
155
- debug("Removing channel: %s from session", channel._rascal_id);
152
+ debug('Removing channel: %s from session', channel._rascal_id);
156
153
  delete channels[consumerTag];
157
154
  });
158
155
  }
159
156
 
160
- function doom(consumerTag) {
161
- withConsumerChannel(consumerTag, (channel, consumerTag, entry) => {
162
- if (entry.doomed) return;
163
- entry.doomed = true;
164
- scheduleClose(entry);
165
- });
166
- }
167
-
168
- /*
169
- There may still be delivered messages that have yet to be ack or nacked
170
- but no way of telling how many are outstanding since due to potentially
171
- complicated recovery strategies, with timeouts etc.
172
- Keeping channels around for a minute shouldn't hurt
173
- */
174
- function scheduleClose(entry) {
175
- debug(
176
- "Deferring close channel: %s by %dms",
177
- entry.channel._rascal_id,
178
- config.deferCloseChannel
179
- );
180
- setTimeoutUnref(() => {
181
- withConsumerChannel(entry.consumerTag, (channel) => {
182
- channel.close(() => {
183
- debug("Channel: %s was closed", channel._rascal_id);
184
- });
185
- });
186
- }, config.deferCloseChannel);
157
+ function waitForUnacknowledgedMessages(entry, previousCount, next) {
158
+ const currentCount = entry.unacknowledgedMessages;
159
+ if (currentCount > 0) {
160
+ if (currentCount !== previousCount) {
161
+ debug('Waiting for %d unacknowledged messages from channel: %s', currentCount, entry.channel._rascal_id);
162
+ }
163
+ setTimeoutUnref(() => waitForUnacknowledgedMessages(entry, currentCount, next), 100);
164
+ return;
165
+ }
166
+ next();
187
167
  }
188
168
  }
@@ -1,6 +1,6 @@
1
- const EventEmitter = require("events").EventEmitter;
2
- const inherits = require("util").inherits;
3
- const forwardEvents = require("forward-emitter");
1
+ const EventEmitter = require('events').EventEmitter;
2
+ const inherits = require('util').inherits;
3
+ const forwardEvents = require('forward-emitter');
4
4
 
5
5
  module.exports = SubscriberSessionAsPromised;
6
6