@cap-js-community/event-queue 1.3.3 → 1.3.5
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 +12 -2
- package/src/EventQueueProcessorBase.js +19 -4
- package/src/config.js +2 -1
- package/src/initialize.js +11 -5
- package/src/processEventQueue.js +1 -0
- package/src/redisPubSub.js +29 -13
- package/src/shared/env.js +0 -10
- package/src/shared/redis.js +42 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
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
|
"files": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"redis": "4.6.13",
|
|
46
46
|
"verror": "1.10.1",
|
|
47
|
-
"yaml": "2.4.
|
|
47
|
+
"yaml": "2.4.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@cap-js/hana": "^0.0.6",
|
|
@@ -67,6 +67,16 @@
|
|
|
67
67
|
"url": "https://github.com/cap-js-community/event-queue.git"
|
|
68
68
|
},
|
|
69
69
|
"cds": {
|
|
70
|
+
"eventQueue": {
|
|
71
|
+
"[production]": {
|
|
72
|
+
"disableRedis": false
|
|
73
|
+
},
|
|
74
|
+
"[test]": {
|
|
75
|
+
"registerAsEventProcessor": false,
|
|
76
|
+
"isRunnerDeactivated": true,
|
|
77
|
+
"updatePeriodicEvents": false
|
|
78
|
+
}
|
|
79
|
+
},
|
|
70
80
|
"requires": {
|
|
71
81
|
"event-queue": {
|
|
72
82
|
"model": "@cap-js-community/event-queue"
|
|
@@ -10,6 +10,7 @@ const { arrayToFlatMap } = require("./shared/common");
|
|
|
10
10
|
const eventScheduler = require("./shared/eventScheduler");
|
|
11
11
|
const eventConfig = require("./config");
|
|
12
12
|
const PerformanceTracer = require("./shared/PerformanceTracer");
|
|
13
|
+
const { broadcastEvent } = require("./redisPubSub");
|
|
13
14
|
|
|
14
15
|
const IMPLEMENT_ERROR_MESSAGE = "needs to be reimplemented";
|
|
15
16
|
const COMPONENT_NAME = "/eventQueue/EventQueueProcessorBase";
|
|
@@ -1002,11 +1003,11 @@ class EventQueueProcessorBase {
|
|
|
1002
1003
|
}
|
|
1003
1004
|
|
|
1004
1005
|
async scheduleNextPeriodEvent(queueEntry) {
|
|
1005
|
-
const
|
|
1006
|
+
const intervalInMs = this.#eventConfig.interval * 1000;
|
|
1006
1007
|
const newEvent = {
|
|
1007
1008
|
type: this.#eventType,
|
|
1008
1009
|
subType: this.#eventSubType,
|
|
1009
|
-
startAfter: new Date(new Date(queueEntry.startAfter).getTime() +
|
|
1010
|
+
startAfter: new Date(new Date(queueEntry.startAfter).getTime() + intervalInMs),
|
|
1010
1011
|
};
|
|
1011
1012
|
const { relative } = this.#eventSchedulerInstance.calculateOffset(
|
|
1012
1013
|
this.#eventType,
|
|
@@ -1015,11 +1016,13 @@ class EventQueueProcessorBase {
|
|
|
1015
1016
|
);
|
|
1016
1017
|
|
|
1017
1018
|
// more than one interval behind - shift tick to keep up
|
|
1018
|
-
if (relative < 0 && Math.abs(relative) >=
|
|
1019
|
+
if (relative < 0 && Math.abs(relative) >= intervalInMs) {
|
|
1020
|
+
const plannedStartAfter = newEvent.startAfter;
|
|
1019
1021
|
newEvent.startAfter = new Date(Date.now() + 5 * 1000);
|
|
1020
1022
|
this.logger.info("interval adjusted because shifted more than one interval", {
|
|
1021
1023
|
eventType: this.#eventType,
|
|
1022
1024
|
eventSubType: this.#eventSubType,
|
|
1025
|
+
plannedStartAfter,
|
|
1023
1026
|
newStartAfter: newEvent.startAfter,
|
|
1024
1027
|
});
|
|
1025
1028
|
}
|
|
@@ -1027,7 +1030,7 @@ class EventQueueProcessorBase {
|
|
|
1027
1030
|
this.tx._skipEventQueueBroadcase = true;
|
|
1028
1031
|
await this.tx.run(INSERT.into(this.#config.tableNameEventQueue).entries({ ...newEvent }));
|
|
1029
1032
|
this.tx._skipEventQueueBroadcase = false;
|
|
1030
|
-
if (
|
|
1033
|
+
if (intervalInMs < this.#config.runInterval * 1.5) {
|
|
1031
1034
|
this.#handleDelayedEvents([newEvent]);
|
|
1032
1035
|
const { relative: relativeAfterSchedule } = this.#eventSchedulerInstance.calculateOffset(
|
|
1033
1036
|
this.#eventType,
|
|
@@ -1108,6 +1111,18 @@ class EventQueueProcessorBase {
|
|
|
1108
1111
|
this.__processTx = null;
|
|
1109
1112
|
}
|
|
1110
1113
|
|
|
1114
|
+
broadCastEvent() {
|
|
1115
|
+
setTimeout(() => {
|
|
1116
|
+
broadcastEvent(this.__baseContext.tenant, this.#eventType, this.#eventSubType).catch((err) => {
|
|
1117
|
+
this.logger.error("could not execute scheduled event", err, {
|
|
1118
|
+
tenantId: this.__baseContext.tenant,
|
|
1119
|
+
type: this.#eventType,
|
|
1120
|
+
subType: this.#eventSubType,
|
|
1121
|
+
});
|
|
1122
|
+
});
|
|
1123
|
+
}, 1000).unref();
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1111
1126
|
get logger() {
|
|
1112
1127
|
return this.__logger ?? this.__baseLogger;
|
|
1113
1128
|
}
|
package/src/config.js
CHANGED
|
@@ -99,7 +99,8 @@ class Config {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
checkRedisEnabled() {
|
|
102
|
-
this.#redisEnabled = !this.#disableRedis && this._checkRedisIsBound()
|
|
102
|
+
this.#redisEnabled = !this.#disableRedis && this._checkRedisIsBound();
|
|
103
|
+
return this.#redisEnabled;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
attachConfigChangeHandler() {
|
package/src/initialize.js
CHANGED
|
@@ -12,7 +12,7 @@ const runner = require("./runner");
|
|
|
12
12
|
const dbHandler = require("./dbHandler");
|
|
13
13
|
const config = require("./config");
|
|
14
14
|
const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redisPubSub");
|
|
15
|
-
const
|
|
15
|
+
const redis = require("./shared/redis");
|
|
16
16
|
const eventQueueAsOutbox = require("./outbox/eventQueueAsOutbox");
|
|
17
17
|
const { getAllTenantIds } = require("./shared/cdsHelper");
|
|
18
18
|
const { EventProcessingStatus } = require("./constants");
|
|
@@ -33,7 +33,7 @@ const CONFIG_VARS = [
|
|
|
33
33
|
["runInterval", 25 * 60 * 1000],
|
|
34
34
|
["tableNameEventQueue", BASE_TABLES.EVENT],
|
|
35
35
|
["tableNameEventLock", BASE_TABLES.LOCK],
|
|
36
|
-
["disableRedis",
|
|
36
|
+
["disableRedis", true],
|
|
37
37
|
["skipCsnCheck", false],
|
|
38
38
|
["updatePeriodicEvents", true],
|
|
39
39
|
["thresholdLoggingEventProcessing", 50],
|
|
@@ -84,14 +84,19 @@ const initialize = async ({
|
|
|
84
84
|
);
|
|
85
85
|
|
|
86
86
|
const logger = cds.log(COMPONENT);
|
|
87
|
-
config.checkRedisEnabled();
|
|
87
|
+
const redisEnabled = config.checkRedisEnabled();
|
|
88
|
+
let resolveFn;
|
|
89
|
+
let initFinished = new Promise((resolve) => (resolveFn = resolve));
|
|
88
90
|
cds.on("connect", (service) => {
|
|
89
91
|
if (service.name === "db") {
|
|
90
92
|
config.processEventsAfterPublish && dbHandler.registerEventQueueDbHandler(service);
|
|
91
93
|
config.cleanupLocksAndEventsForDev && registerCleanupForDevDb().catch(() => {});
|
|
92
|
-
registerEventProcessors
|
|
94
|
+
initFinished.then(registerEventProcessors);
|
|
93
95
|
}
|
|
94
96
|
});
|
|
97
|
+
if (redisEnabled) {
|
|
98
|
+
config.redisEnabled = await redis.connectionCheck();
|
|
99
|
+
}
|
|
95
100
|
config.fileContent = await readConfigFromFile(config.configFilePath);
|
|
96
101
|
|
|
97
102
|
!config.skipCsnCheck && (await csnCheck());
|
|
@@ -105,6 +110,7 @@ const initialize = async ({
|
|
|
105
110
|
redisEnabled: config.redisEnabled,
|
|
106
111
|
runInterval: config.runInterval,
|
|
107
112
|
});
|
|
113
|
+
resolveFn();
|
|
108
114
|
};
|
|
109
115
|
|
|
110
116
|
const readConfigFromFile = async (configFilepath) => {
|
|
@@ -219,7 +225,7 @@ const mixConfigVarsWithEnv = (...args) => {
|
|
|
219
225
|
|
|
220
226
|
const registerCdsShutdown = () => {
|
|
221
227
|
cds.on("shutdown", async () => {
|
|
222
|
-
await Promise.allSettled([closeMainClient(), closeSubscribeClient()]);
|
|
228
|
+
await Promise.allSettled([redis.closeMainClient(), closeSubscribeClient()]);
|
|
223
229
|
});
|
|
224
230
|
};
|
|
225
231
|
|
package/src/processEventQueue.js
CHANGED
|
@@ -117,6 +117,7 @@ const reevaluateShouldContinue = (eventTypeInstance, iterationCounter, startTime
|
|
|
117
117
|
if (new Date(startTime.getTime() + config.runInterval) > new Date()) {
|
|
118
118
|
return true;
|
|
119
119
|
}
|
|
120
|
+
|
|
120
121
|
eventTypeInstance.logTimeExceeded(iterationCounter);
|
|
121
122
|
return false;
|
|
122
123
|
};
|
package/src/redisPubSub.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
const { promisify } = require("util");
|
|
4
|
+
|
|
3
5
|
const cds = require("@sap/cds");
|
|
4
6
|
|
|
5
7
|
const redis = require("./shared/redis");
|
|
@@ -10,7 +12,10 @@ const { getSubdomainForTenantId } = require("./shared/cdsHelper");
|
|
|
10
12
|
|
|
11
13
|
const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
|
|
12
14
|
const COMPONENT_NAME = "/eventQueue/redisPubSub";
|
|
15
|
+
const TRIES_FOR_PUBLISH_PERIODIC_EVENT = 10;
|
|
16
|
+
const SLEEP_TIME_FOR_PUBLISH_PERIODIC_EVENT = 30 * 1000;
|
|
13
17
|
|
|
18
|
+
const wait = promisify(setTimeout);
|
|
14
19
|
let subscriberClientPromise;
|
|
15
20
|
|
|
16
21
|
const initEventQueueRedisSubscribe = () => {
|
|
@@ -107,23 +112,34 @@ const broadcastEvent = async (tenantId, type, subType) => {
|
|
|
107
112
|
}
|
|
108
113
|
return;
|
|
109
114
|
}
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
const eventConfig = config.getEventConfig(type, subType);
|
|
116
|
+
for (let i = 0; i < TRIES_FOR_PUBLISH_PERIODIC_EVENT; i++) {
|
|
117
|
+
const result = await checkLockExistsAndReturnValue(
|
|
118
|
+
new cds.EventContext({ tenant: tenantId }),
|
|
119
|
+
[type, subType].join("##")
|
|
120
|
+
);
|
|
121
|
+
if (result) {
|
|
122
|
+
logger.debug("skip publish redis event as no lock is available", {
|
|
123
|
+
type,
|
|
124
|
+
subType,
|
|
125
|
+
index: i,
|
|
126
|
+
isPeriodic: eventConfig.isPeriodic,
|
|
127
|
+
waitInterval: SLEEP_TIME_FOR_PUBLISH_PERIODIC_EVENT,
|
|
128
|
+
});
|
|
129
|
+
if (!eventConfig.isPeriodic) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
await wait(SLEEP_TIME_FOR_PUBLISH_PERIODIC_EVENT);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
logger.debug("publishing redis event", {
|
|
136
|
+
tenantId,
|
|
116
137
|
type,
|
|
117
138
|
subType,
|
|
118
139
|
});
|
|
119
|
-
|
|
140
|
+
await redis.publishMessage(EVENT_MESSAGE_CHANNEL, JSON.stringify({ tenantId, type, subType }));
|
|
141
|
+
break;
|
|
120
142
|
}
|
|
121
|
-
logger.debug("publishing redis event", {
|
|
122
|
-
tenantId,
|
|
123
|
-
type,
|
|
124
|
-
subType,
|
|
125
|
-
});
|
|
126
|
-
await redis.publishMessage(EVENT_MESSAGE_CHANNEL, JSON.stringify({ tenantId, type, subType }));
|
|
127
143
|
} catch (err) {
|
|
128
144
|
logger.error("publish event failed!", err, {
|
|
129
145
|
tenantId,
|
package/src/shared/env.js
CHANGED
|
@@ -4,12 +4,9 @@ let instance;
|
|
|
4
4
|
|
|
5
5
|
class Env {
|
|
6
6
|
#isLocal;
|
|
7
|
-
#isOnCF;
|
|
8
7
|
#vcapServices;
|
|
9
8
|
|
|
10
9
|
constructor() {
|
|
11
|
-
this.#isLocal = process.env.USER !== "vcap";
|
|
12
|
-
this.#isOnCF = !this.#isLocal;
|
|
13
10
|
try {
|
|
14
11
|
this.#vcapServices = JSON.parse(process.env.VCAP_SERVICES);
|
|
15
12
|
} catch {
|
|
@@ -28,13 +25,6 @@ class Env {
|
|
|
28
25
|
return this.#isLocal;
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
set isOnCF(value) {
|
|
32
|
-
this.#isOnCF = value;
|
|
33
|
-
}
|
|
34
|
-
get isOnCF() {
|
|
35
|
-
return this.#isOnCF;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
28
|
set vcapServices(value) {
|
|
39
29
|
this.#vcapServices = value;
|
|
40
30
|
}
|
package/src/shared/redis.js
CHANGED
|
@@ -26,29 +26,23 @@ const createMainClientAndConnect = () => {
|
|
|
26
26
|
|
|
27
27
|
const _createClientBase = () => {
|
|
28
28
|
const env = getEnvInstance();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
return redis.createClient({ url });
|
|
45
|
-
} catch (err) {
|
|
46
|
-
throw EventQueueError.redisConnectionFailure(err);
|
|
29
|
+
try {
|
|
30
|
+
const credentials = env.getRedisCredentialsFromEnv();
|
|
31
|
+
const redisIsCluster = credentials.cluster_mode;
|
|
32
|
+
const url = credentials.uri.replace(/(?<=rediss:\/\/)[\w-]+?(?=:)/, "");
|
|
33
|
+
if (redisIsCluster) {
|
|
34
|
+
return redis.createCluster({
|
|
35
|
+
rootNodes: [{ url }],
|
|
36
|
+
// https://github.com/redis/node-redis/issues/1782
|
|
37
|
+
defaults: {
|
|
38
|
+
password: credentials.password,
|
|
39
|
+
socket: { tls: credentials.tls },
|
|
40
|
+
},
|
|
41
|
+
});
|
|
47
42
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
43
|
+
return redis.createClient({ url });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
throw EventQueueError.redisConnectionFailure(err);
|
|
52
46
|
}
|
|
53
47
|
};
|
|
54
48
|
|
|
@@ -93,11 +87,16 @@ const publishMessage = async (channel, message) => {
|
|
|
93
87
|
return await client.publish(channel, message);
|
|
94
88
|
};
|
|
95
89
|
|
|
96
|
-
const _localReconnectStrategy = () => EventQueueError.redisNoReconnect();
|
|
97
|
-
|
|
98
90
|
const closeMainClient = async () => {
|
|
99
91
|
try {
|
|
100
|
-
|
|
92
|
+
await _resilientClientClose(await mainClientPromise);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
// ignore errors during shutdown
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const _resilientClientClose = async (client) => {
|
|
99
|
+
try {
|
|
101
100
|
if (client?.quit) {
|
|
102
101
|
await client.quit();
|
|
103
102
|
}
|
|
@@ -106,10 +105,28 @@ const closeMainClient = async () => {
|
|
|
106
105
|
}
|
|
107
106
|
};
|
|
108
107
|
|
|
108
|
+
const connectionCheck = async () => {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
createClientAndConnect(reject)
|
|
111
|
+
.then((client) => {
|
|
112
|
+
if (client) {
|
|
113
|
+
_resilientClientClose(client);
|
|
114
|
+
resolve();
|
|
115
|
+
} else {
|
|
116
|
+
reject(new Error());
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.catch(reject);
|
|
120
|
+
})
|
|
121
|
+
.then(() => true)
|
|
122
|
+
.catch(() => false);
|
|
123
|
+
};
|
|
124
|
+
|
|
109
125
|
module.exports = {
|
|
110
126
|
createClientAndConnect,
|
|
111
127
|
createMainClientAndConnect,
|
|
112
128
|
subscribeRedisChannel,
|
|
113
129
|
publishMessage,
|
|
114
130
|
closeMainClient,
|
|
131
|
+
connectionCheck,
|
|
115
132
|
};
|