@cap-js-community/event-queue 1.8.5 → 1.8.7
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 +3 -1
- package/src/index.d.ts +7 -1
- package/src/publishEvent.js +6 -2
- package/src/runner/runner.js +64 -52
- package/src/runner/runnerHelper.js +1 -0
- package/src/shared/WorkerQueue.js +10 -5
- package/src/shared/common.js +11 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.7",
|
|
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
|
"types": "src/index.d.ts",
|
package/src/config.js
CHANGED
|
@@ -17,6 +17,7 @@ const COMPONENT_NAME = "/eventQueue/config";
|
|
|
17
17
|
const MIN_INTERVAL_SEC = 10;
|
|
18
18
|
const DEFAULT_LOAD = 1;
|
|
19
19
|
const DEFAULT_PRIORITY = Priorities.Medium;
|
|
20
|
+
const DEFAULT_INCREASE_PRIORITY = true;
|
|
20
21
|
const SUFFIX_PERIODIC = "_PERIODIC";
|
|
21
22
|
const COMMAND_BLOCK = "EVENT_QUEUE_EVENT_BLOCK";
|
|
22
23
|
const COMMAND_UNBLOCK = "EVENT_QUEUE_EVENT_UNBLOCK";
|
|
@@ -317,6 +318,7 @@ class Config {
|
|
|
317
318
|
retryFailedAfter: config.retryFailedAfter,
|
|
318
319
|
priority: config.priority,
|
|
319
320
|
multiInstanceProcessing: config.multiInstanceProcessing,
|
|
321
|
+
increasePriorityOverTime: config.increasePriorityOverTime,
|
|
320
322
|
internalEvent: true,
|
|
321
323
|
};
|
|
322
324
|
|
|
@@ -364,7 +366,6 @@ class Config {
|
|
|
364
366
|
return result;
|
|
365
367
|
}, {});
|
|
366
368
|
this.#eventMap = config.periodicEvents.reduce((result, event) => {
|
|
367
|
-
event.priority = event.priority ?? DEFAULT_PRIORITY;
|
|
368
369
|
event.type = `${event.type}${SUFFIX_PERIODIC}`;
|
|
369
370
|
event.isPeriodic = true;
|
|
370
371
|
this.#basicEventTransformation(event);
|
|
@@ -378,6 +379,7 @@ class Config {
|
|
|
378
379
|
#basicEventTransformation(event) {
|
|
379
380
|
event.load = event.load ?? DEFAULT_LOAD;
|
|
380
381
|
event.priority = event.priority ?? DEFAULT_PRIORITY;
|
|
382
|
+
event.increasePriorityOverTime = event.increasePriorityOverTime ?? DEFAULT_INCREASE_PRIORITY;
|
|
381
383
|
}
|
|
382
384
|
|
|
383
385
|
#basicEventTransformationAfterValidate(event) {
|
package/src/index.d.ts
CHANGED
|
@@ -269,7 +269,13 @@ export const workerQueue: WorkerQueue;
|
|
|
269
269
|
declare class WorkerQueue {
|
|
270
270
|
constructor(concurrency: number);
|
|
271
271
|
|
|
272
|
-
addToQueue(
|
|
272
|
+
addToQueue(
|
|
273
|
+
load: number,
|
|
274
|
+
label: string,
|
|
275
|
+
priority: Priorities,
|
|
276
|
+
increasePriorityOverTime: boolean,
|
|
277
|
+
cb: () => any
|
|
278
|
+
): Promise<any>;
|
|
273
279
|
|
|
274
280
|
_executeFunction(
|
|
275
281
|
load: number,
|
package/src/publishEvent.js
CHANGED
|
@@ -34,7 +34,8 @@ const publishEvent = async (tx, events, { skipBroadcast = false, skipInsertEvent
|
|
|
34
34
|
throw EventQueueError.notInitialized();
|
|
35
35
|
}
|
|
36
36
|
const eventsForProcessing = Array.isArray(events) ? events : [events];
|
|
37
|
-
for (const
|
|
37
|
+
for (const event of eventsForProcessing) {
|
|
38
|
+
const { type, subType, startAfter } = event;
|
|
38
39
|
const eventConfig = config.getEventConfig(type, subType);
|
|
39
40
|
if (!eventConfig) {
|
|
40
41
|
throw EventQueueError.unknownEventType(type, subType);
|
|
@@ -46,8 +47,11 @@ const publishEvent = async (tx, events, { skipBroadcast = false, skipInsertEvent
|
|
|
46
47
|
if (eventConfig.isPeriodic) {
|
|
47
48
|
throw EventQueueError.manuelPeriodicEventInsert(type, subType);
|
|
48
49
|
}
|
|
49
|
-
}
|
|
50
50
|
|
|
51
|
+
if (typeof event.payload !== "string") {
|
|
52
|
+
event.payload = JSON.stringify(event.payload);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
51
55
|
if (config.insertEventsBeforeCommit && !skipInsertEventsBeforeCommit) {
|
|
52
56
|
_registerHandlerAndAddEvents(tx, events);
|
|
53
57
|
} else {
|
package/src/runner/runner.js
CHANGED
|
@@ -194,33 +194,39 @@ const _executeEventsAllTenants = async (tenantIds, runId) => {
|
|
|
194
194
|
events.map(async (openEvent) => {
|
|
195
195
|
const eventConfig = config.getEventConfig(openEvent.type, openEvent.subType);
|
|
196
196
|
const label = `${eventConfig.type}_${eventConfig.subType}`;
|
|
197
|
-
return await WorkerQueue.instance.addToQueue(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
197
|
+
return await WorkerQueue.instance.addToQueue(
|
|
198
|
+
eventConfig.load,
|
|
199
|
+
label,
|
|
200
|
+
eventConfig.priority,
|
|
201
|
+
eventConfig.increasePriorityOverTime,
|
|
202
|
+
async () => {
|
|
203
|
+
return await cds.tx(tenantContext, async ({ context }) => {
|
|
204
|
+
await trace(
|
|
205
|
+
context,
|
|
206
|
+
label,
|
|
207
|
+
async () => {
|
|
208
|
+
try {
|
|
209
|
+
const lockId = `${runId}_${label}`;
|
|
210
|
+
const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
|
|
211
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
212
|
+
});
|
|
213
|
+
if (!couldAcquireLock) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
await runEventCombinationForTenant(context, eventConfig.type, eventConfig.subType, {
|
|
217
|
+
skipWorkerPool: true,
|
|
218
|
+
});
|
|
219
|
+
} catch (err) {
|
|
220
|
+
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
221
|
+
tenantId,
|
|
222
|
+
});
|
|
210
223
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
{ newRootSpan: true }
|
|
221
|
-
);
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
+
},
|
|
225
|
+
{ newRootSpan: true }
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
);
|
|
224
230
|
})
|
|
225
231
|
);
|
|
226
232
|
}
|
|
@@ -275,33 +281,39 @@ const _singleTenantDb = async () => {
|
|
|
275
281
|
events.map(async (openEvent) => {
|
|
276
282
|
const eventConfig = config.getEventConfig(openEvent.type, openEvent.subType);
|
|
277
283
|
const label = `${eventConfig.type}_${eventConfig.subType}`;
|
|
278
|
-
return await WorkerQueue.instance.addToQueue(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
284
|
+
return await WorkerQueue.instance.addToQueue(
|
|
285
|
+
eventConfig.load,
|
|
286
|
+
label,
|
|
287
|
+
eventConfig.priority,
|
|
288
|
+
eventConfig.increasePriorityOverTime,
|
|
289
|
+
async () => {
|
|
290
|
+
return await cds.tx({}, async ({ context }) => {
|
|
291
|
+
await trace(
|
|
292
|
+
context,
|
|
293
|
+
label,
|
|
294
|
+
async () => {
|
|
295
|
+
try {
|
|
296
|
+
const lockId = `${label}`;
|
|
297
|
+
const couldAcquireLock = eventConfig.multiInstanceProcessing
|
|
298
|
+
? true
|
|
299
|
+
: await distributedLock.acquireLock(context, lockId, {
|
|
300
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
301
|
+
});
|
|
302
|
+
if (!couldAcquireLock) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
await runEventCombinationForTenant(context, eventConfig.type, eventConfig.subType, {
|
|
306
|
+
skipWorkerPool: true,
|
|
307
|
+
});
|
|
308
|
+
} catch (err) {
|
|
309
|
+
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed");
|
|
293
310
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
},
|
|
301
|
-
{ newRootSpan: true }
|
|
302
|
-
);
|
|
303
|
-
});
|
|
304
|
-
});
|
|
311
|
+
},
|
|
312
|
+
{ newRootSpan: true }
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
);
|
|
305
317
|
})
|
|
306
318
|
);
|
|
307
319
|
};
|
|
@@ -23,6 +23,7 @@ const runEventCombinationForTenant = async (context, type, subType, { skipWorker
|
|
|
23
23
|
eventConfig.load,
|
|
24
24
|
label,
|
|
25
25
|
eventConfig.priority,
|
|
26
|
+
eventConfig.increasePriorityOverTime,
|
|
26
27
|
AsyncResource.bind(async () => {
|
|
27
28
|
const _exec = async () => {
|
|
28
29
|
if (!eventConfig.multiInstanceProcessing && lockId) {
|
|
@@ -52,7 +52,7 @@ class WorkerQueue {
|
|
|
52
52
|
runner.run(this.#adjustPriority.bind(this));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
addToQueue(load, label, priority = Priorities.Medium, cb) {
|
|
55
|
+
addToQueue(load, label, priority = Priorities.Medium, increasePriorityOverTime, cb) {
|
|
56
56
|
if (load > this.#concurrencyLimit) {
|
|
57
57
|
throw EventQueueError.loadHigherThanLimit(load, label);
|
|
58
58
|
}
|
|
@@ -63,7 +63,7 @@ class WorkerQueue {
|
|
|
63
63
|
|
|
64
64
|
const startTime = process.hrtime.bigint();
|
|
65
65
|
const p = new Promise((resolve, reject) => {
|
|
66
|
-
this.#queue[priority].push([load, label, cb, resolve, reject, startTime]);
|
|
66
|
+
this.#queue[priority].push([load, label, cb, resolve, reject, increasePriorityOverTime, startTime]);
|
|
67
67
|
});
|
|
68
68
|
this.#checkForNext();
|
|
69
69
|
return p;
|
|
@@ -78,7 +78,12 @@ class WorkerQueue {
|
|
|
78
78
|
const nextPriority = priorityValues[i + 1];
|
|
79
79
|
for (let i = 0; i < this.queue[priority].length; i++) {
|
|
80
80
|
const queueEntry = this.queue[priority][i];
|
|
81
|
-
|
|
81
|
+
// NOTE: index 5 - increasingPrioOverTime
|
|
82
|
+
if (!queueEntry[5]) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// NOTE: index 6 original time; index 7 shifted time
|
|
86
|
+
const startTime = queueEntry[7] ?? queueEntry[6];
|
|
82
87
|
if (Math.round(Number(checkTime - startTime) / NANO_TO_MS) > INCREASE_PRIORITY_AFTER * MIN_TO_MS) {
|
|
83
88
|
const [entry] = this.queue[priority].splice(i, 1);
|
|
84
89
|
entry.push(checkTime);
|
|
@@ -88,7 +93,7 @@ class WorkerQueue {
|
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
_executeFunction(load, label, cb, resolve, reject,
|
|
96
|
+
_executeFunction(priority, load, label, cb, resolve, reject, skipIncreasingPrioOverTime, startTime) {
|
|
92
97
|
this.#checkAndLogWaitingTime(startTime, label, priority);
|
|
93
98
|
const promise = Promise.resolve().then(() => cb());
|
|
94
99
|
this.#runningPromises.push(promise);
|
|
@@ -123,7 +128,7 @@ class WorkerQueue {
|
|
|
123
128
|
const [load] = this.#queue[priority][i];
|
|
124
129
|
if (this.#runningLoad + load <= this.#concurrencyLimit) {
|
|
125
130
|
const [args] = this.#queue[priority].splice(i, 1);
|
|
126
|
-
this._executeFunction(...args
|
|
131
|
+
this._executeFunction(priority, ...args);
|
|
127
132
|
entryFound = true;
|
|
128
133
|
break;
|
|
129
134
|
}
|
package/src/shared/common.js
CHANGED
|
@@ -101,18 +101,27 @@ const _getNewTokenInfo = async (tenantId) => {
|
|
|
101
101
|
return tokenInfo;
|
|
102
102
|
} catch (err) {
|
|
103
103
|
tokenInfoCache[tenantId] = null;
|
|
104
|
-
cds.log(COMPONENT_NAME).warn("failed to request tokenInfo",
|
|
104
|
+
cds.log(COMPONENT_NAME).warn("failed to request tokenInfo", {
|
|
105
|
+
err: err.message,
|
|
106
|
+
responseCode: err.responseCode,
|
|
107
|
+
responseText: err.responseText,
|
|
108
|
+
});
|
|
105
109
|
}
|
|
106
110
|
};
|
|
107
111
|
|
|
108
112
|
const getTokenInfo = async (tenantId) => {
|
|
109
|
-
if (!isTenantIdValidCb(TenantIdCheckTypes.getTokenInfo, tenantId)) {
|
|
113
|
+
if (!(await isTenantIdValidCb(TenantIdCheckTypes.getTokenInfo, tenantId))) {
|
|
110
114
|
return null;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
if (!cds.requires?.auth?.credentials) {
|
|
114
118
|
return null; // no credentials not tokenInfo
|
|
115
119
|
}
|
|
120
|
+
|
|
121
|
+
if (!config.isMultiTenancy) {
|
|
122
|
+
return null; // does only make sense for multi tenancy
|
|
123
|
+
}
|
|
124
|
+
|
|
116
125
|
if (!cds.requires?.auth.kind.match(/jwt|xsuaa/i)) {
|
|
117
126
|
cds.log(COMPONENT_NAME).warn("Only 'jwt' or 'xsuaa' are supported as values for auth.kind.");
|
|
118
127
|
return null;
|