@cap-js-community/event-queue 0.1.50 → 0.1.52
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/cds-plugin.js +1 -1
- package/package.json +10 -3
- package/src/EventQueueError.js +5 -10
- package/src/EventQueueProcessorBase.js +292 -325
- package/src/config.js +1 -4
- package/src/dbHandler.js +1 -4
- package/src/initialize.js +11 -39
- package/src/processEventQueue.js +64 -155
- package/src/publishEvent.js +1 -3
- package/src/redisPubSub.js +6 -20
- package/src/runner.js +40 -78
- package/src/shared/PerformanceTracer.js +1 -3
- package/src/shared/SetIntervalDriftSafe.js +37 -0
- package/src/shared/WorkerQueue.js +3 -14
- package/src/shared/cdsHelper.js +2 -11
- package/src/shared/common.js +2 -7
- package/src/shared/distributedLock.js +41 -85
- package/src/shared/redis.js +1 -3
|
@@ -36,9 +36,7 @@ class PerformanceTracer {
|
|
|
36
36
|
//determine, if an options object was provided as first argument
|
|
37
37
|
if (
|
|
38
38
|
typeof args?.[0] === "object" &&
|
|
39
|
-
(args[0].quantity >= 0 ||
|
|
40
|
-
args[0].threshold > 0 ||
|
|
41
|
-
args[0].additionalQuantityThreshold > 0)
|
|
39
|
+
(args[0].quantity >= 0 || args[0].threshold > 0 || args[0].additionalQuantityThreshold > 0)
|
|
42
40
|
) {
|
|
43
41
|
options = args.shift();
|
|
44
42
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const COMPONENT = "eventQueue/SetIntervalDriftSafe";
|
|
4
|
+
|
|
5
|
+
class SetIntervalDriftSafe {
|
|
6
|
+
#adjustedInterval;
|
|
7
|
+
#interval;
|
|
8
|
+
#expectedCycleTime = 0;
|
|
9
|
+
#nextTickScheduledFor;
|
|
10
|
+
#logger;
|
|
11
|
+
|
|
12
|
+
constructor(interval) {
|
|
13
|
+
this.#interval = interval;
|
|
14
|
+
this.#adjustedInterval = interval;
|
|
15
|
+
this.#logger = cds.log(COMPONENT);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
run(fn) {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
if (this.#expectedCycleTime === 0) {
|
|
21
|
+
this.#expectedCycleTime = now + this.#interval;
|
|
22
|
+
} else if (now + this.#interval - this.#nextTickScheduledFor < this.#interval) {
|
|
23
|
+
this.#logger.info("overlapping ticks, skipping this run");
|
|
24
|
+
return;
|
|
25
|
+
} else {
|
|
26
|
+
this.#adjustedInterval = this.#interval - (now - this.#expectedCycleTime);
|
|
27
|
+
this.#expectedCycleTime += this.#interval;
|
|
28
|
+
}
|
|
29
|
+
this.#nextTickScheduledFor = now + this.#adjustedInterval;
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
this.run(fn);
|
|
32
|
+
fn();
|
|
33
|
+
}, this.#adjustedInterval);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = SetIntervalDriftSafe;
|
|
@@ -32,31 +32,20 @@ class WorkerQueue {
|
|
|
32
32
|
this.__runningPromises.push(promise);
|
|
33
33
|
promise
|
|
34
34
|
.finally(() => {
|
|
35
|
-
this.__runningPromises.splice(
|
|
36
|
-
this.__runningPromises.indexOf(promise),
|
|
37
|
-
1
|
|
38
|
-
);
|
|
35
|
+
this.__runningPromises.splice(this.__runningPromises.indexOf(promise), 1);
|
|
39
36
|
this._checkForNext();
|
|
40
37
|
})
|
|
41
38
|
.then((...results) => {
|
|
42
39
|
resolve(...results);
|
|
43
40
|
})
|
|
44
41
|
.catch((err) => {
|
|
45
|
-
cds
|
|
46
|
-
.log(COMPONENT_NAME)
|
|
47
|
-
.error(
|
|
48
|
-
"Error happened in WorkQueue. Errors should be caught before! Error:",
|
|
49
|
-
err
|
|
50
|
-
);
|
|
42
|
+
cds.log(COMPONENT_NAME).error("Error happened in WorkQueue. Errors should be caught before! Error:", err);
|
|
51
43
|
reject(err);
|
|
52
44
|
});
|
|
53
45
|
}
|
|
54
46
|
|
|
55
47
|
_checkForNext() {
|
|
56
|
-
if (
|
|
57
|
-
!this.__queue.length ||
|
|
58
|
-
this.__runningPromises.length >= this.__concurrencyLimit
|
|
59
|
-
) {
|
|
48
|
+
if (!this.__queue.length || this.__runningPromises.length >= this.__concurrencyLimit) {
|
|
60
49
|
return;
|
|
61
50
|
}
|
|
62
51
|
const [cb, resolve, reject] = this.__queue.shift();
|
package/src/shared/cdsHelper.js
CHANGED
|
@@ -20,13 +20,7 @@ const COMPONENT_NAME = "eventQueue/cdsHelper";
|
|
|
20
20
|
* @param info {object} Additional information object attached to logging
|
|
21
21
|
* @returns {Promise<boolean>} Promise resolving to true if everything worked fine / false if an error occurred
|
|
22
22
|
*/
|
|
23
|
-
async function executeInNewTransaction(
|
|
24
|
-
context = {},
|
|
25
|
-
transactionTag,
|
|
26
|
-
fn,
|
|
27
|
-
args,
|
|
28
|
-
{ info = {} } = {}
|
|
29
|
-
) {
|
|
23
|
+
async function executeInNewTransaction(context = {}, transactionTag, fn, args, { info = {} } = {}) {
|
|
30
24
|
const parameters = Array.isArray(args) ? args : [args];
|
|
31
25
|
const logger = cds.log(COMPONENT_NAME);
|
|
32
26
|
try {
|
|
@@ -48,10 +42,7 @@ async function executeInNewTransaction(
|
|
|
48
42
|
} else {
|
|
49
43
|
const contextTx = cds.tx(context);
|
|
50
44
|
const contextTxState = contextTx.ready;
|
|
51
|
-
if (
|
|
52
|
-
!contextTxState ||
|
|
53
|
-
["committed", "rolled back"].includes(contextTxState)
|
|
54
|
-
) {
|
|
45
|
+
if (!contextTxState || ["committed", "rolled back"].includes(contextTxState)) {
|
|
55
46
|
await cds.tx(
|
|
56
47
|
{
|
|
57
48
|
id: context.id,
|
package/src/shared/common.js
CHANGED
|
@@ -56,10 +56,7 @@ class Funnel {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// map function call to promise
|
|
59
|
-
const p =
|
|
60
|
-
f.constructor.name === "AsyncFunction"
|
|
61
|
-
? f(...args)
|
|
62
|
-
: Promise.resolve().then(() => f(...args));
|
|
59
|
+
const p = f.constructor.name === "AsyncFunction" ? f(...args) : Promise.resolve().then(() => f(...args));
|
|
63
60
|
|
|
64
61
|
// create promise for book keeping
|
|
65
62
|
const workload = p.finally(() => {
|
|
@@ -100,9 +97,7 @@ const limiter = async (limit, payloads, iterator) => {
|
|
|
100
97
|
returnPromises.push(p);
|
|
101
98
|
|
|
102
99
|
if (limit <= payloads.length) {
|
|
103
|
-
const e = p
|
|
104
|
-
.catch(() => {})
|
|
105
|
-
.finally(() => runningPromises.splice(runningPromises.indexOf(e), 1));
|
|
100
|
+
const e = p.catch(() => {}).finally(() => runningPromises.splice(runningPromises.indexOf(e), 1));
|
|
106
101
|
runningPromises.push(e);
|
|
107
102
|
if (limit <= runningPromises.length) {
|
|
108
103
|
await Promise.race(runningPromises);
|
|
@@ -8,10 +8,7 @@ const { getConfigInstance } = require("../config");
|
|
|
8
8
|
const acquireLock = async (
|
|
9
9
|
context,
|
|
10
10
|
key,
|
|
11
|
-
{
|
|
12
|
-
tenantScoped = true,
|
|
13
|
-
expiryTime = config.getConfigInstance().globalTxTimeout,
|
|
14
|
-
} = {}
|
|
11
|
+
{ tenantScoped = true, expiryTime = config.getConfigInstance().globalTxTimeout } = {}
|
|
15
12
|
) => {
|
|
16
13
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
17
14
|
if (config.getConfigInstance().redisEnabled) {
|
|
@@ -25,11 +22,7 @@ const setValueWithExpire = async (
|
|
|
25
22
|
context,
|
|
26
23
|
key,
|
|
27
24
|
value,
|
|
28
|
-
{
|
|
29
|
-
tenantScoped = true,
|
|
30
|
-
expiryTime = config.getConfigInstance().globalTxTimeout,
|
|
31
|
-
overrideValue = false,
|
|
32
|
-
} = {}
|
|
25
|
+
{ tenantScoped = true, expiryTime = config.getConfigInstance().globalTxTimeout, overrideValue = false } = {}
|
|
33
26
|
) => {
|
|
34
27
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
35
28
|
if (config.getConfigInstance().redisEnabled) {
|
|
@@ -54,11 +47,7 @@ const releaseLock = async (context, key, { tenantScoped = true } = {}) => {
|
|
|
54
47
|
}
|
|
55
48
|
};
|
|
56
49
|
|
|
57
|
-
const checkLockExistsAndReturnValue = async (
|
|
58
|
-
context,
|
|
59
|
-
key,
|
|
60
|
-
{ tenantScoped = true } = {}
|
|
61
|
-
) => {
|
|
50
|
+
const checkLockExistsAndReturnValue = async (context, key, { tenantScoped = true } = {}) => {
|
|
62
51
|
const fullKey = _generateKey(context, tenantScoped, key);
|
|
63
52
|
if (config.getConfigInstance().redisEnabled) {
|
|
64
53
|
return await _checkLockExistsRedis(context, fullKey);
|
|
@@ -67,12 +56,7 @@ const checkLockExistsAndReturnValue = async (
|
|
|
67
56
|
}
|
|
68
57
|
};
|
|
69
58
|
|
|
70
|
-
const _acquireLockRedis = async (
|
|
71
|
-
context,
|
|
72
|
-
fullKey,
|
|
73
|
-
expiryTime,
|
|
74
|
-
{ value = "true", overrideValue = false } = {}
|
|
75
|
-
) => {
|
|
59
|
+
const _acquireLockRedis = async (context, fullKey, expiryTime, { value = "true", overrideValue = false } = {}) => {
|
|
76
60
|
const client = await redis.createMainClientAndConnect();
|
|
77
61
|
const result = await client.set(fullKey, value, {
|
|
78
62
|
PX: expiryTime,
|
|
@@ -89,17 +73,9 @@ const _checkLockExistsRedis = async (context, fullKey) => {
|
|
|
89
73
|
const _checkLockExistsDb = async (context, fullKey) => {
|
|
90
74
|
let result;
|
|
91
75
|
const configInstance = getConfigInstance();
|
|
92
|
-
await cdsHelper.executeInNewTransaction(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
async (tx) => {
|
|
96
|
-
result = await tx.run(
|
|
97
|
-
SELECT.one
|
|
98
|
-
.from(configInstance.tableNameEventLock)
|
|
99
|
-
.where("code =", fullKey)
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
);
|
|
76
|
+
await cdsHelper.executeInNewTransaction(context, "distributedLock-checkExists", async (tx) => {
|
|
77
|
+
result = await tx.run(SELECT.one.from(configInstance.tableNameEventLock).where("code =", fullKey));
|
|
78
|
+
});
|
|
103
79
|
return result?.value;
|
|
104
80
|
};
|
|
105
81
|
|
|
@@ -110,69 +86,49 @@ const _releaseLockRedis = async (context, fullKey) => {
|
|
|
110
86
|
|
|
111
87
|
const _releaseLockDb = async (context, fullKey) => {
|
|
112
88
|
const configInstance = getConfigInstance();
|
|
113
|
-
await cdsHelper.executeInNewTransaction(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
async (tx) => {
|
|
117
|
-
await tx.run(
|
|
118
|
-
DELETE.from(configInstance.tableNameEventLock).where("code =", fullKey)
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
);
|
|
89
|
+
await cdsHelper.executeInNewTransaction(context, "distributedLock-release", async (tx) => {
|
|
90
|
+
await tx.run(DELETE.from(configInstance.tableNameEventLock).where("code =", fullKey));
|
|
91
|
+
});
|
|
122
92
|
};
|
|
123
93
|
|
|
124
|
-
const _acquireLockDB = async (
|
|
125
|
-
context,
|
|
126
|
-
fullKey,
|
|
127
|
-
expiryTime,
|
|
128
|
-
{ value = "true", overrideValue = false } = {}
|
|
129
|
-
) => {
|
|
94
|
+
const _acquireLockDB = async (context, fullKey, expiryTime, { value = "true", overrideValue = false } = {}) => {
|
|
130
95
|
let result;
|
|
131
96
|
const configInstance = getConfigInstance();
|
|
132
|
-
await cdsHelper.executeInNewTransaction(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
97
|
+
await cdsHelper.executeInNewTransaction(context, "distributedLock-acquire", async (tx) => {
|
|
98
|
+
try {
|
|
99
|
+
await tx.run(
|
|
100
|
+
INSERT.into(configInstance.tableNameEventLock).entries({
|
|
101
|
+
code: fullKey,
|
|
102
|
+
value,
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
result = true;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
let currentEntry;
|
|
108
|
+
|
|
109
|
+
if (!overrideValue) {
|
|
110
|
+
currentEntry = await tx.run(
|
|
111
|
+
SELECT.one
|
|
112
|
+
.from(configInstance.tableNameEventLock)
|
|
113
|
+
.forUpdate({ wait: config.getConfigInstance().forUpdateTimeout })
|
|
114
|
+
.where("code =", fullKey)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (overrideValue || (currentEntry && new Date(currentEntry.createdAt).getTime() + expiryTime <= Date.now())) {
|
|
137
118
|
await tx.run(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
119
|
+
UPDATE.entity(configInstance.tableNameEventLock)
|
|
120
|
+
.set({
|
|
121
|
+
createdAt: new Date().toISOString(),
|
|
122
|
+
value,
|
|
123
|
+
})
|
|
124
|
+
.where("code =", currentEntry.code)
|
|
142
125
|
);
|
|
143
126
|
result = true;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!overrideValue) {
|
|
148
|
-
currentEntry = await tx.run(
|
|
149
|
-
SELECT.one
|
|
150
|
-
.from(configInstance.tableNameEventLock)
|
|
151
|
-
.forUpdate({ wait: config.getConfigInstance().forUpdateTimeout })
|
|
152
|
-
.where("code =", fullKey)
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
if (
|
|
156
|
-
overrideValue ||
|
|
157
|
-
(currentEntry &&
|
|
158
|
-
new Date(currentEntry.createdAt).getTime() + expiryTime <=
|
|
159
|
-
Date.now())
|
|
160
|
-
) {
|
|
161
|
-
await tx.run(
|
|
162
|
-
UPDATE.entity(configInstance.tableNameEventLock)
|
|
163
|
-
.set({
|
|
164
|
-
createdAt: new Date().toISOString(),
|
|
165
|
-
value,
|
|
166
|
-
})
|
|
167
|
-
.where("code =", currentEntry.code)
|
|
168
|
-
);
|
|
169
|
-
result = true;
|
|
170
|
-
} else {
|
|
171
|
-
result = false;
|
|
172
|
-
}
|
|
127
|
+
} else {
|
|
128
|
+
result = false;
|
|
173
129
|
}
|
|
174
130
|
}
|
|
175
|
-
);
|
|
131
|
+
});
|
|
176
132
|
return result;
|
|
177
133
|
};
|
|
178
134
|
|
package/src/shared/redis.js
CHANGED
|
@@ -15,9 +15,7 @@ const createMainClientAndConnect = () => {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const errorHandlerCreateClient = (err) => {
|
|
18
|
-
cds
|
|
19
|
-
.log(COMPONENT_NAME)
|
|
20
|
-
.error("error from redis client for pub/sub failed", err);
|
|
18
|
+
cds.log(COMPONENT_NAME).error("error from redis client for pub/sub failed", err);
|
|
21
19
|
subscriberClientPromise = null;
|
|
22
20
|
setTimeout(createMainClientAndConnect, 5 * 1000).unref();
|
|
23
21
|
};
|