rascal 13.0.6 → 13.1.3
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/.husky/pre-commit +1 -1
- package/.prettierrc.json +1 -0
- package/CHANGELOG.md +204 -1
- package/README.md +634 -396
- package/examples/advanced/cluster.js +8 -8
- package/examples/advanced/config.js +117 -114
- package/examples/advanced/handlers/deleteUser.js +23 -17
- package/examples/advanced/handlers/saveUser.js +38 -32
- package/examples/advanced/index.js +105 -78
- package/examples/busy-publisher/config.js +14 -17
- package/examples/busy-publisher/index.js +27 -22
- package/examples/default-exchange/config.js +10 -10
- package/examples/default-exchange/index.js +27 -18
- package/examples/mocha/config.js +9 -11
- package/examples/mocha/test.js +42 -35
- package/examples/promises/config.js +11 -13
- package/examples/promises/index.js +24 -17
- package/examples/simple/config.js +18 -18
- package/examples/simple/index.js +25 -23
- package/index.js +7 -7
- package/lib/amqp/Broker.js +154 -99
- package/lib/amqp/BrokerAsPromised.js +56 -35
- package/lib/amqp/Publication.js +219 -78
- package/lib/amqp/PublicationSession.js +13 -14
- package/lib/amqp/SubscriberError.js +293 -132
- package/lib/amqp/SubscriberSession.js +95 -56
- package/lib/amqp/SubscriberSessionAsPromised.js +4 -6
- package/lib/amqp/Subscription.js +328 -110
- package/lib/amqp/Vhost.js +366 -171
- package/lib/amqp/tasks/applyBindings.js +51 -18
- package/lib/amqp/tasks/assertExchanges.js +20 -11
- package/lib/amqp/tasks/assertQueues.js +13 -9
- package/lib/amqp/tasks/assertVhost.js +21 -17
- package/lib/amqp/tasks/bounceVhost.js +1 -1
- package/lib/amqp/tasks/checkExchanges.js +13 -9
- package/lib/amqp/tasks/checkQueues.js +13 -9
- package/lib/amqp/tasks/checkVhost.js +21 -17
- package/lib/amqp/tasks/closeChannel.js +3 -4
- package/lib/amqp/tasks/closeConnection.js +3 -3
- package/lib/amqp/tasks/createChannel.js +3 -4
- package/lib/amqp/tasks/createConnection.js +71 -51
- package/lib/amqp/tasks/deleteExchanges.js +14 -10
- package/lib/amqp/tasks/deleteQueues.js +13 -9
- package/lib/amqp/tasks/deleteVhost.js +26 -17
- package/lib/amqp/tasks/forewarnVhost.js +1 -1
- package/lib/amqp/tasks/index.js +25 -25
- package/lib/amqp/tasks/initCounters.js +18 -13
- package/lib/amqp/tasks/initPublications.js +17 -13
- package/lib/amqp/tasks/initShovels.js +29 -20
- package/lib/amqp/tasks/initSubscriptions.js +23 -13
- package/lib/amqp/tasks/initVhosts.js +21 -17
- package/lib/amqp/tasks/nukeVhost.js +1 -1
- package/lib/amqp/tasks/purgeQueues.js +13 -9
- package/lib/amqp/tasks/purgeVhost.js +1 -1
- package/lib/amqp/tasks/shutdownVhost.js +1 -1
- package/lib/backoff/exponential.js +9 -8
- package/lib/backoff/index.js +3 -3
- package/lib/backoff/linear.js +5 -7
- package/lib/config/baseline.js +26 -34
- package/lib/config/configure.js +274 -101
- package/lib/config/fqn.js +3 -3
- package/lib/config/tests.js +32 -29
- package/lib/config/validate.js +460 -70
- package/lib/counters/inMemory.js +3 -3
- package/lib/counters/inMemoryCluster.js +48 -30
- package/lib/counters/index.js +4 -4
- package/lib/counters/stub.js +2 -3
- package/lib/management/client.js +47 -17
- package/lib/utils/setTimeoutUnref.js +1 -1
- package/package.json +12 -4
|
@@ -1,150 +1,311 @@
|
|
|
1
|
-
const debug = require(
|
|
2
|
-
const format = require(
|
|
3
|
-
const _ = require(
|
|
4
|
-
const async = require(
|
|
5
|
-
const setTimeoutUnref = require(
|
|
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
|
+
this.handle = function (session, message, err, recoveryProcess, next) {
|
|
9
|
+
debug(
|
|
10
|
+
"Handling subscriber error for message: %s",
|
|
11
|
+
message.properties.messageId
|
|
12
|
+
);
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
async.eachSeries(
|
|
15
|
+
[].concat(recoveryProcess || []).concat({ strategy: "fallback-nack" }),
|
|
16
|
+
(recoveryConfig, cb) => {
|
|
17
|
+
debug(
|
|
18
|
+
"Attempting to recover message: %s using strategy: %s",
|
|
19
|
+
message.properties.messageId,
|
|
20
|
+
recoveryConfig.strategy
|
|
21
|
+
);
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
const once = _.once(cb);
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
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();
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}, recoveryConfig.defer);
|
|
54
|
+
},
|
|
55
|
+
next
|
|
56
|
+
);
|
|
34
57
|
};
|
|
35
58
|
|
|
36
|
-
const recoveryStrategies = _.keyBy(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
session
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
name: 'nack',
|
|
47
|
-
execute(session, message, err, strategyConfig, next) {
|
|
48
|
-
session._nack(message, { requeue: strategyConfig.requeue }, (err) => {
|
|
49
|
-
next(err, true);
|
|
50
|
-
});
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'fallback-nack',
|
|
55
|
-
execute(session, message, err, strategyConfig, next) {
|
|
56
|
-
session._nack(message, { requeue: strategyConfig.requeue }, (err) => {
|
|
57
|
-
next(err, true);
|
|
58
|
-
});
|
|
59
|
+
const recoveryStrategies = _.keyBy(
|
|
60
|
+
[
|
|
61
|
+
{
|
|
62
|
+
name: "ack",
|
|
63
|
+
execute(session, message, err, strategyConfig, next) {
|
|
64
|
+
session._ack(message, (err) => {
|
|
65
|
+
next(err, true);
|
|
66
|
+
});
|
|
67
|
+
},
|
|
59
68
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const originalQueue = _.get(message, 'properties.headers.rascal.originalQueue');
|
|
67
|
-
const republished = _.get(message, format('properties.headers.rascal.recovery.%s.republished', originalQueue), 0);
|
|
68
|
-
|
|
69
|
-
if (strategyConfig.attempts && strategyConfig.attempts <= republished) {
|
|
70
|
-
debug('Skipping recovery - message: %s has already been republished %d times.', message.properties.messageId, republished);
|
|
71
|
-
return next(null, false);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const publishOptions = _.cloneDeep(message.properties);
|
|
75
|
-
_.set(publishOptions, format('headers.rascal.recovery.%s.republished', originalQueue), republished + 1);
|
|
76
|
-
_.set(publishOptions, 'headers.rascal.originalExchange', message.fields.exchange);
|
|
77
|
-
_.set(publishOptions, 'headers.rascal.originalRoutingKey', message.fields.routingKey);
|
|
78
|
-
_.set(publishOptions, 'headers.rascal.error.message', _.truncate(err.message, { length: 1024 }));
|
|
79
|
-
_.set(publishOptions, 'headers.rascal.error.code', err.code);
|
|
80
|
-
_.set(publishOptions, 'headers.rascal.restoreRoutingHeaders', _.has(strategyConfig, 'restoreRoutingHeaders') ? strategyConfig.restoreRoutingHeaders : true);
|
|
81
|
-
|
|
82
|
-
if (strategyConfig.immediateNack) _.set(publishOptions, format('headers.rascal.recovery.%s.immediateNack', originalQueue), true);
|
|
83
|
-
|
|
84
|
-
vhost.getConfirmChannel((err, publisherChannel) => {
|
|
85
|
-
|
|
86
|
-
if (err) return next(err);
|
|
87
|
-
if (!publisherChannel) return next(new Error('Unable to handle subscriber error by republishing. The VHost is shutting down'));
|
|
88
|
-
|
|
89
|
-
publisherChannel.on('error', next);
|
|
90
|
-
|
|
91
|
-
publisherChannel.publish(undefined, originalQueue, message.content, publishOptions, (err) => {
|
|
92
|
-
if (err) return next(err); // Channel will already be closed, reclosing will trigger an error
|
|
93
|
-
publisherChannel.close();
|
|
94
|
-
debug('Message: %s was republished to queue: %s %d times', message.properties.messageId, originalQueue, republished + 1);
|
|
95
|
-
session._ack(message, (err) => {
|
|
96
|
-
next(err, true);
|
|
97
|
-
});
|
|
69
|
+
{
|
|
70
|
+
name: "nack",
|
|
71
|
+
execute(session, message, err, strategyConfig, next) {
|
|
72
|
+
session._nack(message, { requeue: strategyConfig.requeue }, (err) => {
|
|
73
|
+
next(err, true);
|
|
98
74
|
});
|
|
99
|
-
}
|
|
75
|
+
},
|
|
100
76
|
},
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const originalQueue = _.get(message, 'properties.headers.rascal.originalQueue');
|
|
108
|
-
const forwarded = _.get(message, format('properties.headers.rascal.recovery.%s.forwarded', originalQueue), 0);
|
|
109
|
-
|
|
110
|
-
if (strategyConfig.attempts && strategyConfig.attempts <= forwarded) {
|
|
111
|
-
debug('Skipping recovery - message: %s has already been forwarded %d times.', message.properties.messageId, forwarded);
|
|
112
|
-
return next(null, false);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// See https://github.com/rabbitmq/rabbitmq-server/issues/161
|
|
116
|
-
if (strategyConfig.xDeathFix) delete message.properties.headers['x-death'];
|
|
117
|
-
|
|
118
|
-
const forwardOverrides = _.cloneDeep(strategyConfig.options) || {};
|
|
119
|
-
_.set(forwardOverrides, 'restoreRoutingHeaders', _.has(strategyConfig, 'restoreRoutingHeaders') ? strategyConfig.restoreRoutingHeaders : true);
|
|
120
|
-
_.set(forwardOverrides, format('options.headers.rascal.recovery.%s.forwarded', originalQueue), forwarded + 1);
|
|
121
|
-
_.set(forwardOverrides, 'options.headers.rascal.error.message', _.truncate(err.message, { length: 1024 }));
|
|
122
|
-
_.set(forwardOverrides, 'options.headers.rascal.error.code', err.code);
|
|
123
|
-
|
|
124
|
-
broker.forward(strategyConfig.publication, message, forwardOverrides, (err, publication) => {
|
|
125
|
-
if (err) return next(err);
|
|
126
|
-
publication.on('success', () => {
|
|
127
|
-
debug('Message: %s was forwarded to publication: %s %d times', message.properties.messageId, strategyConfig.publication, forwarded + 1);
|
|
128
|
-
session._ack(message, (err) => {
|
|
129
|
-
next(err, true);
|
|
130
|
-
});
|
|
77
|
+
{
|
|
78
|
+
name: "fallback-nack",
|
|
79
|
+
execute(session, message, err, strategyConfig, next) {
|
|
80
|
+
session._nack(message, { requeue: strategyConfig.requeue }, (err) => {
|
|
81
|
+
next(err, true);
|
|
131
82
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "republish",
|
|
87
|
+
execute(session, message, err, strategyConfig, next) {
|
|
88
|
+
debug("Republishing message: %s", message.properties.messageId);
|
|
89
|
+
|
|
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
|
+
);
|
|
106
|
+
|
|
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);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
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
|
+
);
|
|
148
|
+
|
|
149
|
+
if (strategyConfig.immediateNack)
|
|
150
|
+
_.set(
|
|
151
|
+
publishOptions,
|
|
152
|
+
["headers", "rascal", "recovery", originalQueue, "immediateNack"],
|
|
153
|
+
true
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
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
|
+
);
|
|
164
|
+
|
|
165
|
+
publisherChannel.on("error", next);
|
|
166
|
+
|
|
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
|
+
);
|
|
135
186
|
});
|
|
136
|
-
}
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "forward",
|
|
191
|
+
execute(session, message, err, strategyConfig, next) {
|
|
192
|
+
debug("Forwarding message: %s", message.properties.messageId);
|
|
193
|
+
|
|
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
|
+
);
|
|
210
|
+
|
|
211
|
+
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);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// See https://github.com/rabbitmq/rabbitmq-server/issues/161
|
|
221
|
+
if (strategyConfig.xDeathFix)
|
|
222
|
+
delete message.properties.headers["x-death"];
|
|
223
|
+
|
|
224
|
+
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
|
+
);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
},
|
|
137
287
|
},
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
288
|
+
{
|
|
289
|
+
name: "unknown",
|
|
290
|
+
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
|
+
);
|
|
300
|
+
},
|
|
143
301
|
},
|
|
144
|
-
|
|
145
|
-
|
|
302
|
+
],
|
|
303
|
+
"name"
|
|
304
|
+
);
|
|
146
305
|
|
|
147
306
|
function getStrategy(recoveryConfig) {
|
|
148
|
-
return
|
|
307
|
+
return (
|
|
308
|
+
recoveryStrategies[recoveryConfig.strategy] || recoveryStrategies.unknown
|
|
309
|
+
);
|
|
149
310
|
}
|
|
150
311
|
};
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
const debug = require(
|
|
2
|
-
const EventEmitter = require(
|
|
3
|
-
const inherits = require(
|
|
4
|
-
const _ = require(
|
|
5
|
-
const setTimeoutUnref = require(
|
|
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");
|
|
6
6
|
|
|
7
7
|
module.exports = SubscriberSession;
|
|
8
8
|
|
|
9
9
|
inherits(SubscriberSession, EventEmitter);
|
|
10
10
|
|
|
11
11
|
function SubscriberSession(sequentialChannelOperations, config) {
|
|
12
|
-
|
|
13
12
|
let index = 0;
|
|
14
13
|
const channels = {};
|
|
15
14
|
let cancelled = false;
|
|
@@ -19,20 +18,24 @@ function SubscriberSession(sequentialChannelOperations, config) {
|
|
|
19
18
|
this.name = config.name;
|
|
20
19
|
this.config = _.cloneDeep(config);
|
|
21
20
|
|
|
22
|
-
this.isCancelled = function() {
|
|
21
|
+
this.isCancelled = function () {
|
|
23
22
|
return cancelled;
|
|
24
23
|
};
|
|
25
24
|
|
|
26
|
-
this._open = function(channel, consumerTag, next) {
|
|
27
|
-
if (cancelled) return next(new Error(
|
|
28
|
-
debug(
|
|
25
|
+
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
|
+
);
|
|
29
32
|
channels[consumerTag] = { index: index++, channel, consumerTag };
|
|
30
|
-
channel.once(
|
|
31
|
-
channel.once(
|
|
33
|
+
channel.once("close", unref.bind(null, consumerTag));
|
|
34
|
+
channel.once("error", unref.bind(null, consumerTag));
|
|
32
35
|
next();
|
|
33
36
|
};
|
|
34
37
|
|
|
35
|
-
this.cancel = function(next) {
|
|
38
|
+
this.cancel = function (next) {
|
|
36
39
|
clearTimeout(timeout);
|
|
37
40
|
sequentialChannelOperations.push((done) => {
|
|
38
41
|
cancelled = true;
|
|
@@ -40,35 +43,42 @@ function SubscriberSession(sequentialChannelOperations, config) {
|
|
|
40
43
|
}, next);
|
|
41
44
|
};
|
|
42
45
|
|
|
43
|
-
this._close = function(next) {
|
|
46
|
+
this._close = function (next) {
|
|
44
47
|
sequentialChannelOperations.push((done) => {
|
|
45
48
|
self._unsafeClose(done);
|
|
46
49
|
}, next);
|
|
47
50
|
};
|
|
48
51
|
|
|
49
|
-
this._unsafeClose = function(next) {
|
|
50
|
-
withCurrentChannel(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
this._unsafeClose = function (next) {
|
|
53
|
+
withCurrentChannel(
|
|
54
|
+
(channel, consumerTag) => {
|
|
55
|
+
debug(
|
|
56
|
+
"Cancelling subscriber session: %s on channel: %s",
|
|
57
|
+
consumerTag,
|
|
58
|
+
channel._rascal_id
|
|
59
|
+
);
|
|
60
|
+
channel.cancel(consumerTag, (err) => {
|
|
61
|
+
if (err) return next(err);
|
|
62
|
+
doom(consumerTag);
|
|
63
|
+
next();
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
() => {
|
|
67
|
+
debug("No current subscriber session");
|
|
55
68
|
next();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
debug('No current subscriber session');
|
|
59
|
-
next();
|
|
60
|
-
});
|
|
69
|
+
}
|
|
70
|
+
);
|
|
61
71
|
};
|
|
62
72
|
|
|
63
|
-
this._schedule = function(fn, delay) {
|
|
73
|
+
this._schedule = function (fn, delay) {
|
|
64
74
|
timeout = setTimeoutUnref(fn, delay);
|
|
65
75
|
};
|
|
66
76
|
|
|
67
|
-
this._maxDeferCloseChannel = function(other) {
|
|
68
|
-
return Math.max(config.deferCloseChannel, other)
|
|
77
|
+
this._maxDeferCloseChannel = function (other) {
|
|
78
|
+
return Math.max(config.deferCloseChannel, other);
|
|
69
79
|
};
|
|
70
80
|
|
|
71
|
-
this._getRascalChannelId = function() {
|
|
81
|
+
this._getRascalChannelId = function () {
|
|
72
82
|
let rascalChannelId = null;
|
|
73
83
|
withCurrentChannel((channel) => {
|
|
74
84
|
rascalChannelId = channel._rascal_id;
|
|
@@ -76,35 +86,60 @@ function SubscriberSession(sequentialChannelOperations, config) {
|
|
|
76
86
|
return rascalChannelId;
|
|
77
87
|
};
|
|
78
88
|
|
|
79
|
-
this._ack = function(message, next) {
|
|
80
|
-
withConsumerChannel(
|
|
81
|
-
|
|
82
|
-
channel
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
this._ack = function (message, next) {
|
|
90
|
+
withConsumerChannel(
|
|
91
|
+
message.fields.consumerTag,
|
|
92
|
+
(channel) => {
|
|
93
|
+
debug(
|
|
94
|
+
"Acknowledging message: %s on channel: %s",
|
|
95
|
+
message.properties.messageId,
|
|
96
|
+
channel._rascal_id
|
|
97
|
+
);
|
|
98
|
+
channel.ack(message);
|
|
99
|
+
setImmediate(next);
|
|
100
|
+
},
|
|
101
|
+
() => {
|
|
102
|
+
setImmediate(() => {
|
|
103
|
+
next(new Error("The channel has been closed. Unable to ack message"));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
);
|
|
89
107
|
};
|
|
90
108
|
|
|
91
|
-
this._nack = function(message, options, next) {
|
|
92
|
-
if (arguments.length === 2)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
this._nack = function (message, options, next) {
|
|
110
|
+
if (arguments.length === 2)
|
|
111
|
+
return self._nack(arguments[0], {}, arguments[1]);
|
|
112
|
+
withConsumerChannel(
|
|
113
|
+
message.fields.consumerTag,
|
|
114
|
+
(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
|
+
);
|
|
121
|
+
channel.nack(message, false, !!options.requeue);
|
|
122
|
+
setImmediate(next);
|
|
123
|
+
},
|
|
124
|
+
() => {
|
|
125
|
+
setImmediate(() => {
|
|
126
|
+
next(
|
|
127
|
+
new Error("The channel has been closed. Unable to nack message")
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
);
|
|
102
132
|
};
|
|
103
133
|
|
|
104
134
|
function withCurrentChannel(fn, altFn) {
|
|
105
|
-
const entry = _.chain(channels)
|
|
106
|
-
|
|
107
|
-
|
|
135
|
+
const entry = _.chain(channels)
|
|
136
|
+
.values()
|
|
137
|
+
.filter((channel) => {
|
|
138
|
+
return !channel.doomed;
|
|
139
|
+
})
|
|
140
|
+
.sortBy("index")
|
|
141
|
+
.last()
|
|
142
|
+
.value();
|
|
108
143
|
if (entry) return fn(entry.channel, entry.consumerTag, entry);
|
|
109
144
|
return altFn && altFn();
|
|
110
145
|
}
|
|
@@ -117,7 +152,7 @@ function SubscriberSession(sequentialChannelOperations, config) {
|
|
|
117
152
|
|
|
118
153
|
function unref(consumerTag) {
|
|
119
154
|
withConsumerChannel(consumerTag, (channel) => {
|
|
120
|
-
debug(
|
|
155
|
+
debug("Removing channel: %s from session", channel._rascal_id);
|
|
121
156
|
delete channels[consumerTag];
|
|
122
157
|
});
|
|
123
158
|
}
|
|
@@ -137,11 +172,15 @@ function SubscriberSession(sequentialChannelOperations, config) {
|
|
|
137
172
|
Keeping channels around for a minute shouldn't hurt
|
|
138
173
|
*/
|
|
139
174
|
function scheduleClose(entry) {
|
|
140
|
-
debug(
|
|
175
|
+
debug(
|
|
176
|
+
"Deferring close channel: %s by %dms",
|
|
177
|
+
entry.channel._rascal_id,
|
|
178
|
+
config.deferCloseChannel
|
|
179
|
+
);
|
|
141
180
|
setTimeoutUnref(() => {
|
|
142
181
|
withConsumerChannel(entry.consumerTag, (channel) => {
|
|
143
182
|
channel.close(() => {
|
|
144
|
-
debug(
|
|
183
|
+
debug("Channel: %s was closed", channel._rascal_id);
|
|
145
184
|
});
|
|
146
185
|
});
|
|
147
186
|
}, config.deferCloseChannel);
|