@cap-js-community/event-queue 0.2.2 → 0.2.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 +1 -1
- package/src/EventQueueProcessorBase.js +52 -18
- package/src/config.js +20 -14
- package/src/dbHandler.js +2 -3
- package/src/index.js +1 -1
- package/src/initialize.js +22 -30
- package/src/periodicEvents.js +9 -11
- package/src/processEventQueue.js +57 -48
- package/src/publishEvent.js +3 -4
- package/src/redisPubSub.js +3 -4
- package/src/runner.js +21 -23
- package/src/shared/WorkerQueue.js +2 -3
- package/src/shared/cdsHelper.js +2 -2
- package/src/shared/distributedLock.js +12 -20
- package/src/shared/eventScheduler.js +13 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "An event queue that enables secure transactional processing of asynchronous 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": [
|
|
@@ -8,7 +8,7 @@ const distributedLock = require("./shared/distributedLock");
|
|
|
8
8
|
const EventQueueError = require("./EventQueueError");
|
|
9
9
|
const { arrayToFlatMap } = require("./shared/common");
|
|
10
10
|
const eventScheduler = require("./shared/eventScheduler");
|
|
11
|
-
const
|
|
11
|
+
const eventConfig = require("./config");
|
|
12
12
|
const PerformanceTracer = require("./shared/PerformanceTracer");
|
|
13
13
|
|
|
14
14
|
const IMPLEMENT_ERROR_MESSAGE = "needs to be reimplemented";
|
|
@@ -31,37 +31,43 @@ class EventQueueProcessorBase {
|
|
|
31
31
|
#eventType = null;
|
|
32
32
|
#eventSubType = null;
|
|
33
33
|
#config = null;
|
|
34
|
+
#eventSchedulerInstance = null;
|
|
35
|
+
#eventConfig;
|
|
36
|
+
#isPeriodic;
|
|
34
37
|
|
|
35
38
|
constructor(context, eventType, eventSubType, config) {
|
|
36
39
|
this.__context = context;
|
|
37
40
|
this.__baseContext = context;
|
|
38
41
|
this.__tx = cds.tx(context);
|
|
39
42
|
this.__baseLogger = cds.log(COMPONENT_NAME);
|
|
43
|
+
this.#eventSchedulerInstance = eventScheduler.getInstance();
|
|
44
|
+
this.#config = eventConfig;
|
|
45
|
+
this.#isPeriodic = this.#config.isPeriodicEvent(eventType, eventSubType);
|
|
40
46
|
this.__logger = null;
|
|
41
47
|
this.__eventProcessingMap = {};
|
|
42
48
|
this.__statusMap = {};
|
|
43
49
|
this.__commitedStatusMap = {};
|
|
44
50
|
this.#eventType = eventType;
|
|
45
51
|
this.#eventSubType = eventSubType;
|
|
46
|
-
this
|
|
47
|
-
this.__parallelEventProcessing = this.
|
|
52
|
+
this.#eventConfig = config ?? {};
|
|
53
|
+
this.__parallelEventProcessing = this.#eventConfig.parallelEventProcessing ?? DEFAULT_PARALLEL_EVENT_PROCESSING;
|
|
48
54
|
if (this.__parallelEventProcessing > LIMIT_PARALLEL_EVENT_PROCESSING) {
|
|
49
55
|
this.__parallelEventProcessing = LIMIT_PARALLEL_EVENT_PROCESSING;
|
|
50
56
|
}
|
|
51
57
|
// NOTE: keep the feature, this might be needed again
|
|
52
58
|
this.__concurrentEventProcessing = false;
|
|
53
|
-
this.__startTime = this.
|
|
54
|
-
this.__retryAttempts = this.
|
|
55
|
-
this.__selectMaxChunkSize = this.
|
|
56
|
-
this.__selectNextChunk = !!this.
|
|
59
|
+
this.__startTime = this.#eventConfig.startTime ?? new Date();
|
|
60
|
+
this.__retryAttempts = this.#eventConfig.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS;
|
|
61
|
+
this.__selectMaxChunkSize = this.#eventConfig.selectMaxChunkSize ?? SELECT_LIMIT_EVENTS_PER_TICK;
|
|
62
|
+
this.__selectNextChunk = !!this.#eventConfig.checkForNextChunk;
|
|
57
63
|
this.__keepalivePromises = {};
|
|
58
|
-
this.__outdatedCheckEnabled = this.
|
|
59
|
-
this.__transactionMode = this.
|
|
60
|
-
if (this.
|
|
64
|
+
this.__outdatedCheckEnabled = this.#eventConfig.eventOutdatedCheck ?? true;
|
|
65
|
+
this.__transactionMode = this.#eventConfig.transactionMode ?? TransactionMode.isolated;
|
|
66
|
+
if (this.#eventConfig.deleteFinishedEventsAfterDays) {
|
|
61
67
|
this.__deleteFinishedEventsAfter =
|
|
62
|
-
Number.isInteger(this.
|
|
63
|
-
this.
|
|
64
|
-
? this.
|
|
68
|
+
Number.isInteger(this.#eventConfig.deleteFinishedEventsAfterDays) &&
|
|
69
|
+
this.#eventConfig.deleteFinishedEventsAfterDays > 0
|
|
70
|
+
? this.#eventConfig.deleteFinishedEventsAfterDays
|
|
65
71
|
: DEFAULT_DELETE_FINISHED_EVENTS_AFTER;
|
|
66
72
|
} else {
|
|
67
73
|
this.__deleteFinishedEventsAfter = DEFAULT_DELETE_FINISHED_EVENTS_AFTER;
|
|
@@ -71,7 +77,6 @@ class EventQueueProcessorBase {
|
|
|
71
77
|
this.__txUsageAllowed = true;
|
|
72
78
|
this.__txMap = {};
|
|
73
79
|
this.__txRollback = {};
|
|
74
|
-
this.#config = eventQueueConfig.getConfigInstance();
|
|
75
80
|
this.__queueEntries = [];
|
|
76
81
|
}
|
|
77
82
|
|
|
@@ -610,9 +615,8 @@ class EventQueueProcessorBase {
|
|
|
610
615
|
}
|
|
611
616
|
|
|
612
617
|
#handleDelayedEvents(delayedEvents) {
|
|
613
|
-
const eventSchedulerInstance = eventScheduler.getInstance();
|
|
614
618
|
for (const delayedEvent of delayedEvents) {
|
|
615
|
-
eventSchedulerInstance.scheduleEvent(
|
|
619
|
+
this.#eventSchedulerInstance.scheduleEvent(
|
|
616
620
|
this.__context.tenant,
|
|
617
621
|
this.#eventType,
|
|
618
622
|
this.#eventSubType,
|
|
@@ -836,17 +840,47 @@ class EventQueueProcessorBase {
|
|
|
836
840
|
}
|
|
837
841
|
|
|
838
842
|
async scheduleNextPeriodEvent(queueEntry) {
|
|
839
|
-
const interval = this.
|
|
843
|
+
const interval = this.#eventConfig.interval;
|
|
840
844
|
const newEvent = {
|
|
841
845
|
type: this.#eventType,
|
|
842
846
|
subType: this.#eventSubType,
|
|
843
847
|
startAfter: new Date(new Date(queueEntry.startAfter).getTime() + interval * 1000),
|
|
844
848
|
};
|
|
849
|
+
const { relative } = this.#eventSchedulerInstance.calculateOffset(
|
|
850
|
+
this.#eventType,
|
|
851
|
+
this.#eventSubType,
|
|
852
|
+
newEvent.startAfter
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
// more than one interval behind - shift tick to keep up
|
|
856
|
+
if (relative < 0 && Math.abs(relative) >= this.#eventConfig.interval * 1000) {
|
|
857
|
+
newEvent.startAfter = new Date(Date.now() + 5 * 1000);
|
|
858
|
+
this.logger.info("interval adjusted because shifted more than one interval", {
|
|
859
|
+
eventType: this.#eventType,
|
|
860
|
+
eventSubType: this.#eventSubType,
|
|
861
|
+
newStartAfter: newEvent.startAfter,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
845
865
|
this.tx._skipEventQueueBroadcase = true;
|
|
846
866
|
await this.tx.run(INSERT.into(this.#config.tableNameEventQueue).entries({ ...newEvent }));
|
|
847
867
|
this.tx._skipEventQueueBroadcase = false;
|
|
848
868
|
if (interval < this.#config.runInterval) {
|
|
849
869
|
this.#handleDelayedEvents([newEvent]);
|
|
870
|
+
const { relative: relativeAfterSchedule } = this.#eventSchedulerInstance.calculateOffset(
|
|
871
|
+
this.#eventType,
|
|
872
|
+
this.#eventSubType,
|
|
873
|
+
newEvent.startAfter
|
|
874
|
+
);
|
|
875
|
+
// next tick is already behind schedule --> execute direct
|
|
876
|
+
if (relativeAfterSchedule <= 0) {
|
|
877
|
+
this.logger.info("running behind schedule - executing next tick immediately", {
|
|
878
|
+
eventType: this.#eventType,
|
|
879
|
+
eventSubType: this.#eventSubType,
|
|
880
|
+
newStartAfter: newEvent.startAfter,
|
|
881
|
+
});
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
850
884
|
}
|
|
851
885
|
}
|
|
852
886
|
|
|
@@ -986,7 +1020,7 @@ class EventQueueProcessorBase {
|
|
|
986
1020
|
}
|
|
987
1021
|
|
|
988
1022
|
get isPeriodicEvent() {
|
|
989
|
-
return this.
|
|
1023
|
+
return this.#eventConfig.isPeriodic;
|
|
990
1024
|
}
|
|
991
1025
|
}
|
|
992
1026
|
|
package/src/config.js
CHANGED
|
@@ -6,8 +6,6 @@ const { getEnvInstance } = require("./shared/env");
|
|
|
6
6
|
const redis = require("./shared/redis");
|
|
7
7
|
const EventQueueError = require("./EventQueueError");
|
|
8
8
|
|
|
9
|
-
let instance;
|
|
10
|
-
|
|
11
9
|
const FOR_UPDATE_TIMEOUT = 10;
|
|
12
10
|
const GLOBAL_TX_TIMEOUT = 30 * 60 * 1000;
|
|
13
11
|
const REDIS_CONFIG_CHANNEL = "EVENT_QUEUE_CONFIG_CHANNEL";
|
|
@@ -33,6 +31,7 @@ class Config {
|
|
|
33
31
|
#env;
|
|
34
32
|
#eventMap;
|
|
35
33
|
#updatePeriodicEvents;
|
|
34
|
+
static #instance;
|
|
36
35
|
constructor() {
|
|
37
36
|
this.#logger = cds.log(COMPONENT_NAME);
|
|
38
37
|
this.#config = null;
|
|
@@ -112,15 +111,18 @@ class Config {
|
|
|
112
111
|
return result;
|
|
113
112
|
}, {});
|
|
114
113
|
this.#eventMap = config.periodicEvents.reduce((result, event) => {
|
|
115
|
-
|
|
114
|
+
const SUFFIX_PERIODIC = "_PERIODIC";
|
|
115
|
+
event.type = `${event.type}${SUFFIX_PERIODIC}`;
|
|
116
116
|
event.isPeriodic = true;
|
|
117
|
+
this.validatePeriodicConfig(result, event);
|
|
117
118
|
result[[event.type, event.subType].join("##")] = event;
|
|
118
119
|
return result;
|
|
119
120
|
}, this.#eventMap);
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
validatePeriodicConfig(eventMap, config) {
|
|
123
|
-
|
|
124
|
+
const key = this.generateKey(config.type, config.subType);
|
|
125
|
+
if (eventMap[key] && eventMap[key].isPeriodic) {
|
|
124
126
|
throw EventQueueError.duplicateEventRegistration(config.type, config.subType);
|
|
125
127
|
}
|
|
126
128
|
|
|
@@ -134,7 +136,8 @@ class Config {
|
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
validateAdHocEvents(eventMap, config) {
|
|
137
|
-
|
|
139
|
+
const key = this.generateKey(config.type, config.subType);
|
|
140
|
+
if (eventMap[key] && !eventMap[key].isPeriodic) {
|
|
138
141
|
throw EventQueueError.duplicateEventRegistration(config.type, config.subType);
|
|
139
142
|
}
|
|
140
143
|
|
|
@@ -274,15 +277,18 @@ class Config {
|
|
|
274
277
|
get isMultiTenancy() {
|
|
275
278
|
return !!cds.requires.multitenancy;
|
|
276
279
|
}
|
|
277
|
-
}
|
|
278
280
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
/**
|
|
282
|
+
@return { Config }
|
|
283
|
+
**/
|
|
284
|
+
static get instance() {
|
|
285
|
+
if (!Config.#instance) {
|
|
286
|
+
Config.#instance = new Config();
|
|
287
|
+
}
|
|
288
|
+
return Config.#instance;
|
|
282
289
|
}
|
|
283
|
-
|
|
284
|
-
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const instance = Config.instance;
|
|
285
293
|
|
|
286
|
-
module.exports =
|
|
287
|
-
getConfigInstance,
|
|
288
|
-
};
|
|
294
|
+
module.exports = instance;
|
package/src/dbHandler.js
CHANGED
|
@@ -8,8 +8,7 @@ const config = require("./config");
|
|
|
8
8
|
const COMPONENT_NAME = "eventQueue/dbHandler";
|
|
9
9
|
|
|
10
10
|
const registerEventQueueDbHandler = (dbService) => {
|
|
11
|
-
const
|
|
12
|
-
const def = dbService.model.definitions[configInstance.tableNameEventQueue];
|
|
11
|
+
const def = dbService.model.definitions[config.tableNameEventQueue];
|
|
13
12
|
dbService.after("CREATE", def, (_, req) => {
|
|
14
13
|
if (req.tx._skipEventQueueBroadcase) {
|
|
15
14
|
return;
|
|
@@ -21,7 +20,7 @@ const registerEventQueueDbHandler = (dbService) => {
|
|
|
21
20
|
const eventCombinations = Object.keys(
|
|
22
21
|
data.reduce((result, event) => {
|
|
23
22
|
const key = [event.type, event.subType].join("##");
|
|
24
|
-
if (!
|
|
23
|
+
if (!config.hasEventAfterCommitFlag(event.type, event.subType) || eventQueuePublishEvents[key]) {
|
|
25
24
|
return result;
|
|
26
25
|
}
|
|
27
26
|
eventQueuePublishEvents[key] = true;
|
package/src/index.js
CHANGED
package/src/initialize.js
CHANGED
|
@@ -11,7 +11,7 @@ const VError = require("verror");
|
|
|
11
11
|
const EventQueueError = require("./EventQueueError");
|
|
12
12
|
const runner = require("./runner");
|
|
13
13
|
const dbHandler = require("./dbHandler");
|
|
14
|
-
const
|
|
14
|
+
const config = require("./config");
|
|
15
15
|
const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redisPubSub");
|
|
16
16
|
const { closeMainClient } = require("./shared/redis");
|
|
17
17
|
|
|
@@ -54,11 +54,10 @@ const initialize = async ({
|
|
|
54
54
|
// - content of yaml check
|
|
55
55
|
// - betweenRuns and parallelTenantProcessing
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
if (configInstance.initialized) {
|
|
57
|
+
if (config.initialized) {
|
|
59
58
|
return;
|
|
60
59
|
}
|
|
61
|
-
|
|
60
|
+
config.initialized = true;
|
|
62
61
|
|
|
63
62
|
mixConfigVarsWithEnv(
|
|
64
63
|
configFilePath,
|
|
@@ -75,24 +74,24 @@ const initialize = async ({
|
|
|
75
74
|
);
|
|
76
75
|
|
|
77
76
|
const logger = cds.log(COMPONENT);
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
config.fileContent = await readConfigFromFile(config.configFilePath);
|
|
78
|
+
config.checkRedisEnabled();
|
|
80
79
|
|
|
81
80
|
const dbService = await cds.connect.to("db");
|
|
82
81
|
await (cds.model ? Promise.resolve() : new Promise((resolve) => cds.on("serving", resolve)));
|
|
83
|
-
!
|
|
84
|
-
if (
|
|
82
|
+
!config.skipCsnCheck && (await csnCheck());
|
|
83
|
+
if (config.processEventsAfterPublish) {
|
|
85
84
|
dbHandler.registerEventQueueDbHandler(dbService);
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
registerEventProcessors();
|
|
89
88
|
registerCdsShutdown();
|
|
90
89
|
logger.info("event queue initialized", {
|
|
91
|
-
registerAsEventProcessor:
|
|
92
|
-
multiTenancyEnabled:
|
|
93
|
-
redisEnabled:
|
|
94
|
-
runInterval:
|
|
95
|
-
|
|
90
|
+
registerAsEventProcessor: config.registerAsEventProcessor,
|
|
91
|
+
multiTenancyEnabled: config.isMultiTenancy,
|
|
92
|
+
redisEnabled: config.redisEnabled,
|
|
93
|
+
runInterval: config.runInterval,
|
|
94
|
+
config: config.parallelTenantProcessing,
|
|
96
95
|
});
|
|
97
96
|
};
|
|
98
97
|
|
|
@@ -114,22 +113,20 @@ const readConfigFromFile = async (configFilepath) => {
|
|
|
114
113
|
};
|
|
115
114
|
|
|
116
115
|
const registerEventProcessors = () => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (!configInstance.registerAsEventProcessor) {
|
|
116
|
+
if (!config.registerAsEventProcessor) {
|
|
120
117
|
return;
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
const errorHandler = (err) => cds.log(COMPONENT).error("error during init runner", err);
|
|
124
121
|
|
|
125
|
-
if (!
|
|
122
|
+
if (!config.isMultiTenancy) {
|
|
126
123
|
runner.singleTenant().catch(errorHandler);
|
|
127
124
|
return;
|
|
128
125
|
}
|
|
129
126
|
|
|
130
|
-
if (
|
|
127
|
+
if (config.redisEnabled) {
|
|
131
128
|
initEventQueueRedisSubscribe();
|
|
132
|
-
|
|
129
|
+
config.attachConfigChangeHandler();
|
|
133
130
|
runner.multiTenancyRedis().catch(errorHandler);
|
|
134
131
|
} else {
|
|
135
132
|
runner.multiTenancyDb().catch(errorHandler);
|
|
@@ -137,21 +134,17 @@ const registerEventProcessors = () => {
|
|
|
137
134
|
};
|
|
138
135
|
|
|
139
136
|
const csnCheck = async () => {
|
|
140
|
-
const
|
|
141
|
-
const eventCsn = cds.model.definitions[configInstance.tableNameEventQueue];
|
|
137
|
+
const eventCsn = cds.model.definitions[config.tableNameEventQueue];
|
|
142
138
|
if (!eventCsn) {
|
|
143
|
-
throw EventQueueError.missingTableInCsn(
|
|
139
|
+
throw EventQueueError.missingTableInCsn(config.tableNameEventQueue);
|
|
144
140
|
}
|
|
145
141
|
|
|
146
|
-
const lockCsn = cds.model.definitions[
|
|
142
|
+
const lockCsn = cds.model.definitions[config.tableNameEventLock];
|
|
147
143
|
if (!lockCsn) {
|
|
148
|
-
throw EventQueueError.missingTableInCsn(
|
|
144
|
+
throw EventQueueError.missingTableInCsn(config.tableNameEventLock);
|
|
149
145
|
}
|
|
150
146
|
|
|
151
|
-
if (
|
|
152
|
-
configInstance.tableNameEventQueue === BASE_TABLES.EVENT &&
|
|
153
|
-
configInstance.tableNameEventLock === BASE_TABLES.LOCK
|
|
154
|
-
) {
|
|
147
|
+
if (config.tableNameEventQueue === BASE_TABLES.EVENT && config.tableNameEventLock === BASE_TABLES.LOCK) {
|
|
155
148
|
return; // no need to check base tables
|
|
156
149
|
}
|
|
157
150
|
|
|
@@ -181,10 +174,9 @@ const checkCustomTable = (baseCsn, customCsn) => {
|
|
|
181
174
|
};
|
|
182
175
|
|
|
183
176
|
const mixConfigVarsWithEnv = (...args) => {
|
|
184
|
-
const configInstance = getConfigInstance();
|
|
185
177
|
CONFIG_VARS.forEach(([configName, defaultValue], index) => {
|
|
186
178
|
const configValue = args[index];
|
|
187
|
-
|
|
179
|
+
config[configName] = configValue ?? cds.env.eventQueue?.[configName] ?? defaultValue;
|
|
188
180
|
});
|
|
189
181
|
};
|
|
190
182
|
|
package/src/periodicEvents.js
CHANGED
|
@@ -4,19 +4,18 @@ const cds = require("@sap/cds");
|
|
|
4
4
|
|
|
5
5
|
const { EventProcessingStatus } = require("./constants");
|
|
6
6
|
const { processChunkedSync } = require("./shared/common");
|
|
7
|
-
const
|
|
7
|
+
const eventConfig = require("./config");
|
|
8
8
|
|
|
9
9
|
const COMPONENT_NAME = "eventQueue/periodicEvents";
|
|
10
10
|
|
|
11
11
|
const checkAndInsertPeriodicEvents = async (context) => {
|
|
12
12
|
const tx = cds.tx(context);
|
|
13
|
-
const
|
|
14
|
-
const baseCqn = SELECT.from(configInstance.tableNameEventQueue)
|
|
13
|
+
const baseCqn = SELECT.from(eventConfig.tableNameEventQueue)
|
|
15
14
|
.where([
|
|
16
15
|
{ list: [{ ref: ["type"] }, { ref: ["subType"] }] },
|
|
17
16
|
"IN",
|
|
18
17
|
{
|
|
19
|
-
list:
|
|
18
|
+
list: eventConfig.periodicEvents.map((periodicEvent) => ({
|
|
20
19
|
list: [{ val: periodicEvent.type }, { val: periodicEvent.subType }],
|
|
21
20
|
})),
|
|
22
21
|
},
|
|
@@ -30,7 +29,7 @@ const checkAndInsertPeriodicEvents = async (context) => {
|
|
|
30
29
|
|
|
31
30
|
if (!currentPeriodEvents.length) {
|
|
32
31
|
// fresh insert all
|
|
33
|
-
return await insertPeriodEvents(tx,
|
|
32
|
+
return await insertPeriodEvents(tx, eventConfig.periodicEvents);
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
const exitingEventMap = currentPeriodEvents.reduce((result, current) => {
|
|
@@ -39,7 +38,7 @@ const checkAndInsertPeriodicEvents = async (context) => {
|
|
|
39
38
|
return result;
|
|
40
39
|
}, {});
|
|
41
40
|
|
|
42
|
-
const { newEvents, existingEvents } =
|
|
41
|
+
const { newEvents, existingEvents } = eventConfig.periodicEvents.reduce(
|
|
43
42
|
(result, event) => {
|
|
44
43
|
if (exitingEventMap[_generateKey(event)]) {
|
|
45
44
|
result.existingEvents.push(exitingEventMap[_generateKey(event)]);
|
|
@@ -53,7 +52,7 @@ const checkAndInsertPeriodicEvents = async (context) => {
|
|
|
53
52
|
|
|
54
53
|
const currentDate = new Date();
|
|
55
54
|
const exitingWithNotMatchingInterval = existingEvents.filter((existingEvent) => {
|
|
56
|
-
const config =
|
|
55
|
+
const config = eventConfig.getEventConfig(existingEvent.type, existingEvent.subType);
|
|
57
56
|
const eventStartAfter = new Date(existingEvent.startAfter);
|
|
58
57
|
// check if to far in future
|
|
59
58
|
const dueInWithNewInterval = new Date(currentDate.getTime() + config.interval * 1000);
|
|
@@ -65,7 +64,7 @@ const checkAndInsertPeriodicEvents = async (context) => {
|
|
|
65
64
|
changedEvents: exitingWithNotMatchingInterval.map(({ type, subType }) => ({ type, subType })),
|
|
66
65
|
});
|
|
67
66
|
await tx.run(
|
|
68
|
-
DELETE.from(
|
|
67
|
+
DELETE.from(eventConfig.tableNameEventQueue).where(
|
|
69
68
|
"ID IN",
|
|
70
69
|
exitingWithNotMatchingInterval.map(({ ID }) => ID)
|
|
71
70
|
)
|
|
@@ -82,11 +81,10 @@ const checkAndInsertPeriodicEvents = async (context) => {
|
|
|
82
81
|
|
|
83
82
|
const insertPeriodEvents = async (tx, events) => {
|
|
84
83
|
const startAfter = new Date();
|
|
85
|
-
const configInstance = getConfigInstance();
|
|
86
84
|
processChunkedSync(events, 4, (chunk) => {
|
|
87
85
|
cds.log(COMPONENT_NAME).info("inserting changed or new periodic events", {
|
|
88
86
|
events: chunk.map(({ type, subType }) => {
|
|
89
|
-
const { interval } =
|
|
87
|
+
const { interval } = eventConfig.getEventConfig(type, subType);
|
|
90
88
|
return { type, subType, interval };
|
|
91
89
|
}),
|
|
92
90
|
});
|
|
@@ -98,7 +96,7 @@ const insertPeriodEvents = async (tx, events) => {
|
|
|
98
96
|
}));
|
|
99
97
|
|
|
100
98
|
tx._skipEventQueueBroadcase = true;
|
|
101
|
-
await tx.run(INSERT.into(
|
|
99
|
+
await tx.run(INSERT.into(eventConfig.tableNameEventQueue).entries(periodEventsInsert));
|
|
102
100
|
tx._skipEventQueueBroadcase = false;
|
|
103
101
|
};
|
|
104
102
|
|
package/src/processEventQueue.js
CHANGED
|
@@ -4,7 +4,7 @@ const pathLib = require("path");
|
|
|
4
4
|
|
|
5
5
|
const cds = require("@sap/cds");
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const config = require("./config");
|
|
8
8
|
const { TransactionMode } = require("./constants");
|
|
9
9
|
const { limiter, Funnel } = require("./shared/common");
|
|
10
10
|
|
|
@@ -29,7 +29,7 @@ const processEventQueue = async (context, eventType, eventSubType, startTime = n
|
|
|
29
29
|
let baseInstance;
|
|
30
30
|
try {
|
|
31
31
|
let eventTypeInstance;
|
|
32
|
-
const eventConfig =
|
|
32
|
+
const eventConfig = config.getEventConfig(eventType, eventSubType);
|
|
33
33
|
const [err, EventTypeClass] = resilientRequire(eventConfig?.impl);
|
|
34
34
|
if (!eventConfig || err || !(typeof EventTypeClass.constructor === "function")) {
|
|
35
35
|
cds.log(COMPONENT_NAME).error("No Implementation found in the provided configuration file.", {
|
|
@@ -137,58 +137,67 @@ const reevaluateShouldContinue = (eventTypeInstance, iterationCounter, startTime
|
|
|
137
137
|
// TODO: don't forget to release lock
|
|
138
138
|
const processPeriodicEvent = async (eventTypeInstance) => {
|
|
139
139
|
let queueEntry;
|
|
140
|
+
let processNext = true;
|
|
141
|
+
|
|
140
142
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
143
|
+
while (processNext) {
|
|
144
|
+
await executeInNewTransaction(
|
|
145
|
+
eventTypeInstance.context,
|
|
146
|
+
`eventQueue-periodic-scheduleNext-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
|
|
147
|
+
async (tx) => {
|
|
148
|
+
eventTypeInstance.processEventContext = tx.context;
|
|
149
|
+
const queueEntries = await eventTypeInstance.getQueueEntriesAndSetToInProgress();
|
|
150
|
+
if (!queueEntries.length) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (queueEntries.length > 1) {
|
|
154
|
+
queueEntry = await eventTypeInstance.handleDuplicatedPeriodicEventEntry(queueEntries);
|
|
155
|
+
} else {
|
|
156
|
+
queueEntry = queueEntries[0];
|
|
157
|
+
}
|
|
158
|
+
processNext = await eventTypeInstance.scheduleNextPeriodEvent(queueEntry);
|
|
154
159
|
}
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
);
|
|
160
|
+
);
|
|
158
161
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
if (!queueEntry) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
162
165
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
166
|
+
await executeInNewTransaction(
|
|
167
|
+
eventTypeInstance.context,
|
|
168
|
+
`eventQueue-periodic-process-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
|
|
169
|
+
async (tx) => {
|
|
170
|
+
eventTypeInstance.processEventContext = tx.context;
|
|
171
|
+
eventTypeInstance.setTxForEventProcessing(queueEntry.ID, cds.tx(tx.context));
|
|
172
|
+
try {
|
|
173
|
+
await eventTypeInstance.processEvent(tx.context, queueEntry.ID, [queueEntry]);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
eventTypeInstance.handleErrorDuringPeriodicEventProcessing(err, queueEntry);
|
|
176
|
+
throw new TriggerRollback();
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
eventTypeInstance.transactionMode !== TransactionMode.alwaysCommit ||
|
|
180
|
+
eventTypeInstance.shouldRollbackTransaction(queueEntry.ID)
|
|
181
|
+
) {
|
|
182
|
+
throw new TriggerRollback();
|
|
183
|
+
}
|
|
180
184
|
}
|
|
181
|
-
|
|
182
|
-
);
|
|
185
|
+
);
|
|
183
186
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
await executeInNewTransaction(
|
|
188
|
+
eventTypeInstance.context,
|
|
189
|
+
`eventQueue-periodic-setStatus-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
|
|
190
|
+
async (tx) => {
|
|
191
|
+
eventTypeInstance.processEventContext = tx.context;
|
|
192
|
+
await eventTypeInstance.setPeriodicEventStatus(queueEntry.ID);
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
cds.log(COMPONENT_NAME).error("Processing periodic events failed with unexpected error. Error:", err, {
|
|
198
|
+
eventType: eventTypeInstance?.eventType,
|
|
199
|
+
eventSubType: eventTypeInstance?.eventSubType,
|
|
200
|
+
});
|
|
192
201
|
} finally {
|
|
193
202
|
await eventTypeInstance?.handleReleaseLock();
|
|
194
203
|
}
|
package/src/publishEvent.js
CHANGED
|
@@ -28,13 +28,12 @@ const EventQueueError = require("./EventQueueError");
|
|
|
28
28
|
* @returns {Promise} Returns a promise which resolves to the result of the database insert operation.
|
|
29
29
|
*/
|
|
30
30
|
const publishEvent = async (tx, events, skipBroadcast = false) => {
|
|
31
|
-
|
|
32
|
-
if (!configInstance.initialized) {
|
|
31
|
+
if (!config.initialized) {
|
|
33
32
|
throw EventQueueError.notInitialized();
|
|
34
33
|
}
|
|
35
34
|
const eventsForProcessing = Array.isArray(events) ? events : [events];
|
|
36
35
|
for (const { type, subType, startAfter } of eventsForProcessing) {
|
|
37
|
-
const eventConfig =
|
|
36
|
+
const eventConfig = config.getEventConfig(type, subType);
|
|
38
37
|
if (!eventConfig) {
|
|
39
38
|
throw EventQueueError.unknownEventType(type, subType);
|
|
40
39
|
}
|
|
@@ -47,7 +46,7 @@ const publishEvent = async (tx, events, skipBroadcast = false) => {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
tx._skipEventQueueBroadcase = skipBroadcast;
|
|
50
|
-
const result = await tx.run(INSERT.into(
|
|
49
|
+
const result = await tx.run(INSERT.into(config.tableNameEventQueue).entries(eventsForProcessing));
|
|
51
50
|
tx._skipEventQueueBroadcase = false;
|
|
52
51
|
return result;
|
|
53
52
|
};
|
package/src/redisPubSub.js
CHANGED
|
@@ -11,7 +11,7 @@ const COMPONENT_NAME = "eventQueue/redisPubSub";
|
|
|
11
11
|
let subscriberClientPromise;
|
|
12
12
|
|
|
13
13
|
const initEventQueueRedisSubscribe = () => {
|
|
14
|
-
if (subscriberClientPromise || !config.
|
|
14
|
+
if (subscriberClientPromise || !config.redisEnabled) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
redis.subscribeRedisChannel(EVENT_MESSAGE_CHANNEL, messageHandlerProcessEvents);
|
|
@@ -36,9 +36,8 @@ const messageHandlerProcessEvents = async (messageData) => {
|
|
|
36
36
|
|
|
37
37
|
const broadcastEvent = async (tenantId, type, subType) => {
|
|
38
38
|
const logger = cds.log(COMPONENT_NAME);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (configInstance.registerAsEventProcessor) {
|
|
39
|
+
if (!config.redisEnabled) {
|
|
40
|
+
if (config.registerAsEventProcessor) {
|
|
42
41
|
await runEventCombinationForTenant(tenantId, type, subType);
|
|
43
42
|
}
|
|
44
43
|
return;
|
package/src/runner.js
CHANGED
|
@@ -19,6 +19,7 @@ const EVENT_QUEUE_RUN_PERIODIC_EVENT = "EVENT_QUEUE_RUN_PERIODIC_EVENT";
|
|
|
19
19
|
const OFFSET_FIRST_RUN = 10 * 1000;
|
|
20
20
|
|
|
21
21
|
let tenantIdHash;
|
|
22
|
+
let singleRunDone;
|
|
22
23
|
|
|
23
24
|
const singleTenant = () => _scheduleFunction(_checkPeriodicEventsSingleTenant, _executeRunForTenant);
|
|
24
25
|
|
|
@@ -28,8 +29,7 @@ const multiTenancyRedis = () => _scheduleFunction(_multiTenancyPeriodicEvents, _
|
|
|
28
29
|
|
|
29
30
|
const _scheduleFunction = async (singleRunFn, periodicFn) => {
|
|
30
31
|
const logger = cds.log(COMPONENT_NAME);
|
|
31
|
-
const
|
|
32
|
-
const eventsForAutomaticRun = configInstance.allEvents;
|
|
32
|
+
const eventsForAutomaticRun = eventQueueConfig.allEvents;
|
|
33
33
|
if (!eventsForAutomaticRun.length) {
|
|
34
34
|
logger.warn("no events for automatic run are configured - skipping runner registration");
|
|
35
35
|
return;
|
|
@@ -37,10 +37,14 @@ const _scheduleFunction = async (singleRunFn, periodicFn) => {
|
|
|
37
37
|
|
|
38
38
|
const fnWithRunningCheck = () => {
|
|
39
39
|
const logger = cds.log(COMPONENT_NAME);
|
|
40
|
-
if (
|
|
40
|
+
if (eventQueueConfig.isRunnerDeactivated) {
|
|
41
41
|
logger.info("runner is deactivated via config variable. Skipping this run.");
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
+
if (!singleRunDone) {
|
|
45
|
+
singleRunDone = true;
|
|
46
|
+
singleRunFn().catch(() => (singleRunDone = false));
|
|
47
|
+
}
|
|
44
48
|
return periodicFn();
|
|
45
49
|
};
|
|
46
50
|
|
|
@@ -51,9 +55,8 @@ const _scheduleFunction = async (singleRunFn, periodicFn) => {
|
|
|
51
55
|
});
|
|
52
56
|
|
|
53
57
|
setTimeout(() => {
|
|
54
|
-
singleRunFn();
|
|
55
58
|
fnWithRunningCheck();
|
|
56
|
-
const intervalRunner = new SetIntervalDriftSafe(
|
|
59
|
+
const intervalRunner = new SetIntervalDriftSafe(eventQueueConfig.runInterval);
|
|
57
60
|
intervalRunner.run(fnWithRunningCheck);
|
|
58
61
|
}, offsetDependingOnLastRun).unref();
|
|
59
62
|
};
|
|
@@ -63,7 +66,7 @@ const _multiTenancyRedis = async () => {
|
|
|
63
66
|
const emptyContext = new cds.EventContext({});
|
|
64
67
|
logger.info("executing event queue run for multi instance and tenant");
|
|
65
68
|
const tenantIds = await cdsHelper.getAllTenantIds();
|
|
66
|
-
|
|
69
|
+
_checkAndTriggerPeriodicEventUpdate(tenantIds);
|
|
67
70
|
|
|
68
71
|
const runId = await _acquireRunId(emptyContext);
|
|
69
72
|
|
|
@@ -75,7 +78,7 @@ const _multiTenancyRedis = async () => {
|
|
|
75
78
|
_executeAllTenants(tenantIds, runId);
|
|
76
79
|
};
|
|
77
80
|
|
|
78
|
-
const
|
|
81
|
+
const _checkAndTriggerPeriodicEventUpdate = (tenantIds) => {
|
|
79
82
|
const hash = hashStringTo32Bit(JSON.stringify(tenantIds));
|
|
80
83
|
if (!tenantIdHash) {
|
|
81
84
|
tenantIdHash = hash;
|
|
@@ -90,14 +93,13 @@ const _checkAndTriggerPriodicEventUpdate = (tenantIds) => {
|
|
|
90
93
|
};
|
|
91
94
|
|
|
92
95
|
const _executeAllTenantsGeneric = (tenantIds, runId, fn) => {
|
|
93
|
-
const configInstance = eventQueueConfig.getConfigInstance();
|
|
94
96
|
const workerQueueInstance = getWorkerPoolInstance();
|
|
95
97
|
tenantIds.forEach((tenantId) => {
|
|
96
98
|
workerQueueInstance.addToQueue(async () => {
|
|
97
99
|
try {
|
|
98
100
|
const tenantContext = new cds.EventContext({ tenant: tenantId });
|
|
99
101
|
const couldAcquireLock = await distributedLock.acquireLock(tenantContext, runId, {
|
|
100
|
-
expiryTime:
|
|
102
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
101
103
|
});
|
|
102
104
|
if (!couldAcquireLock) {
|
|
103
105
|
return;
|
|
@@ -119,9 +121,8 @@ const _executePeriodicEventsAllTenants = (tenantIds, runId) =>
|
|
|
119
121
|
|
|
120
122
|
const _executeRunForTenant = async (tenantId, runId) => {
|
|
121
123
|
const logger = cds.log(COMPONENT_NAME);
|
|
122
|
-
const configInstance = eventQueueConfig.getConfigInstance();
|
|
123
124
|
try {
|
|
124
|
-
const eventsForAutomaticRun =
|
|
125
|
+
const eventsForAutomaticRun = eventQueueConfig.allEvents;
|
|
125
126
|
const subdomain = await cdsHelper.getSubdomainForTenantId(tenantId);
|
|
126
127
|
const context = new cds.EventContext({
|
|
127
128
|
tenant: tenantId,
|
|
@@ -138,23 +139,22 @@ const _executeRunForTenant = async (tenantId, runId) => {
|
|
|
138
139
|
} catch (err) {
|
|
139
140
|
logger.error(`Couldn't process eventQueue for tenant! Next try after defined interval. Error: ${err}`, {
|
|
140
141
|
tenantId,
|
|
141
|
-
redisEnabled:
|
|
142
|
+
redisEnabled: eventQueueConfig.redisEnabled,
|
|
142
143
|
});
|
|
143
144
|
}
|
|
144
145
|
};
|
|
145
146
|
|
|
146
147
|
const _acquireRunId = async (context) => {
|
|
147
|
-
const configInstance = eventQueueConfig.getConfigInstance();
|
|
148
148
|
let runId = randomUUID();
|
|
149
149
|
const couldSetValue = await distributedLock.setValueWithExpire(context, EVENT_QUEUE_RUN_ID, runId, {
|
|
150
150
|
tenantScoped: false,
|
|
151
|
-
expiryTime:
|
|
151
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
if (couldSetValue) {
|
|
155
155
|
await distributedLock.setValueWithExpire(context, EVENT_QUEUE_RUN_TS, new Date().toISOString(), {
|
|
156
156
|
tenantScoped: false,
|
|
157
|
-
expiryTime:
|
|
157
|
+
expiryTime: eventQueueConfig.runInterval,
|
|
158
158
|
overrideValue: true,
|
|
159
159
|
});
|
|
160
160
|
} else {
|
|
@@ -167,13 +167,12 @@ const _acquireRunId = async (context) => {
|
|
|
167
167
|
};
|
|
168
168
|
|
|
169
169
|
const _calculateOffsetForFirstRun = async () => {
|
|
170
|
-
const configInstance = eventQueueConfig.getConfigInstance();
|
|
171
170
|
let offsetDependingOnLastRun = OFFSET_FIRST_RUN;
|
|
172
171
|
const now = Date.now();
|
|
173
172
|
// NOTE: this is only supported with Redis, because this is a tenant agnostic information
|
|
174
173
|
// currently there is no proper place to store this information beside t0 schema
|
|
175
174
|
try {
|
|
176
|
-
if (
|
|
175
|
+
if (eventQueueConfig.redisEnabled) {
|
|
177
176
|
const dummyContext = new cds.EventContext({});
|
|
178
177
|
let lastRunTs = await distributedLock.checkLockExistsAndReturnValue(dummyContext, EVENT_QUEUE_RUN_TS, {
|
|
179
178
|
tenantScoped: false,
|
|
@@ -182,7 +181,7 @@ const _calculateOffsetForFirstRun = async () => {
|
|
|
182
181
|
const ts = new Date(now).toISOString();
|
|
183
182
|
const couldSetValue = await distributedLock.setValueWithExpire(dummyContext, EVENT_QUEUE_RUN_TS, ts, {
|
|
184
183
|
tenantScoped: false,
|
|
185
|
-
expiryTime:
|
|
184
|
+
expiryTime: eventQueueConfig.runInterval,
|
|
186
185
|
});
|
|
187
186
|
if (couldSetValue) {
|
|
188
187
|
lastRunTs = ts;
|
|
@@ -192,7 +191,7 @@ const _calculateOffsetForFirstRun = async () => {
|
|
|
192
191
|
});
|
|
193
192
|
}
|
|
194
193
|
}
|
|
195
|
-
offsetDependingOnLastRun = new Date(lastRunTs).getTime() +
|
|
194
|
+
offsetDependingOnLastRun = new Date(lastRunTs).getTime() + eventQueueConfig.runInterval - now;
|
|
196
195
|
}
|
|
197
196
|
} catch (err) {
|
|
198
197
|
cds
|
|
@@ -230,7 +229,7 @@ const _multiTenancyDb = async () => {
|
|
|
230
229
|
try {
|
|
231
230
|
logger.info("executing event queue run for single instance and multi tenant");
|
|
232
231
|
const tenantIds = await cdsHelper.getAllTenantIds();
|
|
233
|
-
|
|
232
|
+
_checkAndTriggerPeriodicEventUpdate(tenantIds);
|
|
234
233
|
_executeAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
|
|
235
234
|
} catch (err) {
|
|
236
235
|
logger.error(
|
|
@@ -252,8 +251,7 @@ const _multiTenancyPeriodicEvents = async () => {
|
|
|
252
251
|
|
|
253
252
|
const _checkPeriodicEventsSingleTenant = async (tenantId) => {
|
|
254
253
|
const logger = cds.log(COMPONENT_NAME);
|
|
255
|
-
|
|
256
|
-
if (!configInstance.updatePeriodicEvents) {
|
|
254
|
+
if (!eventQueueConfig.updatePeriodicEvents) {
|
|
257
255
|
logger.info("updating of periodic events is disabled");
|
|
258
256
|
}
|
|
259
257
|
try {
|
|
@@ -274,7 +272,7 @@ const _checkPeriodicEventsSingleTenant = async (tenantId) => {
|
|
|
274
272
|
} catch (err) {
|
|
275
273
|
logger.error(`Couldn't process eventQueue for tenant! Next try after defined interval. Error: ${err}`, {
|
|
276
274
|
tenantId,
|
|
277
|
-
redisEnabled:
|
|
275
|
+
redisEnabled: eventQueueConfig.redisEnabled,
|
|
278
276
|
});
|
|
279
277
|
}
|
|
280
278
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const cds = require("@sap/cds");
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const config = require("../config");
|
|
6
6
|
|
|
7
7
|
const COMPONENT_NAME = "eventQueue/WorkerQueue";
|
|
8
8
|
|
|
@@ -56,8 +56,7 @@ class WorkerQueue {
|
|
|
56
56
|
module.exports = {
|
|
57
57
|
getWorkerPoolInstance: () => {
|
|
58
58
|
if (!instance) {
|
|
59
|
-
|
|
60
|
-
instance = new WorkerQueue(configInstance.parallelTenantProcessing);
|
|
59
|
+
instance = new WorkerQueue(config.parallelTenantProcessing);
|
|
61
60
|
}
|
|
62
61
|
return instance;
|
|
63
62
|
},
|
package/src/shared/cdsHelper.js
CHANGED
|
@@ -94,7 +94,7 @@ class TriggerRollback extends VError {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
const getSubdomainForTenantId = async (tenantId) => {
|
|
97
|
-
if (!config.
|
|
97
|
+
if (!config.isMultiTenancy) {
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
100
|
if (subdomainCache[tenantId]) {
|
|
@@ -111,7 +111,7 @@ const getSubdomainForTenantId = async (tenantId) => {
|
|
|
111
111
|
};
|
|
112
112
|
|
|
113
113
|
const getAllTenantIds = async () => {
|
|
114
|
-
if (!config.
|
|
114
|
+
if (!config.isMultiTenancy) {
|
|
115
115
|
return null;
|
|
116
116
|
}
|
|
117
117
|
const ssp = await cds.connect.to("cds.xt.SaasProvisioningService");
|
|
@@ -3,15 +3,10 @@
|
|
|
3
3
|
const redis = require("./redis");
|
|
4
4
|
const config = require("../config");
|
|
5
5
|
const cdsHelper = require("./cdsHelper");
|
|
6
|
-
const { getConfigInstance } = require("../config");
|
|
7
6
|
|
|
8
|
-
const acquireLock = async (
|
|
9
|
-
context,
|
|
10
|
-
key,
|
|
11
|
-
{ tenantScoped = true, expiryTime = config.getConfigInstance().globalTxTimeout } = {}
|
|
12
|
-
) => {
|
|
7
|
+
const acquireLock = async (context, key, { tenantScoped = true, expiryTime = config.globalTxTimeout } = {}) => {
|
|
13
8
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
14
|
-
if (config.
|
|
9
|
+
if (config.redisEnabled) {
|
|
15
10
|
return await _acquireLockRedis(context, fullKey, expiryTime);
|
|
16
11
|
} else {
|
|
17
12
|
return await _acquireLockDB(context, fullKey, expiryTime);
|
|
@@ -22,10 +17,10 @@ const setValueWithExpire = async (
|
|
|
22
17
|
context,
|
|
23
18
|
key,
|
|
24
19
|
value,
|
|
25
|
-
{ tenantScoped = true, expiryTime = config.
|
|
20
|
+
{ tenantScoped = true, expiryTime = config.globalTxTimeout, overrideValue = false } = {}
|
|
26
21
|
) => {
|
|
27
22
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
28
|
-
if (config.
|
|
23
|
+
if (config.redisEnabled) {
|
|
29
24
|
return await _acquireLockRedis(context, fullKey, expiryTime, {
|
|
30
25
|
value,
|
|
31
26
|
overrideValue,
|
|
@@ -40,7 +35,7 @@ const setValueWithExpire = async (
|
|
|
40
35
|
|
|
41
36
|
const releaseLock = async (context, key, { tenantScoped = true } = {}) => {
|
|
42
37
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
43
|
-
if (config.
|
|
38
|
+
if (config.redisEnabled) {
|
|
44
39
|
return await _releaseLockRedis(context, fullKey);
|
|
45
40
|
} else {
|
|
46
41
|
return await _releaseLockDb(context, fullKey);
|
|
@@ -49,7 +44,7 @@ const releaseLock = async (context, key, { tenantScoped = true } = {}) => {
|
|
|
49
44
|
|
|
50
45
|
const checkLockExistsAndReturnValue = async (context, key, { tenantScoped = true } = {}) => {
|
|
51
46
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
52
|
-
if (config.
|
|
47
|
+
if (config.redisEnabled) {
|
|
53
48
|
return await _checkLockExistsRedis(context, fullKey);
|
|
54
49
|
} else {
|
|
55
50
|
return await _checkLockExistsDb(context, fullKey);
|
|
@@ -72,9 +67,8 @@ const _checkLockExistsRedis = async (context, fullKey) => {
|
|
|
72
67
|
|
|
73
68
|
const _checkLockExistsDb = async (context, fullKey) => {
|
|
74
69
|
let result;
|
|
75
|
-
const configInstance = getConfigInstance();
|
|
76
70
|
await cdsHelper.executeInNewTransaction(context, "distributedLock-checkExists", async (tx) => {
|
|
77
|
-
result = await tx.run(SELECT.one.from(
|
|
71
|
+
result = await tx.run(SELECT.one.from(config.tableNameEventLock).where("code =", fullKey));
|
|
78
72
|
});
|
|
79
73
|
return result?.value;
|
|
80
74
|
};
|
|
@@ -85,19 +79,17 @@ const _releaseLockRedis = async (context, fullKey) => {
|
|
|
85
79
|
};
|
|
86
80
|
|
|
87
81
|
const _releaseLockDb = async (context, fullKey) => {
|
|
88
|
-
const configInstance = getConfigInstance();
|
|
89
82
|
await cdsHelper.executeInNewTransaction(context, "distributedLock-release", async (tx) => {
|
|
90
|
-
await tx.run(DELETE.from(
|
|
83
|
+
await tx.run(DELETE.from(config.tableNameEventLock).where("code =", fullKey));
|
|
91
84
|
});
|
|
92
85
|
};
|
|
93
86
|
|
|
94
87
|
const _acquireLockDB = async (context, fullKey, expiryTime, { value = "true", overrideValue = false } = {}) => {
|
|
95
88
|
let result;
|
|
96
|
-
const configInstance = getConfigInstance();
|
|
97
89
|
await cdsHelper.executeInNewTransaction(context, "distributedLock-acquire", async (tx) => {
|
|
98
90
|
try {
|
|
99
91
|
await tx.run(
|
|
100
|
-
INSERT.into(
|
|
92
|
+
INSERT.into(config.tableNameEventLock).entries({
|
|
101
93
|
code: fullKey,
|
|
102
94
|
value,
|
|
103
95
|
})
|
|
@@ -109,14 +101,14 @@ const _acquireLockDB = async (context, fullKey, expiryTime, { value = "true", ov
|
|
|
109
101
|
if (!overrideValue) {
|
|
110
102
|
currentEntry = await tx.run(
|
|
111
103
|
SELECT.one
|
|
112
|
-
.from(
|
|
113
|
-
.forUpdate({ wait: config.
|
|
104
|
+
.from(config.tableNameEventLock)
|
|
105
|
+
.forUpdate({ wait: config.forUpdateTimeout })
|
|
114
106
|
.where("code =", fullKey)
|
|
115
107
|
);
|
|
116
108
|
}
|
|
117
109
|
if (overrideValue || (currentEntry && new Date(currentEntry.createdAt).getTime() + expiryTime <= Date.now())) {
|
|
118
110
|
await tx.run(
|
|
119
|
-
UPDATE.entity(
|
|
111
|
+
UPDATE.entity(config.tableNameEventLock)
|
|
120
112
|
.set({
|
|
121
113
|
createdAt: new Date().toISOString(),
|
|
122
114
|
value,
|
|
@@ -13,11 +13,8 @@ class EventScheduler {
|
|
|
13
13
|
constructor() {}
|
|
14
14
|
|
|
15
15
|
scheduleEvent(tenantId, type, subType, startAfter) {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const scheduleWithoutDelay = configInstance.isPeriodicEvent(type, subType) && eventConfig.interval < 30 * 1000;
|
|
19
|
-
const roundUpDate = scheduleWithoutDelay ? startAfter : this.calculateFutureTime(startAfter, 10);
|
|
20
|
-
const key = [tenantId, type, subType, roundUpDate.toISOString()].join("##");
|
|
16
|
+
const { date, relative } = this.calculateOffset(type, subType, startAfter);
|
|
17
|
+
const key = [tenantId, type, subType, date.toISOString()].join("##");
|
|
21
18
|
if (this.#scheduledEvents[key]) {
|
|
22
19
|
return; // event combination already scheduled
|
|
23
20
|
}
|
|
@@ -25,7 +22,7 @@ class EventScheduler {
|
|
|
25
22
|
cds.log(COMPONENT_NAME).info("scheduling event queue run for delayed event", {
|
|
26
23
|
type,
|
|
27
24
|
subType,
|
|
28
|
-
delaySeconds: (
|
|
25
|
+
delaySeconds: (date.getTime() - Date.now()) / 1000,
|
|
29
26
|
});
|
|
30
27
|
setTimeout(() => {
|
|
31
28
|
delete this.#scheduledEvents[key];
|
|
@@ -34,10 +31,18 @@ class EventScheduler {
|
|
|
34
31
|
tenantId,
|
|
35
32
|
type,
|
|
36
33
|
subType,
|
|
37
|
-
scheduledFor:
|
|
34
|
+
scheduledFor: date.toISOString(),
|
|
38
35
|
});
|
|
39
36
|
});
|
|
40
|
-
},
|
|
37
|
+
}, relative).unref();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
calculateOffset(type, subType, startAfter) {
|
|
41
|
+
const eventConfig = config.getEventConfig(type, subType);
|
|
42
|
+
const scheduleWithoutDelay = config.isPeriodicEvent(type, subType) && eventConfig.interval < 30 * 1000;
|
|
43
|
+
const date = scheduleWithoutDelay ? startAfter : this.calculateFutureTime(startAfter, 10);
|
|
44
|
+
|
|
45
|
+
return { date, relative: date.getTime() - Date.now() };
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
calculateFutureTime(date, seoncds) {
|