rascal 17.0.0 → 17.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Change Log
2
2
 
3
+ ## 17.0.1
4
+
5
+ - Rework the republish and forward recovery strategies to remove remote chance of repeat ack/nack
6
+
3
7
  ## 17.0.0
4
8
 
5
9
  - Updated the configuration processing as per https://github.com/guidesmiths/rascal/issues/219
@@ -68,13 +68,12 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
68
68
  execute(session, message, err, strategyConfig, next) {
69
69
  debug('Republishing message: %s', message.properties.messageId);
70
70
 
71
- const once = _.once(next);
72
71
  const originalQueue = _.get(message, 'properties.headers.rascal.originalQueue');
73
72
  const republished = _.get(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'republished'], 0);
74
73
 
75
74
  if (strategyConfig.attempts && strategyConfig.attempts <= republished) {
76
75
  debug('Skipping recovery - message: %s has already been republished %d times.', message.properties.messageId, republished);
77
- return once(null, false);
76
+ return next(null, false);
78
77
  }
79
78
 
80
79
  const publishOptions = _.cloneDeep(message.properties);
@@ -87,33 +86,50 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
87
86
 
88
87
  if (strategyConfig.immediateNack) _.set(publishOptions, ['headers', 'rascal', 'recovery', originalQueue, 'immediateNack'], true);
89
88
 
90
- vhost.getConfirmChannel((err, publisherChannel) => {
91
- const nackMessage = (err) => {
92
- session._nack(message, (_nackErr) => {
93
- // nackError just means the channel was already closed meaning the original message would have been rolled back
94
- once(err);
95
- });
96
- };
89
+ const ackMessage = () => {
90
+ session._ack(message, (err) => {
91
+ next(err, true);
92
+ });
93
+ };
97
94
 
98
- if (err) return nackMessage(err);
95
+ const nackMessage = (err) => {
96
+ session._nack(message, (_nackErr) => {
97
+ // nackError just means the channel was already closed meaning the original message would have been rolled back
98
+ next(err);
99
+ });
100
+ };
101
+
102
+ const ackOrNack = _.once((err) => {
103
+ return err ? nackMessage(err) : ackMessage();
104
+ });
105
+
106
+ vhost.getConfirmChannel((err, publisherChannel) => {
107
+ if (err) return ackOrNack(err);
99
108
 
100
- if (!publisherChannel) return nackMessage(new Error('Unable to handle subscriber error by republishing. The VHost is shutting down'));
109
+ if (!publisherChannel) return ackOrNack(new Error('Unable to handle subscriber error by republishing. The VHost is shutting down'));
101
110
 
102
111
  publisherChannel.on('error', (err) => {
103
- nackMessage(err);
112
+ ackOrNack(err);
104
113
  });
114
+
105
115
  publisherChannel.on('return', () => {
106
- nackMessage(new Error(format('Message: %s was republished to queue: %s, but was returned', message.properties.messageId, originalQueue)));
116
+ ackOrNack(new Error(format('Message: %s was republished to queue: %s, but was returned', message.properties.messageId, originalQueue)));
107
117
  });
108
118
 
109
119
  publisherChannel.publish(undefined, originalQueue, message.content, publishOptions, (err) => {
110
- if (err) return nackMessage(err); // Channel will already be closed, reclosing will trigger an error
111
-
112
- publisherChannel.close();
113
- debug('Message: %s was republished to queue: %s %d times', message.properties.messageId, originalQueue, republished + 1);
114
- session._ack(message, (err) => {
115
- once(err, true);
116
- });
120
+ if (err) {
121
+ // Channel will already be closed, reclosing will trigger an error
122
+ publisherChannel.removeAllListeners();
123
+
124
+ debug('Message: %s failed to be republished to queue: %s %d times - %s', message.properties.messageId, originalQueue, republished + 1, err.message);
125
+ ackOrNack(err);
126
+ } else {
127
+ publisherChannel.close();
128
+ publisherChannel.removeAllListeners();
129
+
130
+ debug('Message: %s was republished to queue: %s %d times', message.properties.messageId, originalQueue, republished + 1);
131
+ ackOrNack();
132
+ }
117
133
  });
118
134
  });
119
135
  },
@@ -123,13 +139,12 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
123
139
  execute(session, message, err, strategyConfig, next) {
124
140
  debug('Forwarding message: %s to publication: %s', message.properties.messageId, strategyConfig.publication);
125
141
 
126
- const once = _.once(next);
127
142
  const originalQueue = _.get(message, 'properties.headers.rascal.originalQueue');
128
143
  const forwarded = _.get(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'forwarded'], 0);
129
144
 
130
145
  if (strategyConfig.attempts && strategyConfig.attempts <= forwarded) {
131
146
  debug('Skipping recovery - message: %s has already been forwarded %d times.', message.properties.messageId, forwarded);
132
- return once(null, false);
147
+ return next(null, false);
133
148
  }
134
149
 
135
150
  // See https://github.com/rabbitmq/rabbitmq-server/issues/161
@@ -141,30 +156,39 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
141
156
  _.set(forwardOverrides, 'options.headers.rascal.error.message', _.truncate(err.message, { length: 1024 }));
142
157
  _.set(forwardOverrides, 'options.headers.rascal.error.code', err.code);
143
158
 
159
+ const ackMessage = () => {
160
+ session._ack(message, (err) => {
161
+ next(err, true);
162
+ });
163
+ };
164
+
144
165
  const nackMessage = (err) => {
145
166
  session._nack(message, (_nackErr) => {
146
167
  // nackError just means the channel was already closed meaning the original message would have been rolled back
147
- once(err);
168
+ next(err);
148
169
  });
149
170
  };
150
171
 
172
+ const ackOrNack = _.once((err) => {
173
+ return err ? nackMessage(err) : ackMessage();
174
+ });
175
+
151
176
  broker.forward(strategyConfig.publication, message, forwardOverrides, (err, publication) => {
152
177
  if (err) return nackMessage(err);
153
178
 
154
179
  publication.on('success', () => {
155
180
  debug('Message: %s was forwarded to publication: %s %d times', message.properties.messageId, strategyConfig.publication, forwarded + 1);
156
- session._ack(message, (ackErr) => {
157
- once(ackErr, true);
158
- });
181
+ ackOrNack();
159
182
  });
160
183
 
161
184
  publication.on('error', (err) => {
162
- nackMessage(err);
185
+ debug('Message: %s failed to be forwarded to publication: %s %d times - %s', message.properties.messageId, strategyConfig.publication, forwarded + 1, err.message);
186
+ ackOrNack(err);
163
187
  });
164
188
 
165
189
  publication.on('return', () => {
166
190
  publication.removeAllListeners('success');
167
- nackMessage(new Error(format('Message: %s was forwarded to publication: %s, but was returned', message.properties.messageId, strategyConfig.publication)));
191
+ ackOrNack(new Error(format('Message: %s was forwarded to publication: %s, but was returned', message.properties.messageId, strategyConfig.publication)));
168
192
  });
169
193
  });
170
194
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rascal",
3
- "version": "17.0.0",
3
+ "version": "17.0.1",
4
4
  "description": "A config driven wrapper for amqplib supporting multi-host connections, automatic error recovery, redelivery flood protection, transparent encryption / decryption, channel pooling and publication timeouts",
5
5
  "main": "index.js",
6
6
  "dependencies": {