@cap-js-community/event-queue 0.1.53 → 0.1.54
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 +1 -1
- package/src/config.js +31 -22
- package/src/initialize.js +6 -1
- package/src/redisPubSub.js +21 -55
- package/src/runner.js +21 -16
- package/src/shared/SetIntervalDriftSafe.js +1 -1
- package/src/shared/env.js +48 -4
- package/src/shared/redis.js +37 -10
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -2,30 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
const cds = require("@sap/cds");
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const { getEnvInstance: getEnvInstance } = require("./shared/env");
|
|
6
|
+
const redis = require("./shared/redis");
|
|
6
7
|
|
|
7
8
|
let instance;
|
|
8
9
|
|
|
9
10
|
const FOR_UPDATE_TIMEOUT = 10;
|
|
10
11
|
const GLOBAL_TX_TIMEOUT = 30 * 60 * 1000;
|
|
12
|
+
const REDIS_CONFIG_CHANNEL = "EVENT_QUEUE_CONFIG_CHANNEL";
|
|
13
|
+
const COMPONENT_NAME = "eventQueue/config";
|
|
11
14
|
|
|
12
15
|
class Config {
|
|
13
16
|
constructor() {
|
|
17
|
+
this.__logger = cds.log(COMPONENT_NAME);
|
|
14
18
|
this.__config = null;
|
|
15
19
|
this.__forUpdateTimeout = FOR_UPDATE_TIMEOUT;
|
|
16
20
|
this.__globalTxTimeout = GLOBAL_TX_TIMEOUT;
|
|
17
21
|
this.__runInterval = null;
|
|
18
22
|
this.__redisEnabled = null;
|
|
19
|
-
this.__isOnCF = env.isOnCF;
|
|
20
23
|
this.__initialized = false;
|
|
21
24
|
this.__parallelTenantProcessing = null;
|
|
22
25
|
this.__tableNameEventQueue = null;
|
|
23
26
|
this.__tableNameEventLock = null;
|
|
24
|
-
this.__vcapServices = this._parseVcapServices();
|
|
25
27
|
this.__isRunnerDeactivated = false;
|
|
26
28
|
this.__configFilePath = null;
|
|
27
29
|
this.__processEventsAfterPublish = null;
|
|
28
30
|
this.__skipCsnCheck = null;
|
|
31
|
+
this.__env = getEnvInstance();
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
getEventConfig(type, subType) {
|
|
@@ -37,23 +40,37 @@ class Config {
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
_checkRedisIsBound() {
|
|
40
|
-
return !!this.getRedisCredentialsFromEnv();
|
|
43
|
+
return !!this.__env.getRedisCredentialsFromEnv();
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
checkRedisEnabled() {
|
|
47
|
+
this.__redisEnabled = this._checkRedisIsBound() && this.__env.isOnCF;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
attachConfigChangeHandler() {
|
|
51
|
+
redis.subscribeRedisChannel(REDIS_CONFIG_CHANNEL, (messageData) => {
|
|
52
|
+
try {
|
|
53
|
+
const { key, value } = JSON.parse(messageData);
|
|
54
|
+
if (this[key] !== value) {
|
|
55
|
+
this.__logger.info("received config change", { key, value });
|
|
56
|
+
this[key] = value;
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
this.__logger.error("could not parse event config change", {
|
|
60
|
+
messageData,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
publishConfigChange(key, value) {
|
|
67
|
+
if (!this.redisEnabled) {
|
|
68
|
+
this.__logger.info("redis not connected, config change won't be published", { key, value });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
redis.publishMessage(REDIS_CONFIG_CHANNEL, JSON.stringify({ key, value })).catch((error) => {
|
|
72
|
+
this.__logger.error(`publishing config change failed key: ${key}, value: ${value}`, error);
|
|
73
|
+
});
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
get isRunnerDeactivated() {
|
|
@@ -112,14 +129,6 @@ class Config {
|
|
|
112
129
|
this.__redisEnabled = value;
|
|
113
130
|
}
|
|
114
131
|
|
|
115
|
-
get isOnCF() {
|
|
116
|
-
return this.__isOnCF;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
set isOnCF(value) {
|
|
120
|
-
this.__isOnCF = value;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
132
|
get initialized() {
|
|
124
133
|
return this.__initialized;
|
|
125
134
|
}
|
package/src/initialize.js
CHANGED
|
@@ -28,6 +28,7 @@ const initialize = async ({
|
|
|
28
28
|
configFilePath,
|
|
29
29
|
registerAsEventProcessor,
|
|
30
30
|
processEventsAfterPublish,
|
|
31
|
+
isRunnerDeactivated,
|
|
31
32
|
runInterval,
|
|
32
33
|
parallelTenantProcessing,
|
|
33
34
|
tableNameEventQueue,
|
|
@@ -48,6 +49,7 @@ const initialize = async ({
|
|
|
48
49
|
configFilePath,
|
|
49
50
|
registerAsEventProcessor,
|
|
50
51
|
processEventsAfterPublish,
|
|
52
|
+
isRunnerDeactivated,
|
|
51
53
|
runInterval,
|
|
52
54
|
parallelTenantProcessing,
|
|
53
55
|
tableNameEventQueue,
|
|
@@ -57,7 +59,7 @@ const initialize = async ({
|
|
|
57
59
|
|
|
58
60
|
const logger = cds.log(COMPONENT);
|
|
59
61
|
configInstance.fileContent = await readConfigFromFile(configInstance.configFilePath);
|
|
60
|
-
configInstance.
|
|
62
|
+
configInstance.checkRedisEnabled();
|
|
61
63
|
|
|
62
64
|
const dbService = await cds.connect.to("db");
|
|
63
65
|
await (cds.model ? Promise.resolve() : new Promise((resolve) => cds.on("serving", resolve)));
|
|
@@ -107,6 +109,7 @@ const registerEventProcessors = () => {
|
|
|
107
109
|
|
|
108
110
|
if (configInstance.redisEnabled) {
|
|
109
111
|
initEventQueueRedisSubscribe();
|
|
112
|
+
configInstance.attachConfigChangeHandler();
|
|
110
113
|
runner.multiTenancyRedis();
|
|
111
114
|
} else {
|
|
112
115
|
runner.multiTenancyDb();
|
|
@@ -161,6 +164,7 @@ const mixConfigVarsWithEnv = (
|
|
|
161
164
|
configFilePath,
|
|
162
165
|
registerAsEventProcessor,
|
|
163
166
|
processEventsAfterPublish,
|
|
167
|
+
isRunnerDeactivated,
|
|
164
168
|
runInterval,
|
|
165
169
|
parallelTenantProcessing,
|
|
166
170
|
tableNameEventQueue,
|
|
@@ -172,6 +176,7 @@ const mixConfigVarsWithEnv = (
|
|
|
172
176
|
configInstance.configFilePath = configFilePath ?? cds.env.eventQueue?.configFilePath;
|
|
173
177
|
configInstance.registerAsEventProcessor =
|
|
174
178
|
registerAsEventProcessor ?? cds.env.eventQueue?.registerAsEventProcessor ?? true;
|
|
179
|
+
configInstance.isRunnerDeactivated = isRunnerDeactivated ?? cds.env.eventQueue?.isRunnerDeactivated ?? false;
|
|
175
180
|
configInstance.processEventsAfterPublish =
|
|
176
181
|
processEventsAfterPublish ?? cds.env.eventQueue?.processEventsAfterPublish ?? true;
|
|
177
182
|
configInstance.runInterval = runInterval ?? cds.env.eventQueue?.runInterval ?? 5 * 60 * 1000;
|
package/src/redisPubSub.js
CHANGED
|
@@ -7,83 +7,50 @@ const { checkLockExistsAndReturnValue } = require("./shared/distributedLock");
|
|
|
7
7
|
const config = require("./config");
|
|
8
8
|
const { getWorkerPoolInstance } = require("./shared/WorkerQueue");
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
|
|
11
11
|
const COMPONENT_NAME = "eventQueue/redisPubSub";
|
|
12
12
|
|
|
13
|
-
let publishClient;
|
|
14
13
|
let subscriberClientPromise;
|
|
15
14
|
|
|
16
15
|
const initEventQueueRedisSubscribe = () => {
|
|
17
16
|
if (subscriberClientPromise || !config.getConfigInstance().redisEnabled) {
|
|
18
17
|
return;
|
|
19
18
|
}
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const subscribeRedisClient = () => {
|
|
24
|
-
const errorHandlerCreateClient = (err) => {
|
|
25
|
-
cds.log(COMPONENT_NAME).error("error from redis client for pub/sub failed", err);
|
|
26
|
-
subscriberClientPromise = null;
|
|
27
|
-
setTimeout(subscribeRedisClient, 5 * 1000).unref();
|
|
28
|
-
};
|
|
29
|
-
subscriberClientPromise = redis.createClientAndConnect(errorHandlerCreateClient);
|
|
30
|
-
subscriberClientPromise
|
|
31
|
-
.then((client) => {
|
|
32
|
-
cds.log(COMPONENT_NAME).info("subscribe redis client connected");
|
|
33
|
-
client.subscribe(MESSAGE_CHANNEL, messageHandlerProcessEvents);
|
|
34
|
-
})
|
|
35
|
-
.catch((err) => {
|
|
36
|
-
cds
|
|
37
|
-
.log(COMPONENT_NAME)
|
|
38
|
-
.error("error from redis client for pub/sub failed during startup - trying to reconnect", err);
|
|
39
|
-
});
|
|
19
|
+
redis.subscribeRedisChannel(EVENT_MESSAGE_CHANNEL, messageHandlerProcessEvents);
|
|
40
20
|
};
|
|
41
21
|
|
|
42
22
|
const messageHandlerProcessEvents = async (messageData) => {
|
|
43
|
-
|
|
23
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
44
24
|
try {
|
|
45
|
-
|
|
25
|
+
const { tenantId, type, subType } = JSON.parse(messageData);
|
|
26
|
+
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
27
|
+
const context = new cds.EventContext({
|
|
28
|
+
tenant: tenantId,
|
|
29
|
+
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
30
|
+
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
31
|
+
});
|
|
32
|
+
cds.context = context;
|
|
33
|
+
logger.debug("received redis event", {
|
|
34
|
+
tenantId,
|
|
35
|
+
type,
|
|
36
|
+
subType,
|
|
37
|
+
});
|
|
38
|
+
getWorkerPoolInstance().addToQueue(async () => processEventQueue(context, type, subType));
|
|
46
39
|
} catch (err) {
|
|
47
|
-
|
|
40
|
+
logger.error("could not parse event information", {
|
|
48
41
|
messageData,
|
|
49
42
|
});
|
|
50
|
-
return;
|
|
51
43
|
}
|
|
52
|
-
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
53
|
-
const context = new cds.EventContext({
|
|
54
|
-
tenant: tenantId,
|
|
55
|
-
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
56
|
-
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
57
|
-
});
|
|
58
|
-
cds.context = context;
|
|
59
|
-
cds.log(COMPONENT_NAME).debug("received redis event", {
|
|
60
|
-
tenantId,
|
|
61
|
-
type,
|
|
62
|
-
subType,
|
|
63
|
-
});
|
|
64
|
-
getWorkerPoolInstance().addToQueue(async () => processEventQueue(context, type, subType));
|
|
65
44
|
};
|
|
66
45
|
|
|
67
46
|
const publishEvent = async (tenantId, type, subType) => {
|
|
47
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
68
48
|
const configInstance = config.getConfigInstance();
|
|
69
49
|
if (!configInstance.redisEnabled) {
|
|
70
50
|
await _handleEventInternally(tenantId, type, subType);
|
|
71
51
|
return;
|
|
72
52
|
}
|
|
73
|
-
|
|
74
|
-
const logger = cds.log(COMPONENT_NAME);
|
|
75
|
-
const errorHandlerCreateClient = (err) => {
|
|
76
|
-
logger.error("error from redis client for pub/sub failed", {
|
|
77
|
-
err,
|
|
78
|
-
});
|
|
79
|
-
publishClient = null;
|
|
80
|
-
};
|
|
81
53
|
try {
|
|
82
|
-
if (!publishClient) {
|
|
83
|
-
publishClient = await redis.createClientAndConnect(errorHandlerCreateClient);
|
|
84
|
-
logger.info("publish redis client connected");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
54
|
const result = await checkLockExistsAndReturnValue(
|
|
88
55
|
new cds.EventContext({ tenant: tenantId }),
|
|
89
56
|
[type, subType].join("##")
|
|
@@ -97,7 +64,7 @@ const publishEvent = async (tenantId, type, subType) => {
|
|
|
97
64
|
type,
|
|
98
65
|
subType,
|
|
99
66
|
});
|
|
100
|
-
await
|
|
67
|
+
await redis.publishMessage(EVENT_MESSAGE_CHANNEL, JSON.stringify({ tenantId, type, subType }));
|
|
101
68
|
} catch (err) {
|
|
102
69
|
logger.error(`publish event failed with error: ${err.toString()}`, {
|
|
103
70
|
tenantId,
|
|
@@ -108,8 +75,7 @@ const publishEvent = async (tenantId, type, subType) => {
|
|
|
108
75
|
};
|
|
109
76
|
|
|
110
77
|
const _handleEventInternally = async (tenantId, type, subType) => {
|
|
111
|
-
|
|
112
|
-
logger.info("processEventQueue internally", {
|
|
78
|
+
cds.log(COMPONENT_NAME).info("processEventQueue internally", {
|
|
113
79
|
tenantId,
|
|
114
80
|
type,
|
|
115
81
|
subType,
|
package/src/runner.js
CHANGED
|
@@ -14,8 +14,6 @@ const EVENT_QUEUE_RUN_ID = "EVENT_QUEUE_RUN_ID";
|
|
|
14
14
|
const EVENT_QUEUE_RUN_TS = "EVENT_QUEUE_RUN_TS";
|
|
15
15
|
const OFFSET_FIRST_RUN = 10 * 1000;
|
|
16
16
|
|
|
17
|
-
const LOGGER = cds.log(COMPONENT_NAME);
|
|
18
|
-
|
|
19
17
|
const singleTenant = () => _scheduleFunction(_executeRunForTenant);
|
|
20
18
|
|
|
21
19
|
const multiTenancyDb = () => _scheduleFunction(_multiTenancyDb);
|
|
@@ -23,16 +21,18 @@ const multiTenancyDb = () => _scheduleFunction(_multiTenancyDb);
|
|
|
23
21
|
const multiTenancyRedis = () => _scheduleFunction(_multiTenancyRedis);
|
|
24
22
|
|
|
25
23
|
const _scheduleFunction = async (fn) => {
|
|
24
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
26
25
|
const configInstance = eventQueueConfig.getConfigInstance();
|
|
27
26
|
const eventsForAutomaticRun = configInstance.events;
|
|
28
27
|
if (!eventsForAutomaticRun.length) {
|
|
29
|
-
|
|
28
|
+
logger.warn("no events for automatic run are configured - skipping runner registration");
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
const fnWithRunningCheck = () => {
|
|
33
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
34
34
|
if (configInstance.isRunnerDeactivated) {
|
|
35
|
-
|
|
35
|
+
logger.info("runner is deactivated via config variable. Skipping this run.");
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
return fn();
|
|
@@ -40,7 +40,7 @@ const _scheduleFunction = async (fn) => {
|
|
|
40
40
|
|
|
41
41
|
const offsetDependingOnLastRun = await _calculateOffsetForFirstRun();
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
logger.info("first event-queue run scheduled", {
|
|
44
44
|
firstRunScheduledFor: new Date(Date.now() + offsetDependingOnLastRun).toISOString(),
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -52,13 +52,14 @@ const _scheduleFunction = async (fn) => {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const _multiTenancyRedis = async () => {
|
|
55
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
55
56
|
const emptyContext = new cds.EventContext({});
|
|
56
|
-
|
|
57
|
+
logger.info("executing event queue run for multi instance and tenant");
|
|
57
58
|
const tenantIds = await cdsHelper.getAllTenantIds();
|
|
58
59
|
const runId = await _acquireRunId(emptyContext);
|
|
59
60
|
|
|
60
61
|
if (!runId) {
|
|
61
|
-
|
|
62
|
+
logger.error("could not acquire runId, skip processing events!");
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -66,12 +67,13 @@ const _multiTenancyRedis = async () => {
|
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
const _multiTenancyDb = async () => {
|
|
70
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
69
71
|
try {
|
|
70
|
-
|
|
72
|
+
logger.info("executing event queue run for single instance and multi tenant");
|
|
71
73
|
const tenantIds = await cdsHelper.getAllTenantIds();
|
|
72
74
|
_executeAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
|
|
73
75
|
} catch (err) {
|
|
74
|
-
|
|
76
|
+
logger.error(
|
|
75
77
|
`Couldn't fetch tenant ids for event queue processing! Next try after defined interval. Error: ${err}`
|
|
76
78
|
);
|
|
77
79
|
}
|
|
@@ -92,7 +94,7 @@ const _executeAllTenants = (tenantIds, runId) => {
|
|
|
92
94
|
}
|
|
93
95
|
await _executeRunForTenant(tenantId, runId);
|
|
94
96
|
} catch (err) {
|
|
95
|
-
|
|
97
|
+
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
96
98
|
tenantId,
|
|
97
99
|
});
|
|
98
100
|
}
|
|
@@ -101,6 +103,7 @@ const _executeAllTenants = (tenantIds, runId) => {
|
|
|
101
103
|
};
|
|
102
104
|
|
|
103
105
|
const _executeRunForTenant = async (tenantId, runId) => {
|
|
106
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
104
107
|
const configInstance = eventQueueConfig.getConfigInstance();
|
|
105
108
|
try {
|
|
106
109
|
const eventsForAutomaticRun = configInstance.events;
|
|
@@ -111,14 +114,14 @@ const _executeRunForTenant = async (tenantId, runId) => {
|
|
|
111
114
|
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
112
115
|
});
|
|
113
116
|
cds.context = context;
|
|
114
|
-
|
|
117
|
+
logger.info("executing eventQueue run", {
|
|
115
118
|
tenantId,
|
|
116
119
|
subdomain,
|
|
117
120
|
...(runId ? { runId } : null),
|
|
118
121
|
});
|
|
119
122
|
await eventQueueRunner(context, eventsForAutomaticRun);
|
|
120
123
|
} catch (err) {
|
|
121
|
-
|
|
124
|
+
logger.error(`Couldn't process eventQueue for tenant! Next try after defined interval. Error: ${err}`, {
|
|
122
125
|
tenantId,
|
|
123
126
|
redisEnabled: configInstance.redisEnabled,
|
|
124
127
|
});
|
|
@@ -177,10 +180,12 @@ const _calculateOffsetForFirstRun = async () => {
|
|
|
177
180
|
offsetDependingOnLastRun = new Date(lastRunTs).getTime() + configInstance.runInterval - now;
|
|
178
181
|
}
|
|
179
182
|
} catch (err) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
cds
|
|
184
|
+
.log(COMPONENT_NAME)
|
|
185
|
+
.error(
|
|
186
|
+
"calculating offset for first run failed, falling back to default. Runs might be out-of-sync. Error:",
|
|
187
|
+
err
|
|
188
|
+
);
|
|
184
189
|
}
|
|
185
190
|
return offsetDependingOnLastRun;
|
|
186
191
|
};
|
package/src/shared/env.js
CHANGED
|
@@ -1,9 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
let instance;
|
|
4
|
+
|
|
5
|
+
class Env {
|
|
6
|
+
#isLocal;
|
|
7
|
+
#isOnCF;
|
|
8
|
+
#vcapServices;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.#isLocal = process.env.USER !== "vcap";
|
|
12
|
+
this.#isOnCF = !this.#isLocal;
|
|
13
|
+
try {
|
|
14
|
+
this.#vcapServices = JSON.parse(process.env.VCAP_SERVICES);
|
|
15
|
+
} catch {
|
|
16
|
+
this.#vcapServices = {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getRedisCredentialsFromEnv() {
|
|
21
|
+
return this.#vcapServices["redis-cache"]?.[0]?.credentials;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
set isLocal(value) {
|
|
25
|
+
this.#isLocal = value;
|
|
26
|
+
}
|
|
27
|
+
get isLocal() {
|
|
28
|
+
return this.#isLocal;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
set isOnCF(value) {
|
|
32
|
+
this.#isOnCF = value;
|
|
33
|
+
}
|
|
34
|
+
get isOnCF() {
|
|
35
|
+
return this.#isOnCF;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set vcapServices(value) {
|
|
39
|
+
this.#vcapServices = value;
|
|
40
|
+
}
|
|
41
|
+
get vcapServices() {
|
|
42
|
+
return this.#vcapServices;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
5
45
|
|
|
6
46
|
module.exports = {
|
|
7
|
-
|
|
8
|
-
|
|
47
|
+
getEnvInstance: () => {
|
|
48
|
+
if (!instance) {
|
|
49
|
+
instance = new Env();
|
|
50
|
+
}
|
|
51
|
+
return instance;
|
|
52
|
+
},
|
|
9
53
|
};
|
package/src/shared/redis.js
CHANGED
|
@@ -2,32 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
const redis = require("redis");
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const { getInstance: getEnvInstance } = require("./env");
|
|
6
6
|
const EventQueueError = require("../EventQueueError");
|
|
7
7
|
|
|
8
8
|
const COMPONENT_NAME = "eventQueue/shared/redis";
|
|
9
9
|
|
|
10
|
-
let
|
|
10
|
+
let mainClientPromise;
|
|
11
|
+
const subscriberChannelClientPromise = {};
|
|
11
12
|
|
|
12
13
|
const createMainClientAndConnect = () => {
|
|
13
|
-
if (
|
|
14
|
-
return
|
|
14
|
+
if (mainClientPromise) {
|
|
15
|
+
return mainClientPromise;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const errorHandlerCreateClient = (err) => {
|
|
18
19
|
cds.log(COMPONENT_NAME).error("error from redis client for pub/sub failed", err);
|
|
19
|
-
|
|
20
|
+
mainClientPromise = null;
|
|
20
21
|
setTimeout(createMainClientAndConnect, 5 * 1000).unref();
|
|
21
22
|
};
|
|
22
|
-
|
|
23
|
-
return
|
|
23
|
+
mainClientPromise = createClientAndConnect(errorHandlerCreateClient);
|
|
24
|
+
return mainClientPromise;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
const _createClientBase = () => {
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
28
|
+
const env = getEnvInstance();
|
|
29
|
+
if (env.isOnCF) {
|
|
29
30
|
try {
|
|
30
|
-
const credentials =
|
|
31
|
+
const credentials = env.getRedisCredentialsFromEnv();
|
|
31
32
|
// NOTE: settings the user explicitly to empty resolves auth problems, see
|
|
32
33
|
// https://github.com/go-redis/redis/issues/1343
|
|
33
34
|
const url = credentials.uri.replace(/(?<=rediss:\/\/)[\w-]+?(?=:)/, "");
|
|
@@ -60,9 +61,35 @@ const createClientAndConnect = async (errorHandlerCreateClient) => {
|
|
|
60
61
|
return client;
|
|
61
62
|
};
|
|
62
63
|
|
|
64
|
+
const subscribeRedisChannel = (channel, subscribeCb) => {
|
|
65
|
+
const errorHandlerCreateClient = (err) => {
|
|
66
|
+
cds.log(COMPONENT_NAME).error(`error from redis client for pub/sub failed for channel ${channel}`, err);
|
|
67
|
+
subscriberChannelClientPromise[channel] = null;
|
|
68
|
+
setTimeout(() => subscribeRedisChannel(channel, subscribeCb), 5 * 1000).unref();
|
|
69
|
+
};
|
|
70
|
+
subscriberChannelClientPromise[channel] = createClientAndConnect(errorHandlerCreateClient);
|
|
71
|
+
subscriberChannelClientPromise[channel]
|
|
72
|
+
.then((client) => {
|
|
73
|
+
cds.log(COMPONENT_NAME).info("subscribe redis client connected channel", { channel });
|
|
74
|
+
client.subscribe(channel, subscribeCb);
|
|
75
|
+
})
|
|
76
|
+
.catch((err) => {
|
|
77
|
+
cds
|
|
78
|
+
.log(COMPONENT_NAME)
|
|
79
|
+
.error(`error from redis client for pub/sub failed during startup - trying to reconnect - ${channel}`, err);
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const publishMessage = async (channel, message) => {
|
|
84
|
+
const client = await createMainClientAndConnect();
|
|
85
|
+
return await client.publish(channel, message);
|
|
86
|
+
};
|
|
87
|
+
|
|
63
88
|
const _localReconnectStrategy = () => EventQueueError.redisNoReconnect();
|
|
64
89
|
|
|
65
90
|
module.exports = {
|
|
66
91
|
createClientAndConnect,
|
|
67
92
|
createMainClientAndConnect,
|
|
93
|
+
subscribeRedisChannel,
|
|
94
|
+
publishMessage,
|
|
68
95
|
};
|