@cap-js-community/event-queue 1.8.2 → 1.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js-community/event-queue",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "description": "An event queue that enables secure transactional processing of asynchronous and periodic events, featuring instant event processing with Redis Pub/Sub and load distribution across all application instances.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
package/src/initialize.js CHANGED
@@ -10,7 +10,7 @@ const VError = require("verror");
10
10
  const runner = require("./runner/runner");
11
11
  const dbHandler = require("./dbHandler");
12
12
  const config = require("./config");
13
- const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redis/redisSub");
13
+ const redisSub = require("./redis/redisSub");
14
14
  const redis = require("./shared/redis");
15
15
  const eventQueueAsOutbox = require("./outbox/eventQueueAsOutbox");
16
16
  const { getAllTenantIds } = require("./shared/cdsHelper");
@@ -22,6 +22,7 @@ const readFileAsync = promisify(fs.readFile);
22
22
 
23
23
  const VERROR_CLUSTER_NAME = "EventQueueInitialization";
24
24
  const COMPONENT = "eventQueue/initialize";
25
+ const TIMEOUT_SHUTDOWN = 2500;
25
26
 
26
27
  const CONFIG_VARS = [
27
28
  ["configFilePath", null],
@@ -148,7 +149,7 @@ const registerEventProcessors = () => {
148
149
  const errorHandler = (err) => cds.log(COMPONENT).error("error during init runner", err);
149
150
 
150
151
  if (config.redisEnabled) {
151
- initEventQueueRedisSubscribe();
152
+ redisSub.initEventQueueRedisSubscribe();
152
153
  config.attachConfigChangeHandler();
153
154
  if (config.isMultiTenancy) {
154
155
  runner.multiTenancyRedis().catch(errorHandler);
@@ -186,9 +187,24 @@ const mixConfigVarsWithEnv = (options) => {
186
187
  };
187
188
 
188
189
  const registerCdsShutdown = () => {
190
+ const isTestProfile = cds.env.profiles.find((profile) => profile.includes("test"));
191
+ if (isTestProfile) {
192
+ return;
193
+ }
189
194
  cds.on("shutdown", async () => {
190
- await distributedLock.shutdownHandler();
191
- await Promise.allSettled([redis.closeMainClient(), closeSubscribeClient()]);
195
+ return await new Promise((resolve) => {
196
+ const timeoutRef = setTimeout(() => {
197
+ clearTimeout(timeoutRef);
198
+ cds.log(COMPONENT).info("shutdown timeout reached - some locks might not have been released!");
199
+ resolve();
200
+ }, TIMEOUT_SHUTDOWN);
201
+ distributedLock.shutdownHandler().then(() =>
202
+ Promise.allSettled([redis.closeMainClient(), redis.closeSubscribeClient()]).then((result) => {
203
+ clearTimeout(timeoutRef);
204
+ resolve(result);
205
+ })
206
+ );
207
+ });
192
208
  });
193
209
  };
194
210
 
@@ -9,12 +9,12 @@ const common = require("../shared/common");
9
9
 
10
10
  const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
11
11
  const COMPONENT_NAME = "/eventQueue/redisSub";
12
- let subscriberClientPromise;
13
12
 
14
13
  const initEventQueueRedisSubscribe = () => {
15
- if (subscriberClientPromise || !config.redisEnabled) {
14
+ if (initEventQueueRedisSubscribe._initDone || !config.redisEnabled) {
16
15
  return;
17
16
  }
17
+ initEventQueueRedisSubscribe._initDone = true;
18
18
  redis.subscribeRedisChannel(config.redisOptions, EVENT_MESSAGE_CHANNEL, _messageHandlerProcessEvents);
19
19
  };
20
20
 
@@ -83,20 +83,8 @@ const _messageHandlerProcessEvents = async (messageData) => {
83
83
  }
84
84
  };
85
85
 
86
- const closeSubscribeClient = async () => {
87
- try {
88
- const client = await subscriberClientPromise;
89
- if (client?.quit) {
90
- await client.quit();
91
- }
92
- } catch (err) {
93
- // ignore errors during shutdown
94
- }
95
- };
96
-
97
86
  module.exports = {
98
87
  initEventQueueRedisSubscribe,
99
- closeSubscribeClient,
100
88
  __: {
101
89
  _messageHandlerProcessEvents,
102
90
  },
package/src/shared/env.js CHANGED
@@ -18,7 +18,7 @@ class Env {
18
18
  }
19
19
 
20
20
  get redisRequires() {
21
- return cds.requires["redis-eventQueue"] ?? cds.requires["redis"];
21
+ return cds.requires["redis-eventQueue"] || cds.requires["redis"];
22
22
  }
23
23
 
24
24
  get applicationName() {
@@ -9,7 +9,8 @@ const COMPONENT_NAME = "/eventQueue/shared/redis";
9
9
  const LOG_AFTER_SEC = 5;
10
10
 
11
11
  let mainClientPromise;
12
- const subscriberChannelClientPromise = {};
12
+ let subscriberClientPromise;
13
+ const subscribedChannels = {};
13
14
  let lastErrorLog = Date.now();
14
15
 
15
16
  const createMainClientAndConnect = (options) => {
@@ -18,7 +19,8 @@ const createMainClientAndConnect = (options) => {
18
19
  }
19
20
 
20
21
  const errorHandlerCreateClient = (err) => {
21
- cds.log(COMPONENT_NAME).error("error from redis client for pub/sub failed", err);
22
+ mainClientPromise?.then?.(_resilientClientClose);
23
+ cds.log(COMPONENT_NAME).error("error from redis main client:", err);
22
24
  mainClientPromise = null;
23
25
  setTimeout(() => createMainClientAndConnect(options), LOG_AFTER_SEC * 1000).unref();
24
26
  };
@@ -63,7 +65,7 @@ const createClientAndConnect = async (options, errorHandlerCreateClient, isConne
63
65
  client.on("error", (err) => {
64
66
  const dateNow = Date.now();
65
67
  if (dateNow - lastErrorLog > LOG_AFTER_SEC * 1000) {
66
- cds.log(COMPONENT_NAME).error("error from redis client for pub/sub failed", err);
68
+ cds.log(COMPONENT_NAME).error("error redis client:", err);
67
69
  lastErrorLog = dateNow;
68
70
  }
69
71
  });
@@ -84,21 +86,48 @@ const createClientAndConnect = async (options, errorHandlerCreateClient, isConne
84
86
  };
85
87
 
86
88
  const subscribeRedisChannel = (options, channel, subscribeHandler) => {
89
+ subscribedChannels[channel] = subscribeHandler;
87
90
  const errorHandlerCreateClient = (err) => {
88
91
  cds.log(COMPONENT_NAME).error(`error from redis client for pub/sub failed for channel ${channel}`, err);
89
- subscriberChannelClientPromise[channel] = null;
90
- setTimeout(() => subscribeRedisChannel(options, channel, subscribeHandler), LOG_AFTER_SEC * 1000).unref();
92
+ subscriberClientPromise?.then?.(_resilientClientClose);
93
+ subscriberClientPromise = null;
94
+ setTimeout(() => _subscribeChannels(options, subscribedChannels, subscribeHandler), LOG_AFTER_SEC * 1000).unref();
91
95
  };
92
96
 
93
- subscriberChannelClientPromise[channel] = createClientAndConnect(options, errorHandlerCreateClient)
97
+ _subscribeChannels(options, { [channel]: subscribeHandler }, errorHandlerCreateClient);
98
+ };
99
+
100
+ const _subscribeChannels = (options, subscribedChannels, errorHandlerCreateClient) => {
101
+ subscriberClientPromise = createClientAndConnect(options, errorHandlerCreateClient)
94
102
  .then((client) => {
95
- cds.log(COMPONENT_NAME).info("subscribe redis client connected channel", { channel });
96
- client.subscribe(channel, subscribeHandler).catch(errorHandlerCreateClient);
103
+ for (const channel in subscribedChannels) {
104
+ const fn = subscribedChannels[channel];
105
+ client._subscribedChannels ??= {};
106
+ if (client._subscribedChannels[channel]) {
107
+ continue;
108
+ }
109
+ cds.log(COMPONENT_NAME).info("subscribe redis client connected channel", { channel });
110
+ client
111
+ .subscribe(channel, fn)
112
+ .then(() => {
113
+ client._subscribedChannels ??= {};
114
+ client._subscribedChannels[channel] = 1;
115
+ })
116
+ .catch(() => {
117
+ cds.log(COMPONENT_NAME).error("error subscribe to channel - retrying...");
118
+ setTimeout(() => _subscribeChannels(options, [channel], fn), LOG_AFTER_SEC * 1000).unref();
119
+ });
120
+ }
97
121
  })
98
122
  .catch((err) => {
99
123
  cds
100
124
  .log(COMPONENT_NAME)
101
- .error(`error from redis client for pub/sub failed during startup - trying to reconnect - ${channel}`, err);
125
+ .error(
126
+ `error from redis client for pub/sub failed during startup - trying to reconnect - ${Object.keys(
127
+ subscribedChannels
128
+ ).join(", ")}`,
129
+ err
130
+ );
102
131
  });
103
132
  };
104
133
 
@@ -108,11 +137,13 @@ const publishMessage = async (options, channel, message) => {
108
137
  };
109
138
 
110
139
  const closeMainClient = async () => {
111
- try {
112
- await _resilientClientClose(await mainClientPromise);
113
- } catch (err) {
114
- // ignore errors during shutdown
115
- }
140
+ await _resilientClientClose(await mainClientPromise);
141
+ cds.log(COMPONENT_NAME).info("main redis client closed!");
142
+ };
143
+
144
+ const closeSubscribeClient = async () => {
145
+ await _resilientClientClose(await subscriberClientPromise);
146
+ cds.log(COMPONENT_NAME).info("subscribe redis client closed!");
116
147
  };
117
148
 
118
149
  const _resilientClientClose = async (client) => {
@@ -121,7 +152,7 @@ const _resilientClientClose = async (client) => {
121
152
  await client.quit();
122
153
  }
123
154
  } catch (err) {
124
- // ignore errors during shutdown
155
+ cds.log(COMPONENT_NAME).info("error during redis close - continuing...", err);
125
156
  }
126
157
  };
127
158
 
@@ -151,5 +182,6 @@ module.exports = {
151
182
  subscribeRedisChannel,
152
183
  publishMessage,
153
184
  closeMainClient,
185
+ closeSubscribeClient,
154
186
  connectionCheck,
155
187
  };