@cap-js-community/event-queue 1.2.4 → 1.2.6
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/README.md +0 -1
- package/package.json +10 -10
- package/src/EventQueueError.js +15 -0
- package/src/EventQueueProcessorBase.js +47 -1
- package/src/config.js +12 -0
- package/src/initialize.js +49 -18
- package/src/runner.js +94 -83
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "An event queue that enables secure transactional processing of asynchronous and periodic events, featuring instant event processing with Redis Pub/Sub and load distribution across all application instances.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -49,15 +49,15 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@sap/cds": "^7.5.3",
|
|
51
51
|
"@sap/cds-dk": "^7.5.1",
|
|
52
|
-
"eslint": "8.56.0",
|
|
53
|
-
"eslint-config-prettier": "9.1.0",
|
|
54
|
-
"eslint-plugin-jest": "27.6.3",
|
|
55
|
-
"eslint-plugin-node": "11.1.0",
|
|
56
|
-
"express": "4.18.2",
|
|
57
|
-
"hdb": "0.19.7",
|
|
58
|
-
"jest": "29.7.0",
|
|
59
|
-
"prettier": "2.8.8",
|
|
60
|
-
"sqlite3": "5.1.7"
|
|
52
|
+
"eslint": "^8.56.0",
|
|
53
|
+
"eslint-config-prettier": "^9.1.0",
|
|
54
|
+
"eslint-plugin-jest": "^27.6.3",
|
|
55
|
+
"eslint-plugin-node": "^11.1.0",
|
|
56
|
+
"express": "^4.18.2",
|
|
57
|
+
"hdb": "^0.19.7",
|
|
58
|
+
"jest": "^29.7.0",
|
|
59
|
+
"prettier": "^2.8.8",
|
|
60
|
+
"sqlite3": "^5.1.7"
|
|
61
61
|
},
|
|
62
62
|
"homepage": "https://cap-js-community.github.io/event-queue/",
|
|
63
63
|
"repository": {
|
package/src/EventQueueError.js
CHANGED
|
@@ -18,6 +18,7 @@ const ERROR_CODES = {
|
|
|
18
18
|
NO_MANUEL_INSERT_OF_PERIODIC: "NO_MANUEL_INSERT_OF_PERIODIC",
|
|
19
19
|
LOAD_HIGHER_THAN_LIMIT: "LOAD_HIGHER_THAN_LIMIT",
|
|
20
20
|
SCHEMA_TENANT_MISMATCH: "SCHEMA_TENANT_MISMATCH",
|
|
21
|
+
GLOBAL_CDS_CONTEXT_MISSMATCH: "GLOBAL_CDS_CONTEXT_MISSMATCH",
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
const ERROR_CODES_META = {
|
|
@@ -67,6 +68,9 @@ const ERROR_CODES_META = {
|
|
|
67
68
|
[ERROR_CODES.SCHEMA_TENANT_MISMATCH]: {
|
|
68
69
|
message: "The db client associated to the tenant context does not match! Processing will be skipped.",
|
|
69
70
|
},
|
|
71
|
+
[ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH]: {
|
|
72
|
+
message: "The global cds context does not match the local cds context.",
|
|
73
|
+
},
|
|
70
74
|
};
|
|
71
75
|
|
|
72
76
|
class EventQueueError extends VError {
|
|
@@ -235,6 +239,17 @@ class EventQueueError extends VError {
|
|
|
235
239
|
message
|
|
236
240
|
);
|
|
237
241
|
}
|
|
242
|
+
|
|
243
|
+
static globalCdsContextNotMatchingLocal(globalProperties, localProperties) {
|
|
244
|
+
const { message } = ERROR_CODES_META[ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH];
|
|
245
|
+
return new EventQueueError(
|
|
246
|
+
{
|
|
247
|
+
name: ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH,
|
|
248
|
+
info: { globalProperties, localProperties },
|
|
249
|
+
},
|
|
250
|
+
message
|
|
251
|
+
);
|
|
252
|
+
}
|
|
238
253
|
}
|
|
239
254
|
|
|
240
255
|
module.exports = EventQueueError;
|
|
@@ -70,6 +70,8 @@ class EventQueueProcessorBase {
|
|
|
70
70
|
this.__txMap = {};
|
|
71
71
|
this.__txRollback = {};
|
|
72
72
|
this.__queueEntries = [];
|
|
73
|
+
|
|
74
|
+
this.#checkGlobalContextToLocalContext();
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/**
|
|
@@ -533,7 +535,9 @@ class EventQueueProcessorBase {
|
|
|
533
535
|
async getQueueEntriesAndSetToInProgress() {
|
|
534
536
|
let result = [];
|
|
535
537
|
const refDateStartAfter = new Date(Date.now() + this.#config.runInterval * 1.2);
|
|
538
|
+
this.#checkGlobalContextToLocalContext();
|
|
536
539
|
await executeInNewTransaction(this.__baseContext, "eventQueue-getQueueEntriesAndSetToInProgress", async (tx) => {
|
|
540
|
+
this.#checkGlobalContextToLocalContext();
|
|
537
541
|
await this.checkTxConsistency(tx);
|
|
538
542
|
const entries = await tx.run(
|
|
539
543
|
SELECT.from(this.#config.tableNameEventQueue)
|
|
@@ -692,7 +696,7 @@ class EventQueueProcessorBase {
|
|
|
692
696
|
} catch (err) {
|
|
693
697
|
errorHandler(err);
|
|
694
698
|
}
|
|
695
|
-
if (txSchema !== serviceManagerSchema) {
|
|
699
|
+
if (serviceManagerSchema && txSchema !== serviceManagerSchema) {
|
|
696
700
|
const err = EventQueueError.dbClientSchemaMismatch(tx.context.tenant, txSchema, serviceManagerSchema);
|
|
697
701
|
errorHandler(err);
|
|
698
702
|
throw err;
|
|
@@ -724,6 +728,48 @@ class EventQueueProcessorBase {
|
|
|
724
728
|
return entry.lastAttemptsTs;
|
|
725
729
|
}
|
|
726
730
|
|
|
731
|
+
#checkGlobalContextToLocalContext() {
|
|
732
|
+
if (!this.#config.enableTxConsistencyCheck) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (this.__context.tenant !== cds.context.tenant) {
|
|
736
|
+
throw EventQueueError.globalCdsContextNotMatchingLocal(
|
|
737
|
+
JSON.stringify(
|
|
738
|
+
{
|
|
739
|
+
correlationId: cds.context.id,
|
|
740
|
+
tenantId: cds.context.tenant,
|
|
741
|
+
timestamp: cds.context.timestamp,
|
|
742
|
+
base: JSON.stringify(
|
|
743
|
+
{
|
|
744
|
+
correlationId: cds.context.context?.id,
|
|
745
|
+
tenantId: cds.context.context?.tenant,
|
|
746
|
+
timestamp: cds.context.context?.timestamp,
|
|
747
|
+
},
|
|
748
|
+
null,
|
|
749
|
+
2
|
|
750
|
+
),
|
|
751
|
+
},
|
|
752
|
+
null,
|
|
753
|
+
2
|
|
754
|
+
),
|
|
755
|
+
JSON.stringify(
|
|
756
|
+
{
|
|
757
|
+
correlationId: this.__context.id,
|
|
758
|
+
tenantId: this.__context.tenant,
|
|
759
|
+
timestamp: this.__context.timestamp,
|
|
760
|
+
base: JSON.stringify({
|
|
761
|
+
correlationId: this.__context.context?.id,
|
|
762
|
+
tenantId: this.__context.context?.tenant,
|
|
763
|
+
timestamp: this.__context.context?.timestamp,
|
|
764
|
+
}),
|
|
765
|
+
},
|
|
766
|
+
null,
|
|
767
|
+
2
|
|
768
|
+
)
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
727
773
|
#handleDelayedEvents(delayedEvents) {
|
|
728
774
|
for (const delayedEvent of delayedEvents) {
|
|
729
775
|
this.#eventSchedulerInstance.scheduleEvent(
|
package/src/config.js
CHANGED
|
@@ -57,6 +57,7 @@ class Config {
|
|
|
57
57
|
#useAsCAPOutbox;
|
|
58
58
|
#userId;
|
|
59
59
|
#enableTxConsistencyCheck;
|
|
60
|
+
#cleanupLocksAndEventsForDev;
|
|
60
61
|
static #instance;
|
|
61
62
|
constructor() {
|
|
62
63
|
this.#logger = cds.log(COMPONENT_NAME);
|
|
@@ -340,6 +341,9 @@ class Config {
|
|
|
340
341
|
}
|
|
341
342
|
|
|
342
343
|
set runInterval(value) {
|
|
344
|
+
if (!Number.isInteger(value) || value <= 10 * 1000) {
|
|
345
|
+
throw EventQueueError.invalidInterval();
|
|
346
|
+
}
|
|
343
347
|
this.#runInterval = value;
|
|
344
348
|
}
|
|
345
349
|
|
|
@@ -471,6 +475,14 @@ class Config {
|
|
|
471
475
|
return this.#enableTxConsistencyCheck;
|
|
472
476
|
}
|
|
473
477
|
|
|
478
|
+
set cleanupLocksAndEventsForDev(value) {
|
|
479
|
+
this.#cleanupLocksAndEventsForDev = value;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
get cleanupLocksAndEventsForDev() {
|
|
483
|
+
return this.#cleanupLocksAndEventsForDev;
|
|
484
|
+
}
|
|
485
|
+
|
|
474
486
|
get isMultiTenancy() {
|
|
475
487
|
return !!cds.requires.multitenancy;
|
|
476
488
|
}
|
package/src/initialize.js
CHANGED
|
@@ -14,6 +14,8 @@ const config = require("./config");
|
|
|
14
14
|
const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redisPubSub");
|
|
15
15
|
const { closeMainClient } = require("./shared/redis");
|
|
16
16
|
const eventQueueAsOutbox = require("./outbox/eventQueueAsOutbox");
|
|
17
|
+
const { getAllTenantIds } = require("./shared/cdsHelper");
|
|
18
|
+
const { EventProcessingStatus } = require("./constants");
|
|
17
19
|
|
|
18
20
|
const readFileAsync = promisify(fs.readFile);
|
|
19
21
|
|
|
@@ -38,6 +40,7 @@ const CONFIG_VARS = [
|
|
|
38
40
|
["useAsCAPOutbox", false],
|
|
39
41
|
["userId", null],
|
|
40
42
|
["enableTxConsistencyCheck", false],
|
|
43
|
+
["registerCleanupForDev", true],
|
|
41
44
|
];
|
|
42
45
|
|
|
43
46
|
const initialize = async ({
|
|
@@ -55,11 +58,8 @@ const initialize = async ({
|
|
|
55
58
|
useAsCAPOutbox,
|
|
56
59
|
userId,
|
|
57
60
|
enableTxConsistencyCheck,
|
|
61
|
+
cleanupLocksAndEventsForDev,
|
|
58
62
|
} = {}) => {
|
|
59
|
-
// TODO: initialize check:
|
|
60
|
-
// - content of yaml check
|
|
61
|
-
// - betweenRuns
|
|
62
|
-
|
|
63
63
|
if (config.initialized) {
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -79,7 +79,8 @@ const initialize = async ({
|
|
|
79
79
|
thresholdLoggingEventProcessing,
|
|
80
80
|
useAsCAPOutbox,
|
|
81
81
|
userId,
|
|
82
|
-
enableTxConsistencyCheck
|
|
82
|
+
enableTxConsistencyCheck,
|
|
83
|
+
cleanupLocksAndEventsForDev
|
|
83
84
|
);
|
|
84
85
|
|
|
85
86
|
const logger = cds.log(COMPONENT);
|
|
@@ -90,6 +91,7 @@ const initialize = async ({
|
|
|
90
91
|
cds.on("connect", (service) => {
|
|
91
92
|
if (service.name === "db") {
|
|
92
93
|
dbHandler.registerEventQueueDbHandler(service);
|
|
94
|
+
config.cleanupLocksAndEventsForDev && registerCleanupForDevDb().catch(() => {});
|
|
93
95
|
}
|
|
94
96
|
});
|
|
95
97
|
}
|
|
@@ -108,20 +110,28 @@ const initialize = async ({
|
|
|
108
110
|
};
|
|
109
111
|
|
|
110
112
|
const readConfigFromFile = async (configFilepath) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
try {
|
|
114
|
+
const fileData = await readFileAsync(configFilepath);
|
|
115
|
+
if (/\.ya?ml$/i.test(configFilepath)) {
|
|
116
|
+
return yaml.parse(fileData.toString());
|
|
117
|
+
}
|
|
118
|
+
if (/\.json$/i.test(configFilepath)) {
|
|
119
|
+
return JSON.parse(fileData.toString());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
throw new VError(
|
|
123
|
+
{
|
|
124
|
+
name: VERROR_CLUSTER_NAME,
|
|
125
|
+
info: { configFilepath },
|
|
126
|
+
},
|
|
127
|
+
"configFilepath with unsupported extension, allowed extensions are .yaml and .json"
|
|
128
|
+
);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (config.useAsCAPOutbox) {
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
117
134
|
}
|
|
118
|
-
throw new VError(
|
|
119
|
-
{
|
|
120
|
-
name: VERROR_CLUSTER_NAME,
|
|
121
|
-
info: { configFilepath },
|
|
122
|
-
},
|
|
123
|
-
"configFilepath with unsupported extension, allowed extensions are .yaml and .json"
|
|
124
|
-
);
|
|
125
135
|
};
|
|
126
136
|
|
|
127
137
|
const registerEventProcessors = () => {
|
|
@@ -149,9 +159,11 @@ const monkeyPatchCAPOutbox = () => {
|
|
|
149
159
|
if (config.useAsCAPOutbox) {
|
|
150
160
|
Object.defineProperty(cds, "outboxed", {
|
|
151
161
|
get: () => eventQueueAsOutbox.outboxed,
|
|
162
|
+
configurable: true,
|
|
152
163
|
});
|
|
153
164
|
Object.defineProperty(cds, "unboxed", {
|
|
154
165
|
get: () => eventQueueAsOutbox.unboxed,
|
|
166
|
+
configurable: true,
|
|
155
167
|
});
|
|
156
168
|
}
|
|
157
169
|
};
|
|
@@ -213,6 +225,25 @@ const registerCdsShutdown = () => {
|
|
|
213
225
|
});
|
|
214
226
|
};
|
|
215
227
|
|
|
228
|
+
const registerCleanupForDevDb = async () => {
|
|
229
|
+
const profile = cds.env.profiles.find((profile) => profile === "development");
|
|
230
|
+
if (!profile) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const tenantIds = await getAllTenantIds();
|
|
235
|
+
for (const tenantId of tenantIds) {
|
|
236
|
+
await cds.tx({ tenant: tenantId }, async (tx) => {
|
|
237
|
+
await tx.run(DELETE.from(config.tableNameEventLock));
|
|
238
|
+
await tx.run(
|
|
239
|
+
UPDATE.entity(config.tableNameEventQueue).where({ status: EventProcessingStatus.InProgress }).set({
|
|
240
|
+
status: EventProcessingStatus.Error,
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
216
247
|
module.exports = {
|
|
217
248
|
initialize,
|
|
218
249
|
};
|
package/src/runner.js
CHANGED
|
@@ -26,9 +26,9 @@ let singleRunDone;
|
|
|
26
26
|
|
|
27
27
|
const singleTenant = () => _scheduleFunction(_checkPeriodicEventsSingleTenant, _singleTenantDb);
|
|
28
28
|
|
|
29
|
-
const multiTenancyDb = () => _scheduleFunction(
|
|
29
|
+
const multiTenancyDb = () => _scheduleFunction(async () => {}, _multiTenancyDb);
|
|
30
30
|
|
|
31
|
-
const multiTenancyRedis = () => _scheduleFunction(
|
|
31
|
+
const multiTenancyRedis = () => _scheduleFunction(async () => {}, _multiTenancyRedis);
|
|
32
32
|
|
|
33
33
|
const _scheduleFunction = async (singleRunFn, periodicFn) => {
|
|
34
34
|
const logger = cds.log(COMPONENT_NAME);
|
|
@@ -69,7 +69,7 @@ const _multiTenancyRedis = async () => {
|
|
|
69
69
|
const emptyContext = new cds.EventContext({});
|
|
70
70
|
logger.info("executing event queue run for multi instance and tenant");
|
|
71
71
|
const tenantIds = await cdsHelper.getAllTenantIds();
|
|
72
|
-
|
|
72
|
+
await _checkPeriodicEventUpdate(tenantIds);
|
|
73
73
|
|
|
74
74
|
const runId = await _acquireRunId(emptyContext);
|
|
75
75
|
|
|
@@ -78,22 +78,21 @@ const _multiTenancyRedis = async () => {
|
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
return _executeEventsAllTenants(tenantIds, runId);
|
|
81
|
+
return await _executeEventsAllTenants(tenantIds, runId);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
const
|
|
84
|
+
const _checkPeriodicEventUpdate = async (tenantIds) => {
|
|
85
85
|
const hash = hashStringTo32Bit(JSON.stringify(tenantIds));
|
|
86
86
|
if (!tenantIdHash) {
|
|
87
87
|
tenantIdHash = hash;
|
|
88
|
-
_multiTenancyPeriodicEvents().catch((err) => {
|
|
88
|
+
return await _multiTenancyPeriodicEvents(tenantIds).catch((err) => {
|
|
89
89
|
cds.log(COMPONENT_NAME).error("Error during triggering updating periodic events!", err);
|
|
90
90
|
});
|
|
91
|
-
return;
|
|
92
91
|
}
|
|
93
92
|
if (tenantIdHash && tenantIdHash !== hash) {
|
|
94
93
|
tenantIdHash = hash;
|
|
95
94
|
cds.log(COMPONENT_NAME).info("tenant id hash changed, triggering updating periodic events!");
|
|
96
|
-
_multiTenancyPeriodicEvents().catch((err) => {
|
|
95
|
+
return await _multiTenancyPeriodicEvents(tenantIds).catch((err) => {
|
|
97
96
|
cds.log(COMPONENT_NAME).error("Error during triggering updating periodic events!", err);
|
|
98
97
|
});
|
|
99
98
|
}
|
|
@@ -108,91 +107,103 @@ const _executeEventsAllTenants = (tenantIds, runId) => {
|
|
|
108
107
|
return result;
|
|
109
108
|
}, []);
|
|
110
109
|
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
return Promise.allSettled(
|
|
111
|
+
product.map(async ([tenantId, event]) => {
|
|
112
|
+
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
113
|
+
const user = new cds.User.Privileged(config.userId);
|
|
114
|
+
const tenantContext = {
|
|
115
|
+
tenant: tenantId,
|
|
116
|
+
user,
|
|
117
|
+
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
118
|
+
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
119
|
+
};
|
|
121
120
|
const label = `${event.type}_${event.subType}`;
|
|
122
121
|
return await WorkerQueue.instance.addToQueue(event.load, label, async () => {
|
|
122
|
+
return await cds.tx(tenantContext, async ({ context }) => {
|
|
123
|
+
try {
|
|
124
|
+
const lockId = `${runId}_${label}`;
|
|
125
|
+
const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
|
|
126
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
127
|
+
});
|
|
128
|
+
if (!couldAcquireLock) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
await runEventCombinationForTenant(context, event.type, event.subType, true);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
134
|
+
tenantId,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const _executePeriodicEventsAllTenants = async (tenantIds, runId) => {
|
|
144
|
+
return await Promise.allSettled(
|
|
145
|
+
tenantIds.map(async (tenantId) => {
|
|
146
|
+
const label = `UPDATE_PERIODIC_EVENTS_${tenantId}`;
|
|
147
|
+
return await WorkerQueue.instance.addToQueue(1, label, async () => {
|
|
123
148
|
try {
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
149
|
+
const subdomain = await getSubdomainForTenantId(tenantId);
|
|
150
|
+
const user = new cds.User.Privileged(config.userId);
|
|
151
|
+
const tenantContext = {
|
|
152
|
+
tenant: tenantId,
|
|
153
|
+
user,
|
|
154
|
+
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
155
|
+
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return await cds.tx(tenantContext, async ({ context }) => {
|
|
159
|
+
const couldAcquireLock = await distributedLock.acquireLock(context, runId, {
|
|
160
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
161
|
+
});
|
|
162
|
+
if (!couldAcquireLock) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await _checkPeriodicEventsSingleTenant(context);
|
|
127
166
|
});
|
|
128
|
-
if (!couldAcquireLock) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
await runEventCombinationForTenant(context, event.type, event.subType, true);
|
|
132
167
|
} catch (err) {
|
|
133
168
|
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
134
169
|
tenantId,
|
|
135
170
|
});
|
|
136
171
|
}
|
|
137
172
|
});
|
|
138
|
-
})
|
|
139
|
-
|
|
173
|
+
})
|
|
174
|
+
);
|
|
140
175
|
};
|
|
141
176
|
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
153
|
-
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
154
|
-
};
|
|
155
|
-
|
|
177
|
+
const _singleTenantDb = async (tenantId) => {
|
|
178
|
+
return Promise.allSettled(
|
|
179
|
+
eventQueueConfig.allEvents.map(async (event) => {
|
|
180
|
+
const label = `${event.type}_${event.subType}`;
|
|
181
|
+
const user = new cds.User.Privileged(config.userId);
|
|
182
|
+
const tenantContext = {
|
|
183
|
+
tenant: tenantId,
|
|
184
|
+
user,
|
|
185
|
+
};
|
|
186
|
+
return await WorkerQueue.instance.addToQueue(event.load, label, async () => {
|
|
156
187
|
return await cds.tx(tenantContext, async ({ context }) => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
188
|
+
try {
|
|
189
|
+
const lockId = `${EVENT_QUEUE_RUN_ID}_${label}`;
|
|
190
|
+
const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
|
|
191
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
192
|
+
});
|
|
193
|
+
if (!couldAcquireLock) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
await runEventCombinationForTenant(context, event.type, event.subType, true);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
199
|
+
tenantId,
|
|
200
|
+
redisEnabled: eventQueueConfig.redisEnabled,
|
|
201
|
+
});
|
|
162
202
|
}
|
|
163
|
-
await _checkPeriodicEventsSingleTenant(context);
|
|
164
203
|
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const _singleTenantDb = async (tenantId) => {
|
|
175
|
-
return eventQueueConfig.allEvents.map((event) => {
|
|
176
|
-
const label = `${event.type}_${event.subType}`;
|
|
177
|
-
return WorkerQueue.instance.addToQueue(event.load, label, async () => {
|
|
178
|
-
try {
|
|
179
|
-
const context = new cds.EventContext({ tenant: tenantId });
|
|
180
|
-
const lockId = `${EVENT_QUEUE_RUN_ID}_${label}`;
|
|
181
|
-
const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
|
|
182
|
-
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
183
|
-
});
|
|
184
|
-
if (!couldAcquireLock) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
await runEventCombinationForTenant(context, event.type, event.subType, true);
|
|
188
|
-
} catch (err) {
|
|
189
|
-
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
190
|
-
tenantId,
|
|
191
|
-
redisEnabled: eventQueueConfig.redisEnabled,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
});
|
|
204
|
+
});
|
|
205
|
+
})
|
|
206
|
+
);
|
|
196
207
|
};
|
|
197
208
|
|
|
198
209
|
const _acquireRunId = async (context) => {
|
|
@@ -280,19 +291,19 @@ const _multiTenancyDb = async () => {
|
|
|
280
291
|
try {
|
|
281
292
|
logger.info("executing event queue run for single instance and multi tenant");
|
|
282
293
|
const tenantIds = await cdsHelper.getAllTenantIds();
|
|
283
|
-
|
|
284
|
-
return _executeEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
|
|
294
|
+
await _checkPeriodicEventUpdate(tenantIds);
|
|
295
|
+
return await _executeEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
|
|
285
296
|
} catch (err) {
|
|
286
297
|
logger.error("Couldn't fetch tenant ids for event queue processing! Next try after defined interval.", err);
|
|
287
298
|
}
|
|
288
299
|
};
|
|
289
300
|
|
|
290
|
-
const _multiTenancyPeriodicEvents = async () => {
|
|
301
|
+
const _multiTenancyPeriodicEvents = async (tenantIds) => {
|
|
291
302
|
const logger = cds.log(COMPONENT_NAME);
|
|
292
303
|
try {
|
|
293
304
|
logger.info("executing event queue update periodic events");
|
|
294
|
-
|
|
295
|
-
_executePeriodicEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_PERIODIC_EVENT);
|
|
305
|
+
tenantIds = tenantIds ?? (await cdsHelper.getAllTenantIds());
|
|
306
|
+
return await _executePeriodicEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_PERIODIC_EVENT);
|
|
296
307
|
} catch (err) {
|
|
297
308
|
logger.error("Couldn't fetch tenant ids for updating periodic event processing!", err);
|
|
298
309
|
}
|