@cap-js-community/event-queue 1.7.3 → 1.7.4
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/db/Event.cds +1 -0
- package/package.json +4 -4
- package/src/config.js +9 -0
- package/src/constants.js +5 -0
- package/src/initialize.js +2 -0
- package/src/outbox/EventQueueGenericOutboxHandler.js +1 -1
- package/src/redis/redisPub.js +1 -1
- package/src/redis/redisSub.js +3 -3
- package/src/runner/openEvents.js +19 -4
- package/src/runner/runner.js +7 -7
- package/src/shared/SetIntervalDriftSafe.js +0 -8
- package/src/shared/cdsHelper.js +3 -4
- package/src/shared/common.js +35 -24
- package/src/shared/distributedLock.js +5 -2
- package/src/shared/eventScheduler.js +1 -10
package/db/Event.cds
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
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",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"cron-parser": "^4.9.0",
|
|
48
48
|
"redis": "^4.7.0",
|
|
49
49
|
"verror": "^1.10.1",
|
|
50
|
-
"yaml": "^2.
|
|
50
|
+
"yaml": "^2.6.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@cap-js/hana": "^1.3.0",
|
|
54
54
|
"@cap-js/sqlite": "^1.7.3",
|
|
55
|
-
"@sap/cds": "^8.
|
|
56
|
-
"@sap/cds-dk": "^8.
|
|
55
|
+
"@sap/cds": "^8.4.2",
|
|
56
|
+
"@sap/cds-dk": "^8.4.2",
|
|
57
57
|
"eslint": "^8.57.0",
|
|
58
58
|
"eslint-config-prettier": "^9.1.0",
|
|
59
59
|
"eslint-plugin-jest": "^28.6.0",
|
package/src/config.js
CHANGED
|
@@ -78,6 +78,7 @@ class Config {
|
|
|
78
78
|
#cronTimezone;
|
|
79
79
|
#publishEventBlockList;
|
|
80
80
|
#crashOnRedisUnavailable;
|
|
81
|
+
#tenantIdFilterCb;
|
|
81
82
|
static #instance;
|
|
82
83
|
constructor() {
|
|
83
84
|
this.#logger = cds.log(COMPONENT_NAME);
|
|
@@ -519,6 +520,14 @@ class Config {
|
|
|
519
520
|
this.#crashOnRedisUnavailable = value;
|
|
520
521
|
}
|
|
521
522
|
|
|
523
|
+
get tenantIdFilterCb() {
|
|
524
|
+
return this.#tenantIdFilterCb;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
set tenantIdFilterCb(value) {
|
|
528
|
+
this.#tenantIdFilterCb = value;
|
|
529
|
+
}
|
|
530
|
+
|
|
522
531
|
set globalTxTimeout(value) {
|
|
523
532
|
this.#globalTxTimeout = value;
|
|
524
533
|
}
|
package/src/constants.js
CHANGED
|
@@ -7,6 +7,7 @@ module.exports = {
|
|
|
7
7
|
Done: 2,
|
|
8
8
|
Error: 3,
|
|
9
9
|
Exceeded: 4,
|
|
10
|
+
Suspended: 5,
|
|
10
11
|
},
|
|
11
12
|
TransactionMode: {
|
|
12
13
|
isolated: "isolated",
|
|
@@ -19,4 +20,8 @@ module.exports = {
|
|
|
19
20
|
High: "high",
|
|
20
21
|
VeryHigh: "veryHigh",
|
|
21
22
|
},
|
|
23
|
+
TenantIdCheckTypes: {
|
|
24
|
+
getAllTenantIds: "getAllTenantIds",
|
|
25
|
+
getTokenInfo: "getTokenInfo",
|
|
26
|
+
},
|
|
22
27
|
};
|
package/src/initialize.js
CHANGED
|
@@ -41,6 +41,7 @@ const CONFIG_VARS = [
|
|
|
41
41
|
["cronTimezone", null],
|
|
42
42
|
["publishEventBlockList", true],
|
|
43
43
|
["crashOnRedisUnavailable", false],
|
|
44
|
+
["tenantIdFilterCb", null],
|
|
44
45
|
];
|
|
45
46
|
|
|
46
47
|
/**
|
|
@@ -64,6 +65,7 @@ const CONFIG_VARS = [
|
|
|
64
65
|
* @param {string} [options.cronTimezone=null] - Default timezone for cron jobs.
|
|
65
66
|
* @param {string} [options.publishEventBlockList=true] - If redis is available event blocklist is distributed to all application instances
|
|
66
67
|
* @param {string} [options.crashOnRedisUnavailable=true] - If enabled an error is thrown if the redis connection check is not successful
|
|
68
|
+
* @param {function} [options.tenantIdFilterCb=null] - Allows to set customer filter function to filter the tenants ids which should be processed in the event-queue
|
|
67
69
|
*/
|
|
68
70
|
const initialize = async (options = {}) => {
|
|
69
71
|
if (config.initialized) {
|
|
@@ -27,7 +27,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
27
27
|
delete msg.contextUser;
|
|
28
28
|
processContext.user = new cds.User.Privileged({
|
|
29
29
|
id: userId,
|
|
30
|
-
authInfo: await common.
|
|
30
|
+
authInfo: await common.getTokenInfo(processContext.tenant),
|
|
31
31
|
});
|
|
32
32
|
processContext._eventQueue = { processor: this, key, queueEntries, payload };
|
|
33
33
|
await cds.unboxed(service).tx(processContext)[invocationFn](msg);
|
package/src/redis/redisPub.js
CHANGED
|
@@ -109,7 +109,7 @@ const _processLocalWithoutRedis = async (tenantId, events) => {
|
|
|
109
109
|
let context = {};
|
|
110
110
|
if (tenantId) {
|
|
111
111
|
const user = await cds.tx({ tenant: tenantId }, async () => {
|
|
112
|
-
return new cds.User.Privileged({ id: config.userId,
|
|
112
|
+
return new cds.User.Privileged({ id: config.userId, tokenInfo: await common.getTokenInfo(tenantId) });
|
|
113
113
|
});
|
|
114
114
|
context = {
|
|
115
115
|
tenant: tenantId,
|
package/src/redis/redisSub.js
CHANGED
|
@@ -41,14 +41,14 @@ const _messageHandlerProcessEvents = async (messageData) => {
|
|
|
41
41
|
const service = await cds.connect.to(subType);
|
|
42
42
|
cds.outboxed(service);
|
|
43
43
|
} catch (err) {
|
|
44
|
-
logger.
|
|
44
|
+
logger.warn("could not connect to outboxed service", err, {
|
|
45
45
|
type,
|
|
46
46
|
subType,
|
|
47
47
|
});
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
} else {
|
|
51
|
-
logger.
|
|
51
|
+
logger.warn("cannot find configuration for published event. Event won't be processed", {
|
|
52
52
|
type,
|
|
53
53
|
subType,
|
|
54
54
|
});
|
|
@@ -66,7 +66,7 @@ const _messageHandlerProcessEvents = async (messageData) => {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const user = await cds.tx({ tenant: tenantId }, async () => {
|
|
69
|
-
return new cds.User.Privileged({ id: config.userId,
|
|
69
|
+
return new cds.User.Privileged({ id: config.userId, tokenInfo: await common.getTokenInfo(tenantId) });
|
|
70
70
|
});
|
|
71
71
|
const tenantContext = {
|
|
72
72
|
tenant: tenantId,
|
package/src/runner/openEvents.js
CHANGED
|
@@ -5,6 +5,8 @@ const cds = require("@sap/cds");
|
|
|
5
5
|
const eventConfig = require("../config");
|
|
6
6
|
const { EventProcessingStatus } = require("../constants");
|
|
7
7
|
|
|
8
|
+
const MS_IN_DAYS = 24 * 60 * 60 * 1000;
|
|
9
|
+
|
|
8
10
|
const getOpenQueueEntries = async (tx, filterAppSpecificEvents = true) => {
|
|
9
11
|
const startTime = new Date();
|
|
10
12
|
const refDateStartAfter = new Date(startTime.getTime() + eventConfig.runInterval * 1.2);
|
|
@@ -21,7 +23,11 @@ const getOpenQueueEntries = async (tx, filterAppSpecificEvents = true) => {
|
|
|
21
23
|
EventProcessingStatus.InProgress,
|
|
22
24
|
"AND lastAttemptTimestamp <=",
|
|
23
25
|
new Date(startTime.getTime() - eventConfig.globalTxTimeout).toISOString(),
|
|
24
|
-
") )"
|
|
26
|
+
") ) AND (createdAt >=",
|
|
27
|
+
new Date(startTime.getTime() - 30 * MS_IN_DAYS).toISOString(),
|
|
28
|
+
" OR startAfter >=",
|
|
29
|
+
new Date(startTime.getTime() - 30 * MS_IN_DAYS).toISOString(),
|
|
30
|
+
")"
|
|
25
31
|
)
|
|
26
32
|
.columns("type", "subType")
|
|
27
33
|
.groupBy("type", "subType")
|
|
@@ -30,9 +36,13 @@ const getOpenQueueEntries = async (tx, filterAppSpecificEvents = true) => {
|
|
|
30
36
|
const result = [];
|
|
31
37
|
for (const { type, subType } of entries) {
|
|
32
38
|
if (eventConfig.isCapOutboxEvent(type)) {
|
|
33
|
-
|
|
39
|
+
cds.connect
|
|
34
40
|
.to(subType)
|
|
35
41
|
.then((service) => {
|
|
42
|
+
if (!filterAppSpecificEvents) {
|
|
43
|
+
return; // will be done in finally
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
if (!service) {
|
|
37
47
|
return;
|
|
38
48
|
}
|
|
@@ -45,7 +55,12 @@ const getOpenQueueEntries = async (tx, filterAppSpecificEvents = true) => {
|
|
|
45
55
|
result.push({ type, subType });
|
|
46
56
|
}
|
|
47
57
|
})
|
|
48
|
-
.catch(() => {})
|
|
58
|
+
.catch(() => {})
|
|
59
|
+
.finally(() => {
|
|
60
|
+
if (!filterAppSpecificEvents) {
|
|
61
|
+
result.push({ type, subType });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
49
64
|
} else {
|
|
50
65
|
if (filterAppSpecificEvents) {
|
|
51
66
|
if (
|
|
@@ -55,7 +70,7 @@ const getOpenQueueEntries = async (tx, filterAppSpecificEvents = true) => {
|
|
|
55
70
|
result.push({ type, subType });
|
|
56
71
|
}
|
|
57
72
|
} else {
|
|
58
|
-
|
|
73
|
+
result.push({ type, subType });
|
|
59
74
|
}
|
|
60
75
|
}
|
|
61
76
|
}
|
package/src/runner/runner.js
CHANGED
|
@@ -134,7 +134,7 @@ const _executeEventsAllTenantsRedis = async (tenantIds) => {
|
|
|
134
134
|
async () => {
|
|
135
135
|
tx.context.user = new cds.User.Privileged({
|
|
136
136
|
id: config.userId,
|
|
137
|
-
authInfo: await common.
|
|
137
|
+
authInfo: await common.getTokenInfo(tenantId),
|
|
138
138
|
});
|
|
139
139
|
const entries = await openEvents.getOpenQueueEntries(tx, false);
|
|
140
140
|
logger.info("broadcasting events for run", {
|
|
@@ -168,10 +168,10 @@ const _executeEventsAllTenants = async (tenantIds, runId) => {
|
|
|
168
168
|
let tenantContext;
|
|
169
169
|
const events = await trace(
|
|
170
170
|
{ id, tenant: tenantId },
|
|
171
|
-
"fetch-openEvents-and-
|
|
171
|
+
"fetch-openEvents-and-tokenInfo",
|
|
172
172
|
async () => {
|
|
173
173
|
const user = await cds.tx({ tenant: tenantId }, async () => {
|
|
174
|
-
return new cds.User.Privileged({ id: config.userId,
|
|
174
|
+
return new cds.User.Privileged({ id: config.userId, tokenInfo: await common.getTokenInfo(tenantId) });
|
|
175
175
|
});
|
|
176
176
|
tenantContext = {
|
|
177
177
|
tenant: tenantId,
|
|
@@ -229,7 +229,7 @@ const _executePeriodicEventsAllTenants = async (tenantIds) => {
|
|
|
229
229
|
for (const tenantId of tenantIds) {
|
|
230
230
|
try {
|
|
231
231
|
const user = await cds.tx({ tenant: tenantId }, async () => {
|
|
232
|
-
return new cds.User.Privileged({ id: config.userId,
|
|
232
|
+
return new cds.User.Privileged({ id: config.userId, tokenInfo: await common.getTokenInfo(tenantId) });
|
|
233
233
|
});
|
|
234
234
|
const tenantContext = {
|
|
235
235
|
tenant: tenantId,
|
|
@@ -260,7 +260,7 @@ const _singleTenantDb = async () => {
|
|
|
260
260
|
const id = cds.utils.uuid();
|
|
261
261
|
const events = await trace(
|
|
262
262
|
{ id },
|
|
263
|
-
"fetch-openEvents-and-
|
|
263
|
+
"fetch-openEvents-and-tokenInfo",
|
|
264
264
|
async () => {
|
|
265
265
|
return await cds.tx({}, async (tx) => {
|
|
266
266
|
return await openEvents.getOpenQueueEntries(tx);
|
|
@@ -328,7 +328,7 @@ const _singleTenantRedis = async () => {
|
|
|
328
328
|
"get-openEvents-and-publish",
|
|
329
329
|
async () => {
|
|
330
330
|
return await cds.tx({}, async (tx) => {
|
|
331
|
-
const entries = await openEvents.getOpenQueueEntries(tx);
|
|
331
|
+
const entries = await openEvents.getOpenQueueEntries(tx, false);
|
|
332
332
|
logger.info("broadcasting events for run", {
|
|
333
333
|
entries: entries.length,
|
|
334
334
|
});
|
|
@@ -491,7 +491,7 @@ const _checkPeriodicEventsSingleTenant = async (context) => {
|
|
|
491
491
|
try {
|
|
492
492
|
logger.info("executing updating periodic events", {
|
|
493
493
|
tenantId: context.tenant,
|
|
494
|
-
subdomain: context.user?.
|
|
494
|
+
subdomain: context.user?.tokenInfo?.extAttributes?.zdn,
|
|
495
495
|
});
|
|
496
496
|
await periodicEvents.checkAndInsertPeriodicEvents(context);
|
|
497
497
|
} catch (err) {
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const COMPONENT = "eventQueue/SetIntervalDriftSafe";
|
|
4
4
|
|
|
5
|
-
const ALLOWED_SHIFT_IN_PROCENT = 0.1;
|
|
6
|
-
|
|
7
5
|
class SetIntervalDriftSafe {
|
|
8
6
|
#adjustedInterval;
|
|
9
7
|
#interval;
|
|
@@ -21,12 +19,6 @@ class SetIntervalDriftSafe {
|
|
|
21
19
|
const now = Date.now();
|
|
22
20
|
if (this.#expectedCycleTime === 0) {
|
|
23
21
|
this.#expectedCycleTime = now + this.#interval;
|
|
24
|
-
} else if (
|
|
25
|
-
Math.abs(now + this.#interval - this.#nextTickScheduledFor - this.#interval) >
|
|
26
|
-
this.#interval * ALLOWED_SHIFT_IN_PROCENT
|
|
27
|
-
) {
|
|
28
|
-
this.#logger.log("overlapping ticks, skipping this run");
|
|
29
|
-
return;
|
|
30
22
|
} else {
|
|
31
23
|
this.#adjustedInterval = this.#interval - (now - this.#expectedCycleTime);
|
|
32
24
|
this.#expectedCycleTime += this.#interval;
|
package/src/shared/cdsHelper.js
CHANGED
|
@@ -5,6 +5,7 @@ const cds = require("@sap/cds");
|
|
|
5
5
|
|
|
6
6
|
const config = require("../config");
|
|
7
7
|
const common = require("./common");
|
|
8
|
+
const { TenantIdCheckTypes } = require("../constants");
|
|
8
9
|
|
|
9
10
|
const VERROR_CLUSTER_NAME = "ExecuteInNewTransactionError";
|
|
10
11
|
const COMPONENT_NAME = "/eventQueue/cdsHelper";
|
|
@@ -23,7 +24,7 @@ async function executeInNewTransaction(context = {}, transactionTag, fn, args, {
|
|
|
23
24
|
const parameters = Array.isArray(args) ? args : [args];
|
|
24
25
|
const logger = cds.log(COMPONENT_NAME);
|
|
25
26
|
try {
|
|
26
|
-
const user = new cds.User.Privileged({ id: config.userId,
|
|
27
|
+
const user = new cds.User.Privileged({ id: config.userId, tokenInfo: await common.getTokenInfo(context.tenant) });
|
|
27
28
|
if (cds.db.kind === "hana") {
|
|
28
29
|
await cds.tx(
|
|
29
30
|
{
|
|
@@ -116,11 +117,9 @@ const getAllTenantIds = async () => {
|
|
|
116
117
|
const response = await ssp.get("/tenant");
|
|
117
118
|
return response
|
|
118
119
|
.map((tenant) => tenant.subscribedTenantId ?? tenant.tenant)
|
|
119
|
-
.filter((tenantId) =>
|
|
120
|
+
.filter((tenantId) => common.isTenantIdValidCb(TenantIdCheckTypes.getAllTenantIds, tenantId));
|
|
120
121
|
};
|
|
121
122
|
|
|
122
|
-
const isFakeTenant = (tenantId) => /00000000-0000-4000-8000-\d{12}/.test(tenantId);
|
|
123
|
-
|
|
124
123
|
module.exports = {
|
|
125
124
|
executeInNewTransaction,
|
|
126
125
|
TriggerRollback,
|
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 config = require("../config");
|
|
8
|
+
const { TenantIdCheckTypes } = require("../constants");
|
|
7
9
|
|
|
8
10
|
const MARGIN_AUTH_INFO_EXPIRY = 60 * 1000;
|
|
9
11
|
const COMPONENT_NAME = "/eventQueue/common";
|
|
@@ -68,49 +70,57 @@ const processChunkedSync = (inputs, chunkSize, chunkHandler) => {
|
|
|
68
70
|
|
|
69
71
|
const hashStringTo32Bit = (value) => crypto.createHash("sha256").update(String(value)).digest("base64").slice(0, 32);
|
|
70
72
|
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
73
|
+
const _getNewTokenInfo = async (tenantId) => {
|
|
74
|
+
const tokenInfoCache = getTokenInfo._tokenInfoCache;
|
|
75
|
+
tokenInfoCache[tenantId] = tokenInfoCache[tenantId] ?? {};
|
|
74
76
|
try {
|
|
75
|
-
if (!
|
|
76
|
-
|
|
77
|
+
if (!_getNewTokenInfo._xsuaaService) {
|
|
78
|
+
_getNewTokenInfo._xsuaaService = new xssec.XsuaaService(cds.requires.auth.credentials);
|
|
77
79
|
}
|
|
78
|
-
const authService =
|
|
80
|
+
const authService = _getNewTokenInfo._xsuaaService;
|
|
79
81
|
const token = await authService.fetchClientCredentialsToken({ zid: tenantId });
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
return
|
|
82
|
+
const tokenInfo = new xssec.XsuaaToken(token.access_token);
|
|
83
|
+
tokenInfoCache[tenantId].expireTs = tokenInfo.getExpirationDate().getTime() - MARGIN_AUTH_INFO_EXPIRY;
|
|
84
|
+
return tokenInfo;
|
|
83
85
|
} catch (err) {
|
|
84
|
-
|
|
85
|
-
cds.log(COMPONENT_NAME).warn("failed to request
|
|
86
|
+
tokenInfoCache[tenantId] = null;
|
|
87
|
+
cds.log(COMPONENT_NAME).warn("failed to request tokenInfo", err);
|
|
86
88
|
}
|
|
87
89
|
};
|
|
88
90
|
|
|
89
|
-
const
|
|
90
|
-
if (!tenantId) {
|
|
91
|
+
const getTokenInfo = async (tenantId) => {
|
|
92
|
+
if (!isTenantIdValidCb(TenantIdCheckTypes.getTokenInfo, tenantId)) {
|
|
91
93
|
return null;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
if (!cds.requires?.auth?.credentials) {
|
|
95
|
-
return null; // no credentials not
|
|
97
|
+
return null; // no credentials not tokenInfo
|
|
96
98
|
}
|
|
97
99
|
if (!cds.requires?.auth.kind.match(/jwt|xsuaa/i)) {
|
|
98
100
|
cds.log(COMPONENT_NAME).warn("Only 'jwt' or 'xsuaa' are supported as values for auth.kind.");
|
|
99
101
|
return null;
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
+
getTokenInfo._tokenInfoCache = getTokenInfo._tokenInfoCache ?? {};
|
|
105
|
+
const tokenInfoCache = getTokenInfo._tokenInfoCache;
|
|
104
106
|
// not existing or existing but expired
|
|
105
107
|
if (
|
|
106
|
-
!
|
|
107
|
-
(
|
|
108
|
+
!tokenInfoCache[tenantId] ||
|
|
109
|
+
(tokenInfoCache[tenantId] && tokenInfoCache[tenantId].expireTs && Date.now() > tokenInfoCache[tenantId].expireTs)
|
|
108
110
|
) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
tokenInfoCache[tenantId] ??= {};
|
|
112
|
+
tokenInfoCache[tenantId].value = _getNewTokenInfo(tenantId);
|
|
113
|
+
tokenInfoCache[tenantId].expireTs = null;
|
|
114
|
+
}
|
|
115
|
+
return await tokenInfoCache[tenantId].value;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const isTenantIdValidCb = (checkType, tenantId) => {
|
|
119
|
+
if (config.tenantIdFilterCb) {
|
|
120
|
+
return config.tenantIdFilterCb(checkType, tenantId);
|
|
121
|
+
} else {
|
|
122
|
+
return true;
|
|
112
123
|
}
|
|
113
|
-
return await authInfoCache[tenantId].value;
|
|
114
124
|
};
|
|
115
125
|
|
|
116
126
|
module.exports = {
|
|
@@ -119,8 +129,9 @@ module.exports = {
|
|
|
119
129
|
isValidDate,
|
|
120
130
|
processChunkedSync,
|
|
121
131
|
hashStringTo32Bit,
|
|
122
|
-
|
|
132
|
+
getTokenInfo,
|
|
133
|
+
isTenantIdValidCb,
|
|
123
134
|
__: {
|
|
124
|
-
|
|
135
|
+
clearTokenInfoCache: () => (getTokenInfo._tokenInfoCache = {}),
|
|
125
136
|
},
|
|
126
137
|
};
|
|
@@ -60,7 +60,7 @@ const checkLockExistsAndReturnValue = async (context, key, { tenantScoped = true
|
|
|
60
60
|
const _acquireLockRedis = async (context, fullKey, expiryTime, { value = "true", overrideValue = false } = {}) => {
|
|
61
61
|
const client = await redis.createMainClientAndConnect(config.redisOptions);
|
|
62
62
|
const result = await client.set(fullKey, value, {
|
|
63
|
-
PX: expiryTime,
|
|
63
|
+
PX: Math.round(expiryTime),
|
|
64
64
|
...(overrideValue ? null : { NX: true }),
|
|
65
65
|
});
|
|
66
66
|
const isOk = result === REDIS_COMMAND_OK;
|
|
@@ -117,7 +117,10 @@ const _acquireLockDB = async (context, fullKey, expiryTime, { value = "true", ov
|
|
|
117
117
|
.where("code =", fullKey)
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
|
-
if (
|
|
120
|
+
if (
|
|
121
|
+
overrideValue ||
|
|
122
|
+
(currentEntry && new Date(currentEntry.createdAt).getTime() + Math.round(expiryTime) <= Date.now())
|
|
123
|
+
) {
|
|
121
124
|
await tx.run(
|
|
122
125
|
UPDATE.entity(config.tableNameEventLock)
|
|
123
126
|
.set({
|
|
@@ -51,19 +51,10 @@ class EventScheduler {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
calculateOffset(type, subType, startAfter) {
|
|
54
|
-
const
|
|
55
|
-
const scheduleWithoutDelay = config.isPeriodicEvent(type, subType) && eventConfig.interval < 30 * 1000;
|
|
56
|
-
const date = scheduleWithoutDelay ? startAfter : this.calculateFutureTime(startAfter, 10);
|
|
57
|
-
|
|
54
|
+
const date = startAfter;
|
|
58
55
|
return { date, relative: date.getTime() - Date.now() };
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
calculateFutureTime(date, seoncds) {
|
|
62
|
-
const startAfterSeconds = date.getSeconds();
|
|
63
|
-
const secondsUntil = seoncds - (startAfterSeconds % seoncds);
|
|
64
|
-
return new Date(date.getTime() + secondsUntil * 1000);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
58
|
clearScheduledEvents() {
|
|
68
59
|
this.#scheduledEvents = {};
|
|
69
60
|
}
|