@cap-js-community/event-queue 1.4.1 → 1.4.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 +4 -3
- package/src/EventQueueProcessorBase.js +23 -11
- package/src/config.js +11 -2
- package/src/dbHandler.js +24 -0
- package/src/index.d.ts +138 -0
- package/src/index.js +0 -1
- package/src/initialize.js +7 -1
- package/src/outbox/EventQueueGenericOutboxHandler.js +1 -0
- package/src/processEventQueue.js +1 -0
- package/src/publishEvent.js +10 -4
- package/src/redis/redisPub.js +3 -1
- package/src/redis/redisSub.js +3 -1
- package/src/runner/openEvents.js +1 -0
- package/src/runner/runner.js +7 -2
- package/src/shared/distributedLock.js +15 -1
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
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
|
+
"types": "src/index.d.ts",
|
|
6
7
|
"files": [
|
|
7
8
|
"src",
|
|
8
9
|
"db",
|
|
@@ -50,8 +51,8 @@
|
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@cap-js/hana": "^0.1.0",
|
|
52
53
|
"@cap-js/sqlite": "^1.5.0",
|
|
53
|
-
"@sap/cds": "^7.
|
|
54
|
-
"@sap/cds-dk": "^7.
|
|
54
|
+
"@sap/cds": "^7.8.0",
|
|
55
|
+
"@sap/cds-dk": "^7.8.0",
|
|
55
56
|
"eslint": "^8.56.0",
|
|
56
57
|
"eslint-config-prettier": "^9.1.0",
|
|
57
58
|
"eslint-plugin-jest": "^27.9.0",
|
|
@@ -22,6 +22,7 @@ const SELECT_LIMIT_EVENTS_PER_TICK = 100;
|
|
|
22
22
|
const TRIES_FOR_EXCEEDED_EVENTS = 3;
|
|
23
23
|
const EVENT_START_AFTER_HEADROOM = 3 * 1000;
|
|
24
24
|
const ETAG_CHECK_AFTER_MIN = 10;
|
|
25
|
+
const SUFFIX_PERIODIC = "_PERIODIC";
|
|
25
26
|
|
|
26
27
|
class EventQueueProcessorBase {
|
|
27
28
|
#eventsWithExceededTries = [];
|
|
@@ -279,22 +280,33 @@ class EventQueueProcessorBase {
|
|
|
279
280
|
eventSubType: this.#eventSubType,
|
|
280
281
|
});
|
|
281
282
|
const statusMap = this.commitOnEventLevel || returnMap ? {} : this.__statusMap;
|
|
282
|
-
|
|
283
|
-
queueEntryProcessingStatusTuple.forEach(([id, processingStatus]) =>
|
|
284
|
-
this.#determineAndAddEventStatusToMap(id, processingStatus, statusMap)
|
|
285
|
-
);
|
|
286
|
-
} catch (error) {
|
|
283
|
+
const errorHandler = (error) => {
|
|
287
284
|
queueEntries.forEach((queueEntry) =>
|
|
288
285
|
this.#determineAndAddEventStatusToMap(queueEntry.ID, EventProcessingStatus.Error, statusMap)
|
|
289
286
|
);
|
|
290
287
|
this.logger.error(
|
|
291
288
|
"The supplied status tuple doesn't have the required structure. Setting all entries to error.",
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
289
|
+
...[
|
|
290
|
+
error,
|
|
291
|
+
{
|
|
292
|
+
eventType: this.#eventType,
|
|
293
|
+
eventSubType: this.#eventSubType,
|
|
294
|
+
},
|
|
295
|
+
].filter((a) => a)
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
if (!queueEntryProcessingStatusTuple) {
|
|
300
|
+
errorHandler();
|
|
301
|
+
return statusMap;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
queueEntryProcessingStatusTuple.forEach(([id, processingStatus]) =>
|
|
306
|
+
this.#determineAndAddEventStatusToMap(id, processingStatus, statusMap)
|
|
297
307
|
);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
errorHandler(error);
|
|
298
310
|
}
|
|
299
311
|
return statusMap;
|
|
300
312
|
}
|
|
@@ -1084,7 +1096,7 @@ class EventQueueProcessorBase {
|
|
|
1084
1096
|
}
|
|
1085
1097
|
|
|
1086
1098
|
get eventType() {
|
|
1087
|
-
return this.#eventType;
|
|
1099
|
+
return this.#eventType.replace(SUFFIX_PERIODIC, "");
|
|
1088
1100
|
}
|
|
1089
1101
|
|
|
1090
1102
|
get eventSubType() {
|
package/src/config.js
CHANGED
|
@@ -66,6 +66,7 @@ class Config {
|
|
|
66
66
|
#userId;
|
|
67
67
|
#cleanupLocksAndEventsForDev;
|
|
68
68
|
#redisOptions;
|
|
69
|
+
#insertEventsBeforeCommit;
|
|
69
70
|
static #instance;
|
|
70
71
|
constructor() {
|
|
71
72
|
this.#logger = cds.log(COMPONENT_NAME);
|
|
@@ -241,8 +242,8 @@ class Config {
|
|
|
241
242
|
return key;
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
isEventBlocked(type, subType, tenant) {
|
|
245
|
-
const map = this.#blockedEvents[this.generateKey(type
|
|
245
|
+
isEventBlocked(type, subType, isPeriodicEvent, tenant) {
|
|
246
|
+
const map = this.#blockedEvents[this.generateKey(`${type}${isPeriodicEvent ? SUFFIX_PERIODIC : ""}`, subType)];
|
|
246
247
|
if (!map) {
|
|
247
248
|
return false;
|
|
248
249
|
}
|
|
@@ -494,6 +495,14 @@ class Config {
|
|
|
494
495
|
return this.#redisOptions;
|
|
495
496
|
}
|
|
496
497
|
|
|
498
|
+
set insertEventsBeforeCommit(value) {
|
|
499
|
+
this.#insertEventsBeforeCommit = value;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
get insertEventsBeforeCommit() {
|
|
503
|
+
return this.#insertEventsBeforeCommit;
|
|
504
|
+
}
|
|
505
|
+
|
|
497
506
|
get isMultiTenancy() {
|
|
498
507
|
return !!cds.requires.multitenancy;
|
|
499
508
|
}
|
package/src/dbHandler.js
CHANGED
|
@@ -6,8 +6,17 @@ const { broadcastEvent } = require("./redis/redisPub");
|
|
|
6
6
|
const config = require("./config");
|
|
7
7
|
|
|
8
8
|
const COMPONENT_NAME = "/eventQueue/dbHandler";
|
|
9
|
+
const registeredHandlers = {
|
|
10
|
+
eventQueueDbHandler: false,
|
|
11
|
+
beforeDbHandler: false,
|
|
12
|
+
};
|
|
9
13
|
|
|
10
14
|
const registerEventQueueDbHandler = (dbService) => {
|
|
15
|
+
if (registeredHandlers.eventQueueDbHandler) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
registeredHandlers.eventQueueDbHandler = true;
|
|
11
20
|
const def = dbService.model.definitions[config.tableNameEventQueue];
|
|
12
21
|
dbService.after("CREATE", def, (_, req) => {
|
|
13
22
|
if (req.tx._skipEventQueueBroadcase) {
|
|
@@ -46,6 +55,21 @@ const registerEventQueueDbHandler = (dbService) => {
|
|
|
46
55
|
});
|
|
47
56
|
};
|
|
48
57
|
|
|
58
|
+
const registerBeforeDbHandler = (dbService) => {
|
|
59
|
+
if (!config.insertEventsBeforeCommit || registeredHandlers.beforeDbHandler) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
registeredHandlers.beforeDbHandler = true;
|
|
64
|
+
dbService.before("COMMIT", async (req) => {
|
|
65
|
+
if (req.context._eventQueueEvents?.length) {
|
|
66
|
+
await cds.tx(req).run(INSERT.into(config.tableNameEventQueue).entries(req.context._eventQueueEvents));
|
|
67
|
+
req.context._eventQueueEvents = null;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
49
72
|
module.exports = {
|
|
50
73
|
registerEventQueueDbHandler,
|
|
74
|
+
registerBeforeDbHandler,
|
|
51
75
|
};
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as cds from "@sap/cds";
|
|
2
|
+
|
|
3
|
+
export declare const EventProcessingStatus: {
|
|
4
|
+
Open: 0;
|
|
5
|
+
InProgress: 1;
|
|
6
|
+
Done: 2;
|
|
7
|
+
Error: 3;
|
|
8
|
+
Exceeded: 4;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
declare type EventProcessingStatusKeysType = keyof typeof EventProcessingStatus;
|
|
12
|
+
export declare type EventProcessingStatusType = (typeof EventProcessingStatus)[EventProcessingStatusKeysType];
|
|
13
|
+
|
|
14
|
+
export declare const TransactionMode: {
|
|
15
|
+
isolated: "isolated";
|
|
16
|
+
alwaysCommit: "alwaysCommit";
|
|
17
|
+
alwaysRollback: "alwaysRollback";
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export declare type TransactionModeType = keyof typeof TransactionMode;
|
|
21
|
+
|
|
22
|
+
export declare const Priorities: {
|
|
23
|
+
Low: "low";
|
|
24
|
+
Medium: "medium";
|
|
25
|
+
High: "high";
|
|
26
|
+
VeryHigh: "veryHigh";
|
|
27
|
+
};
|
|
28
|
+
export declare type PrioritiesType = keyof typeof Priorities;
|
|
29
|
+
|
|
30
|
+
interface RedisOptions {
|
|
31
|
+
host: string;
|
|
32
|
+
port: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface InitializeParams {
|
|
36
|
+
configFilePath: string;
|
|
37
|
+
registerAsEventProcessor?: boolean;
|
|
38
|
+
processEventsAfterPublish?: boolean;
|
|
39
|
+
isEventQueueActive?: boolean;
|
|
40
|
+
runInterval?: number;
|
|
41
|
+
disableRedis?: boolean;
|
|
42
|
+
updatePeriodicEvents?: boolean;
|
|
43
|
+
thresholdLoggingEventProcessing?: number;
|
|
44
|
+
useAsCAPOutbox?: boolean;
|
|
45
|
+
userId?: string | null;
|
|
46
|
+
cleanupLocksAndEventsForDev?: boolean;
|
|
47
|
+
redisOptions?: RedisOptions;
|
|
48
|
+
insertEventsBeforeCommit?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type CdsLogger = ReturnType<typeof cds.log>;
|
|
52
|
+
|
|
53
|
+
export function initialize(params: InitializeParams): Promise<void>;
|
|
54
|
+
|
|
55
|
+
type EventConfigType = {
|
|
56
|
+
type: string;
|
|
57
|
+
subType: string;
|
|
58
|
+
priority: string;
|
|
59
|
+
impl: string;
|
|
60
|
+
load: number;
|
|
61
|
+
interval: number;
|
|
62
|
+
internalEvent: boolean;
|
|
63
|
+
isPeriodic: boolean;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Define Status Type
|
|
67
|
+
type Status = 0 | 1 | 2 | 3 | 4;
|
|
68
|
+
|
|
69
|
+
// Define Event Entity Type
|
|
70
|
+
interface EventEntity {
|
|
71
|
+
type: string;
|
|
72
|
+
subType: string;
|
|
73
|
+
referenceEntity?: string;
|
|
74
|
+
referenceEntityKey?: string;
|
|
75
|
+
status: Status;
|
|
76
|
+
payload?: string;
|
|
77
|
+
attempts: number;
|
|
78
|
+
lastAttemptTimestamp?: string;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
startAfter?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface EventEntityPublish {
|
|
84
|
+
type: string;
|
|
85
|
+
subType: string;
|
|
86
|
+
referenceEntity?: string;
|
|
87
|
+
referenceEntityKey?: string;
|
|
88
|
+
payload?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface QueueEntriesPayloadMap {
|
|
92
|
+
[key: string]: {
|
|
93
|
+
queueEntry: EventEntity;
|
|
94
|
+
payload: Object;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export declare class EventQueueProcessorBase {
|
|
99
|
+
constructor(context: cds.EventContext, eventType: string, eventSubType: string, config: EventConfigType);
|
|
100
|
+
|
|
101
|
+
processEvent(
|
|
102
|
+
processContext: cds.EventContext,
|
|
103
|
+
key: string,
|
|
104
|
+
queueEntries: EventEntity[],
|
|
105
|
+
payload: Object
|
|
106
|
+
): Promise<Array<[string, EventProcessingStatusType]>>;
|
|
107
|
+
|
|
108
|
+
processPeriodicEvent(processContext: cds.EventContext, key: string, queueEntry: EventEntity): Promise<undefined>;
|
|
109
|
+
checkEventAndGeneratePayload(queueEntry: EventEntity): Promise<Object>;
|
|
110
|
+
addEventWithPayloadForProcessing(queueEntry: EventEntity, payload: Object): void;
|
|
111
|
+
clusterQueueEntries(queueEntriesWithPayloadMap: Object): void;
|
|
112
|
+
hookForExceededEvents(exceededEvent: EventEntity): Promise<void>;
|
|
113
|
+
clusterQueueEntries(queueEntriesWithPayloadMap: QueueEntriesPayloadMap): void;
|
|
114
|
+
getLastSuccessfulRunTimestamp(): Promise<string | null>;
|
|
115
|
+
getContextForEventProcessing(key: string): cds.EventContext;
|
|
116
|
+
getTxForEventProcessing(key: string): cds.Transaction;
|
|
117
|
+
setShouldRollbackTransaction(key: string): void;
|
|
118
|
+
shouldRollbackTransaction(key: string): boolean;
|
|
119
|
+
|
|
120
|
+
set logger(value: CdsLogger);
|
|
121
|
+
get logger(): CdsLogger;
|
|
122
|
+
get tx(): cds.Transaction;
|
|
123
|
+
get context(): cds.EventContext;
|
|
124
|
+
get isPeriodicEvent(): boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function publishEvent(
|
|
128
|
+
tx: cds.Transaction,
|
|
129
|
+
events: EventEntityPublish[] | EventEntityPublish,
|
|
130
|
+
skipBroadcast?: boolean
|
|
131
|
+
): Promise<any>;
|
|
132
|
+
|
|
133
|
+
export function processEventQueue(
|
|
134
|
+
context: cds.EventContext,
|
|
135
|
+
eventType: string,
|
|
136
|
+
eventSubType: string,
|
|
137
|
+
startTime: Date
|
|
138
|
+
): Promise<any>;
|
package/src/index.js
CHANGED
package/src/initialize.js
CHANGED
|
@@ -15,6 +15,7 @@ const redis = require("./shared/redis");
|
|
|
15
15
|
const eventQueueAsOutbox = require("./outbox/eventQueueAsOutbox");
|
|
16
16
|
const { getAllTenantIds } = require("./shared/cdsHelper");
|
|
17
17
|
const { EventProcessingStatus } = require("./constants");
|
|
18
|
+
const distributedLock = require("./shared/distributedLock");
|
|
18
19
|
|
|
19
20
|
const readFileAsync = promisify(fs.readFile);
|
|
20
21
|
|
|
@@ -34,6 +35,7 @@ const CONFIG_VARS = [
|
|
|
34
35
|
["userId", null],
|
|
35
36
|
["cleanupLocksAndEventsForDev", false],
|
|
36
37
|
["redisOptions", {}],
|
|
38
|
+
["insertEventsBeforeCommit", false],
|
|
37
39
|
];
|
|
38
40
|
|
|
39
41
|
const initialize = async ({
|
|
@@ -49,6 +51,7 @@ const initialize = async ({
|
|
|
49
51
|
userId,
|
|
50
52
|
cleanupLocksAndEventsForDev,
|
|
51
53
|
redisOptions,
|
|
54
|
+
insertEventsBeforeCommit,
|
|
52
55
|
} = {}) => {
|
|
53
56
|
if (config.initialized) {
|
|
54
57
|
return;
|
|
@@ -67,7 +70,8 @@ const initialize = async ({
|
|
|
67
70
|
useAsCAPOutbox,
|
|
68
71
|
userId,
|
|
69
72
|
cleanupLocksAndEventsForDev,
|
|
70
|
-
redisOptions
|
|
73
|
+
redisOptions,
|
|
74
|
+
insertEventsBeforeCommit
|
|
71
75
|
);
|
|
72
76
|
|
|
73
77
|
const logger = cds.log(COMPONENT);
|
|
@@ -77,6 +81,7 @@ const initialize = async ({
|
|
|
77
81
|
cds.on("connect", (service) => {
|
|
78
82
|
if (service.name === "db") {
|
|
79
83
|
config.processEventsAfterPublish && dbHandler.registerEventQueueDbHandler(service);
|
|
84
|
+
config.insertEventsBeforeCommit && dbHandler.registerBeforeDbHandler(service);
|
|
80
85
|
config.cleanupLocksAndEventsForDev && registerCleanupForDevDb().catch(() => {});
|
|
81
86
|
initFinished.then(registerEventProcessors);
|
|
82
87
|
}
|
|
@@ -166,6 +171,7 @@ const mixConfigVarsWithEnv = (...args) => {
|
|
|
166
171
|
|
|
167
172
|
const registerCdsShutdown = () => {
|
|
168
173
|
cds.on("shutdown", async () => {
|
|
174
|
+
await distributedLock.shutdownHandler();
|
|
169
175
|
await Promise.allSettled([redis.closeMainClient(), closeSubscribeClient()]);
|
|
170
176
|
});
|
|
171
177
|
};
|
|
@@ -23,6 +23,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
23
23
|
delete msg._fromSend;
|
|
24
24
|
delete msg.contextUser;
|
|
25
25
|
processContext.user = new cds.User.Privileged(userId);
|
|
26
|
+
processContext._eventQueue = { processor: this, key, queueEntries, payload };
|
|
26
27
|
await cds.unboxed(service).tx(processContext)[invocationFn](msg);
|
|
27
28
|
} catch (err) {
|
|
28
29
|
status = EventProcessingStatus.Error;
|
package/src/processEventQueue.js
CHANGED
package/src/publishEvent.js
CHANGED
|
@@ -45,10 +45,16 @@ const publishEvent = async (tx, events, skipBroadcast = false) => {
|
|
|
45
45
|
throw EventQueueError.manuelPeriodicEventInsert(type, subType);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
|
|
49
|
+
if (config.insertEventsBeforeCommit) {
|
|
50
|
+
tx.context._eventQueueEvents ??= [];
|
|
51
|
+
tx.context._eventQueueEvents = tx.context._eventQueueEvents.concat(events);
|
|
52
|
+
} else {
|
|
53
|
+
tx._skipEventQueueBroadcase = skipBroadcast;
|
|
54
|
+
const result = await tx.run(INSERT.into(config.tableNameEventQueue).entries(eventsForProcessing));
|
|
55
|
+
tx._skipEventQueueBroadcase = false;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
52
58
|
};
|
|
53
59
|
|
|
54
60
|
module.exports = {
|
package/src/redis/redisPub.js
CHANGED
|
@@ -29,7 +29,9 @@ const broadcastEvent = async (tenantId, events) => {
|
|
|
29
29
|
if (config.registerAsEventProcessor) {
|
|
30
30
|
let context = {};
|
|
31
31
|
if (tenantId) {
|
|
32
|
-
const user =
|
|
32
|
+
const user = await cds.tx({ tenant: tenantId }, async () => {
|
|
33
|
+
return new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
|
|
34
|
+
});
|
|
33
35
|
context = {
|
|
34
36
|
tenant: tenantId,
|
|
35
37
|
user,
|
package/src/redis/redisSub.js
CHANGED
|
@@ -35,7 +35,9 @@ const _messageHandlerProcessEvents = async (messageData) => {
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const user =
|
|
38
|
+
const user = await cds.tx({ tenant: tenantId }, async () => {
|
|
39
|
+
return new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
|
|
40
|
+
});
|
|
39
41
|
const tenantContext = {
|
|
40
42
|
tenant: tenantId,
|
|
41
43
|
user,
|
package/src/runner/openEvents.js
CHANGED
|
@@ -31,6 +31,7 @@ const getOpenQueueEntries = async (tx) => {
|
|
|
31
31
|
for (const { type, subType } of entries) {
|
|
32
32
|
if (type.startsWith("CAP_OUTBOX")) {
|
|
33
33
|
if (cds.requires[subType]) {
|
|
34
|
+
await cds.connect.to(subType).catch(() => {});
|
|
34
35
|
result.push({ type, subType });
|
|
35
36
|
} else {
|
|
36
37
|
const service = await cds.connect.to(subType).catch(() => {});
|
package/src/runner/runner.js
CHANGED
|
@@ -111,6 +111,7 @@ const _executeEventsAllTenantsRedis = async (tenantIds) => {
|
|
|
111
111
|
|
|
112
112
|
for (const tenantId of tenantIds) {
|
|
113
113
|
await cds.tx({ tenant: tenantId }, async (tx) => {
|
|
114
|
+
tx.context.user = new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
|
|
114
115
|
const entries = await openEvents.getOpenQueueEntries(tx);
|
|
115
116
|
logger.info("broadcasting events for run", {
|
|
116
117
|
tenantId,
|
|
@@ -136,7 +137,9 @@ const _executeEventsAllTenants = async (tenantIds, runId) => {
|
|
|
136
137
|
const promises = [];
|
|
137
138
|
|
|
138
139
|
for (const tenantId of tenantIds) {
|
|
139
|
-
const user =
|
|
140
|
+
const user = await cds.tx({ tenant: tenantId }, async () => {
|
|
141
|
+
return new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
|
|
142
|
+
});
|
|
140
143
|
const tenantContext = {
|
|
141
144
|
tenant: tenantId,
|
|
142
145
|
user,
|
|
@@ -180,7 +183,9 @@ const _executeEventsAllTenants = async (tenantIds, runId) => {
|
|
|
180
183
|
const _executePeriodicEventsAllTenants = async (tenantIds) => {
|
|
181
184
|
for (const tenantId of tenantIds) {
|
|
182
185
|
try {
|
|
183
|
-
const user =
|
|
186
|
+
const user = await cds.tx({ tenant: tenantId }, async () => {
|
|
187
|
+
return new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
|
|
188
|
+
});
|
|
184
189
|
const tenantContext = {
|
|
185
190
|
tenant: tenantId,
|
|
186
191
|
user,
|
|
@@ -6,6 +6,10 @@ const cdsHelper = require("./cdsHelper");
|
|
|
6
6
|
|
|
7
7
|
const KEY_PREFIX = "EVENT_QUEUE";
|
|
8
8
|
|
|
9
|
+
const existingLocks = {};
|
|
10
|
+
|
|
11
|
+
const REDIS_COMMAND_OK = "OK";
|
|
12
|
+
|
|
9
13
|
const acquireLock = async (context, key, { tenantScoped = true, expiryTime = config.globalTxTimeout } = {}) => {
|
|
10
14
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
11
15
|
if (config.redisEnabled) {
|
|
@@ -59,7 +63,11 @@ const _acquireLockRedis = async (context, fullKey, expiryTime, { value = "true",
|
|
|
59
63
|
PX: expiryTime,
|
|
60
64
|
...(overrideValue ? null : { NX: true }),
|
|
61
65
|
});
|
|
62
|
-
|
|
66
|
+
const isOk = result === REDIS_COMMAND_OK;
|
|
67
|
+
if (isOk) {
|
|
68
|
+
existingLocks[fullKey] = 1;
|
|
69
|
+
}
|
|
70
|
+
return isOk;
|
|
63
71
|
};
|
|
64
72
|
|
|
65
73
|
const _checkLockExistsRedis = async (context, fullKey) => {
|
|
@@ -78,6 +86,7 @@ const _checkLockExistsDb = async (context, fullKey) => {
|
|
|
78
86
|
const _releaseLockRedis = async (context, fullKey) => {
|
|
79
87
|
const client = await redis.createMainClientAndConnect(config.redisOptions);
|
|
80
88
|
await client.del(fullKey);
|
|
89
|
+
delete existingLocks[fullKey];
|
|
81
90
|
};
|
|
82
91
|
|
|
83
92
|
const _releaseLockDb = async (context, fullKey) => {
|
|
@@ -133,9 +142,14 @@ const _generateKey = (context, tenantScoped, key) => {
|
|
|
133
142
|
return `${KEY_PREFIX}_${keyParts.join("##")}`;
|
|
134
143
|
};
|
|
135
144
|
|
|
145
|
+
const shutdownHandler = async () => {
|
|
146
|
+
await Promise.allSettled(Object.keys(existingLocks).map((key) => _releaseLockRedis(null, key)));
|
|
147
|
+
};
|
|
148
|
+
|
|
136
149
|
module.exports = {
|
|
137
150
|
acquireLock,
|
|
138
151
|
releaseLock,
|
|
139
152
|
checkLockExistsAndReturnValue,
|
|
140
153
|
setValueWithExpire,
|
|
154
|
+
shutdownHandler,
|
|
141
155
|
};
|