@autofleet/rabbit 4.1.0-beta.0 → 4.1.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.
- package/dist/index.d.ts +81 -76
- package/dist/index.js +581 -472
- package/dist/index.js.map +1 -0
- package/dist/lib/celery.js +1 -0
- package/dist/lib/celery.js.map +1 -0
- package/dist/lib/consts.d.ts +12 -4
- package/dist/lib/consts.js +12 -3
- package/dist/lib/consts.js.map +1 -0
- package/dist/lib/rabbitError.js +1 -0
- package/dist/lib/rabbitError.js.map +1 -0
- package/dist/lib/redis.d.ts +2 -1
- package/dist/lib/redis.js +3 -8
- package/dist/lib/redis.js.map +1 -0
- package/dist/lib/types.d.ts +18 -5
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.d.ts +12 -3
- package/dist/lib/utils.js +23 -13
- package/dist/lib/utils.js.map +1 -0
- package/dist/logger.js +1 -0
- package/dist/logger.js.map +1 -0
- package/dist/{mock.d.ts → mock/index.d.ts} +1 -1
- package/dist/{mock.js → mock/index.js} +2 -1
- package/dist/mock/index.js.map +1 -0
- package/dist/mock/vitest.d.ts +13 -0
- package/dist/mock/vitest.js +18 -0
- package/dist/mock/vitest.js.map +1 -0
- package/package.json +24 -19
- package/src/index.ts +702 -591
- package/src/lib/consts.ts +17 -4
- package/src/lib/redis.ts +2 -7
- package/src/lib/types.ts +22 -7
- package/src/lib/utils.ts +35 -12
- package/src/{mock.ts → mock/index.ts} +2 -2
- package/src/mock/vitest.ts +24 -0
- package/src/redis-lock.d.ts +5 -3
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +2 -2
- package/vitest.config.ts +17 -0
- package/coverage/clover.xml +0 -669
- package/coverage/coverage-final.json +0 -9
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov-report/src/index.html +0 -131
- package/coverage/lcov-report/src/index.ts.html +0 -3826
- package/coverage/lcov-report/src/lib/celery.ts.html +0 -352
- package/coverage/lcov-report/src/lib/consts.ts.html +0 -142
- package/coverage/lcov-report/src/lib/index.html +0 -191
- package/coverage/lcov-report/src/lib/rabbitError.ts.html +0 -103
- package/coverage/lcov-report/src/lib/redis.ts.html +0 -133
- package/coverage/lcov-report/src/lib/types.ts.html +0 -268
- package/coverage/lcov-report/src/lib/utils.ts.html +0 -142
- package/coverage/lcov-report/src/logger.ts.html +0 -100
- package/coverage/lcov.info +0 -1076
package/src/index.ts
CHANGED
|
@@ -1,99 +1,107 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { EventEmitter, once } from 'events';
|
|
4
|
-
import { promisify } from 'util';
|
|
1
|
+
import { env } from 'node:process';
|
|
2
|
+
import { setImmediate } from 'node:timers/promises';
|
|
3
|
+
import { EventEmitter, once } from 'node:events';
|
|
5
4
|
import moment from 'moment';
|
|
6
5
|
import RedisLock from 'redis-lock';
|
|
7
6
|
import {
|
|
8
|
-
AmqpConnectionManager, ChannelWrapper, connect, CreateChannelOpts,
|
|
7
|
+
type AmqpConnectionManager, type ChannelWrapper, connect, type CreateChannelOpts,
|
|
9
8
|
} from 'amqp-connection-manager';
|
|
9
|
+
// eslint-disable-next-line import/no-unresolved
|
|
10
|
+
import type { PublishOptions } from 'amqp-connection-manager/dist/types/ChannelWrapper';
|
|
10
11
|
import {
|
|
11
12
|
ConfirmChannel, ConsumeMessage, Options, Replies,
|
|
12
13
|
} from 'amqplib';
|
|
14
|
+
import { backOff } from 'exponential-backoff';
|
|
15
|
+
import type { LoggerInstanceManager } from '@autofleet/logger';
|
|
13
16
|
import {
|
|
14
|
-
getCurrentPayload, newTrace, traceTypes, createOrSetRabbitTrace, outbreak,
|
|
17
|
+
getUser, getCurrentPayload, newTrace, traceTypes, createOrSetRabbitTrace, outbreak,
|
|
15
18
|
} from '@autofleet/zehut';
|
|
16
19
|
import { randomUUID } from 'node:crypto';
|
|
17
|
-
import axios from 'axios';
|
|
18
20
|
import logger from './logger';
|
|
19
21
|
import RabbitError from './lib/rabbitError';
|
|
20
22
|
import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
21
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
assertExchangeFanout, createDeferredPromise, getAssertVhostExponentialBackoffConfig, rand,
|
|
25
|
+
} from './lib/utils';
|
|
22
26
|
import {
|
|
23
27
|
AUTOMATION_ID_HEADER,
|
|
24
|
-
CONNECTION_CREATED_CONST,
|
|
25
|
-
CONNECTION_FAILED_CONST,
|
|
26
28
|
DEFAULT_LOCK_TIMEOUT,
|
|
27
29
|
DEFAULT_OPTIONS,
|
|
28
30
|
RETRY_HEADER,
|
|
29
31
|
TRACING_HEADER,
|
|
30
|
-
USER_OBJECT,
|
|
31
32
|
USER_TRACING_HEADER,
|
|
32
33
|
} from './lib/consts';
|
|
33
34
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
RedisLockType,
|
|
40
|
-
ExchangesCache,
|
|
35
|
+
type AssertExchangePromisesDictionary,
|
|
36
|
+
type CallbackFunction,
|
|
37
|
+
type ConnectionData,
|
|
38
|
+
type ConsumeMessageOrNull,
|
|
39
|
+
type ConsumeOptions,
|
|
41
40
|
CONSUMER_DEFAULT_OPTIONS,
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
type CustomMessageHeaders,
|
|
42
|
+
type ExchangesCache,
|
|
43
|
+
type Message,
|
|
44
|
+
type NackOptions,
|
|
45
|
+
type QueuesCache,
|
|
46
|
+
type QueueSetupPromisesDictionary,
|
|
44
47
|
} from './lib/types';
|
|
45
48
|
|
|
46
|
-
// const debug = nodeDebug('af-rabbitmq')
|
|
47
|
-
const debug = logger.debug.bind(logger);
|
|
48
|
-
|
|
49
49
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
assertQueue: any;
|
|
57
|
-
consume: any;
|
|
58
|
-
consumeFromExchange: any;
|
|
59
|
-
publish: any;
|
|
60
|
-
sendToQueue: any;
|
|
61
|
-
redisClient?: any;
|
|
62
|
-
}
|
|
51
|
+
// TODO: [QUORUM-PHASE-3] Delete these env vars
|
|
52
|
+
const {
|
|
53
|
+
DISABLE_QUORUM_QUEUES_CONSUME,
|
|
54
|
+
DISABLE_QUORUM_QUEUES_PUBLISH,
|
|
55
|
+
} = env;
|
|
63
56
|
|
|
64
|
-
interface
|
|
65
|
-
|
|
57
|
+
export interface IAfRabbitMq {
|
|
58
|
+
ack(channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: (() => Promise<void>) | null): (userMsg: ConsumeMessage) => Promise<void>;
|
|
59
|
+
nack(
|
|
60
|
+
channel: ConfirmChannel,
|
|
61
|
+
queue: string,
|
|
62
|
+
options: ConsumeOptions,
|
|
63
|
+
deadQueueOptions: Options.AssertQueue,
|
|
64
|
+
msg: ConsumeMessageOrNull,
|
|
65
|
+
releaseLock?: () => Promise<void>,
|
|
66
|
+
): (userMsg: ConsumeMessageOrNull, nackOptions?: NackOptions) => Promise<void> ;
|
|
67
|
+
assertChannel(options?: assertChannelOpts): Promise<ChannelWrapper>;
|
|
68
|
+
assertExchange(exchangeName: string, connection: ConnectionData): Promise<Replies.AssertExchange>
|
|
69
|
+
assertQueue(queueName: string, options?: Options.AssertQueue | null): Promise<Replies.AssertQueue>;
|
|
70
|
+
consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void>;
|
|
71
|
+
consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void>
|
|
72
|
+
publish(exchange: string, content: any, customHeaders?: PublishOptions['headers']): Promise<void>
|
|
73
|
+
sendToQueue(queue: string, content: any, options?: Options.AssertQueue | null, customHeaders?: PublishOptions['headers']): Promise<boolean | undefined>;
|
|
74
|
+
redisClient?: ReturnType<typeof getRedisInstance>;
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
export interface AfRabbitOptions {
|
|
69
78
|
disableReconnect?: boolean;
|
|
70
|
-
|
|
71
79
|
/**
|
|
72
80
|
* When you want your own grace-full shutdown, set this to true.
|
|
73
81
|
* @default false
|
|
74
82
|
*/
|
|
75
83
|
dontGracefulShutdown?: boolean;
|
|
76
|
-
|
|
77
84
|
/**
|
|
78
|
-
*
|
|
85
|
+
* don't retry on creation error
|
|
79
86
|
* @default false
|
|
80
87
|
*/
|
|
81
88
|
dontRetryAssert?: boolean;
|
|
82
|
-
|
|
83
89
|
rabbitHost?: string;
|
|
84
|
-
|
|
85
90
|
vhost?: string;
|
|
91
|
+
logger?: LoggerInstanceManager;
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
type newChannelOpts = {
|
|
89
95
|
name?: string;
|
|
90
96
|
onClose?: null | ((args: any | null) => void);
|
|
91
97
|
options?: CreateChannelOpts | undefined;
|
|
98
|
+
connection: ConnectionData;
|
|
92
99
|
};
|
|
93
100
|
|
|
94
101
|
type assertChannelOpts = {
|
|
95
102
|
channelName?: string;
|
|
96
103
|
force?: boolean;
|
|
104
|
+
connection: ConnectionData;
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
type AfConsumer = {
|
|
@@ -105,13 +113,12 @@ type AfConsumer = {
|
|
|
105
113
|
const HEARTBEAT = '60';
|
|
106
114
|
|
|
107
115
|
class RabbitMq implements IAfRabbitMq {
|
|
108
|
-
static parseMsg(msg:
|
|
109
|
-
let
|
|
110
|
-
content = content.toString();
|
|
116
|
+
static parseMsg(msg: ConsumeMessage): Message {
|
|
117
|
+
let content = msg.content.toString();
|
|
111
118
|
|
|
112
119
|
try {
|
|
113
120
|
content = JSON.parse(content);
|
|
114
|
-
} catch
|
|
121
|
+
} catch { /* ignore error */ }
|
|
115
122
|
|
|
116
123
|
return {
|
|
117
124
|
...msg,
|
|
@@ -125,11 +132,10 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
125
132
|
}
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
static getPublishOptions(customHeaders: CustomMessageHeaders = {}) {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const outbreakTrace = outbreak.getCurrentContext();
|
|
135
|
+
static getPublishOptions(customHeaders: CustomMessageHeaders = {}): PublishOptions {
|
|
136
|
+
const user = getUser();
|
|
137
|
+
const traceId = getCurrentPayload()?.context?.get(TRACING_HEADER) || outbreak.getCurrentContext()?.context?.get(TRACING_HEADER);
|
|
138
|
+
|
|
133
139
|
return {
|
|
134
140
|
timestamp: moment().unix(),
|
|
135
141
|
timeout: PUBLISH_TIMEOUT,
|
|
@@ -137,97 +143,110 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
137
143
|
creationTimestamp: moment().valueOf(),
|
|
138
144
|
...customHeaders,
|
|
139
145
|
[USER_TRACING_HEADER]: user?.id,
|
|
140
|
-
[TRACING_HEADER]: traceId
|
|
146
|
+
[TRACING_HEADER]: traceId,
|
|
141
147
|
},
|
|
142
148
|
};
|
|
143
149
|
}
|
|
144
150
|
|
|
145
|
-
DISCONNECT_MSG = 'rabbit: connection disconnect';
|
|
146
|
-
|
|
147
|
-
RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
148
|
-
|
|
149
|
-
channel: ChannelWrapper | null;
|
|
151
|
+
readonly DISCONNECT_MSG = 'rabbit: connection disconnect';
|
|
150
152
|
|
|
151
|
-
|
|
153
|
+
readonly RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
publishChannel: ChannelWrapper | null = null;
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
publishChannelSetupPromise: Promise<ChannelWrapper> | null = null;
|
|
156
158
|
|
|
157
|
-
|
|
159
|
+
readonly publishConnection: ConnectionData = {
|
|
160
|
+
amqpConnection: null,
|
|
161
|
+
creatingConnection: false,
|
|
162
|
+
connectionCreatedEventName: 'publishConnectionCreated',
|
|
163
|
+
connectionFailedEventName: 'publishConnectionFailed',
|
|
164
|
+
blockReconnect: false,
|
|
165
|
+
};
|
|
158
166
|
|
|
159
|
-
|
|
167
|
+
readonly consumeConnection: ConnectionData = {
|
|
168
|
+
amqpConnection: null,
|
|
169
|
+
creatingConnection: false,
|
|
170
|
+
connectionCreatedEventName: 'consumeConnectionCreated',
|
|
171
|
+
connectionFailedEventName: 'consumeConnectionFailed',
|
|
172
|
+
blockReconnect: false,
|
|
173
|
+
};
|
|
160
174
|
|
|
161
|
-
|
|
175
|
+
readonly em: EventEmitter = new EventEmitter();
|
|
162
176
|
|
|
163
|
-
|
|
177
|
+
readonly exchanges: ExchangesCache = {};
|
|
164
178
|
|
|
165
|
-
|
|
179
|
+
readonly queues: QueuesCache = {};
|
|
166
180
|
|
|
167
|
-
|
|
181
|
+
readonly queueSetupPromises: QueueSetupPromisesDictionary = {};
|
|
168
182
|
|
|
169
|
-
|
|
183
|
+
readonly assertExchangePromises: AssertExchangePromisesDictionary = {};
|
|
170
184
|
|
|
171
|
-
redisClient
|
|
185
|
+
readonly redisClient?: ReturnType<typeof getRedisInstance>;
|
|
172
186
|
|
|
173
|
-
redisLock?:
|
|
187
|
+
readonly redisLock?: ReturnType<typeof RedisLock>;
|
|
174
188
|
|
|
175
189
|
/** Array of consumers tags used for canceling consumption */
|
|
176
|
-
consumersTags:
|
|
190
|
+
readonly consumersTags: Map<string, { channel: ConfirmChannel; consumerTag: string }> = new Map<string, { channel: ConfirmChannel; consumerTag: string }>();
|
|
177
191
|
|
|
178
|
-
private
|
|
192
|
+
private readonly consumersToRegister: Array<AfConsumer> = [];
|
|
179
193
|
|
|
180
|
-
private
|
|
194
|
+
private doesVHostExist = false;
|
|
181
195
|
|
|
182
|
-
|
|
196
|
+
private readonly vhost = 'quorum-vhost';
|
|
183
197
|
|
|
184
|
-
|
|
198
|
+
private gracefulShutdownStarted = false;
|
|
185
199
|
|
|
186
|
-
|
|
200
|
+
// TODO:[QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
|
|
201
|
+
oldPublishChannel: ChannelWrapper | null = null;
|
|
187
202
|
|
|
188
|
-
|
|
203
|
+
oldPublishChannelSetupPromise: Promise<ChannelWrapper> | null = null;
|
|
189
204
|
|
|
190
|
-
|
|
205
|
+
readonly oldPublishConnection: ConnectionData = {
|
|
206
|
+
amqpConnection: null,
|
|
207
|
+
creatingConnection: false,
|
|
208
|
+
connectionCreatedEventName: 'oldPublishConnectionCreated',
|
|
209
|
+
connectionFailedEventName: 'oldPublishConnectionFailed',
|
|
210
|
+
blockReconnect: false,
|
|
211
|
+
};
|
|
191
212
|
|
|
192
|
-
|
|
213
|
+
readonly oldConsumeConnection: ConnectionData = {
|
|
214
|
+
amqpConnection: null,
|
|
215
|
+
creatingConnection: false,
|
|
216
|
+
connectionCreatedEventName: 'oldConsumeConnectionCreated',
|
|
217
|
+
connectionFailedEventName: 'oldConsumeConnectionFailed',
|
|
218
|
+
blockReconnect: false,
|
|
219
|
+
};
|
|
193
220
|
|
|
194
|
-
|
|
221
|
+
readonly oldEm: EventEmitter = new EventEmitter();
|
|
195
222
|
|
|
196
|
-
oldExchanges: ExchangesCache;
|
|
223
|
+
readonly oldExchanges: ExchangesCache = {};
|
|
197
224
|
|
|
198
|
-
oldQueues: QueuesCache;
|
|
225
|
+
readonly oldQueues: QueuesCache = {};
|
|
199
226
|
|
|
200
|
-
oldQueueSetupPromises: QueueSetupPromisesDictionary;
|
|
227
|
+
readonly oldQueueSetupPromises: QueueSetupPromisesDictionary = {};
|
|
201
228
|
|
|
202
|
-
oldAssertExchangePromises: AssertExchangePromisesDictionary;
|
|
229
|
+
readonly oldAssertExchangePromises: AssertExchangePromisesDictionary = {};
|
|
203
230
|
|
|
204
|
-
oldConsumersTags:
|
|
231
|
+
readonly oldConsumersTags: Map<string, { channel: ConfirmChannel; consumerTag: string }> = new Map<string, { channel: ConfirmChannel; consumerTag: string }>();
|
|
205
232
|
|
|
206
|
-
private
|
|
233
|
+
private readonly oldConsumersToRegister: Array<AfConsumer> = [];
|
|
207
234
|
|
|
208
|
-
|
|
209
|
-
this.em = new EventEmitter();
|
|
210
|
-
this.channel = null;
|
|
211
|
-
this.publishChannelSetupPromise = null;
|
|
212
|
-
this.connection = null;
|
|
213
|
-
this.creatingConnection = false;
|
|
214
|
-
this.exchanges = {};
|
|
215
|
-
this.queues = {};
|
|
216
|
-
this.queueSetupPromises = {};
|
|
217
|
-
this.assertExchangePromises = {};
|
|
218
|
-
this.consumers = [];
|
|
219
|
-
this.options = options;
|
|
235
|
+
#logger: LoggerInstanceManager;
|
|
220
236
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
237
|
+
constructor(public readonly options: AfRabbitOptions = {}, private readonly redisConfig?: RedisConfig) {
|
|
238
|
+
this.#logger = options?.logger || logger;
|
|
224
239
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
240
|
+
if (redisConfig) {
|
|
241
|
+
this.redisClient = getRedisInstance(redisConfig).on('error', (err) => {
|
|
242
|
+
this.#logger.error('rabbit: Redis error', { err, redisConfig });
|
|
243
|
+
});
|
|
244
|
+
this.redisClient.connect().catch((err) => {
|
|
245
|
+
this.#logger.error('rabbit: Failed to connect to Redis', { err, redisConfig });
|
|
246
|
+
});
|
|
247
|
+
this.redisLock = RedisLock(this.redisClient);
|
|
228
248
|
}
|
|
229
|
-
this.
|
|
230
|
-
logger.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
|
|
249
|
+
this.#logger.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
|
|
231
250
|
if (!this.options?.dontGracefulShutdown) {
|
|
232
251
|
process.on('SIGTERM', async () => {
|
|
233
252
|
await this.gracefulShutdown('SIGTERM');
|
|
@@ -236,81 +255,73 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
236
255
|
await this.gracefulShutdown('SIGINT');
|
|
237
256
|
});
|
|
238
257
|
}
|
|
258
|
+
}
|
|
239
259
|
|
|
240
|
-
|
|
241
|
-
this.
|
|
242
|
-
this.oldChannel = null;
|
|
243
|
-
this.oldPublishChannelSetupPromise = null;
|
|
244
|
-
this.oldConnection = null;
|
|
245
|
-
this.oldCreatingConnection = false;
|
|
246
|
-
this.oldExchanges = {};
|
|
247
|
-
this.oldQueues = {};
|
|
248
|
-
this.oldQueueSetupPromises = {};
|
|
249
|
-
this.oldAssertExchangePromises = {};
|
|
250
|
-
this.oldConsumers = [];
|
|
251
|
-
this.oldConsumersTags = [];
|
|
260
|
+
private getRedisKey(key: string): string {
|
|
261
|
+
return `${this.redisConfig?.prefix || ''}${key}`;
|
|
252
262
|
}
|
|
253
263
|
|
|
254
264
|
private assertVHost = async () => {
|
|
255
|
-
if (this.
|
|
265
|
+
if (this.doesVHostExist) {
|
|
256
266
|
return;
|
|
257
267
|
}
|
|
258
268
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
auth: {
|
|
266
|
-
username: process.env.RABBITMQ_USERNAME || 'guest',
|
|
267
|
-
password: process.env.RABBITMQ_PASSWORD || 'guest',
|
|
268
|
-
},
|
|
269
|
+
const username = process.env.RABBITMQ_USERNAME || 'guest';
|
|
270
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
271
|
+
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
|
|
272
|
+
const headers = {
|
|
273
|
+
Authorization: `Basic ${credentials}`,
|
|
274
|
+
'Content-Type': 'application/json',
|
|
269
275
|
};
|
|
270
|
-
|
|
276
|
+
|
|
277
|
+
const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
|
|
278
|
+
|
|
279
|
+
const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
|
|
271
280
|
|
|
272
281
|
try {
|
|
273
|
-
const response = await
|
|
282
|
+
const response = await fetch(url, {
|
|
283
|
+
method: 'GET',
|
|
284
|
+
headers,
|
|
285
|
+
});
|
|
274
286
|
|
|
275
|
-
if (response.status
|
|
276
|
-
|
|
287
|
+
if (response.status === 200) {
|
|
288
|
+
this.doesVHostExist = true;
|
|
289
|
+
this.#logger.info('Vhost exists', { vhost: this.vhost });
|
|
290
|
+
return;
|
|
277
291
|
}
|
|
278
292
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (error instanceof axios.AxiosError && error.response?.status === 404) {
|
|
284
|
-
try {
|
|
285
|
-
await axios.put(
|
|
286
|
-
`${rabbitHost}/api/vhosts/${encodeURIComponent(vhost)}`,
|
|
287
|
-
{
|
|
288
|
-
default_queue_type: 'quorum',
|
|
289
|
-
},
|
|
290
|
-
auth,
|
|
291
|
-
);
|
|
293
|
+
if (response.status !== 404) {
|
|
294
|
+
this.#logger.error('Failed to check vhost', { response });
|
|
295
|
+
throw new RabbitError('Failed to check vhost');
|
|
296
|
+
}
|
|
292
297
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
298
|
+
const createResponse = await fetch(url, {
|
|
299
|
+
method: 'PUT',
|
|
300
|
+
headers,
|
|
301
|
+
body: JSON.stringify({ default_queue_type: 'quorum' }),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (!createResponse.ok) {
|
|
305
|
+
this.#logger.error('Failed to create vhost', { response: createResponse });
|
|
306
|
+
throw new RabbitError('Failed to create vhost');
|
|
300
307
|
}
|
|
301
308
|
|
|
302
|
-
|
|
309
|
+
this.doesVHostExist = true;
|
|
310
|
+
this.#logger.info('Vhost created', { vhost: this.vhost });
|
|
311
|
+
} catch (error) {
|
|
312
|
+
this.#logger.error('Failed to check or create vhost', { error });
|
|
303
313
|
throw error;
|
|
304
314
|
}
|
|
305
|
-
}
|
|
315
|
+
};
|
|
306
316
|
|
|
307
317
|
private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
|
|
308
318
|
if (msg) {
|
|
309
|
-
const {
|
|
319
|
+
const { headers } = msg.properties;
|
|
310
320
|
const timestamp = headers?.creationTimestamp;
|
|
311
321
|
|
|
312
322
|
if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
|
|
313
|
-
const
|
|
323
|
+
const key = this.getRedisKey(headers.redisTimestampValidationKey);
|
|
324
|
+
const lastMessageTimestamp = await this.redisClient.get(key);
|
|
314
325
|
return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
|
|
315
326
|
}
|
|
316
327
|
return true;
|
|
@@ -318,16 +329,22 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
318
329
|
return false;
|
|
319
330
|
}
|
|
320
331
|
|
|
321
|
-
public ack = (
|
|
332
|
+
public ack = (
|
|
333
|
+
channel: ConfirmChannel,
|
|
334
|
+
msg: ConsumeMessageOrNull,
|
|
335
|
+
shouldUpdateRedisTimestamp = false,
|
|
336
|
+
releaseLock: (() => Promise<void>) | null = null,
|
|
337
|
+
) => async (userMsg: ConsumeMessage) : Promise<void> => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
322
338
|
if (msg) {
|
|
323
|
-
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
339
|
+
this.#logger.debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
324
340
|
await channel.ack(msg);
|
|
325
|
-
const {
|
|
341
|
+
const { headers } = msg.properties;
|
|
326
342
|
const timestamp = headers?.creationTimestamp;
|
|
327
343
|
|
|
328
344
|
if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
|
|
329
345
|
const parsedTimestamp = parseInt(timestamp, 10);
|
|
330
|
-
|
|
346
|
+
const key = this.getRedisKey(headers.redisTimestampValidationKey);
|
|
347
|
+
await this.redisClient.set(key, parsedTimestamp, { EX: 3600 });
|
|
331
348
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
332
349
|
}
|
|
333
350
|
}
|
|
@@ -336,223 +353,224 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
336
353
|
public nack = (
|
|
337
354
|
channel: ConfirmChannel,
|
|
338
355
|
queue: string,
|
|
339
|
-
options:
|
|
356
|
+
options: ConsumeOptions,
|
|
340
357
|
deadQueueOptions: Options.AssertQueue,
|
|
341
358
|
msg: ConsumeMessageOrNull,
|
|
342
|
-
releaseLock
|
|
359
|
+
releaseLock?: (() => Promise<void>) | null,
|
|
343
360
|
) => async (
|
|
344
361
|
userMsg: ConsumeMessageOrNull,
|
|
345
362
|
{
|
|
346
363
|
skipRetry = false,
|
|
347
364
|
}: NackOptions = { },
|
|
348
|
-
)
|
|
365
|
+
): Promise<void> => {
|
|
349
366
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
350
|
-
if (channel
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
&& (
|
|
354
|
-
!msg.properties.headers[RETRY_HEADER]
|
|
355
|
-
|| parseInt(msg.properties.headers[RETRY_HEADER], 10) < options.retries
|
|
356
|
-
)
|
|
357
|
-
) {
|
|
358
|
-
await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
|
|
359
|
-
...msg.properties.headers,
|
|
360
|
-
[RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
|
|
361
|
-
? msg.properties.headers[RETRY_HEADER] + 1
|
|
362
|
-
: 1,
|
|
363
|
-
});
|
|
364
|
-
} else {
|
|
365
|
-
const deadQueue = `${queue}-dead`;
|
|
366
|
-
await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
|
|
367
|
-
...msg.properties.headers,
|
|
368
|
-
[RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
|
|
369
|
-
? msg.properties.headers[RETRY_HEADER] + 1
|
|
370
|
-
: 1,
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
|
|
374
|
-
await channel.ack(msg);
|
|
375
|
-
} else {
|
|
376
|
-
logger.error('no channel or msg', {
|
|
377
|
-
msg,
|
|
378
|
-
});
|
|
367
|
+
if (!channel || !msg) {
|
|
368
|
+
this.#logger.error('no channel or msg', { msg });
|
|
369
|
+
return;
|
|
379
370
|
}
|
|
371
|
+
const currentRetryHeader = Number.parseInt(msg.properties.headers?.[RETRY_HEADER] || '0', 10) || 0;
|
|
372
|
+
const sendToDeadQueue = skipRetry || currentRetryHeader >= options.retries!;
|
|
373
|
+
await this.sendToQueue(`${queue}${sendToDeadQueue ? '-dead' : ''}`, RabbitMq.parseMsg(msg).content, sendToDeadQueue ? deadQueueOptions : options, {
|
|
374
|
+
...msg.properties.headers,
|
|
375
|
+
[RETRY_HEADER]: currentRetryHeader + 1,
|
|
376
|
+
});
|
|
377
|
+
this.#logger.debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
|
|
378
|
+
await channel.ack(msg);
|
|
380
379
|
}
|
|
381
380
|
|
|
382
|
-
async getConnection() {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
381
|
+
async getConnection(connection: ConnectionData): Promise<AmqpConnectionManager> {
|
|
382
|
+
const {
|
|
383
|
+
amqpConnection: connectionLocal,
|
|
384
|
+
creatingConnection,
|
|
385
|
+
connectionCreatedEventName,
|
|
386
|
+
connectionFailedEventName,
|
|
387
|
+
blockReconnect,
|
|
388
|
+
} = connection;
|
|
389
|
+
|
|
390
|
+
if (blockReconnect) {
|
|
391
|
+
this.#logger.debug('rabbit: block reconnect');
|
|
392
|
+
// @ts-expect-error we are returning undefined while the function clearly expects a connection.
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (connectionLocal !== null) {
|
|
397
|
+
if (this.options?.disableReconnect || connectionLocal?.isConnected()) {
|
|
398
|
+
this.#logger.debug('rabbit: connection - is connected');
|
|
399
|
+
return connectionLocal;
|
|
398
400
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
401
|
+
this.#logger.debug('rabbit: connection - reconnecting');
|
|
402
|
+
}
|
|
403
|
+
if (creatingConnection) {
|
|
404
|
+
this.#logger.debug('rabbit: creating connection emi');
|
|
405
|
+
const [event, value] = await Promise.race([connectionCreatedEventName, connectionFailedEventName].map((e) => once(this.em, e).then(([result]) => [e, result] as const)));
|
|
406
|
+
if (event === connectionCreatedEventName) {
|
|
407
|
+
return value;
|
|
404
408
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const connection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
422
|
-
findServers,
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
this.connection = connection;
|
|
426
|
-
this.connection.on('error', (err) => {
|
|
427
|
-
logger.error('rabbit: connection error', { err });
|
|
428
|
-
if (!isResolved) {
|
|
429
|
-
isResolved = true;
|
|
430
|
-
reject(err);
|
|
431
|
-
this.em.emit(CONNECTION_FAILED_CONST, err);
|
|
432
|
-
}
|
|
433
|
-
});
|
|
409
|
+
throw value;
|
|
410
|
+
}
|
|
411
|
+
connection.creatingConnection = true;
|
|
412
|
+
let isResolved = false;
|
|
413
|
+
|
|
414
|
+
// It is import to use it as a function and not as a variable
|
|
415
|
+
// because of k8s changes the env variables
|
|
416
|
+
// and we want to use the new values
|
|
417
|
+
const findServers = () => {
|
|
418
|
+
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
419
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
420
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
421
|
+
|
|
422
|
+
this.#logger.debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
423
|
+
return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
|
|
424
|
+
};
|
|
434
425
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (!isResolved) {
|
|
439
|
-
isResolved = true;
|
|
440
|
-
reject(err);
|
|
441
|
-
this.em.emit(CONNECTION_FAILED_CONST, err);
|
|
442
|
-
}
|
|
443
|
-
});
|
|
426
|
+
const defaultUrls = findServers();
|
|
427
|
+
const newConnection: AmqpConnectionManager = connect(defaultUrls, { findServers });
|
|
428
|
+
connection.amqpConnection = newConnection;
|
|
444
429
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
});
|
|
430
|
+
const { promise, reject, resolve } = createDeferredPromise<AmqpConnectionManager>();
|
|
431
|
+
newConnection.on('error', (err) => {
|
|
432
|
+
this.#logger.error('rabbit: connection error', { err });
|
|
433
|
+
if (!isResolved) {
|
|
434
|
+
isResolved = true;
|
|
435
|
+
reject(err);
|
|
436
|
+
this.em.emit(connectionFailedEventName, err);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
456
439
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
this.
|
|
440
|
+
newConnection.on('connectFailed', (err) => {
|
|
441
|
+
this.consumersTags.clear();
|
|
442
|
+
if (typeof err.url === 'string') {
|
|
443
|
+
err.url = this.maskURL(err.url);
|
|
444
|
+
}
|
|
445
|
+
this.#logger.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
|
|
446
|
+
if (!isResolved) {
|
|
461
447
|
isResolved = true;
|
|
462
|
-
|
|
463
|
-
|
|
448
|
+
reject(err);
|
|
449
|
+
this.em.emit(connectionFailedEventName, err);
|
|
450
|
+
}
|
|
464
451
|
});
|
|
452
|
+
|
|
453
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
454
|
+
this.consumersTags.clear();
|
|
455
|
+
this.#logger.debug('rabbit: connection closed');
|
|
456
|
+
if (this.options?.disableReconnect) {
|
|
457
|
+
this.#logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
458
|
+
connection.blockReconnect = true;
|
|
459
|
+
} else {
|
|
460
|
+
this.#logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
newConnection.once('connect', async () => {
|
|
465
|
+
this.#logger.debug('rabbit: connection established');
|
|
466
|
+
connection.creatingConnection = false;
|
|
467
|
+
this.em.emit(connectionCreatedEventName, newConnection);
|
|
468
|
+
isResolved = true;
|
|
469
|
+
resolve(newConnection);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return promise;
|
|
465
473
|
}
|
|
466
474
|
|
|
467
|
-
async getNewChannel({
|
|
468
|
-
|
|
475
|
+
async getNewChannel({
|
|
476
|
+
name = rand().toString(),
|
|
477
|
+
onClose = null,
|
|
478
|
+
options = {},
|
|
479
|
+
connection,
|
|
480
|
+
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
481
|
+
let localConnection!: AmqpConnectionManager;
|
|
469
482
|
try {
|
|
470
|
-
|
|
483
|
+
localConnection = await this.getConnection(connection);
|
|
471
484
|
} catch (e) {
|
|
472
|
-
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
485
|
+
this.#logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
473
486
|
throw e;
|
|
474
487
|
}
|
|
475
|
-
const channel =
|
|
488
|
+
const channel = localConnection?.createChannel({ ...options });
|
|
476
489
|
once(channel, 'close').then((args) => {
|
|
477
|
-
logger.error(`rabbit: channel ${name} closed`);
|
|
490
|
+
this.#logger.error(`rabbit: channel ${name} closed`);
|
|
478
491
|
onClose?.(args);
|
|
479
492
|
});
|
|
480
493
|
try {
|
|
481
494
|
await once(channel, 'connect');
|
|
482
|
-
debug(`rabbit: channel ${name} CONNECTED`);
|
|
495
|
+
this.#logger.debug(`rabbit: channel ${name} CONNECTED`);
|
|
483
496
|
return channel;
|
|
484
497
|
} catch (err) {
|
|
485
|
-
logger.error(`rabbit: channel error ${name} error`, { err });
|
|
498
|
+
this.#logger.error(`rabbit: channel error ${name} error`, { err });
|
|
486
499
|
throw err;
|
|
487
500
|
}
|
|
488
501
|
}
|
|
489
502
|
|
|
490
|
-
async assertChannel({ force = false }
|
|
491
|
-
if (
|
|
492
|
-
this.publishChannelSetupPromise
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
503
|
+
async assertChannel({ force = false, connection }: assertChannelOpts): Promise<ChannelWrapper> {
|
|
504
|
+
if (this.publishChannelSetupPromise) {
|
|
505
|
+
return this.publishChannelSetupPromise;
|
|
506
|
+
}
|
|
507
|
+
const { promise, resolve, reject } = createDeferredPromise<ChannelWrapper>();
|
|
508
|
+
this.publishChannelSetupPromise = promise;
|
|
509
|
+
if (this.publishChannel && !force) {
|
|
510
|
+
resolve(this.publishChannel);
|
|
511
|
+
return promise;
|
|
512
|
+
}
|
|
496
513
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
});
|
|
502
|
-
this.channel = channel;
|
|
503
|
-
resolve(channel);
|
|
504
|
-
} catch (e) {
|
|
505
|
-
reject(e);
|
|
506
|
-
}
|
|
514
|
+
try {
|
|
515
|
+
const channel = await this.getNewChannel({ connection });
|
|
516
|
+
channel.on('error', (err) => {
|
|
517
|
+
this.#logger.error('rabbit: channel error', { err });
|
|
507
518
|
});
|
|
519
|
+
if (this.publishConnection === connection) {
|
|
520
|
+
this.publishChannel = channel;
|
|
521
|
+
}
|
|
522
|
+
resolve(channel);
|
|
523
|
+
} catch (e) {
|
|
524
|
+
reject(e);
|
|
508
525
|
}
|
|
509
|
-
return
|
|
526
|
+
return promise;
|
|
510
527
|
}
|
|
511
528
|
|
|
512
|
-
async assertExchange(exchangeName: string,
|
|
513
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
529
|
+
async assertExchange(exchangeName: string, connection: ConnectionData): Promise<Replies.AssertExchange> {
|
|
530
|
+
const channel: ChannelWrapper = await this.assertChannel({ connection });
|
|
514
531
|
|
|
515
532
|
if (this.exchanges[exchangeName]) {
|
|
516
533
|
delete this.assertExchangePromises[exchangeName];
|
|
517
|
-
return this.exchanges[exchangeName]
|
|
534
|
+
return this.exchanges[exchangeName]!;
|
|
518
535
|
}
|
|
519
536
|
|
|
520
537
|
if (this.assertExchangePromises[exchangeName]) {
|
|
521
|
-
return this.assertExchangePromises[exchangeName]
|
|
538
|
+
return this.assertExchangePromises[exchangeName]!;
|
|
522
539
|
}
|
|
523
540
|
|
|
524
541
|
this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
525
542
|
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
526
|
-
return this.exchanges[exchangeName]
|
|
543
|
+
return this.exchanges[exchangeName]!;
|
|
527
544
|
}
|
|
528
545
|
|
|
529
|
-
async getQueueLength(queue: string) {
|
|
546
|
+
async getQueueLength(queue: string): Promise<Replies.AssertQueue> {
|
|
530
547
|
RabbitMq.validateName('queue', queue);
|
|
531
|
-
const { channel } = this;
|
|
548
|
+
const { publishChannel: channel } = this;
|
|
532
549
|
if (!channel) {
|
|
533
|
-
throw new
|
|
550
|
+
throw new RabbitError('channel is not defined');
|
|
534
551
|
}
|
|
535
|
-
debug('rabbit: getting queue length', { queue, connected: this.
|
|
552
|
+
this.#logger.debug('rabbit: getting queue length', { queue, connected: this.publishConnection.amqpConnection?.isConnected() });
|
|
536
553
|
return channel?.checkQueue(queue);
|
|
537
554
|
}
|
|
538
555
|
|
|
539
|
-
private async deleteQueue(queue: string) {
|
|
556
|
+
private async deleteQueue(queue: string, connection: ConnectionData): Promise<Replies.DeleteQueue> {
|
|
540
557
|
RabbitMq.validateName('queue', queue);
|
|
541
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
542
|
-
logger.info('rabbit: deleting queue', { queue });
|
|
558
|
+
const channel: ChannelWrapper = await this.assertChannel({ connection });
|
|
559
|
+
this.#logger.info('rabbit: deleting queue', { queue });
|
|
543
560
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
544
|
-
debug('queue deleted', deleteQueueRes);
|
|
561
|
+
this.#logger.debug('queue deleted', deleteQueueRes);
|
|
545
562
|
return deleteQueueRes;
|
|
546
563
|
}
|
|
547
564
|
|
|
548
|
-
async bindQueue(queue: string, exchange: string) {
|
|
549
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
565
|
+
async bindQueue(queue: string, exchange: string): Promise<void> {
|
|
566
|
+
const channel: ChannelWrapper = await this.assertChannel({ connection: this.publishConnection });
|
|
550
567
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
551
568
|
return channel.bindQueue(queue, exchange, '');
|
|
552
569
|
}
|
|
553
570
|
|
|
554
571
|
async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
555
572
|
let queue: Replies.AssertQueue;
|
|
573
|
+
const connection = this.publishConnection;
|
|
556
574
|
const localeOptions = {
|
|
557
575
|
...options,
|
|
558
576
|
durable: true,
|
|
@@ -563,23 +581,23 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
563
581
|
},
|
|
564
582
|
};
|
|
565
583
|
try {
|
|
566
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
567
|
-
debug('assertQueue->channel.addSetup', { queueName });
|
|
584
|
+
const channel: ChannelWrapper = await this.assertChannel({ connection });
|
|
585
|
+
this.#logger.debug('assertQueue->channel.addSetup', { queueName });
|
|
568
586
|
await channel.addSetup(async (setupChannel: ConfirmChannel) => {
|
|
569
587
|
await setupChannel.assertQueue(queueName, localeOptions);
|
|
570
588
|
});
|
|
571
|
-
debug('assertQueue->channel.assertQueue', { queueName });
|
|
589
|
+
this.#logger.debug('assertQueue->channel.assertQueue', { queueName });
|
|
572
590
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
573
591
|
} catch (e) {
|
|
574
|
-
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
592
|
+
this.#logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
575
593
|
if (!this.options?.dontRetryAssert) {
|
|
576
|
-
debug('retrying assertQueue', { queueName });
|
|
577
|
-
const channel = await this.assertChannel({ force: true });
|
|
578
|
-
await this.deleteQueue(queueName);
|
|
594
|
+
this.#logger.debug('retrying assertQueue', { queueName });
|
|
595
|
+
const channel = await this.assertChannel({ force: true, connection });
|
|
596
|
+
await this.deleteQueue(queueName, connection);
|
|
579
597
|
|
|
580
|
-
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
598
|
+
this.#logger.debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
581
599
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
582
|
-
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
600
|
+
this.#logger.debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
583
601
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
584
602
|
} else {
|
|
585
603
|
throw e;
|
|
@@ -590,7 +608,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
590
608
|
return queue;
|
|
591
609
|
}
|
|
592
610
|
|
|
593
|
-
// TODO: Can be deleted after deleting the old consumers
|
|
611
|
+
// TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
|
|
594
612
|
static shouldUseQuorum(queueName: string): boolean {
|
|
595
613
|
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
596
614
|
|
|
@@ -606,26 +624,22 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
606
624
|
return false;
|
|
607
625
|
}
|
|
608
626
|
|
|
609
|
-
async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<
|
|
627
|
+
async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
610
628
|
RabbitMq.validateName('queue', queueName);
|
|
611
629
|
if (this.queues[queueName]) {
|
|
612
630
|
delete this.queueSetupPromises[queueName];
|
|
613
|
-
return this.queues[queueName]
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
if (this.queueSetupPromises[queueName]) {
|
|
617
|
-
return this.queueSetupPromises[queueName];
|
|
631
|
+
return this.queues[queueName]!;
|
|
618
632
|
}
|
|
619
633
|
|
|
620
|
-
this.queueSetupPromises[queueName]
|
|
621
|
-
return this.queueSetupPromises[queueName]
|
|
634
|
+
this.queueSetupPromises[queueName] ||= this.setupQueue(queueName, options);
|
|
635
|
+
return this.queueSetupPromises[queueName]!;
|
|
622
636
|
}
|
|
623
637
|
|
|
624
|
-
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
625
|
-
const isConsumerExist
|
|
638
|
+
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined): void {
|
|
639
|
+
const isConsumerExist: boolean = this.consumersToRegister.some((consumer) => consumer.queue === queue);
|
|
626
640
|
if (!isConsumerExist) {
|
|
627
|
-
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
628
|
-
this.
|
|
641
|
+
this.#logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
642
|
+
this.consumersToRegister.push({
|
|
629
643
|
queue,
|
|
630
644
|
callback,
|
|
631
645
|
options,
|
|
@@ -634,27 +648,30 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
634
648
|
}
|
|
635
649
|
|
|
636
650
|
// Used by the microservices to consume messages from the queue
|
|
637
|
-
async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
651
|
+
async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void> {
|
|
652
|
+
this.saveConsumerOld(queue, callback, options);
|
|
653
|
+
|
|
654
|
+
// TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
|
|
655
|
+
if (options?.isQuorumQueue !== false && DISABLE_QUORUM_QUEUES_CONSUME !== 'true') {
|
|
656
|
+
this.saveConsumer(queue, callback, options);
|
|
657
|
+
const backoffConfig = getAssertVhostExponentialBackoffConfig();
|
|
658
|
+
await backOff(() => this.assertVHost(), backoffConfig);
|
|
641
659
|
await this.consumeNew(queue, callback, options);
|
|
642
660
|
}
|
|
643
|
-
|
|
644
661
|
await this.consumeOld(queue, callback, options);
|
|
645
662
|
}
|
|
646
663
|
|
|
647
|
-
// TODO:
|
|
648
|
-
async consumeNew(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<
|
|
664
|
+
// TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
|
|
665
|
+
async consumeNew(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void> {
|
|
649
666
|
await this.consumeFromRabbit(queue, callback, options);
|
|
650
667
|
}
|
|
651
668
|
|
|
652
|
-
// TODO:
|
|
653
|
-
async consumeOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<
|
|
669
|
+
// TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
|
|
670
|
+
async consumeOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void> {
|
|
654
671
|
await this.consumeFromRabbitOld(queue, callback, options);
|
|
655
672
|
}
|
|
656
673
|
|
|
657
|
-
private async lockRedisIfNeeded(msg:
|
|
674
|
+
private async lockRedisIfNeeded(msg: ReturnType<typeof RabbitMq.parseMsg>, options: ConsumeOptions): Promise<(() => Promise<void>) | null> {
|
|
658
675
|
const { properties: { headers } } = msg;
|
|
659
676
|
const timestamp = headers?.creationTimestamp;
|
|
660
677
|
let releaseLock = null;
|
|
@@ -668,29 +685,28 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
668
685
|
return releaseLock;
|
|
669
686
|
}
|
|
670
687
|
|
|
671
|
-
private async unlockRedisIfNeeded(releaseLock
|
|
688
|
+
private async unlockRedisIfNeeded(releaseLock?: (() => Promise<void>) | null): Promise<void> {
|
|
672
689
|
if (this.redisLock && releaseLock) {
|
|
673
690
|
await releaseLock();
|
|
674
691
|
}
|
|
675
692
|
}
|
|
676
693
|
|
|
677
|
-
private async consumeFromRabbit(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<
|
|
694
|
+
private async consumeFromRabbit(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void> {
|
|
678
695
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
679
696
|
RabbitMq.validateName('queue', queue);
|
|
680
|
-
this.saveConsumer(queue, callback, options);
|
|
681
697
|
const uniqueId = randomUUID();
|
|
682
698
|
const {
|
|
683
699
|
limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
|
|
684
700
|
} = optionsWithDefaults;
|
|
685
701
|
if (useConsumeWithLock) {
|
|
686
702
|
if (!this.redisLock) {
|
|
687
|
-
throw new
|
|
703
|
+
throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
|
|
688
704
|
}
|
|
689
|
-
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
705
|
+
this.#logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
690
706
|
}
|
|
691
|
-
const channel = await this.getNewChannel({});
|
|
707
|
+
const channel = await this.getNewChannel({ connection: this.consumeConnection });
|
|
692
708
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
693
|
-
|
|
709
|
+
await this.assertQueue(queue, optionsWithDefaults);
|
|
694
710
|
await confirmChannel.prefetch(limit, false);
|
|
695
711
|
const { consumerTag } = await confirmChannel.consume(
|
|
696
712
|
queue,
|
|
@@ -699,9 +715,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
699
715
|
return null;
|
|
700
716
|
}
|
|
701
717
|
|
|
702
|
-
const traceId = msg.properties.headers
|
|
703
|
-
const userId = msg.properties.headers[USER_TRACING_HEADER];
|
|
704
|
-
const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
|
|
718
|
+
const { [TRACING_HEADER]: traceId, [USER_TRACING_HEADER]: userId, [AUTOMATION_ID_HEADER]: automationId } = msg.properties.headers ?? {};
|
|
705
719
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
706
720
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
707
721
|
const trace = newTrace(traceTypes.RABBIT);
|
|
@@ -716,13 +730,13 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
716
730
|
createOrSetRabbitTrace(outbreakTrace, userId),
|
|
717
731
|
]);
|
|
718
732
|
} catch (e) {
|
|
719
|
-
logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
733
|
+
this.#logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
720
734
|
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
721
735
|
}
|
|
722
736
|
}
|
|
723
737
|
|
|
724
738
|
if (traceId) {
|
|
725
|
-
|
|
739
|
+
trace?.context?.set(TRACING_HEADER, traceId);
|
|
726
740
|
(outbreakTrace as any)?.context.set(TRACING_HEADER, traceId);
|
|
727
741
|
}
|
|
728
742
|
|
|
@@ -746,48 +760,53 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
746
760
|
return;
|
|
747
761
|
}
|
|
748
762
|
messageAcked = true;
|
|
749
|
-
|
|
763
|
+
await this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
750
764
|
};
|
|
751
765
|
|
|
752
766
|
const localNack = async (_: ConsumeMessageOrNull, nackOptions: NackOptions = {}) => {
|
|
753
767
|
if (messageAcked) {
|
|
754
768
|
return;
|
|
755
769
|
}
|
|
756
|
-
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
770
|
+
this.#logger.debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
757
771
|
messageAcked = true;
|
|
758
|
-
|
|
772
|
+
await this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
759
773
|
};
|
|
760
774
|
|
|
761
775
|
try {
|
|
762
|
-
await callback(
|
|
776
|
+
return await callback(
|
|
763
777
|
parsedMessage,
|
|
764
778
|
localAck,
|
|
765
779
|
localNack,
|
|
766
780
|
);
|
|
767
781
|
} catch (e) {
|
|
768
|
-
|
|
782
|
+
return localNack(msg);
|
|
769
783
|
}
|
|
770
784
|
}, CONSUMER_DEFAULT_OPTIONS,
|
|
771
785
|
);
|
|
772
786
|
if (!consumerTag) {
|
|
773
|
-
logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
787
|
+
this.#logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
774
788
|
} else {
|
|
775
|
-
logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
776
|
-
this.consumersTags.
|
|
789
|
+
this.#logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
790
|
+
this.consumersTags.set(queue, { channel: confirmChannel, consumerTag });
|
|
777
791
|
}
|
|
778
792
|
});
|
|
779
793
|
}
|
|
780
794
|
|
|
781
795
|
// Used by the microservices to consume messages from the exchange
|
|
782
|
-
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<
|
|
796
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<void> {
|
|
783
797
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
784
798
|
RabbitMq.validateName('exchange', exchange);
|
|
785
799
|
RabbitMq.validateName('queue', queue);
|
|
786
|
-
const { limit
|
|
787
|
-
if
|
|
788
|
-
|
|
800
|
+
const { limit } = optionsWithDefaults;
|
|
801
|
+
// TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
|
|
802
|
+
if (options?.isQuorumQueue !== false && DISABLE_QUORUM_QUEUES_CONSUME !== 'true') {
|
|
789
803
|
await this.saveConsumer(queue, callback, options);
|
|
790
|
-
const
|
|
804
|
+
const backoffConfig = getAssertVhostExponentialBackoffConfig();
|
|
805
|
+
await backOff(() => this.assertVHost(), backoffConfig);
|
|
806
|
+
const channel: ChannelWrapper = await this.getNewChannel({
|
|
807
|
+
name: `consume-exchange-${exchange}-queue-${queue}`,
|
|
808
|
+
connection: this.consumeConnection,
|
|
809
|
+
});
|
|
791
810
|
|
|
792
811
|
await channel.addSetup(async (c: ConfirmChannel) => {
|
|
793
812
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
@@ -804,10 +823,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
804
823
|
]);
|
|
805
824
|
});
|
|
806
825
|
}
|
|
807
|
-
|
|
808
|
-
// TODO: DELETE OLD
|
|
826
|
+
// TODO: [QUORUM-PHASE-3] Delete the old implementation
|
|
809
827
|
await this.saveConsumerOld(queue, callback, options);
|
|
810
|
-
const channelOld: ChannelWrapper = await this.getNewChannelOld({
|
|
828
|
+
const channelOld: ChannelWrapper = await this.getNewChannelOld({
|
|
829
|
+
name: `consume-exchange-${exchange}-queue-${queue}-old`,
|
|
830
|
+
connection: this.oldConsumeConnection,
|
|
831
|
+
});
|
|
811
832
|
await channelOld.addSetup(async (c: ConfirmChannel) => {
|
|
812
833
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
813
834
|
await c.assertQueue(queue);
|
|
@@ -825,46 +846,40 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
825
846
|
}
|
|
826
847
|
|
|
827
848
|
// Used by the microservices to publish messages to the exchange
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
RabbitMq.getPublishOptions(customHeaders));
|
|
839
|
-
});
|
|
849
|
+
// TODO: [QUORUM-PHASE-3] isQuorumQueue flag is not needed anymore because all the queues are created as quorum queues
|
|
850
|
+
async publish(exchange: string, content: any, customHeaders?: PublishOptions['headers'], isQuorumQueue = true): Promise<void> {
|
|
851
|
+
await setImmediate();
|
|
852
|
+
if (isQuorumQueue && DISABLE_QUORUM_QUEUES_PUBLISH !== 'true') {
|
|
853
|
+
await this.assertVHost();
|
|
854
|
+
RabbitMq.validateName('exchange', exchange);
|
|
855
|
+
const channel: ChannelWrapper = await this.assertChannel({ connection: this.publishConnection });
|
|
856
|
+
await this.assertExchange(exchange, this.publishConnection);
|
|
857
|
+
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
858
|
+
return;
|
|
840
859
|
}
|
|
841
860
|
|
|
842
|
-
// TODO:
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
await channel.publish(exchange, '',
|
|
848
|
-
Buffer.from(JSON.stringify(content)),
|
|
849
|
-
RabbitMq.getPublishOptions(customHeaders));
|
|
850
|
-
});
|
|
861
|
+
// TODO: [QUORUM-PHASE-3] Delete the old implementation
|
|
862
|
+
RabbitMq.validateName('exchange', exchange);
|
|
863
|
+
const channel: ChannelWrapper = await this.assertChannelOld({ connection: this.oldPublishConnection });
|
|
864
|
+
await this.assertExchangeOld(exchange, this.oldPublishConnection);
|
|
865
|
+
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
851
866
|
}
|
|
852
867
|
|
|
853
868
|
// Used by the microservices to send messages to the queue
|
|
869
|
+
// TODO: [QUORUM-PHASE-3] isQuorumQueue flag is not needed anymore because all the queues are created as quorum queues
|
|
854
870
|
async sendToQueue(
|
|
855
871
|
queue: string,
|
|
856
872
|
content: any,
|
|
857
|
-
options?:
|
|
858
|
-
customHeaders?:
|
|
873
|
+
options?: Options.AssertQueue,
|
|
874
|
+
customHeaders?: PublishOptions['headers'],
|
|
859
875
|
isQuorumQueue = true,
|
|
860
876
|
): Promise<boolean | undefined> {
|
|
861
|
-
|
|
862
|
-
if (isQuorumQueue) {
|
|
877
|
+
if (isQuorumQueue && DISABLE_QUORUM_QUEUES_PUBLISH !== 'true') {
|
|
863
878
|
try {
|
|
864
879
|
await this.assertVHost();
|
|
865
|
-
await this.assertChannel();
|
|
880
|
+
await this.assertChannel({ connection: this.publishConnection });
|
|
866
881
|
} catch (e) {
|
|
867
|
-
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
882
|
+
this.#logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
868
883
|
throw e;
|
|
869
884
|
}
|
|
870
885
|
|
|
@@ -872,107 +887,132 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
872
887
|
RabbitMq.validateName('queue', queue);
|
|
873
888
|
await this.assertQueue(queue, options);
|
|
874
889
|
} catch (e) {
|
|
875
|
-
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
890
|
+
this.#logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
876
891
|
throw e;
|
|
877
892
|
}
|
|
878
893
|
|
|
879
894
|
try {
|
|
880
|
-
const res = await this.
|
|
881
|
-
|
|
882
|
-
RabbitMq.getPublishOptions(customHeaders));
|
|
883
|
-
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
895
|
+
const res = await this.publishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
896
|
+
this.#logger.debug(`rabbit: sending to queue ${queue}`, { res });
|
|
884
897
|
return res;
|
|
885
898
|
} catch (e) {
|
|
886
899
|
const isConnected = await this.isConnected();
|
|
887
|
-
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
900
|
+
this.#logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
888
901
|
throw e;
|
|
889
902
|
}
|
|
890
903
|
} else {
|
|
891
904
|
try {
|
|
892
|
-
await this.assertChannelOld();
|
|
905
|
+
await this.assertChannelOld({ connection: this.oldPublishConnection });
|
|
893
906
|
} catch (e) {
|
|
894
|
-
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
907
|
+
this.#logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
895
908
|
throw e;
|
|
896
909
|
}
|
|
897
|
-
}
|
|
898
910
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
911
|
+
try {
|
|
912
|
+
RabbitMq.validateName('queue', queue);
|
|
913
|
+
await this.assertQueueOld(queue, options);
|
|
914
|
+
} catch (e) {
|
|
915
|
+
this.#logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
916
|
+
throw e;
|
|
917
|
+
}
|
|
906
918
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
throw e;
|
|
919
|
+
try {
|
|
920
|
+
const res = await this.oldPublishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
921
|
+
this.#logger.debug(`rabbit: sending to queue ${queue}`, { res });
|
|
922
|
+
return res;
|
|
923
|
+
} catch (e) {
|
|
924
|
+
const isConnected = await this.isConnectedOld();
|
|
925
|
+
this.#logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
926
|
+
throw e;
|
|
927
|
+
}
|
|
917
928
|
}
|
|
918
929
|
}
|
|
919
930
|
|
|
920
|
-
async isConnected()
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
if (!
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
931
|
+
async isConnected(): Promise<boolean> {
|
|
932
|
+
let isConnected = true;
|
|
933
|
+
// TODO:[QUORUM-PHASE-3] Remove the condition
|
|
934
|
+
if (!this.gracefulShutdownStarted) {
|
|
935
|
+
if (DISABLE_QUORUM_QUEUES_CONSUME !== 'true' || DISABLE_QUORUM_QUEUES_PUBLISH !== 'true') {
|
|
936
|
+
this.#logger.debug('rabbit: start is connected');
|
|
937
|
+
const [consumeConnection, publishConnection] = await Promise.all([
|
|
938
|
+
this.getConnection(this.consumeConnection),
|
|
939
|
+
this.getConnection(this.publishConnection),
|
|
940
|
+
]);
|
|
941
|
+
isConnected = consumeConnection.isConnected() && publishConnection.isConnected();
|
|
942
|
+
if (!isConnected) {
|
|
943
|
+
this.#logger.error('rabbit: isConnected - false');
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
const unRegisteredConsumers = this.consumersToRegister.filter((c) => !this.consumersTags.get(c.queue));
|
|
948
|
+
|
|
949
|
+
if (unRegisteredConsumers.length > 0) {
|
|
950
|
+
const queueNames = unRegisteredConsumers.map((c) => c.queue);
|
|
951
|
+
|
|
952
|
+
this.#logger.error('rabbit: found unregistered consumers for queues', {
|
|
953
|
+
count: queueNames.length,
|
|
954
|
+
queues: queueNames,
|
|
955
|
+
});
|
|
956
|
+
throw new RabbitError('Found unregistered consumers');
|
|
957
|
+
}
|
|
958
|
+
const channel: ChannelWrapper = await this.assertChannel({ connection: this.publishConnection });
|
|
959
|
+
await channel.waitForConnect();
|
|
960
|
+
|
|
961
|
+
await Promise.all(
|
|
962
|
+
this.consumersToRegister.map((c) => channel.checkQueue(c.queue)),
|
|
963
|
+
);
|
|
964
|
+
} catch (e: RabbitError | any) {
|
|
965
|
+
this.#logger.error('rabbit: isConnected - false', { msg: e.message });
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
// TODO:[QUORUM-PHASE-3] Delete the old is connected and adjust the return value to return true
|
|
970
|
+
isConnected = await this.isConnectedOld();
|
|
936
971
|
}
|
|
937
|
-
|
|
938
|
-
|
|
972
|
+
|
|
973
|
+
this.#logger.debug(`rabbit: isConnected - ${isConnected}`);
|
|
974
|
+
return isConnected;
|
|
939
975
|
}
|
|
940
976
|
|
|
941
|
-
async gracefulShutdown(signal: string)
|
|
942
|
-
// TODO:
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const
|
|
977
|
+
async gracefulShutdown(signal: string): Promise<void> {
|
|
978
|
+
// TODO: [QUORUM-PHASE-3] Delete the old implementation they are not needed anymore
|
|
979
|
+
this.gracefulShutdownStarted = true;
|
|
980
|
+
const tagsNumber = this.consumersTags.size + this.oldConsumersTags.size;
|
|
981
|
+
this.#logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
982
|
+
const cancelTagPromises = [...this.consumersTags].map(
|
|
983
|
+
([, { channel, consumerTag }]) => channel.cancel(consumerTag),
|
|
984
|
+
);
|
|
985
|
+
const cancelTagPromisesOld = [...this.oldConsumersTags].map(
|
|
986
|
+
([, { channel, consumerTag }]) => channel.cancel(consumerTag),
|
|
987
|
+
);
|
|
947
988
|
// Clean the array to avoid race
|
|
948
|
-
this.consumersTags
|
|
949
|
-
this.oldConsumersTags
|
|
989
|
+
this.consumersTags.clear();
|
|
990
|
+
this.oldConsumersTags.clear();
|
|
950
991
|
const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
|
|
951
992
|
const rejected = results.filter((p) => p.status === 'rejected');
|
|
952
993
|
if (rejected.length > 0) {
|
|
953
|
-
logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
994
|
+
this.#logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
954
995
|
} else {
|
|
955
|
-
logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
|
|
996
|
+
this.#logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
|
|
956
997
|
}
|
|
957
998
|
}
|
|
958
999
|
|
|
959
|
-
private async consumeFromRabbitOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<
|
|
1000
|
+
private async consumeFromRabbitOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void> {
|
|
960
1001
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
961
1002
|
RabbitMq.validateName('queue', queue);
|
|
962
|
-
this.saveConsumerOld(queue, callback, options);
|
|
963
1003
|
const uniqueId = randomUUID();
|
|
964
1004
|
const {
|
|
965
1005
|
limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
|
|
966
1006
|
} = optionsWithDefaults;
|
|
967
1007
|
if (useConsumeWithLock) {
|
|
968
1008
|
if (!this.redisLock) {
|
|
969
|
-
throw new
|
|
1009
|
+
throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
|
|
970
1010
|
}
|
|
971
|
-
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
1011
|
+
this.#logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
972
1012
|
}
|
|
973
|
-
const channel = await this.getNewChannelOld({});
|
|
1013
|
+
const channel = await this.getNewChannelOld({ connection: this.oldConsumeConnection });
|
|
974
1014
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
975
|
-
|
|
1015
|
+
await this.assertQueueOld(queue, optionsWithDefaults);
|
|
976
1016
|
await confirmChannel.prefetch(limit, false);
|
|
977
1017
|
const { consumerTag } = await confirmChannel.consume(
|
|
978
1018
|
queue,
|
|
@@ -981,9 +1021,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
981
1021
|
return null;
|
|
982
1022
|
}
|
|
983
1023
|
|
|
984
|
-
const traceId = msg.properties.headers
|
|
985
|
-
const userId = msg.properties.headers[USER_TRACING_HEADER];
|
|
986
|
-
const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
|
|
1024
|
+
const { [TRACING_HEADER]: traceId, [USER_TRACING_HEADER]: userId, [AUTOMATION_ID_HEADER]: automationId } = msg.properties.headers ?? {};
|
|
987
1025
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
988
1026
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
989
1027
|
const trace = newTrace(traceTypes.RABBIT);
|
|
@@ -998,13 +1036,13 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
998
1036
|
createOrSetRabbitTrace(outbreakTrace, userId),
|
|
999
1037
|
]);
|
|
1000
1038
|
} catch (e) {
|
|
1001
|
-
logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
1039
|
+
this.#logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
1002
1040
|
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
1003
1041
|
}
|
|
1004
1042
|
}
|
|
1005
1043
|
|
|
1006
1044
|
if (traceId) {
|
|
1007
|
-
|
|
1045
|
+
trace?.context?.set(TRACING_HEADER, traceId);
|
|
1008
1046
|
(outbreakTrace as any)?.context.set(TRACING_HEADER, traceId);
|
|
1009
1047
|
}
|
|
1010
1048
|
|
|
@@ -1028,152 +1066,173 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
1028
1066
|
return;
|
|
1029
1067
|
}
|
|
1030
1068
|
messageAcked = true;
|
|
1031
|
-
|
|
1069
|
+
await this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
1032
1070
|
};
|
|
1033
1071
|
|
|
1034
1072
|
const localNack = async (_: ConsumeMessageOrNull, nackOptions: NackOptions = {}) => {
|
|
1035
1073
|
if (messageAcked) {
|
|
1036
1074
|
return;
|
|
1037
1075
|
}
|
|
1038
|
-
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
1076
|
+
this.#logger.debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
1039
1077
|
messageAcked = true;
|
|
1040
|
-
|
|
1078
|
+
await this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
1041
1079
|
};
|
|
1042
1080
|
|
|
1043
1081
|
try {
|
|
1044
|
-
await callback(
|
|
1082
|
+
return await callback(
|
|
1045
1083
|
parsedMessage,
|
|
1046
1084
|
localAck,
|
|
1047
1085
|
localNack,
|
|
1048
1086
|
);
|
|
1049
1087
|
} catch (e) {
|
|
1050
|
-
|
|
1088
|
+
return localNack(msg);
|
|
1051
1089
|
}
|
|
1052
1090
|
}, CONSUMER_DEFAULT_OPTIONS,
|
|
1053
1091
|
);
|
|
1054
1092
|
if (!consumerTag) {
|
|
1055
|
-
logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
1093
|
+
this.#logger.error(`rabbit: failed to consume from queue ${queue} old`);
|
|
1056
1094
|
} else {
|
|
1057
|
-
logger.info(`rabbit: adding tag ${consumerTag} to the array
|
|
1058
|
-
this.oldConsumersTags.
|
|
1095
|
+
this.#logger.info(`rabbit: adding tag ${consumerTag} to the array old`);
|
|
1096
|
+
this.oldConsumersTags.set(queue, { channel: confirmChannel, consumerTag });
|
|
1059
1097
|
}
|
|
1060
1098
|
});
|
|
1061
1099
|
}
|
|
1062
1100
|
|
|
1063
|
-
// TODO:
|
|
1064
|
-
async getNewChannelOld({
|
|
1065
|
-
|
|
1101
|
+
// TODO: [QUORUM-PHASE-3] Delete all the function under this line.
|
|
1102
|
+
async getNewChannelOld({
|
|
1103
|
+
name = rand().toString(),
|
|
1104
|
+
onClose = null,
|
|
1105
|
+
options = {},
|
|
1106
|
+
connection,
|
|
1107
|
+
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
1108
|
+
let localConnection!: AmqpConnectionManager;
|
|
1066
1109
|
try {
|
|
1067
|
-
|
|
1110
|
+
localConnection = await this.getConnectionOld(connection);
|
|
1068
1111
|
} catch (e) {
|
|
1069
|
-
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
1112
|
+
this.#logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
1070
1113
|
throw e;
|
|
1071
1114
|
}
|
|
1072
|
-
|
|
1115
|
+
|
|
1116
|
+
if (!localConnection) {
|
|
1117
|
+
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const channel = localConnection?.createChannel({ ...options });
|
|
1073
1121
|
once(channel, 'close').then((args) => {
|
|
1074
|
-
logger.error(`rabbit: channel ${name} closed`);
|
|
1122
|
+
this.#logger.error(`rabbit: channel ${name} closed`);
|
|
1075
1123
|
onClose?.(args);
|
|
1076
1124
|
});
|
|
1077
1125
|
try {
|
|
1078
1126
|
await once(channel, 'connect');
|
|
1079
|
-
debug(`rabbit: channel ${name} CONNECTED`);
|
|
1127
|
+
this.#logger.debug(`rabbit: channel ${name} CONNECTED`);
|
|
1080
1128
|
return channel;
|
|
1081
1129
|
} catch (err) {
|
|
1082
|
-
logger.error(`rabbit: channel error ${name} error`, { err });
|
|
1130
|
+
this.#logger.error(`rabbit: channel error ${name} error`, { err });
|
|
1083
1131
|
throw err;
|
|
1084
1132
|
}
|
|
1085
1133
|
}
|
|
1086
1134
|
|
|
1087
|
-
async getConnectionOld() {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1135
|
+
async getConnectionOld(connection: ConnectionData): Promise<AmqpConnectionManager> {
|
|
1136
|
+
const {
|
|
1137
|
+
amqpConnection: connectionLocal,
|
|
1138
|
+
creatingConnection,
|
|
1139
|
+
connectionCreatedEventName,
|
|
1140
|
+
connectionFailedEventName,
|
|
1141
|
+
blockReconnect,
|
|
1142
|
+
} = connection;
|
|
1143
|
+
|
|
1144
|
+
if (blockReconnect) {
|
|
1145
|
+
this.#logger.debug('rabbit: block reconnect');
|
|
1146
|
+
// @ts-expect-error we are returning undefined while the function clearly expects a connection.
|
|
1147
|
+
return undefined;
|
|
1148
|
+
}
|
|
1149
|
+
if (connectionLocal !== null) {
|
|
1150
|
+
if (this.options?.disableReconnect || connectionLocal?.isConnected()) {
|
|
1151
|
+
this.#logger.debug('rabbit: connection - is connected');
|
|
1091
1152
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1092
1153
|
// @ts-ignore
|
|
1093
|
-
return
|
|
1154
|
+
return connectionLocal;
|
|
1094
1155
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
}
|
|
1102
|
-
debug('rabbit: connection - reconnecting');
|
|
1103
|
-
}
|
|
1104
|
-
if (this.oldCreatingConnection) {
|
|
1105
|
-
debug('rabbit: creating connection emi');
|
|
1106
|
-
this.oldEm.once(CONNECTION_CREATED_CONST, resolve);
|
|
1107
|
-
this.oldEm.once(CONNECTION_FAILED_CONST, reject);
|
|
1108
|
-
return;
|
|
1156
|
+
this.#logger.debug('rabbit: connection - reconnecting');
|
|
1157
|
+
}
|
|
1158
|
+
if (creatingConnection) {
|
|
1159
|
+
const [event, value] = await Promise.race([connectionCreatedEventName, connectionFailedEventName].map((e) => once(this.oldEm, e).then(([result]) => [e, result] as const)));
|
|
1160
|
+
if (event === connectionCreatedEventName) {
|
|
1161
|
+
return value;
|
|
1109
1162
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
// because of k8s changes the env variables
|
|
1115
|
-
// and we want to use the new values
|
|
1116
|
-
const findServers = () => {
|
|
1117
|
-
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
1118
|
-
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
1119
|
-
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
1163
|
+
throw value;
|
|
1164
|
+
}
|
|
1165
|
+
connection.creatingConnection = true;
|
|
1166
|
+
let isResolved = false;
|
|
1120
1167
|
|
|
1121
|
-
|
|
1168
|
+
// It is import to use it as a function and not as a variable
|
|
1169
|
+
// because of k8s changes the env variables
|
|
1170
|
+
// and we want to use the new values
|
|
1171
|
+
const findServers = () => {
|
|
1172
|
+
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
1173
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
1174
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
1122
1175
|
|
|
1123
|
-
|
|
1124
|
-
};
|
|
1176
|
+
this.#logger.debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
1125
1177
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
findServers,
|
|
1129
|
-
});
|
|
1178
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
1179
|
+
};
|
|
1130
1180
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
logger.error('rabbit: connection error', { err });
|
|
1134
|
-
if (!isResolved) {
|
|
1135
|
-
isResolved = true;
|
|
1136
|
-
reject(err);
|
|
1137
|
-
this.oldEm.emit(CONNECTION_FAILED_CONST, err);
|
|
1138
|
-
}
|
|
1139
|
-
});
|
|
1181
|
+
const defaultUrls = findServers();
|
|
1182
|
+
const newConnection: AmqpConnectionManager = await connect(defaultUrls, { findServers });
|
|
1140
1183
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
logger.error('rabbit: connection connectFailed', { err });
|
|
1144
|
-
if (!isResolved) {
|
|
1145
|
-
isResolved = true;
|
|
1146
|
-
reject(err);
|
|
1147
|
-
this.oldEm.emit(CONNECTION_FAILED_CONST, err);
|
|
1148
|
-
}
|
|
1149
|
-
});
|
|
1184
|
+
connection.amqpConnection = newConnection;
|
|
1185
|
+
const { promise, reject, resolve } = createDeferredPromise<AmqpConnectionManager>();
|
|
1150
1186
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
}
|
|
1160
|
-
});
|
|
1187
|
+
newConnection.on('error', (err) => {
|
|
1188
|
+
this.#logger.error('rabbit: connection error', { err });
|
|
1189
|
+
if (!isResolved) {
|
|
1190
|
+
isResolved = true;
|
|
1191
|
+
reject(err);
|
|
1192
|
+
this.oldEm.emit(connectionFailedEventName, err);
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1161
1195
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
this.
|
|
1196
|
+
newConnection.on('connectFailed', (err) => {
|
|
1197
|
+
this.oldConsumersTags.clear();
|
|
1198
|
+
if (typeof err.url === 'string') {
|
|
1199
|
+
err.url = this.maskURL(err.url);
|
|
1200
|
+
}
|
|
1201
|
+
logger.error('rabbit: connection connectFailed', { err });
|
|
1202
|
+
if (!isResolved) {
|
|
1166
1203
|
isResolved = true;
|
|
1167
|
-
|
|
1168
|
-
|
|
1204
|
+
reject(err);
|
|
1205
|
+
this.oldEm.emit(connectionFailedEventName, err);
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
1210
|
+
this.oldConsumersTags.clear();
|
|
1211
|
+
this.#logger.debug('rabbit: connection closed');
|
|
1212
|
+
if (this.options?.disableReconnect) {
|
|
1213
|
+
logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
1214
|
+
connection.blockReconnect = true;
|
|
1215
|
+
} else {
|
|
1216
|
+
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
newConnection.once('connect', async () => {
|
|
1221
|
+
this.#logger.debug('rabbit: connection established');
|
|
1222
|
+
connection.creatingConnection = false;
|
|
1223
|
+
this.oldEm.emit(connectionCreatedEventName, newConnection);
|
|
1224
|
+
isResolved = true;
|
|
1225
|
+
resolve(newConnection);
|
|
1169
1226
|
});
|
|
1227
|
+
|
|
1228
|
+
return promise;
|
|
1170
1229
|
}
|
|
1171
1230
|
|
|
1172
|
-
private saveConsumerOld(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
1173
|
-
const isConsumerExist
|
|
1231
|
+
private saveConsumerOld(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined): void {
|
|
1232
|
+
const isConsumerExist: boolean = this.oldConsumersToRegister.some((consumer) => consumer.queue === queue);
|
|
1174
1233
|
if (!isConsumerExist) {
|
|
1175
1234
|
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
1176
|
-
this.
|
|
1235
|
+
this.oldConsumersToRegister.push({
|
|
1177
1236
|
queue,
|
|
1178
1237
|
callback,
|
|
1179
1238
|
options,
|
|
@@ -1181,23 +1240,20 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
1181
1240
|
}
|
|
1182
1241
|
}
|
|
1183
1242
|
|
|
1184
|
-
async assertQueueOld(queueName: string, options?: Options.AssertQueue): Promise<
|
|
1243
|
+
async assertQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
1185
1244
|
RabbitMq.validateName('queue', queueName);
|
|
1186
1245
|
if (this.oldQueues[queueName]) {
|
|
1187
1246
|
delete this.oldQueueSetupPromises[queueName];
|
|
1188
|
-
return this.oldQueues[queueName]
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
if (this.oldQueueSetupPromises[queueName]) {
|
|
1192
|
-
return this.oldQueueSetupPromises[queueName];
|
|
1247
|
+
return this.oldQueues[queueName]!;
|
|
1193
1248
|
}
|
|
1194
1249
|
|
|
1195
|
-
this.oldQueueSetupPromises[queueName]
|
|
1196
|
-
return this.oldQueueSetupPromises[queueName]
|
|
1250
|
+
this.oldQueueSetupPromises[queueName] ||= this.setupQueueOld(queueName, options);
|
|
1251
|
+
return this.oldQueueSetupPromises[queueName]!;
|
|
1197
1252
|
}
|
|
1198
1253
|
|
|
1199
1254
|
async setupQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
1200
1255
|
let queue: Replies.AssertQueue;
|
|
1256
|
+
const connection = this.oldPublishConnection;
|
|
1201
1257
|
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
1202
1258
|
const localeOptions = {
|
|
1203
1259
|
...options,
|
|
@@ -1209,21 +1265,21 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
1209
1265
|
},
|
|
1210
1266
|
};
|
|
1211
1267
|
try {
|
|
1212
|
-
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1213
|
-
debug('assertQueue->channel.addSetup', { queueName });
|
|
1268
|
+
const channel: ChannelWrapper = await this.assertChannelOld({ connection });
|
|
1269
|
+
this.#logger.debug('assertQueue->channel.addSetup', { queueName });
|
|
1214
1270
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
1215
|
-
debug('assertQueue->channel.assertQueue', { queueName });
|
|
1271
|
+
this.#logger.debug('assertQueue->channel.assertQueue', { queueName });
|
|
1216
1272
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
1217
1273
|
} catch (e) {
|
|
1218
1274
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
1219
1275
|
if (!this.options?.dontRetryAssert) {
|
|
1220
|
-
debug('retrying assertQueue', { queueName });
|
|
1221
|
-
const channel = await this.assertChannelOld({ force: true });
|
|
1222
|
-
await this.deleteQueueOld(queueName);
|
|
1276
|
+
this.#logger.debug('retrying assertQueue', { queueName });
|
|
1277
|
+
const channel = await this.assertChannelOld({ force: true, connection });
|
|
1278
|
+
await this.deleteQueueOld(queueName, connection);
|
|
1223
1279
|
|
|
1224
|
-
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
1280
|
+
this.#logger.debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
1225
1281
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
1226
|
-
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
1282
|
+
this.#logger.debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
1227
1283
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
1228
1284
|
} else {
|
|
1229
1285
|
throw e;
|
|
@@ -1234,52 +1290,107 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
1234
1290
|
return queue;
|
|
1235
1291
|
}
|
|
1236
1292
|
|
|
1237
|
-
async assertChannelOld({ force = false }
|
|
1238
|
-
if (
|
|
1239
|
-
this.oldPublishChannelSetupPromise
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1293
|
+
async assertChannelOld({ force = false, connection }: assertChannelOpts): Promise<ChannelWrapper> {
|
|
1294
|
+
if (this.oldPublishChannelSetupPromise) {
|
|
1295
|
+
return this.oldPublishChannelSetupPromise;
|
|
1296
|
+
}
|
|
1297
|
+
const { promise, resolve, reject } = createDeferredPromise<ChannelWrapper>();
|
|
1298
|
+
this.oldPublishChannelSetupPromise = promise;
|
|
1299
|
+
if (this.oldPublishChannel && !force) {
|
|
1300
|
+
resolve(this.oldPublishChannel);
|
|
1301
|
+
return promise;
|
|
1302
|
+
}
|
|
1243
1303
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
});
|
|
1249
|
-
this.oldChannel = channel;
|
|
1250
|
-
resolve(channel);
|
|
1251
|
-
} catch (e) {
|
|
1252
|
-
reject(e);
|
|
1253
|
-
}
|
|
1304
|
+
try {
|
|
1305
|
+
const channel = await this.getNewChannelOld({ connection });
|
|
1306
|
+
channel.on('error', (err) => {
|
|
1307
|
+
logger.error('rabbit: channel error', { err });
|
|
1254
1308
|
});
|
|
1309
|
+
if (this.oldPublishConnection === connection) {
|
|
1310
|
+
this.oldPublishChannel = channel;
|
|
1311
|
+
}
|
|
1312
|
+
resolve(channel);
|
|
1313
|
+
} catch (e) {
|
|
1314
|
+
reject(e);
|
|
1255
1315
|
}
|
|
1256
|
-
return
|
|
1316
|
+
return promise;
|
|
1257
1317
|
}
|
|
1258
1318
|
|
|
1259
|
-
private async deleteQueueOld(queue: string) {
|
|
1319
|
+
private async deleteQueueOld(queue: string, connection: ConnectionData): Promise<Replies.DeleteQueue> {
|
|
1260
1320
|
RabbitMq.validateName('queue', queue);
|
|
1261
|
-
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1321
|
+
const channel: ChannelWrapper = await this.assertChannelOld({ connection });
|
|
1262
1322
|
logger.info('rabbit: deleting queue', { queue });
|
|
1263
1323
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
1264
|
-
debug('queue deleted', deleteQueueRes);
|
|
1324
|
+
this.#logger.debug('queue deleted', deleteQueueRes);
|
|
1265
1325
|
return deleteQueueRes;
|
|
1266
1326
|
}
|
|
1267
1327
|
|
|
1268
|
-
async assertExchangeOld(exchangeName: string,
|
|
1269
|
-
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1328
|
+
async assertExchangeOld(exchangeName: string, connection: ConnectionData): Promise<Replies.AssertExchange> {
|
|
1329
|
+
const channel: ChannelWrapper = await this.assertChannelOld({ connection });
|
|
1270
1330
|
|
|
1271
1331
|
if (this.oldExchanges[exchangeName]) {
|
|
1272
1332
|
delete this.oldAssertExchangePromises[exchangeName];
|
|
1273
|
-
return this.oldExchanges[exchangeName]
|
|
1333
|
+
return this.oldExchanges[exchangeName]!;
|
|
1274
1334
|
}
|
|
1275
1335
|
|
|
1276
1336
|
if (this.oldAssertExchangePromises[exchangeName]) {
|
|
1277
|
-
return this.oldAssertExchangePromises[exchangeName]
|
|
1337
|
+
return this.oldAssertExchangePromises[exchangeName]!;
|
|
1278
1338
|
}
|
|
1279
1339
|
|
|
1280
1340
|
this.oldAssertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
1281
1341
|
this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
|
|
1282
|
-
return this.oldExchanges[exchangeName]
|
|
1342
|
+
return this.oldExchanges[exchangeName]!;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
async isConnectedOld(): Promise<boolean> {
|
|
1346
|
+
if (!this.gracefulShutdownStarted) {
|
|
1347
|
+
this.#logger.debug('rabbit: start is connected old');
|
|
1348
|
+
const [oldConsumeConnection, oldPublishConnection] = await Promise.all([
|
|
1349
|
+
this.getConnectionOld(this.oldConsumeConnection),
|
|
1350
|
+
this.getConnectionOld(this.oldPublishConnection),
|
|
1351
|
+
]);
|
|
1352
|
+
const isConnected = oldConsumeConnection.isConnected() && oldPublishConnection.isConnected();
|
|
1353
|
+
if (!isConnected) {
|
|
1354
|
+
logger.error('rabbit: isConnected old - false');
|
|
1355
|
+
return false;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
try {
|
|
1359
|
+
const unRegisteredConsumersOld = this.consumersToRegister.filter((c) => !this.consumersTags.get(c.queue));
|
|
1360
|
+
|
|
1361
|
+
if (unRegisteredConsumersOld.length > 0) {
|
|
1362
|
+
const queueNames = unRegisteredConsumersOld.map((c) => c.queue);
|
|
1363
|
+
|
|
1364
|
+
logger.error('rabbit: found unregistered consumers for queues old', {
|
|
1365
|
+
count: queueNames.length,
|
|
1366
|
+
queues: queueNames,
|
|
1367
|
+
});
|
|
1368
|
+
throw new RabbitError('Found unregistered consumers old');
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const channelOld = await this.assertChannelOld({ connection: this.oldPublishConnection });
|
|
1372
|
+
await channelOld.waitForConnect();
|
|
1373
|
+
|
|
1374
|
+
await Promise.all(
|
|
1375
|
+
this.oldConsumersToRegister.map((c: AfConsumer) => channelOld.checkQueue(c.queue)),
|
|
1376
|
+
);
|
|
1377
|
+
} catch (e: RabbitError | any) {
|
|
1378
|
+
logger.error('rabbit: isConnected - false old', { msg: e.message });
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
private maskURL = (url: string): string => {
|
|
1386
|
+
try {
|
|
1387
|
+
const urlObj = new URL(url);
|
|
1388
|
+
urlObj.username = '***';
|
|
1389
|
+
urlObj.password = '***';
|
|
1390
|
+
return urlObj.toString();
|
|
1391
|
+
} catch {
|
|
1392
|
+
return url;
|
|
1393
|
+
}
|
|
1283
1394
|
}
|
|
1284
1395
|
}
|
|
1285
1396
|
|