@cap-js-community/event-queue 0.2.5 → 0.3.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/package.json +1 -1
- package/src/config.js +2 -2
- package/src/initialize.js +1 -5
- package/src/processEventQueue.js +1 -1
- package/src/redisPubSub.js +28 -7
- package/src/runner.js +52 -49
- package/src/shared/WorkerQueue.js +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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": [
|
package/src/config.js
CHANGED
|
@@ -12,6 +12,7 @@ const REDIS_CONFIG_CHANNEL = "EVENT_QUEUE_CONFIG_CHANNEL";
|
|
|
12
12
|
const COMPONENT_NAME = "eventQueue/config";
|
|
13
13
|
const MIN_INTERVAL_SEC = 10;
|
|
14
14
|
const DEFAULT_LOAD = 1;
|
|
15
|
+
const SUFFIX_PERIODIC = "_PERIODIC";
|
|
15
16
|
|
|
16
17
|
class Config {
|
|
17
18
|
#logger;
|
|
@@ -42,7 +43,7 @@ class Config {
|
|
|
42
43
|
this.#runInterval = null;
|
|
43
44
|
this.#redisEnabled = null;
|
|
44
45
|
this.#initialized = false;
|
|
45
|
-
this.#instanceLoadLimit =
|
|
46
|
+
this.#instanceLoadLimit = 100;
|
|
46
47
|
this.#tableNameEventQueue = null;
|
|
47
48
|
this.#tableNameEventLock = null;
|
|
48
49
|
this.#isRunnerDeactivated = false;
|
|
@@ -115,7 +116,6 @@ class Config {
|
|
|
115
116
|
}, {});
|
|
116
117
|
this.#eventMap = config.periodicEvents.reduce((result, event) => {
|
|
117
118
|
event.load = event.load ?? DEFAULT_LOAD;
|
|
118
|
-
const SUFFIX_PERIODIC = "_PERIODIC";
|
|
119
119
|
event.type = `${event.type}${SUFFIX_PERIODIC}`;
|
|
120
120
|
event.isPeriodic = true;
|
|
121
121
|
this.validatePeriodicConfig(result, event);
|
package/src/initialize.js
CHANGED
|
@@ -29,7 +29,6 @@ const CONFIG_VARS = [
|
|
|
29
29
|
["processEventsAfterPublish", true],
|
|
30
30
|
["isRunnerDeactivated", false],
|
|
31
31
|
["runInterval", 5 * 60 * 1000],
|
|
32
|
-
["instanceLoadLimit", 20],
|
|
33
32
|
["tableNameEventQueue", BASE_TABLES.EVENT],
|
|
34
33
|
["tableNameEventLock", BASE_TABLES.LOCK],
|
|
35
34
|
["disableRedis", false],
|
|
@@ -43,7 +42,6 @@ const initialize = async ({
|
|
|
43
42
|
processEventsAfterPublish,
|
|
44
43
|
isRunnerDeactivated,
|
|
45
44
|
runInterval,
|
|
46
|
-
instanceLoadLimit,
|
|
47
45
|
tableNameEventQueue,
|
|
48
46
|
tableNameEventLock,
|
|
49
47
|
disableRedis,
|
|
@@ -52,7 +50,7 @@ const initialize = async ({
|
|
|
52
50
|
} = {}) => {
|
|
53
51
|
// TODO: initialize check:
|
|
54
52
|
// - content of yaml check
|
|
55
|
-
// - betweenRuns
|
|
53
|
+
// - betweenRuns
|
|
56
54
|
|
|
57
55
|
if (config.initialized) {
|
|
58
56
|
return;
|
|
@@ -65,7 +63,6 @@ const initialize = async ({
|
|
|
65
63
|
processEventsAfterPublish,
|
|
66
64
|
isRunnerDeactivated,
|
|
67
65
|
runInterval,
|
|
68
|
-
instanceLoadLimit,
|
|
69
66
|
tableNameEventQueue,
|
|
70
67
|
tableNameEventLock,
|
|
71
68
|
disableRedis,
|
|
@@ -92,7 +89,6 @@ const initialize = async ({
|
|
|
92
89
|
multiTenancyEnabled: config.isMultiTenancy,
|
|
93
90
|
redisEnabled: config.redisEnabled,
|
|
94
91
|
runInterval: config.runInterval,
|
|
95
|
-
config: config.instanceLoadLimit,
|
|
96
92
|
});
|
|
97
93
|
};
|
|
98
94
|
|
package/src/processEventQueue.js
CHANGED
|
@@ -166,7 +166,7 @@ const processPeriodicEvent = async (eventTypeInstance) => {
|
|
|
166
166
|
throw new TriggerRollback();
|
|
167
167
|
}
|
|
168
168
|
if (
|
|
169
|
-
eventTypeInstance.transactionMode
|
|
169
|
+
eventTypeInstance.transactionMode === TransactionMode.alwaysRollback ||
|
|
170
170
|
eventTypeInstance.shouldRollbackTransaction(queueEntry.ID)
|
|
171
171
|
) {
|
|
172
172
|
throw new TriggerRollback();
|
package/src/redisPubSub.js
CHANGED
|
@@ -4,6 +4,7 @@ const redis = require("./shared/redis");
|
|
|
4
4
|
const { checkLockExistsAndReturnValue } = require("./shared/distributedLock");
|
|
5
5
|
const config = require("./config");
|
|
6
6
|
const { runEventCombinationForTenant } = require("./runner");
|
|
7
|
+
const { getSubdomainForTenantId } = require("./shared/cdsHelper");
|
|
7
8
|
|
|
8
9
|
const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
|
|
9
10
|
const COMPONENT_NAME = "eventQueue/redisPubSub";
|
|
@@ -26,7 +27,15 @@ const messageHandlerProcessEvents = async (messageData) => {
|
|
|
26
27
|
type,
|
|
27
28
|
subType,
|
|
28
29
|
});
|
|
29
|
-
await
|
|
30
|
+
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
31
|
+
const tenantContext = {
|
|
32
|
+
tenant: tenantId,
|
|
33
|
+
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
34
|
+
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
35
|
+
};
|
|
36
|
+
return await cds.tx(tenantContext, async ({ context }) => {
|
|
37
|
+
return await runEventCombinationForTenant(context, type, subType);
|
|
38
|
+
});
|
|
30
39
|
} catch (err) {
|
|
31
40
|
logger.error("could not parse event information", {
|
|
32
41
|
messageData,
|
|
@@ -36,13 +45,25 @@ const messageHandlerProcessEvents = async (messageData) => {
|
|
|
36
45
|
|
|
37
46
|
const broadcastEvent = async (tenantId, type, subType) => {
|
|
38
47
|
const logger = cds.log(COMPONENT_NAME);
|
|
39
|
-
if (!config.redisEnabled) {
|
|
40
|
-
if (config.registerAsEventProcessor) {
|
|
41
|
-
await runEventCombinationForTenant(tenantId, type, subType);
|
|
42
|
-
}
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
48
|
try {
|
|
49
|
+
if (!config.redisEnabled) {
|
|
50
|
+
if (config.registerAsEventProcessor) {
|
|
51
|
+
let context = {};
|
|
52
|
+
if (tenantId) {
|
|
53
|
+
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
54
|
+
context = {
|
|
55
|
+
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
56
|
+
tenant: tenantId,
|
|
57
|
+
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return await cds.tx(context, async ({ context }) => {
|
|
62
|
+
return await runEventCombinationForTenant(context, type, subType);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
46
67
|
const result = await checkLockExistsAndReturnValue(
|
|
47
68
|
new cds.EventContext({ tenant: tenantId }),
|
|
48
69
|
[type, subType].join("##")
|
package/src/runner.js
CHANGED
|
@@ -94,45 +94,61 @@ const _checkAndTriggerPeriodicEventUpdate = (tenantIds) => {
|
|
|
94
94
|
|
|
95
95
|
const _executeEventsAllTenants = (tenantIds, runId) => {
|
|
96
96
|
const events = eventQueueConfig.allEvents;
|
|
97
|
-
const
|
|
98
|
-
tenantIds.forEach((tenantId) => {
|
|
97
|
+
const product = tenantIds.reduce((result, tenantId) => {
|
|
99
98
|
events.forEach((event) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
99
|
+
result.push([tenantId, event]);
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
return product.map(async ([tenantId, event]) => {
|
|
105
|
+
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
106
|
+
const tenantContext = {
|
|
107
|
+
tenant: tenantId,
|
|
108
|
+
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
109
|
+
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
110
|
+
};
|
|
111
|
+
return await cds.tx(tenantContext, async ({ context }) => {
|
|
112
|
+
return await WorkerQueue.instance.addToQueue(event.load, async () => {
|
|
113
|
+
try {
|
|
114
|
+
const lockId = `${runId}_${event.type}_${event.subType}`;
|
|
115
|
+
const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
|
|
116
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
117
|
+
});
|
|
118
|
+
if (!couldAcquireLock) {
|
|
119
|
+
return;
|
|
116
120
|
}
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
await runEventCombinationForTenant(context, event.type, event.subType, true);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
124
|
+
tenantId,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
119
128
|
});
|
|
120
129
|
});
|
|
121
|
-
return promises;
|
|
122
130
|
};
|
|
123
131
|
|
|
124
132
|
const _executePeriodicEventsAllTenants = (tenantIds, runId) => {
|
|
125
133
|
tenantIds.forEach((tenantId) => {
|
|
126
134
|
WorkerQueue.instance.addToQueue(1, async () => {
|
|
127
135
|
try {
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
136
|
+
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
137
|
+
const tenantContext = {
|
|
138
|
+
tenant: tenantId,
|
|
139
|
+
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
140
|
+
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return await cds.tx(tenantContext, async ({ context }) => {
|
|
144
|
+
const couldAcquireLock = await distributedLock.acquireLock(context, runId, {
|
|
145
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
146
|
+
});
|
|
147
|
+
if (!couldAcquireLock) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
await _checkPeriodicEventsSingleTenant(context);
|
|
131
151
|
});
|
|
132
|
-
if (!couldAcquireLock) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
await _checkPeriodicEventsSingleTenant(tenantId);
|
|
136
152
|
} catch (err) {
|
|
137
153
|
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
138
154
|
tenantId,
|
|
@@ -147,7 +163,8 @@ const _singleTenantDb = async (tenantId) => {
|
|
|
147
163
|
events.forEach((event) => {
|
|
148
164
|
WorkerQueue.instance.addToQueue(event.load, async () => {
|
|
149
165
|
try {
|
|
150
|
-
|
|
166
|
+
const context = new cds.EventContext({ tenant: tenantId });
|
|
167
|
+
await runEventCombinationForTenant(context, event.type, event.subType, true);
|
|
151
168
|
} catch (err) {
|
|
152
169
|
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
153
170
|
tenantId,
|
|
@@ -215,15 +232,8 @@ const _calculateOffsetForFirstRun = async () => {
|
|
|
215
232
|
return offsetDependingOnLastRun;
|
|
216
233
|
};
|
|
217
234
|
|
|
218
|
-
const runEventCombinationForTenant = async (
|
|
235
|
+
const runEventCombinationForTenant = async (context, type, subType, skipWorkerPool) => {
|
|
219
236
|
try {
|
|
220
|
-
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
221
|
-
const context = new cds.EventContext({
|
|
222
|
-
tenant: tenantId,
|
|
223
|
-
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
224
|
-
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
225
|
-
});
|
|
226
|
-
cds.context = context;
|
|
227
237
|
if (skipWorkerPool) {
|
|
228
238
|
return await processEventQueue(context, type, subType);
|
|
229
239
|
} else {
|
|
@@ -236,7 +246,7 @@ const runEventCombinationForTenant = async (tenantId, type, subType, skipWorkerP
|
|
|
236
246
|
} catch (err) {
|
|
237
247
|
const logger = cds.log(COMPONENT_NAME);
|
|
238
248
|
logger.error("error executing event combination for tenant", err, {
|
|
239
|
-
tenantId,
|
|
249
|
+
tenantId: context.tenant,
|
|
240
250
|
type,
|
|
241
251
|
subType,
|
|
242
252
|
});
|
|
@@ -266,7 +276,7 @@ const _multiTenancyPeriodicEvents = async () => {
|
|
|
266
276
|
}
|
|
267
277
|
};
|
|
268
278
|
|
|
269
|
-
const _checkPeriodicEventsSingleTenant = async (
|
|
279
|
+
const _checkPeriodicEventsSingleTenant = async (context = {}) => {
|
|
270
280
|
const logger = cds.log(COMPONENT_NAME);
|
|
271
281
|
if (!eventQueueConfig.updatePeriodicEvents || !eventQueueConfig.periodicEvents.length) {
|
|
272
282
|
logger.info("updating of periodic events is disabled or no periodic events configured", {
|
|
@@ -276,23 +286,16 @@ const _checkPeriodicEventsSingleTenant = async (tenantId) => {
|
|
|
276
286
|
return;
|
|
277
287
|
}
|
|
278
288
|
try {
|
|
279
|
-
const subdomain = await cdsHelper.getSubdomainForTenantId(tenantId);
|
|
280
|
-
const context = new cds.EventContext({
|
|
281
|
-
tenant: tenantId,
|
|
282
|
-
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
283
|
-
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
284
|
-
});
|
|
285
|
-
cds.context = context;
|
|
286
289
|
logger.info("executing updating periotic events", {
|
|
287
|
-
tenantId,
|
|
288
|
-
subdomain,
|
|
290
|
+
tenantId: context.tenant,
|
|
291
|
+
subdomain: context.http?.req.authInfo.getSubdomain(),
|
|
289
292
|
});
|
|
290
293
|
await cdsHelper.executeInNewTransaction(context, "update-periodic-events", async (tx) => {
|
|
291
294
|
await periodicEvents.checkAndInsertPeriodicEvents(tx.context);
|
|
292
295
|
});
|
|
293
296
|
} catch (err) {
|
|
294
297
|
logger.error("Couldn't update periodic events for tenant! Next try after defined interval.", err, {
|
|
295
|
-
tenantId,
|
|
298
|
+
tenantId: context.tenant,
|
|
296
299
|
redisEnabled: eventQueueConfig.redisEnabled,
|
|
297
300
|
});
|
|
298
301
|
}
|
|
@@ -8,9 +8,9 @@ const EventQueueError = require("../EventQueueError");
|
|
|
8
8
|
const COMPONENT_NAME = "eventQueue/WorkerQueue";
|
|
9
9
|
const NANO_TO_MS = 1e6;
|
|
10
10
|
const THRESHOLD = {
|
|
11
|
-
INFO:
|
|
12
|
-
WARN:
|
|
13
|
-
ERROR:
|
|
11
|
+
INFO: 15 * 1000,
|
|
12
|
+
WARN: 30 * 1000,
|
|
13
|
+
ERROR: 45 * 1000,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
class WorkerQueue {
|