@cap-js-community/event-queue 1.8.3 → 1.8.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 +1 -1
- package/src/EventQueueProcessorBase.js +10 -0
- package/src/config.js +22 -16
- package/src/constants.js +1 -1
- package/src/index.d.ts +45 -34
- package/src/initialize.js +1 -3
- package/src/outbox/eventQueueAsOutbox.js +12 -14
- package/src/processEventQueue.js +46 -35
- package/src/redis/redisPub.js +8 -0
- package/src/redis/redisSub.js +5 -0
- package/src/runner/runner.js +15 -13
- package/src/shared/cdsHelper.js +36 -6
- package/src/shared/common.js +36 -5
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.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
|
"types": "src/index.d.ts",
|
|
@@ -546,6 +546,16 @@ class EventQueueProcessorBase {
|
|
|
546
546
|
});
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
+
handleErrorTx(error) {
|
|
550
|
+
this.logger.error("Error in commit|rollback transaction, check handlers and constraints!", error, {
|
|
551
|
+
eventType: this.#eventType,
|
|
552
|
+
eventSubType: this.#eventSubType,
|
|
553
|
+
});
|
|
554
|
+
this.__queueEntries.forEach((queueEntry) => {
|
|
555
|
+
this.#determineAndAddEventStatusToMap(queueEntry.ID, EventProcessingStatus.Error);
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
549
559
|
handleInvalidPayloadReturned(queueEntry) {
|
|
550
560
|
this.logger.error(
|
|
551
561
|
"Undefined payload is not allowed. If status should be done, nulls needs to be returned" +
|
package/src/config.js
CHANGED
|
@@ -58,7 +58,6 @@ class Config {
|
|
|
58
58
|
#isEventQueueActive;
|
|
59
59
|
#configFilePath;
|
|
60
60
|
#processEventsAfterPublish;
|
|
61
|
-
#skipCsnCheck;
|
|
62
61
|
#registerAsEventProcessor;
|
|
63
62
|
#disableRedis;
|
|
64
63
|
#env;
|
|
@@ -78,7 +77,8 @@ class Config {
|
|
|
78
77
|
#cronTimezone;
|
|
79
78
|
#publishEventBlockList;
|
|
80
79
|
#crashOnRedisUnavailable;
|
|
81
|
-
#
|
|
80
|
+
#tenantIdFilterTokenInfoCb;
|
|
81
|
+
#tenantIdFilterEventProcessingCb;
|
|
82
82
|
static #instance;
|
|
83
83
|
constructor() {
|
|
84
84
|
this.#logger = cds.log(COMPONENT_NAME);
|
|
@@ -94,7 +94,6 @@ class Config {
|
|
|
94
94
|
this.#isEventQueueActive = true;
|
|
95
95
|
this.#configFilePath = null;
|
|
96
96
|
this.#processEventsAfterPublish = null;
|
|
97
|
-
this.#skipCsnCheck = null;
|
|
98
97
|
this.#disableRedis = null;
|
|
99
98
|
this.#env = getEnvInstance();
|
|
100
99
|
this.#blockedEvents = {};
|
|
@@ -293,7 +292,10 @@ class Config {
|
|
|
293
292
|
|
|
294
293
|
addCAPOutboxEvent(serviceName, config) {
|
|
295
294
|
if (this.#eventMap[this.generateKey(CAP_EVENT_TYPE, serviceName)]) {
|
|
296
|
-
|
|
295
|
+
const index = this.#config.events.findIndex(
|
|
296
|
+
(event) => event.type === CAP_EVENT_TYPE && event.subType === serviceName
|
|
297
|
+
);
|
|
298
|
+
this.#config.events.splice(index, 1);
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
const eventConfig = {
|
|
@@ -312,8 +314,12 @@ class Config {
|
|
|
312
314
|
appNames: config.appNames,
|
|
313
315
|
appInstances: config.appInstances,
|
|
314
316
|
useEventQueueUser: config.useEventQueueUser,
|
|
317
|
+
retryFailedAfter: config.retryFailedAfter,
|
|
318
|
+
priority: config.priority,
|
|
319
|
+
multiInstanceProcessing: config.multiInstanceProcessing,
|
|
315
320
|
internalEvent: true,
|
|
316
321
|
};
|
|
322
|
+
|
|
317
323
|
this.#basicEventTransformationAfterValidate(eventConfig);
|
|
318
324
|
this.#config.events.push(eventConfig);
|
|
319
325
|
this.#eventMap[this.generateKey(CAP_EVENT_TYPE, serviceName)] = eventConfig;
|
|
@@ -530,12 +536,20 @@ class Config {
|
|
|
530
536
|
this.#crashOnRedisUnavailable = value;
|
|
531
537
|
}
|
|
532
538
|
|
|
533
|
-
get
|
|
534
|
-
return this.#
|
|
539
|
+
get tenantIdFilterTokenInfo() {
|
|
540
|
+
return this.#tenantIdFilterTokenInfoCb;
|
|
535
541
|
}
|
|
536
542
|
|
|
537
|
-
set
|
|
538
|
-
this.#
|
|
543
|
+
set tenantIdFilterTokenInfo(value) {
|
|
544
|
+
this.#tenantIdFilterTokenInfoCb = value;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
get tenantIdFilterEventProcessing() {
|
|
548
|
+
return this.#tenantIdFilterEventProcessingCb;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
set tenantIdFilterEventProcessing(value) {
|
|
552
|
+
this.#tenantIdFilterEventProcessingCb = value;
|
|
539
553
|
}
|
|
540
554
|
|
|
541
555
|
set globalTxTimeout(value) {
|
|
@@ -617,14 +631,6 @@ class Config {
|
|
|
617
631
|
return this.#processEventsAfterPublish;
|
|
618
632
|
}
|
|
619
633
|
|
|
620
|
-
set skipCsnCheck(value) {
|
|
621
|
-
this.#skipCsnCheck = value;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
get skipCsnCheck() {
|
|
625
|
-
return this.#skipCsnCheck;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
634
|
set disableRedis(value) {
|
|
629
635
|
this.#disableRedis = value;
|
|
630
636
|
}
|
package/src/constants.js
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ declare type EventProcessingStatusKeysType = keyof typeof EventProcessingStatus;
|
|
|
12
12
|
export declare type EventProcessingStatusType = (typeof EventProcessingStatus)[EventProcessingStatusKeysType];
|
|
13
13
|
|
|
14
14
|
export declare const TenantIdCheckTypes: {
|
|
15
|
-
|
|
15
|
+
eventProcessing: "eventProcessing";
|
|
16
16
|
getTokenInfo: "getTokenInfo";
|
|
17
17
|
};
|
|
18
18
|
|
|
@@ -182,54 +182,63 @@ export function triggerEventProcessingRedis(
|
|
|
182
182
|
declare class Config {
|
|
183
183
|
constructor();
|
|
184
184
|
|
|
185
|
-
getEventConfig(type:
|
|
186
|
-
isCapOutboxEvent(type:
|
|
187
|
-
hasEventAfterCommitFlag(type:
|
|
188
|
-
|
|
189
|
-
checkRedisEnabled():
|
|
190
|
-
publishEventBlockList(): boolean;
|
|
191
|
-
crashOnRedisUnavailable(): boolean;
|
|
185
|
+
getEventConfig(type: any, subType: any): any;
|
|
186
|
+
isCapOutboxEvent(type: any): boolean;
|
|
187
|
+
hasEventAfterCommitFlag(type: any, subType: any): any;
|
|
188
|
+
shouldBeProcessedInThisApplication(type: any, subType: any): boolean;
|
|
189
|
+
checkRedisEnabled(): any;
|
|
192
190
|
attachConfigChangeHandler(): void;
|
|
193
191
|
attachRedisUnsubscribeHandler(): void;
|
|
194
|
-
executeUnsubscribeHandlers(tenantId:
|
|
195
|
-
handleUnsubscribe(tenantId:
|
|
196
|
-
attachUnsubscribeHandler(cb:
|
|
197
|
-
publishConfigChange(key:
|
|
198
|
-
blockEvent(type:
|
|
192
|
+
executeUnsubscribeHandlers(tenantId: any): void;
|
|
193
|
+
handleUnsubscribe(tenantId: any): void;
|
|
194
|
+
attachUnsubscribeHandler(cb: any): void;
|
|
195
|
+
publishConfigChange(key: any, value: any): void;
|
|
196
|
+
blockEvent(type: any, subType: any, isPeriodic: any, tenant?: string): void;
|
|
199
197
|
clearPeriodicEventBlockList(): void;
|
|
200
|
-
unblockEvent(type:
|
|
201
|
-
addCAPOutboxEvent(serviceName:
|
|
202
|
-
isEventBlocked(type:
|
|
203
|
-
get isEventQueueActive(): boolean;
|
|
198
|
+
unblockEvent(type: any, subType: any, isPeriodic: any, tenant?: string): void;
|
|
199
|
+
addCAPOutboxEvent(serviceName: any, config: any): void;
|
|
200
|
+
isEventBlocked(type: any, subType: any, isPeriodicEvent: any, tenant: any): any;
|
|
204
201
|
set isEventQueueActive(value: boolean);
|
|
202
|
+
get isEventQueueActive(): boolean;
|
|
205
203
|
set fileContent(config: any);
|
|
206
204
|
get fileContent(): any;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
get
|
|
211
|
-
get
|
|
212
|
-
|
|
205
|
+
generateKey(type: any, subType: any): string;
|
|
206
|
+
removeEvent(type: any, subType: any): void;
|
|
207
|
+
isTenantUnsubscribed(tenantId: any): any;
|
|
208
|
+
get events(): any;
|
|
209
|
+
get periodicEvents(): any;
|
|
210
|
+
isPeriodicEvent(type: any, subType: any): any;
|
|
211
|
+
get allEvents(): any;
|
|
213
212
|
set forUpdateTimeout(value: number);
|
|
213
|
+
get forUpdateTimeout(): number;
|
|
214
214
|
set globalTxTimeout(value: number);
|
|
215
|
-
get
|
|
216
|
-
set
|
|
217
|
-
get
|
|
218
|
-
set
|
|
219
|
-
get
|
|
215
|
+
get globalTxTimeout(): number;
|
|
216
|
+
set publishEventBlockList(value: any);
|
|
217
|
+
get publishEventBlockList(): any;
|
|
218
|
+
set crashOnRedisUnavailable(value: any);
|
|
219
|
+
get crashOnRedisUnavailable(): any;
|
|
220
|
+
set tenantIdFilterTokenInfo(value: any);
|
|
221
|
+
get tenantIdFilterTokenInfo(): any;
|
|
222
|
+
set tenantIdFilterEventProcessing(value: any);
|
|
223
|
+
get tenantIdFilterEventProcessing(): any;
|
|
224
|
+
set runInterval(value: any);
|
|
225
|
+
get runInterval(): any;
|
|
226
|
+
set redisEnabled(value: any);
|
|
227
|
+
get redisEnabled(): any;
|
|
220
228
|
set initialized(value: boolean);
|
|
221
|
-
get
|
|
229
|
+
get initialized(): boolean;
|
|
230
|
+
set cronTimezone(value: any);
|
|
231
|
+
get cronTimezone(): any;
|
|
222
232
|
set instanceLoadLimit(value: number);
|
|
223
|
-
get
|
|
233
|
+
get instanceLoadLimit(): number;
|
|
224
234
|
set isEventBlockedCb(value: any);
|
|
235
|
+
get isEventBlockedCb(): any;
|
|
225
236
|
get tableNameEventQueue(): string;
|
|
226
237
|
get tableNameEventLock(): string;
|
|
227
|
-
set configFilePath(value:
|
|
228
|
-
get configFilePath():
|
|
238
|
+
set configFilePath(value: any);
|
|
239
|
+
get configFilePath(): any;
|
|
229
240
|
set processEventsAfterPublish(value: any);
|
|
230
241
|
get processEventsAfterPublish(): any;
|
|
231
|
-
set skipCsnCheck(value: any);
|
|
232
|
-
get skipCsnCheck(): any;
|
|
233
242
|
set disableRedis(value: any);
|
|
234
243
|
get disableRedis(): any;
|
|
235
244
|
set updatePeriodicEvents(value: any);
|
|
@@ -248,6 +257,8 @@ declare class Config {
|
|
|
248
257
|
get redisOptions(): any;
|
|
249
258
|
set insertEventsBeforeCommit(value: any);
|
|
250
259
|
get insertEventsBeforeCommit(): any;
|
|
260
|
+
set enableCAPTelemetry(value: any);
|
|
261
|
+
get enableCAPTelemetry(): any;
|
|
251
262
|
get isMultiTenancy(): boolean;
|
|
252
263
|
}
|
|
253
264
|
|
package/src/initialize.js
CHANGED
|
@@ -42,7 +42,6 @@ const CONFIG_VARS = [
|
|
|
42
42
|
["cronTimezone", null],
|
|
43
43
|
["publishEventBlockList", true],
|
|
44
44
|
["crashOnRedisUnavailable", false],
|
|
45
|
-
["tenantIdFilterCb", null],
|
|
46
45
|
];
|
|
47
46
|
|
|
48
47
|
/**
|
|
@@ -66,7 +65,6 @@ const CONFIG_VARS = [
|
|
|
66
65
|
* @param {string} [options.cronTimezone=null] - Default timezone for cron jobs.
|
|
67
66
|
* @param {string} [options.publishEventBlockList=true] - If redis is available event blocklist is distributed to all application instances
|
|
68
67
|
* @param {string} [options.crashOnRedisUnavailable=true] - If enabled an error is thrown if the redis connection check is not successful
|
|
69
|
-
* @param {function} [options.tenantIdFilterCb=null] - Allows to set customer filter function to filter the tenants ids which should be processed in the event-queue
|
|
70
68
|
*/
|
|
71
69
|
const initialize = async (options = {}) => {
|
|
72
70
|
if (config.initialized) {
|
|
@@ -188,7 +186,7 @@ const mixConfigVarsWithEnv = (options) => {
|
|
|
188
186
|
|
|
189
187
|
const registerCdsShutdown = () => {
|
|
190
188
|
const isTestProfile = cds.env.profiles.find((profile) => profile.includes("test"));
|
|
191
|
-
if (isTestProfile) {
|
|
189
|
+
if (isTestProfile || !config.redisEnabled) {
|
|
192
190
|
return;
|
|
193
191
|
}
|
|
194
192
|
cds.on("shutdown", async () => {
|
|
@@ -12,7 +12,13 @@ const COMPONENT_NAME = "/eventQueue/eventQueueAsOutbox";
|
|
|
12
12
|
const EVENT_QUEUE_SPECIFIC_FIELDS = ["startAfter", "referenceEntity", "referenceEntityKey"];
|
|
13
13
|
|
|
14
14
|
function outboxed(srv, customOpts) {
|
|
15
|
-
|
|
15
|
+
if (!(new.target || customOpts)) {
|
|
16
|
+
const former = srv[OUTBOXED];
|
|
17
|
+
if (former) {
|
|
18
|
+
return former;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
const logger = cds.log(COMPONENT_NAME);
|
|
17
23
|
const outboxOpts = Object.assign(
|
|
18
24
|
{},
|
|
@@ -21,14 +27,8 @@ function outboxed(srv, customOpts) {
|
|
|
21
27
|
customOpts || {}
|
|
22
28
|
);
|
|
23
29
|
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
if (former) {
|
|
27
|
-
if (outboxOpts.kind === "persistent-outbox") {
|
|
28
|
-
config.addCAPOutboxEvent(srv.name, outboxOpts);
|
|
29
|
-
}
|
|
30
|
-
return former;
|
|
31
|
-
}
|
|
30
|
+
if (outboxOpts.kind === "persistent-outbox") {
|
|
31
|
+
config.addCAPOutboxEvent(srv.name, outboxOpts);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const originalSrv = srv[UNBOXED] || srv;
|
|
@@ -36,11 +36,9 @@ function outboxed(srv, customOpts) {
|
|
|
36
36
|
outboxedSrv[UNBOXED] = originalSrv;
|
|
37
37
|
|
|
38
38
|
if (!new.target) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (outboxOpts.kind === "persistent-outbox") {
|
|
43
|
-
config.addCAPOutboxEvent(srv.name, outboxOpts);
|
|
39
|
+
if (!srv[OUTBOXED]) {
|
|
40
|
+
Object.defineProperty(srv, OUTBOXED, { value: outboxedSrv });
|
|
41
|
+
}
|
|
44
42
|
}
|
|
45
43
|
outboxedSrv.handle = async function (req) {
|
|
46
44
|
const context = req.context || cds.context;
|
package/src/processEventQueue.js
CHANGED
|
@@ -201,56 +201,67 @@ const processPeriodicEvent = async (context, eventTypeInstance) => {
|
|
|
201
201
|
}
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
-
const processEventMap = async (
|
|
205
|
-
|
|
206
|
-
await
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
|
|
204
|
+
const processEventMap = async (instance) => {
|
|
205
|
+
instance.startPerformanceTracerEvents();
|
|
206
|
+
await instance.beforeProcessingEvents();
|
|
207
|
+
instance.logStartMessage();
|
|
208
|
+
if (instance.commitOnEventLevel) {
|
|
209
|
+
instance.txUsageAllowed = false;
|
|
210
210
|
}
|
|
211
211
|
await limiter(
|
|
212
|
-
|
|
213
|
-
Object.entries(
|
|
212
|
+
instance.parallelEventProcessing,
|
|
213
|
+
Object.entries(instance.eventProcessingMap),
|
|
214
214
|
async ([key, { queueEntries, payload }]) => {
|
|
215
|
-
if (
|
|
215
|
+
if (instance.commitOnEventLevel) {
|
|
216
216
|
let statusMap;
|
|
217
217
|
await executeInNewTransaction(
|
|
218
|
-
|
|
219
|
-
`eventQueue-processEvent-${
|
|
218
|
+
instance.baseContext,
|
|
219
|
+
`eventQueue-processEvent-${instance.eventType}##${instance.eventSubType}`,
|
|
220
220
|
async (tx) => {
|
|
221
|
-
statusMap = await _processEvent(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
) {
|
|
221
|
+
statusMap = await _processEvent(instance, tx.context, key, queueEntries, payload);
|
|
222
|
+
const shouldRollback =
|
|
223
|
+
instance.statusMapContainsError(statusMap) || instance.shouldRollbackTransaction(key);
|
|
224
|
+
if (shouldRollback) {
|
|
226
225
|
await tx.rollback();
|
|
226
|
+
await _commitStatusInNewTx(instance, statusMap);
|
|
227
|
+
} else {
|
|
228
|
+
await instance.persistEventStatus(tx, {
|
|
229
|
+
skipChecks: true,
|
|
230
|
+
statusMap,
|
|
231
|
+
});
|
|
227
232
|
}
|
|
228
233
|
}
|
|
229
234
|
);
|
|
230
|
-
await executeInNewTransaction(
|
|
231
|
-
eventTypeInstance.baseContext,
|
|
232
|
-
`eventQueue-persistStatus-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
|
|
233
|
-
async (tx) => {
|
|
234
|
-
eventTypeInstance.processEventContext = tx.context;
|
|
235
|
-
await eventTypeInstance.persistEventStatus(tx, {
|
|
236
|
-
skipChecks: true,
|
|
237
|
-
statusMap,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
);
|
|
241
235
|
} else {
|
|
242
|
-
await _processEvent(
|
|
236
|
+
await _processEvent(instance, instance.context, key, queueEntries, payload);
|
|
243
237
|
}
|
|
244
238
|
}
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
239
|
+
)
|
|
240
|
+
.catch((err) => {
|
|
241
|
+
instance.handleErrorTx(err);
|
|
242
|
+
})
|
|
243
|
+
.finally(() => {
|
|
244
|
+
instance.clearEventProcessingContext();
|
|
245
|
+
if (instance.commitOnEventLevel) {
|
|
246
|
+
instance.txUsageAllowed = true;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
instance.endPerformanceTracerEvents();
|
|
252
250
|
};
|
|
253
251
|
|
|
252
|
+
const _commitStatusInNewTx = async (eventTypeInstance, statusMap) =>
|
|
253
|
+
await executeInNewTransaction(
|
|
254
|
+
eventTypeInstance.baseContext,
|
|
255
|
+
`eventQueue-persistStatus-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
|
|
256
|
+
async (tx) => {
|
|
257
|
+
eventTypeInstance.processEventContext = tx.context;
|
|
258
|
+
await eventTypeInstance.persistEventStatus(tx, {
|
|
259
|
+
skipChecks: true,
|
|
260
|
+
statusMap,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
|
|
254
265
|
const _checkEventIsBlocked = async (baseInstance) => {
|
|
255
266
|
const isEventBlockedCb = config.isEventBlockedCb;
|
|
256
267
|
let eventBlocked;
|
package/src/redis/redisPub.js
CHANGED
|
@@ -10,6 +10,7 @@ const config = require("../config");
|
|
|
10
10
|
const common = require("../shared/common");
|
|
11
11
|
const { runEventCombinationForTenant } = require("../runner/runnerHelper");
|
|
12
12
|
const trace = require("../shared/openTelemetry");
|
|
13
|
+
const { TenantIdCheckTypes } = require("../constants");
|
|
13
14
|
|
|
14
15
|
const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
|
|
15
16
|
const COMPONENT_NAME = "/eventQueue/redisPub";
|
|
@@ -59,6 +60,10 @@ const broadcastEvent = async (tenantId, events, forceBroadcast = false) => {
|
|
|
59
60
|
events = Array.isArray(events) ? events : [events];
|
|
60
61
|
try {
|
|
61
62
|
if (!config.redisEnabled) {
|
|
63
|
+
const tenantShouldBeProcessed = await common.isTenantIdValidCb(TenantIdCheckTypes.eventProcessing, tenantId);
|
|
64
|
+
if (!tenantShouldBeProcessed) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
62
67
|
await _processLocalWithoutRedis(tenantId, events);
|
|
63
68
|
return;
|
|
64
69
|
}
|
|
@@ -66,6 +71,9 @@ const broadcastEvent = async (tenantId, events, forceBroadcast = false) => {
|
|
|
66
71
|
await trace(context, "broadcast-inserted-events", async () => {
|
|
67
72
|
for (const { type, subType } of events) {
|
|
68
73
|
const eventConfig = config.getEventConfig(type, subType);
|
|
74
|
+
if (!eventConfig) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
69
77
|
for (let i = 0; i < TRIES_FOR_PUBLISH_PERIODIC_EVENT; i++) {
|
|
70
78
|
const result = eventConfig.multiInstanceProcessing
|
|
71
79
|
? false
|
package/src/redis/redisSub.js
CHANGED
|
@@ -6,6 +6,7 @@ const redis = require("../shared/redis");
|
|
|
6
6
|
const config = require("../config");
|
|
7
7
|
const runnerHelper = require("../runner/runnerHelper");
|
|
8
8
|
const common = require("../shared/common");
|
|
9
|
+
const { TenantIdCheckTypes } = require("../constants");
|
|
9
10
|
|
|
10
11
|
const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
|
|
11
12
|
const COMPONENT_NAME = "/eventQueue/redisSub";
|
|
@@ -22,6 +23,10 @@ const _messageHandlerProcessEvents = async (messageData) => {
|
|
|
22
23
|
const logger = cds.log(COMPONENT_NAME);
|
|
23
24
|
try {
|
|
24
25
|
const { lockId, tenantId, type, subType } = JSON.parse(messageData);
|
|
26
|
+
const tenantShouldBeProcessed = await common.isTenantIdValidCb(TenantIdCheckTypes.eventProcessing, tenantId);
|
|
27
|
+
if (!tenantShouldBeProcessed) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
25
30
|
logger.debug("received redis event", {
|
|
26
31
|
tenantId,
|
|
27
32
|
type,
|
package/src/runner/runner.js
CHANGED
|
@@ -111,17 +111,19 @@ const _executeEventsAllTenantsRedis = async (tenantIds) => {
|
|
|
111
111
|
// NOTE: do checks for all tenants on the same app instance --> acquire lock tenant independent
|
|
112
112
|
// distribute from this instance to all others
|
|
113
113
|
const dummyContext = new cds.EventContext({});
|
|
114
|
-
const couldAcquireLock =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
const couldAcquireLock = config.tenantIdFilterEventProcessing
|
|
115
|
+
? true
|
|
116
|
+
: await trace(
|
|
117
|
+
dummyContext,
|
|
118
|
+
"acquire-lock-master-runner",
|
|
119
|
+
async () => {
|
|
120
|
+
return await distributedLock.acquireLock(dummyContext, EVENT_QUEUE_RUN_REDIS_CHECK, {
|
|
121
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
122
|
+
tenantScoped: false,
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
{ newRootSpan: true }
|
|
126
|
+
);
|
|
125
127
|
if (!couldAcquireLock) {
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
@@ -237,7 +239,7 @@ const _executePeriodicEventsAllTenants = async (tenantIds) => {
|
|
|
237
239
|
};
|
|
238
240
|
await cds.tx(tenantContext, async ({ context }) => {
|
|
239
241
|
await trace(tenantContext, "update-periodic-events-for-tenant", async () => {
|
|
240
|
-
if (!config.redisEnabled) {
|
|
242
|
+
if (!config.redisEnabled && !config.tenantIdFilterEventProcessing) {
|
|
241
243
|
const couldAcquireLock = await distributedLock.acquireLock(context, EVENT_QUEUE_UPDATE_PERIODIC_EVENTS, {
|
|
242
244
|
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
243
245
|
});
|
|
@@ -433,7 +435,7 @@ const _multiTenancyPeriodicEvents = async (tenantIds) => {
|
|
|
433
435
|
dummyContext,
|
|
434
436
|
"update-periodic-events",
|
|
435
437
|
async () => {
|
|
436
|
-
if (config.redisEnabled) {
|
|
438
|
+
if (config.redisEnabled && !config.tenantIdFilterEventProcessing) {
|
|
437
439
|
const couldAcquireLock = await distributedLock.acquireLock(dummyContext, EVENT_QUEUE_UPDATE_PERIODIC_EVENTS, {
|
|
438
440
|
expiryTime: 60 * 1000, // short living lock --> assume we do not have 2 onboards within 1 minute
|
|
439
441
|
tenantScoped: false,
|
package/src/shared/cdsHelper.js
CHANGED
|
@@ -23,6 +23,7 @@ const COMPONENT_NAME = "/eventQueue/cdsHelper";
|
|
|
23
23
|
async function executeInNewTransaction(context = {}, transactionTag, fn, args, { info = {} } = {}) {
|
|
24
24
|
const parameters = Array.isArray(args) ? args : [args];
|
|
25
25
|
const logger = cds.log(COMPONENT_NAME);
|
|
26
|
+
let transactionRollbackPromise = Promise.resolve(false);
|
|
26
27
|
try {
|
|
27
28
|
const user = new cds.User.Privileged({ id: config.userId, tokenInfo: await common.getTokenInfo(context.tenant) });
|
|
28
29
|
if (cds.db.kind === "hana") {
|
|
@@ -36,7 +37,15 @@ async function executeInNewTransaction(context = {}, transactionTag, fn, args, {
|
|
|
36
37
|
},
|
|
37
38
|
async (tx) => {
|
|
38
39
|
tx.context._ = context._ ?? {};
|
|
39
|
-
return
|
|
40
|
+
return new Promise((outerResolve, outerReject) => {
|
|
41
|
+
transactionRollbackPromise = new Promise((resolve) => {
|
|
42
|
+
tx.context.on("succeeded", () => resolve(false));
|
|
43
|
+
tx.context.on("failed", () => resolve(true));
|
|
44
|
+
fn(tx, ...parameters)
|
|
45
|
+
.then(outerResolve)
|
|
46
|
+
.catch(outerReject);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
40
49
|
}
|
|
41
50
|
);
|
|
42
51
|
} else {
|
|
@@ -51,7 +60,10 @@ async function executeInNewTransaction(context = {}, transactionTag, fn, args, {
|
|
|
51
60
|
user,
|
|
52
61
|
headers: context.headers,
|
|
53
62
|
},
|
|
54
|
-
async (tx) =>
|
|
63
|
+
async (tx) => {
|
|
64
|
+
await fn(tx, ...parameters);
|
|
65
|
+
transactionRollbackPromise = false;
|
|
66
|
+
}
|
|
55
67
|
);
|
|
56
68
|
} else {
|
|
57
69
|
contextTx.context.user = user;
|
|
@@ -68,17 +80,24 @@ async function executeInNewTransaction(context = {}, transactionTag, fn, args, {
|
|
|
68
80
|
// change rollback to no opt - closing tx would cause follow-up usage to fail.
|
|
69
81
|
// the process that opened the tx needs to manage it
|
|
70
82
|
};
|
|
71
|
-
await fn(contextTx, ...parameters)
|
|
83
|
+
await fn(contextTx, ...parameters)
|
|
84
|
+
.then(() => (transactionRollbackPromise = false))
|
|
85
|
+
.finally(() => (contextTx.rollback = txRollback));
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
} catch (err) {
|
|
89
|
+
const transactionRollback = await transactionRollbackPromise;
|
|
75
90
|
if (err instanceof VError) {
|
|
76
91
|
Object.assign(err.jse_info, {
|
|
77
92
|
newTx: info,
|
|
78
93
|
});
|
|
79
|
-
|
|
94
|
+
if (transactionRollback) {
|
|
95
|
+
throw err;
|
|
96
|
+
} else {
|
|
97
|
+
logger.error("business transaction commited but succeeded|done|failed threw a error!", err);
|
|
98
|
+
}
|
|
80
99
|
} else {
|
|
81
|
-
|
|
100
|
+
const nestedError = new VError(
|
|
82
101
|
{
|
|
83
102
|
name: VERROR_CLUSTER_NAME,
|
|
84
103
|
cause: err,
|
|
@@ -86,6 +105,11 @@ async function executeInNewTransaction(context = {}, transactionTag, fn, args, {
|
|
|
86
105
|
},
|
|
87
106
|
"Execution in new transaction failed"
|
|
88
107
|
);
|
|
108
|
+
if (transactionRollback) {
|
|
109
|
+
throw err;
|
|
110
|
+
} else {
|
|
111
|
+
logger.error("business transaction commited but succeeded|done|failed threw a error!", nestedError);
|
|
112
|
+
}
|
|
89
113
|
}
|
|
90
114
|
} finally {
|
|
91
115
|
logger.debug("Execution in new transaction finished", info);
|
|
@@ -109,7 +133,13 @@ const getAllTenantIds = async () => {
|
|
|
109
133
|
const response = await ssp.get("/tenant");
|
|
110
134
|
return response
|
|
111
135
|
.map((tenant) => tenant.subscribedTenantId ?? tenant.tenant)
|
|
112
|
-
.
|
|
136
|
+
.reduce(async (result, tenantId) => {
|
|
137
|
+
result = await result;
|
|
138
|
+
if (await common.isTenantIdValidCb(TenantIdCheckTypes.eventProcessing, tenantId)) {
|
|
139
|
+
result.push(tenantId);
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}, []);
|
|
113
143
|
};
|
|
114
144
|
|
|
115
145
|
module.exports = {
|
package/src/shared/common.js
CHANGED
|
@@ -4,6 +4,8 @@ const crypto = require("crypto");
|
|
|
4
4
|
|
|
5
5
|
const cds = require("@sap/cds");
|
|
6
6
|
const xssec = require("@sap/xssec");
|
|
7
|
+
const VError = require("verror");
|
|
8
|
+
|
|
7
9
|
const config = require("../config");
|
|
8
10
|
const { TenantIdCheckTypes } = require("../constants");
|
|
9
11
|
|
|
@@ -44,7 +46,22 @@ const limiter = async (limit, payloads, iterator) => {
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
|
-
return
|
|
49
|
+
return promiseAllDone(returnPromises);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const promiseAllDone = async (iterable) => {
|
|
53
|
+
const results = await Promise.allSettled(iterable);
|
|
54
|
+
const rejects = results.filter((entry) => {
|
|
55
|
+
return entry.status === "rejected";
|
|
56
|
+
});
|
|
57
|
+
if (rejects.length === 1) {
|
|
58
|
+
return Promise.reject(rejects[0].reason);
|
|
59
|
+
} else if (rejects.length > 1) {
|
|
60
|
+
return Promise.reject(new VError.MultiError(rejects.map((reject) => reject.reason)));
|
|
61
|
+
}
|
|
62
|
+
return results.map((entry) => {
|
|
63
|
+
return entry.value;
|
|
64
|
+
});
|
|
48
65
|
};
|
|
49
66
|
|
|
50
67
|
const isValidDate = (value) => {
|
|
@@ -115,10 +132,23 @@ const getTokenInfo = async (tenantId) => {
|
|
|
115
132
|
return await tokenInfoCache[tenantId].value;
|
|
116
133
|
};
|
|
117
134
|
|
|
118
|
-
const isTenantIdValidCb = (checkType, tenantId) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
const isTenantIdValidCb = async (checkType, tenantId) => {
|
|
136
|
+
let cb;
|
|
137
|
+
switch (checkType) {
|
|
138
|
+
case TenantIdCheckTypes.getTokenInfo:
|
|
139
|
+
cb = config.tenantIdFilterTokenInfo;
|
|
140
|
+
break;
|
|
141
|
+
case TenantIdCheckTypes.eventProcessing:
|
|
142
|
+
cb = config.tenantIdFilterEventProcessing;
|
|
143
|
+
break;
|
|
144
|
+
default:
|
|
145
|
+
cb = async () => true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
return cb ? await cb(tenantId) : true;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
cds.log(COMPONENT_NAME).error("failed in custom tenant id filter callback. Returning true.", err);
|
|
122
152
|
return true;
|
|
123
153
|
}
|
|
124
154
|
};
|
|
@@ -131,6 +161,7 @@ module.exports = {
|
|
|
131
161
|
hashStringTo32Bit,
|
|
132
162
|
getTokenInfo,
|
|
133
163
|
isTenantIdValidCb,
|
|
164
|
+
promiseAllDone,
|
|
134
165
|
__: {
|
|
135
166
|
clearTokenInfoCache: () => (getTokenInfo._tokenInfoCache = {}),
|
|
136
167
|
},
|