@cap-js-community/event-queue 1.11.0-beta.5 → 2.0.0-beta.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/db/Event.cds +1 -0
- package/package.json +18 -12
- package/src/EventQueueProcessorBase.js +18 -3
- package/src/config.js +68 -208
- package/src/dbHandler.js +7 -4
- package/src/index.d.ts +10 -9
- package/src/initialize.js +8 -23
- package/src/outbox/eventQueueAsOutbox.js +19 -9
- package/src/periodicEvents.js +13 -12
- package/src/processEventQueue.js +7 -11
- package/src/publishEvent.js +15 -6
- package/src/redis/redisPub.js +10 -8
- package/src/redis/redisSub.js +16 -5
- package/src/runner/openEvents.js +12 -10
- package/src/runner/runner.js +26 -15
- package/src/runner/runnerHelper.js +10 -4
- package/src/shared/cdsHelper.js +0 -31
- package/src/shared/common.js +7 -2
- package/src/shared/distributedLock.js +23 -13
- package/src/shared/eventScheduler.js +3 -3
- package/src/shared/redis/client.js +46 -0
- package/src/shared/redis/index.js +45 -0
- package/srv/service/admin-service.cds +0 -8
- package/srv/service/admin-service.js +1 -10
- package/src/shared/redis.js +0 -199
package/src/shared/cdsHelper.js
CHANGED
|
@@ -165,38 +165,7 @@ const getAllTenantIds = async () => {
|
|
|
165
165
|
}, []);
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
-
const TENANT_COLUMNS = ["subscribedSubdomain", "createdAt", "modifiedAt"];
|
|
169
|
-
|
|
170
|
-
const getAllTenantWithMetadata = async () => {
|
|
171
|
-
const response = await _getAllTenantBase();
|
|
172
|
-
if (!response) {
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return response.reduce(async (result, row) => {
|
|
177
|
-
const tenantId = row.subscribedTenantId ?? row.tenant;
|
|
178
|
-
result = await result;
|
|
179
|
-
if (await common.isTenantIdValidCb(TenantIdCheckTypes.eventProcessing, tenantId)) {
|
|
180
|
-
const data = Object.entries(row).reduce(
|
|
181
|
-
(result, [key, value]) => {
|
|
182
|
-
if (TENANT_COLUMNS.includes(key)) {
|
|
183
|
-
result[key] = value;
|
|
184
|
-
} else {
|
|
185
|
-
result.metadata[key] = value;
|
|
186
|
-
}
|
|
187
|
-
return result;
|
|
188
|
-
},
|
|
189
|
-
{ metadata: {} }
|
|
190
|
-
);
|
|
191
|
-
data.metadata = JSON.stringify(data.metadata);
|
|
192
|
-
result.push(data);
|
|
193
|
-
}
|
|
194
|
-
return result;
|
|
195
|
-
}, []);
|
|
196
|
-
};
|
|
197
|
-
|
|
198
168
|
module.exports = {
|
|
199
169
|
executeInNewTransaction,
|
|
200
170
|
getAllTenantIds,
|
|
201
|
-
getAllTenantWithMetadata,
|
|
202
171
|
};
|
package/src/shared/common.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
const { AsyncResource } = require("async_hooks");
|
|
3
4
|
const crypto = require("crypto");
|
|
4
5
|
|
|
5
6
|
const cds = require("@sap/cds");
|
|
@@ -92,7 +93,7 @@ const hashStringTo32Bit = (value) => crypto.createHash("sha256").update(String(v
|
|
|
92
93
|
const _getNewAuthContext = async (tenantId) => {
|
|
93
94
|
try {
|
|
94
95
|
if (!_getNewAuthContext._xsuaaService) {
|
|
95
|
-
_getNewAuthContext._xsuaaService = new xssec.XsuaaService(cds.requires
|
|
96
|
+
_getNewAuthContext._xsuaaService = new xssec.XsuaaService(cds.requires["xsuaa-eventQueue"]?.credentials);
|
|
96
97
|
}
|
|
97
98
|
const authService = _getNewAuthContext._xsuaaService;
|
|
98
99
|
const token = await authService.fetchClientCredentialsToken({ zid: tenantId });
|
|
@@ -104,6 +105,7 @@ const _getNewAuthContext = async (tenantId) => {
|
|
|
104
105
|
err: err.message,
|
|
105
106
|
responseCode: err.responseCode,
|
|
106
107
|
responseText: err.responseText,
|
|
108
|
+
tenantId,
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
if (err.responseCode === 404) {
|
|
@@ -131,7 +133,10 @@ const getAuthContext = async (tenantId, { returnError = false } = {}) => {
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
getAuthContext._cache = getAuthContext._cache ?? new ExpiringLazyCache();
|
|
134
|
-
const result = await getAuthContext._cache.getSetCb(
|
|
136
|
+
const result = await getAuthContext._cache.getSetCb(
|
|
137
|
+
tenantId,
|
|
138
|
+
AsyncResource.bind(async () => _getNewAuthContext(tenantId))
|
|
139
|
+
);
|
|
135
140
|
if (returnError) {
|
|
136
141
|
return result;
|
|
137
142
|
} else {
|
|
@@ -60,12 +60,21 @@ const releaseLock = async (context, key, { tenantScoped = true } = {}) => {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const
|
|
63
|
+
const checkLockExists = async (context, key, { tenantScoped = true } = {}) => {
|
|
64
64
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
65
65
|
if (config.redisEnabled) {
|
|
66
|
-
return await
|
|
66
|
+
return !!(await _getLockValueRedis(context, fullKey));
|
|
67
67
|
} else {
|
|
68
|
-
return await
|
|
68
|
+
return !!(await _getLockValueDb(context, fullKey));
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const getValue = async (context, key, { tenantScoped = true } = {}) => {
|
|
73
|
+
const fullKey = _generateKey(context, tenantScoped, key);
|
|
74
|
+
if (config.redisEnabled) {
|
|
75
|
+
return await _getLockValueRedis(context, fullKey);
|
|
76
|
+
} else {
|
|
77
|
+
return await _getLockValueDb(context, fullKey);
|
|
69
78
|
}
|
|
70
79
|
};
|
|
71
80
|
|
|
@@ -75,7 +84,7 @@ const _acquireLockRedis = async (
|
|
|
75
84
|
expiryTime,
|
|
76
85
|
{ value = Date.now(), overrideValue = false, keepTrackOfLock } = {}
|
|
77
86
|
) => {
|
|
78
|
-
const client = await redis.createMainClientAndConnect(
|
|
87
|
+
const client = await redis.createMainClientAndConnect();
|
|
79
88
|
const result = await client.set(fullKey, value, {
|
|
80
89
|
PX: Math.round(expiryTime),
|
|
81
90
|
...(overrideValue ? null : { NX: true }),
|
|
@@ -88,7 +97,7 @@ const _acquireLockRedis = async (
|
|
|
88
97
|
};
|
|
89
98
|
|
|
90
99
|
const _renewLockRedis = async (context, fullKey, expiryTime, { value = "true" } = {}) => {
|
|
91
|
-
const client = await redis.createMainClientAndConnect(
|
|
100
|
+
const client = await redis.createMainClientAndConnect();
|
|
92
101
|
let result = await client.set(fullKey, value, {
|
|
93
102
|
PX: Math.round(expiryTime),
|
|
94
103
|
XX: true,
|
|
@@ -106,12 +115,12 @@ const _renewLockRedis = async (context, fullKey, expiryTime, { value = "true" }
|
|
|
106
115
|
return result === REDIS_COMMAND_OK;
|
|
107
116
|
};
|
|
108
117
|
|
|
109
|
-
const
|
|
110
|
-
const client = await redis.createMainClientAndConnect(
|
|
111
|
-
return await client.
|
|
118
|
+
const _getLockValueRedis = async (context, fullKey) => {
|
|
119
|
+
const client = await redis.createMainClientAndConnect();
|
|
120
|
+
return await client.get(fullKey);
|
|
112
121
|
};
|
|
113
122
|
|
|
114
|
-
const
|
|
123
|
+
const _getLockValueDb = async (context, fullKey) => {
|
|
115
124
|
let result;
|
|
116
125
|
await cdsHelper.executeInNewTransaction(context, "distributedLock-checkExists", async (tx) => {
|
|
117
126
|
result = await tx.run(SELECT.one.from(config.tableNameEventLock).where("code =", fullKey));
|
|
@@ -120,7 +129,7 @@ const _checkLockExistsDb = async (context, fullKey) => {
|
|
|
120
129
|
};
|
|
121
130
|
|
|
122
131
|
const _releaseLockRedis = async (context, fullKey) => {
|
|
123
|
-
const client = await redis.createMainClientAndConnect(
|
|
132
|
+
const client = await redis.createMainClientAndConnect();
|
|
124
133
|
const result = await client.del(fullKey);
|
|
125
134
|
delete existingLocks[fullKey];
|
|
126
135
|
return result === 1;
|
|
@@ -186,14 +195,14 @@ const _acquireLockDB = async (
|
|
|
186
195
|
};
|
|
187
196
|
|
|
188
197
|
const _generateKey = (context, tenantScoped, key) => {
|
|
189
|
-
const keyParts = [config.
|
|
198
|
+
const keyParts = [config.redisNamespace];
|
|
190
199
|
tenantScoped && keyParts.push(context.tenant);
|
|
191
200
|
keyParts.push(key);
|
|
192
201
|
return `${keyParts.join("##")}`;
|
|
193
202
|
};
|
|
194
203
|
|
|
195
204
|
const getAllLocksRedis = async () => {
|
|
196
|
-
const clientOrCluster = await redis.createMainClientAndConnect(
|
|
205
|
+
const clientOrCluster = await redis.createMainClientAndConnect();
|
|
197
206
|
const output = [];
|
|
198
207
|
const results = [];
|
|
199
208
|
|
|
@@ -259,7 +268,8 @@ const shutdownHandler = async () => {
|
|
|
259
268
|
module.exports = {
|
|
260
269
|
acquireLock,
|
|
261
270
|
releaseLock,
|
|
262
|
-
|
|
271
|
+
checkLockExists,
|
|
272
|
+
getValue,
|
|
263
273
|
setValueWithExpire,
|
|
264
274
|
shutdownHandler,
|
|
265
275
|
renewLock,
|
|
@@ -15,9 +15,9 @@ class EventScheduler {
|
|
|
15
15
|
config.attachUnsubscribeHandler(this.clearForTenant.bind(this));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
scheduleEvent(tenantId, type, subType, startAfter) {
|
|
18
|
+
scheduleEvent(tenantId, type, subType, namespace, startAfter) {
|
|
19
19
|
const { date, relative } = this.calculateOffset(type, subType, startAfter);
|
|
20
|
-
const key = [tenantId, type, subType, date.toISOString()].join("##");
|
|
20
|
+
const key = [tenantId, type, subType, namespace, date.toISOString()].join("##");
|
|
21
21
|
if (this.#scheduledEvents[key]) {
|
|
22
22
|
return; // event combination already scheduled
|
|
23
23
|
}
|
|
@@ -34,7 +34,7 @@ class EventScheduler {
|
|
|
34
34
|
clearTimeout(timeout);
|
|
35
35
|
delete this.#eventsByTenants[tenantId][timeout];
|
|
36
36
|
delete this.#scheduledEvents[key];
|
|
37
|
-
redisPub.broadcastEvent(tenantId, { type, subType }).catch((err) => {
|
|
37
|
+
redisPub.broadcastEvent(tenantId, { type, subType, namespace }).catch((err) => {
|
|
38
38
|
cds.log(COMPONENT_NAME).error("could not execute scheduled event", err, {
|
|
39
39
|
tenantId,
|
|
40
40
|
type,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { RedisClient } = require("@cap-js-community/common");
|
|
4
|
+
|
|
5
|
+
const config = require("../../config");
|
|
6
|
+
|
|
7
|
+
const REDIS_CLIENT_NAME = "eventQueue";
|
|
8
|
+
|
|
9
|
+
const createMainClientAndConnect = async () => {
|
|
10
|
+
const redisClient = RedisClient.create(REDIS_CLIENT_NAME);
|
|
11
|
+
return await redisClient.createMainClientAndConnect(config.redisOptions);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const subscribeRedisChannel = async (channel, subscribeHandler) => {
|
|
15
|
+
const redisClient = RedisClient.create(REDIS_CLIENT_NAME);
|
|
16
|
+
const channelWithNamespace = [config.redisNamespace, channel].join("##");
|
|
17
|
+
return await redisClient.subscribeChannel(config.redisOptions, channelWithNamespace, subscribeHandler);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const publishMessage = async (channel, message) => {
|
|
21
|
+
const redisClient = RedisClient.create(REDIS_CLIENT_NAME);
|
|
22
|
+
const channelWithNamespace = [config.redisNamespace, channel].join("##");
|
|
23
|
+
return await redisClient.publishMessage(config.redisOptions, channelWithNamespace, message);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const connectionCheck = async () => {
|
|
27
|
+
const redisClient = RedisClient.create(REDIS_CLIENT_NAME);
|
|
28
|
+
return await redisClient.connectionCheck(config.redisOptions);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const isClusterMode = () => {
|
|
32
|
+
return RedisClient.create(REDIS_CLIENT_NAME).isCluster;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const registerShutdownHandler = (cb) => {
|
|
36
|
+
RedisClient.create(REDIS_CLIENT_NAME).beforeCloseHandler = cb;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
createMainClientAndConnect,
|
|
41
|
+
subscribeRedisChannel,
|
|
42
|
+
publishMessage,
|
|
43
|
+
connectionCheck,
|
|
44
|
+
isClusterMode,
|
|
45
|
+
registerShutdownHandler,
|
|
46
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const cds = require("@sap/cds");
|
|
4
|
+
|
|
5
|
+
const client = require("./client");
|
|
6
|
+
const config = require("../../config");
|
|
7
|
+
|
|
8
|
+
const COMPONENT_NAME = "/eventQueue/redis";
|
|
9
|
+
const REDIS_OFFBOARD_TENANT_CHANNEL = "REDIS_OFFBOARD_TENANT_CHANNEL";
|
|
10
|
+
|
|
11
|
+
const attachRedisUnsubscribeHandler = () => {
|
|
12
|
+
cds.log(COMPONENT_NAME).info("attached redis handle for unsubscribe events");
|
|
13
|
+
client
|
|
14
|
+
.subscribeRedisChannel(REDIS_OFFBOARD_TENANT_CHANNEL, (messageData) => {
|
|
15
|
+
try {
|
|
16
|
+
const { tenantId } = JSON.parse(messageData);
|
|
17
|
+
cds.log(COMPONENT_NAME).info("received unsubscribe broadcast event", { tenantId });
|
|
18
|
+
this.executeUnsubscribeHandlers(tenantId);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
cds.log(COMPONENT_NAME).error("could not parse unsubscribe broadcast event", err, {
|
|
21
|
+
messageData,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.catch((err) => _errorHandlerSubscribeChannel(REDIS_OFFBOARD_TENANT_CHANNEL, err));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const handleUnsubscribe = (tenantId) => {
|
|
29
|
+
if (config.redisEnabled) {
|
|
30
|
+
client.publishMessage(REDIS_OFFBOARD_TENANT_CHANNEL, JSON.stringify({ tenantId })).catch((error) => {
|
|
31
|
+
cds.log(COMPONENT_NAME).error(`publishing tenant unsubscribe failed. tenantId: ${tenantId}`, error);
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
config.executeUnsubscribeHandlers(tenantId);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const _errorHandlerSubscribeChannel = (channelName, err) =>
|
|
39
|
+
cds.log(COMPONENT_NAME).error("error subscribing to channel", err, { channelName });
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
...client,
|
|
43
|
+
attachRedisUnsubscribeHandler,
|
|
44
|
+
handleUnsubscribe,
|
|
45
|
+
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const cds = require("@sap/cds");
|
|
4
|
-
const cdsHelper = require("../../src/shared/cdsHelper");
|
|
5
4
|
const { EventProcessingStatus } = require("../../src");
|
|
6
5
|
const config = require("../../src/config");
|
|
7
6
|
const distributedLock = require("../../src/shared/distributedLock");
|
|
@@ -9,7 +8,7 @@ const redisPub = require("../../src/redis/redisPub");
|
|
|
9
8
|
|
|
10
9
|
module.exports = class AdminService extends cds.ApplicationService {
|
|
11
10
|
async init() {
|
|
12
|
-
const { Event: EventService,
|
|
11
|
+
const { Event: EventService, Lock: LockService } = this.entities;
|
|
13
12
|
const { Event: EventDb } = cds.db.entities("sap.eventqueue");
|
|
14
13
|
const { landscape, space } = this.getLandscapeAndSpace();
|
|
15
14
|
|
|
@@ -18,9 +17,6 @@ module.exports = class AdminService extends cds.ApplicationService {
|
|
|
18
17
|
req.reject(403, "Admin service is disabled by configuration");
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
if (req.target.name === Tenant.name) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
20
|
const headers = Object.assign({}, req.headers, req.req?.headers);
|
|
25
21
|
const tenant = headers["z-id"] ?? req.data.tenant;
|
|
26
22
|
|
|
@@ -61,11 +57,6 @@ module.exports = class AdminService extends cds.ApplicationService {
|
|
|
61
57
|
}));
|
|
62
58
|
});
|
|
63
59
|
|
|
64
|
-
this.on("READ", Tenant, async () => {
|
|
65
|
-
const tenants = await cdsHelper.getAllTenantWithMetadata();
|
|
66
|
-
return tenants ?? [];
|
|
67
|
-
});
|
|
68
|
-
|
|
69
60
|
this.on("setStatusAndAttempts", async (req) => {
|
|
70
61
|
const tenant = req.headers["z-id"];
|
|
71
62
|
cds.log("eventQueue").info("Restarting processing for event queue");
|
package/src/shared/redis.js
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const redis = require("redis");
|
|
4
|
-
|
|
5
|
-
const { getEnvInstance } = require("./env");
|
|
6
|
-
const EventQueueError = require("../EventQueueError");
|
|
7
|
-
|
|
8
|
-
const COMPONENT_NAME = "/eventQueue/shared/redis";
|
|
9
|
-
const LOG_AFTER_SEC = 5;
|
|
10
|
-
|
|
11
|
-
let mainClientPromise;
|
|
12
|
-
let subscriberClientPromise;
|
|
13
|
-
const subscribedChannels = {};
|
|
14
|
-
let lastErrorLog = Date.now();
|
|
15
|
-
|
|
16
|
-
const createMainClientAndConnect = (options) => {
|
|
17
|
-
if (mainClientPromise) {
|
|
18
|
-
return mainClientPromise;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const errorHandlerCreateClient = (err) => {
|
|
22
|
-
mainClientPromise?.then?.(_resilientClientClose);
|
|
23
|
-
cds.log(COMPONENT_NAME).error("error from redis main client:", err);
|
|
24
|
-
mainClientPromise = null;
|
|
25
|
-
setTimeout(() => createMainClientAndConnect(options), LOG_AFTER_SEC * 1000).unref();
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
mainClientPromise = createClientAndConnect(options, errorHandlerCreateClient);
|
|
29
|
-
return mainClientPromise;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const _createClientBase = (redisOptions = {}) => {
|
|
33
|
-
const env = getEnvInstance();
|
|
34
|
-
try {
|
|
35
|
-
const { credentials, options } = env.redisRequires;
|
|
36
|
-
const socket = Object.assign(
|
|
37
|
-
{
|
|
38
|
-
host: credentials.hostname,
|
|
39
|
-
tls: !!credentials.tls,
|
|
40
|
-
port: credentials.port,
|
|
41
|
-
},
|
|
42
|
-
options?.socket,
|
|
43
|
-
redisOptions.socket
|
|
44
|
-
);
|
|
45
|
-
const socketOptions = Object.assign({}, options, redisOptions, {
|
|
46
|
-
password: redisOptions.password ?? options.password ?? credentials.password,
|
|
47
|
-
socket,
|
|
48
|
-
});
|
|
49
|
-
delete socketOptions.redisNamespace;
|
|
50
|
-
if (credentials.cluster_mode) {
|
|
51
|
-
return redis.createCluster({
|
|
52
|
-
rootNodes: [socketOptions],
|
|
53
|
-
defaults: socketOptions,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return redis.createClient(socketOptions);
|
|
57
|
-
} catch (err) {
|
|
58
|
-
throw EventQueueError.redisConnectionFailure(err);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const createClientAndConnect = async (options, errorHandlerCreateClient, isConnectionCheck) => {
|
|
63
|
-
try {
|
|
64
|
-
const client = _createClientBase(options);
|
|
65
|
-
if (!isConnectionCheck) {
|
|
66
|
-
client.on("error", (err) => {
|
|
67
|
-
const dateNow = Date.now();
|
|
68
|
-
if (dateNow - lastErrorLog > LOG_AFTER_SEC * 1000) {
|
|
69
|
-
cds.log(COMPONENT_NAME).error("error redis client:", err);
|
|
70
|
-
lastErrorLog = dateNow;
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
client.on("reconnecting", () => {
|
|
75
|
-
const dateNow = Date.now();
|
|
76
|
-
if (dateNow - lastErrorLog > LOG_AFTER_SEC * 1000) {
|
|
77
|
-
cds.log(COMPONENT_NAME).info("redis client trying reconnect...");
|
|
78
|
-
lastErrorLog = dateNow;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
await client.connect();
|
|
83
|
-
return client;
|
|
84
|
-
} catch (err) {
|
|
85
|
-
errorHandlerCreateClient(err);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const subscribeRedisChannel = (options, channel, subscribeHandler) => {
|
|
90
|
-
subscribedChannels[channel] = subscribeHandler;
|
|
91
|
-
const errorHandlerCreateClient = (err) => {
|
|
92
|
-
cds.log(COMPONENT_NAME).error(`error from redis client for pub/sub failed for channel ${channel}`, err);
|
|
93
|
-
subscriberClientPromise?.then?.(_resilientClientClose);
|
|
94
|
-
subscriberClientPromise = null;
|
|
95
|
-
setTimeout(() => _subscribeChannels(options, subscribedChannels, subscribeHandler), LOG_AFTER_SEC * 1000).unref();
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
_subscribeChannels(options, { [channel]: subscribeHandler }, errorHandlerCreateClient);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const _subscribeChannels = (options, subscribedChannels, errorHandlerCreateClient) => {
|
|
102
|
-
subscriberClientPromise = createClientAndConnect(options, errorHandlerCreateClient)
|
|
103
|
-
.then((client) => {
|
|
104
|
-
for (const channel in subscribedChannels) {
|
|
105
|
-
const fn = subscribedChannels[channel];
|
|
106
|
-
client._subscribedChannels ??= {};
|
|
107
|
-
if (client._subscribedChannels[channel]) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
const prefixedChannelName = [options.redisNamespace, channel].join("_");
|
|
111
|
-
cds.log(COMPONENT_NAME).info("subscribe redis client connected channel", { channel: prefixedChannelName });
|
|
112
|
-
client
|
|
113
|
-
.subscribe(prefixedChannelName, fn)
|
|
114
|
-
.then(() => {
|
|
115
|
-
client._subscribedChannels ??= {};
|
|
116
|
-
client._subscribedChannels[channel] = 1;
|
|
117
|
-
})
|
|
118
|
-
.catch(() => {
|
|
119
|
-
cds.log(COMPONENT_NAME).error("error subscribe to channel - retrying...");
|
|
120
|
-
setTimeout(() => _subscribeChannels(options, [channel], fn), LOG_AFTER_SEC * 1000).unref();
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
.catch((err) => {
|
|
125
|
-
cds
|
|
126
|
-
.log(COMPONENT_NAME)
|
|
127
|
-
.error(
|
|
128
|
-
`error from redis client for pub/sub failed during startup - trying to reconnect - ${Object.keys(
|
|
129
|
-
subscribedChannels
|
|
130
|
-
).join(", ")}`,
|
|
131
|
-
err
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const publishMessage = async (options, channel, message) => {
|
|
137
|
-
const client = await createMainClientAndConnect(options);
|
|
138
|
-
return await client.publish([options.redisNamespace, channel].join("_"), message);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const closeMainClient = async () => {
|
|
142
|
-
await _resilientClientClose(await mainClientPromise);
|
|
143
|
-
cds.log(COMPONENT_NAME).info("main redis client closed!");
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const closeSubscribeClient = async () => {
|
|
147
|
-
await _resilientClientClose(await subscriberClientPromise);
|
|
148
|
-
cds.log(COMPONENT_NAME).info("subscribe redis client closed!");
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const _resilientClientClose = async (client) => {
|
|
152
|
-
try {
|
|
153
|
-
if (client?.quit) {
|
|
154
|
-
await client.quit();
|
|
155
|
-
}
|
|
156
|
-
} catch (err) {
|
|
157
|
-
cds.log(COMPONENT_NAME).info("error during redis close - continuing...", err);
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const connectionCheck = async (options) => {
|
|
162
|
-
return new Promise((resolve, reject) => {
|
|
163
|
-
createClientAndConnect(options, reject, true)
|
|
164
|
-
.then((client) => {
|
|
165
|
-
if (client) {
|
|
166
|
-
_resilientClientClose(client);
|
|
167
|
-
resolve();
|
|
168
|
-
} else {
|
|
169
|
-
reject(new Error());
|
|
170
|
-
}
|
|
171
|
-
})
|
|
172
|
-
.catch(reject);
|
|
173
|
-
})
|
|
174
|
-
.then(() => true)
|
|
175
|
-
.catch((err) => {
|
|
176
|
-
cds.log(COMPONENT_NAME).error("Redis connection check failed! Falling back to NO_REDIS mode", err);
|
|
177
|
-
return false;
|
|
178
|
-
});
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const isClusterMode = () => {
|
|
182
|
-
if (!("__clusterMode" in isClusterMode)) {
|
|
183
|
-
const env = getEnvInstance();
|
|
184
|
-
const { credentials } = env.redisRequires;
|
|
185
|
-
isClusterMode.__clusterMode = credentials.cluster_mode;
|
|
186
|
-
}
|
|
187
|
-
return isClusterMode.__clusterMode;
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
module.exports = {
|
|
191
|
-
createClientAndConnect,
|
|
192
|
-
createMainClientAndConnect,
|
|
193
|
-
subscribeRedisChannel,
|
|
194
|
-
publishMessage,
|
|
195
|
-
closeMainClient,
|
|
196
|
-
closeSubscribeClient,
|
|
197
|
-
connectionCheck,
|
|
198
|
-
isClusterMode,
|
|
199
|
-
};
|