@hotmeshio/hotmesh 0.0.37 → 0.0.39
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 +14 -8
- package/build/modules/enums.d.ts +29 -23
- package/build/modules/enums.js +38 -29
- package/build/modules/errors.d.ts +1 -1
- package/build/modules/errors.js +9 -7
- package/build/modules/key.d.ts +1 -34
- package/build/modules/key.js +24 -47
- package/build/package.json +1 -1
- package/build/services/activities/activity.js +1 -1
- package/build/services/activities/hook.js +4 -9
- package/build/services/activities/trigger.d.ts +3 -2
- package/build/services/activities/trigger.js +10 -6
- package/build/services/durable/client.d.ts +9 -1
- package/build/services/durable/client.js +30 -14
- package/build/services/durable/handle.js +2 -2
- package/build/services/durable/worker.js +4 -3
- package/build/services/engine/index.d.ts +2 -1
- package/build/services/engine/index.js +6 -6
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +3 -4
- package/build/services/quorum/index.d.ts +6 -6
- package/build/services/quorum/index.js +47 -11
- package/build/services/router/index.js +16 -14
- package/build/services/store/clients/ioredis.d.ts +1 -0
- package/build/services/store/clients/ioredis.js +9 -0
- package/build/services/store/clients/redis.d.ts +1 -0
- package/build/services/store/clients/redis.js +16 -0
- package/build/services/store/index.d.ts +15 -9
- package/build/services/store/index.js +46 -23
- package/build/services/stream/clients/ioredis.d.ts +1 -0
- package/build/services/stream/clients/ioredis.js +33 -24
- package/build/services/stream/clients/redis.d.ts +1 -0
- package/build/services/stream/clients/redis.js +15 -0
- package/build/services/stream/index.d.ts +1 -0
- package/build/services/task/index.d.ts +10 -3
- package/build/services/task/index.js +35 -17
- package/build/services/worker/index.d.ts +1 -0
- package/build/services/worker/index.js +24 -0
- package/build/types/durable.d.ts +3 -2
- package/build/types/hotmesh.d.ts +43 -2
- package/build/types/hotmesh.js +28 -0
- package/build/types/index.d.ts +3 -2
- package/build/types/index.js +3 -1
- package/build/types/logger.d.ts +1 -0
- package/build/types/logger.js +1 -0
- package/build/types/quorum.d.ts +11 -1
- package/build/types/redisclient.d.ts +1 -0
- package/build/types/task.d.ts +1 -0
- package/build/types/task.js +2 -0
- package/modules/enums.ts +49 -35
- package/modules/errors.ts +17 -8
- package/modules/key.ts +3 -40
- package/package.json +1 -1
- package/services/activities/activity.ts +2 -2
- package/services/activities/hook.ts +18 -9
- package/services/activities/trigger.ts +10 -6
- package/services/durable/client.ts +31 -15
- package/services/durable/handle.ts +3 -3
- package/services/durable/worker.ts +4 -3
- package/services/engine/index.ts +13 -12
- package/services/hotmesh/index.ts +4 -5
- package/services/quorum/index.ts +48 -12
- package/services/router/index.ts +26 -24
- package/services/store/clients/ioredis.ts +9 -0
- package/services/store/clients/redis.ts +16 -0
- package/services/store/index.ts +63 -25
- package/services/stream/clients/ioredis.ts +33 -24
- package/services/stream/clients/redis.ts +14 -0
- package/services/stream/index.ts +1 -0
- package/services/task/index.ts +66 -24
- package/services/worker/index.ts +30 -0
- package/types/durable.ts +6 -5
- package/types/hotmesh.ts +47 -2
- package/types/index.ts +8 -1
- package/types/logger.ts +3 -1
- package/types/quorum.ts +15 -4
- package/types/redisclient.ts +1 -0
- package/types/task.ts +1 -0
|
@@ -8,6 +8,7 @@ const factory_1 = require("./factory");
|
|
|
8
8
|
const hotmesh_1 = require("../hotmesh");
|
|
9
9
|
const search_1 = require("./search");
|
|
10
10
|
const stream_1 = require("../../types/stream");
|
|
11
|
+
const enums_1 = require("../../modules/enums");
|
|
11
12
|
class WorkerService {
|
|
12
13
|
static async activateWorkflow(hotMesh) {
|
|
13
14
|
const app = await hotMesh.engine.store.getApp(hotMesh.engine.appId);
|
|
@@ -85,7 +86,7 @@ class WorkerService {
|
|
|
85
86
|
options: config.connection.options
|
|
86
87
|
};
|
|
87
88
|
const hotMeshWorker = await hotmesh_1.HotMeshService.init({
|
|
88
|
-
logLevel: config.options?.logLevel ??
|
|
89
|
+
logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
89
90
|
appId: config.namespace ?? factory_1.APP_ID,
|
|
90
91
|
engine: { redis: redisConfig },
|
|
91
92
|
workers: [
|
|
@@ -134,7 +135,7 @@ class WorkerService {
|
|
|
134
135
|
options: config.connection.options
|
|
135
136
|
};
|
|
136
137
|
const hotMeshWorker = await hotmesh_1.HotMeshService.init({
|
|
137
|
-
logLevel: config.options?.logLevel ??
|
|
138
|
+
logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
138
139
|
appId: config.namespace ?? factory_1.APP_ID,
|
|
139
140
|
engine: { redis: redisConfig },
|
|
140
141
|
workers: [{
|
|
@@ -260,7 +261,7 @@ WorkerService.getHotMesh = async (workflowTopic, config, options) => {
|
|
|
260
261
|
return await WorkerService.instances.get(workflowTopic);
|
|
261
262
|
}
|
|
262
263
|
const hotMeshClient = hotmesh_1.HotMeshService.init({
|
|
263
|
-
logLevel: options?.logLevel ??
|
|
264
|
+
logLevel: options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
264
265
|
appId: config.namespace ?? factory_1.APP_ID,
|
|
265
266
|
engine: { redis: { ...WorkerService.connection } }
|
|
266
267
|
});
|
|
@@ -21,6 +21,7 @@ import { RedisClient, RedisMulti } from '../../types/redis';
|
|
|
21
21
|
import { StringAnyType } from '../../types/serializer';
|
|
22
22
|
import { GetStatsOptions, IdsResponse, JobStatsInput, StatsResponse } from '../../types/stats';
|
|
23
23
|
import { StreamCode, StreamData, StreamDataResponse, StreamError, StreamStatus } from '../../types/stream';
|
|
24
|
+
import { WorkListTaskType } from '../../types/task';
|
|
24
25
|
declare class EngineService {
|
|
25
26
|
namespace: string;
|
|
26
27
|
apps: HotMeshApps | null;
|
|
@@ -65,7 +66,7 @@ declare class EngineService {
|
|
|
65
66
|
interrupt(topic: string, jobId: string, options?: JobInterruptOptions): Promise<string>;
|
|
66
67
|
scrub(jobId: string): Promise<void>;
|
|
67
68
|
hook(topic: string, data: JobData, status?: StreamStatus, code?: StreamCode): Promise<string>;
|
|
68
|
-
hookTime(jobId: string, gId: string, activityId: string, type?:
|
|
69
|
+
hookTime(jobId: string, gId: string, activityId: string, type?: WorkListTaskType): Promise<string | void>;
|
|
69
70
|
hookAll(hookTopic: string, data: JobData, keyResolver: JobStatsInput, queryFacets?: string[]): Promise<string[]>;
|
|
70
71
|
pub(topic: string, data: JobData, context?: JobState): Promise<string>;
|
|
71
72
|
sub(topic: string, callback: JobMessageCallback): Promise<void>;
|
|
@@ -304,11 +304,11 @@ class EngineService {
|
|
|
304
304
|
}
|
|
305
305
|
else if (emit) {
|
|
306
306
|
streamData.status = stream_1.StreamStatus.PENDING;
|
|
307
|
-
streamData.code = enums_1.
|
|
307
|
+
streamData.code = enums_1.HMSH_CODE_PENDING;
|
|
308
308
|
}
|
|
309
309
|
else {
|
|
310
310
|
streamData.status = stream_1.StreamStatus.SUCCESS;
|
|
311
|
-
streamData.code = enums_1.
|
|
311
|
+
streamData.code = enums_1.HMSH_CODE_SUCCESS;
|
|
312
312
|
}
|
|
313
313
|
return (await this.router?.publishMessage(null, streamData));
|
|
314
314
|
}
|
|
@@ -361,7 +361,6 @@ class EngineService {
|
|
|
361
361
|
else if (type === 'expire') {
|
|
362
362
|
return await this.store.expireJob(jobId, 1);
|
|
363
363
|
}
|
|
364
|
-
//'sleep': parse the activityId into parts
|
|
365
364
|
const [aid, ...dimensions] = activityId.split(',');
|
|
366
365
|
const dad = `,${dimensions.join(',')}`;
|
|
367
366
|
const streamData = {
|
|
@@ -430,7 +429,7 @@ class EngineService {
|
|
|
430
429
|
return await this.subscribe.punsubscribe(key_1.KeyType.QUORUM, this.appId, wild);
|
|
431
430
|
}
|
|
432
431
|
//publish and await (returns the job and data (if ready)); throws error with jobid if not
|
|
433
|
-
async pubsub(topic, data, context, timeout = enums_1.
|
|
432
|
+
async pubsub(topic, data, context, timeout = enums_1.HMSH_OTT_WAIT_TIME) {
|
|
434
433
|
context = {
|
|
435
434
|
metadata: {
|
|
436
435
|
ngn: this.guid,
|
|
@@ -453,9 +452,10 @@ class EngineService {
|
|
|
453
452
|
}
|
|
454
453
|
});
|
|
455
454
|
setTimeout(() => {
|
|
455
|
+
//note: job is still active (the subscriber timed out)
|
|
456
456
|
this.delistJobCallback(jobId);
|
|
457
457
|
reject({
|
|
458
|
-
code: enums_1.
|
|
458
|
+
code: enums_1.HMSH_CODE_TIMEOUT,
|
|
459
459
|
message: 'timeout',
|
|
460
460
|
job_id: jobId
|
|
461
461
|
});
|
|
@@ -526,7 +526,7 @@ class EngineService {
|
|
|
526
526
|
* it will be expired immediately.
|
|
527
527
|
*/
|
|
528
528
|
resolveExpires(context, options) {
|
|
529
|
-
return options.expire ?? context.metadata.expire ?? enums_1.
|
|
529
|
+
return options.expire ?? context.metadata.expire ?? enums_1.HMSH_EXPIRE_JOB_SECONDS;
|
|
530
530
|
}
|
|
531
531
|
// ****** GET JOB STATE/COLLATION STATUS BY ID *********
|
|
532
532
|
async getStatus(jobId) {
|
|
@@ -4,7 +4,7 @@ import { QuorumService } from '../quorum';
|
|
|
4
4
|
import { WorkerService } from '../worker';
|
|
5
5
|
import { JobState, JobData, JobOutput, JobStatus, JobInterruptOptions } from '../../types/job';
|
|
6
6
|
import { HotMeshConfig, HotMeshManifest } from '../../types/hotmesh';
|
|
7
|
-
import { JobMessageCallback } from '../../types/quorum';
|
|
7
|
+
import { JobMessageCallback, QuorumProfile } from '../../types/quorum';
|
|
8
8
|
import { JobStatsInput, GetStatsOptions, IdsResponse, StatsResponse } from '../../types/stats';
|
|
9
9
|
import { StreamCode, StreamData, StreamDataResponse, StreamStatus } from '../../types/stream';
|
|
10
10
|
import { StringAnyType } from '../../types/serializer';
|
|
@@ -31,10 +31,10 @@ declare class HotMeshService {
|
|
|
31
31
|
punsub(wild: string): Promise<void>;
|
|
32
32
|
pubsub(topic: string, data?: JobData, context?: JobState | null, timeout?: number): Promise<JobOutput>;
|
|
33
33
|
add(streamData: StreamData | StreamDataResponse): Promise<string>;
|
|
34
|
+
rollCall(delay?: number): Promise<QuorumProfile[]>;
|
|
34
35
|
plan(path: string): Promise<HotMeshManifest>;
|
|
35
36
|
deploy(pathOrYAML: string): Promise<HotMeshManifest>;
|
|
36
37
|
activate(version: string, delay?: number): Promise<boolean>;
|
|
37
|
-
inventory(version: string, delay?: number): Promise<number>;
|
|
38
38
|
getStats(topic: string, query: JobStatsInput): Promise<StatsResponse>;
|
|
39
39
|
getStatus(jobId: string): Promise<JobStatus>;
|
|
40
40
|
getState(topic: string, jobId: string): Promise<JobOutput>;
|
|
@@ -90,6 +90,9 @@ class HotMeshService {
|
|
|
90
90
|
return await this.engine.add(streamData);
|
|
91
91
|
}
|
|
92
92
|
// ************* COMPILER METHODS *************
|
|
93
|
+
async rollCall(delay) {
|
|
94
|
+
return await this.quorum?.rollCall(delay);
|
|
95
|
+
}
|
|
93
96
|
async plan(path) {
|
|
94
97
|
return await this.engine?.plan(path);
|
|
95
98
|
}
|
|
@@ -100,10 +103,6 @@ class HotMeshService {
|
|
|
100
103
|
//activation is a quorum operation
|
|
101
104
|
return await this.quorum?.activate(version, delay);
|
|
102
105
|
}
|
|
103
|
-
async inventory(version, delay) {
|
|
104
|
-
//get count of all peers
|
|
105
|
-
return await this.quorum?.inventory(delay);
|
|
106
|
-
}
|
|
107
106
|
// ************* REPORTER METHODS *************
|
|
108
107
|
async getStats(topic, query) {
|
|
109
108
|
return await this.engine?.getStats(topic, query);
|
|
@@ -3,15 +3,15 @@ import { ILogger } from '../logger';
|
|
|
3
3
|
import { StoreService } from '../store';
|
|
4
4
|
import { SubService } from '../sub';
|
|
5
5
|
import { CacheMode } from '../../types/cache';
|
|
6
|
-
import { QuorumMessageCallback, SubscriptionCallback, ThrottleMessage } from '../../types/quorum';
|
|
7
|
-
import {
|
|
6
|
+
import { QuorumMessageCallback, QuorumProfile, SubscriptionCallback, ThrottleMessage } from '../../types/quorum';
|
|
7
|
+
import { HotMeshConfig } from '../../types/hotmesh';
|
|
8
8
|
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
9
9
|
declare class QuorumService {
|
|
10
10
|
namespace: string;
|
|
11
|
-
apps: HotMeshApps | null;
|
|
12
11
|
appId: string;
|
|
13
12
|
guid: string;
|
|
14
13
|
engine: EngineService;
|
|
14
|
+
profiles: QuorumProfile[];
|
|
15
15
|
store: StoreService<RedisClient, RedisMulti> | null;
|
|
16
16
|
subscribe: SubService<RedisClient, RedisMulti> | null;
|
|
17
17
|
logger: ILogger;
|
|
@@ -24,12 +24,12 @@ declare class QuorumService {
|
|
|
24
24
|
initStoreChannel(store: RedisClient): Promise<void>;
|
|
25
25
|
initSubChannel(sub: RedisClient): Promise<void>;
|
|
26
26
|
subscriptionHandler(): SubscriptionCallback;
|
|
27
|
-
sayPong(appId: string, guid: string, originator: string): Promise<void>;
|
|
28
|
-
requestQuorum(delay?: number): Promise<number>;
|
|
27
|
+
sayPong(appId: string, guid: string, originator: string, details?: boolean): Promise<void>;
|
|
28
|
+
requestQuorum(delay?: number, details?: boolean): Promise<number>;
|
|
29
29
|
pub(quorumMessage: ThrottleMessage): Promise<boolean>;
|
|
30
30
|
sub(callback: QuorumMessageCallback): Promise<void>;
|
|
31
31
|
unsub(callback: QuorumMessageCallback): Promise<void>;
|
|
32
|
-
|
|
32
|
+
rollCall(delay?: number): Promise<QuorumProfile[]>;
|
|
33
33
|
activate(version: string, delay?: number): Promise<boolean>;
|
|
34
34
|
}
|
|
35
35
|
export { QuorumService };
|
|
@@ -12,6 +12,7 @@ const redis_2 = require("../sub/clients/redis");
|
|
|
12
12
|
const QUORUM_DELAY = 250;
|
|
13
13
|
class QuorumService {
|
|
14
14
|
constructor() {
|
|
15
|
+
this.profiles = [];
|
|
15
16
|
this.cacheMode = 'cache';
|
|
16
17
|
this.untilVersion = null;
|
|
17
18
|
this.quorum = null;
|
|
@@ -68,10 +69,13 @@ class QuorumService {
|
|
|
68
69
|
self.engine.setCacheMode(message.cache_mode, message.until_version);
|
|
69
70
|
}
|
|
70
71
|
else if (message.type === 'ping') {
|
|
71
|
-
|
|
72
|
+
self.sayPong(self.appId, self.guid, message.originator, message.details);
|
|
72
73
|
}
|
|
73
74
|
else if (message.type === 'pong' && self.guid === message.originator) {
|
|
74
75
|
self.quorum = self.quorum + 1;
|
|
76
|
+
if (message.profile) {
|
|
77
|
+
self.profiles.push(message.profile);
|
|
78
|
+
}
|
|
75
79
|
}
|
|
76
80
|
else if (message.type === 'throttle') {
|
|
77
81
|
self.engine.throttle(message.throttle);
|
|
@@ -91,13 +95,31 @@ class QuorumService {
|
|
|
91
95
|
}
|
|
92
96
|
};
|
|
93
97
|
}
|
|
94
|
-
async sayPong(appId, guid, originator) {
|
|
95
|
-
|
|
98
|
+
async sayPong(appId, guid, originator, details = false) {
|
|
99
|
+
let profile;
|
|
100
|
+
if (details) {
|
|
101
|
+
profile = {
|
|
102
|
+
engine_id: this.guid,
|
|
103
|
+
namespace: this.namespace,
|
|
104
|
+
app_id: this.appId,
|
|
105
|
+
stream: this.engine.stream.mintKey(key_1.KeyType.STREAMS, { appId: this.appId })
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
this.store.publish(key_1.KeyType.QUORUM, {
|
|
109
|
+
type: 'pong',
|
|
110
|
+
guid, originator,
|
|
111
|
+
profile,
|
|
112
|
+
}, appId);
|
|
96
113
|
}
|
|
97
|
-
async requestQuorum(delay = QUORUM_DELAY) {
|
|
114
|
+
async requestQuorum(delay = QUORUM_DELAY, details = false) {
|
|
98
115
|
const quorum = this.quorum;
|
|
99
116
|
this.quorum = 0;
|
|
100
|
-
|
|
117
|
+
this.profiles.length = 0;
|
|
118
|
+
await this.store.publish(key_1.KeyType.QUORUM, {
|
|
119
|
+
type: 'ping',
|
|
120
|
+
originator: this.guid,
|
|
121
|
+
details,
|
|
122
|
+
}, this.appId);
|
|
101
123
|
await (0, utils_1.sleepFor)(delay);
|
|
102
124
|
return quorum;
|
|
103
125
|
}
|
|
@@ -117,12 +139,26 @@ class QuorumService {
|
|
|
117
139
|
this.callbacks = this.callbacks.filter(cb => cb !== callback);
|
|
118
140
|
}
|
|
119
141
|
// ************* COMPILER METHODS *************
|
|
120
|
-
async
|
|
121
|
-
await this.requestQuorum(delay);
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
async rollCall(delay = QUORUM_DELAY) {
|
|
143
|
+
await this.requestQuorum(delay, true);
|
|
144
|
+
const targetStreams = [];
|
|
145
|
+
const multi = this.store.getMulti();
|
|
146
|
+
this.profiles.forEach((profile) => {
|
|
147
|
+
if (!targetStreams.includes(profile.stream)) {
|
|
148
|
+
targetStreams.push(profile.stream);
|
|
149
|
+
this.store.xlen(profile.stream, multi);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const stream_depths = await multi.exec();
|
|
153
|
+
this.profiles.forEach(async (profile) => {
|
|
154
|
+
const index = targetStreams.indexOf(profile.stream);
|
|
155
|
+
if (index != -1) {
|
|
156
|
+
profile.stream_depth = Array.isArray(stream_depths[index]) ?
|
|
157
|
+
stream_depths[index][1] :
|
|
158
|
+
stream_depths[index];
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return this.profiles;
|
|
126
162
|
}
|
|
127
163
|
async activate(version, delay = QUORUM_DELAY) {
|
|
128
164
|
version = version.toString();
|
|
@@ -17,8 +17,8 @@ class Router {
|
|
|
17
17
|
this.topic = config.topic;
|
|
18
18
|
this.stream = stream;
|
|
19
19
|
this.store = store;
|
|
20
|
-
this.reclaimDelay = config.reclaimDelay || enums_1.
|
|
21
|
-
this.reclaimCount = config.reclaimCount || enums_1.
|
|
20
|
+
this.reclaimDelay = config.reclaimDelay || enums_1.HMSH_XCLAIM_DELAY_MS;
|
|
21
|
+
this.reclaimCount = config.reclaimCount || enums_1.HMSH_XCLAIM_COUNT;
|
|
22
22
|
this.logger = logger;
|
|
23
23
|
}
|
|
24
24
|
async createGroup(stream, group) {
|
|
@@ -48,7 +48,9 @@ class Router {
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
try {
|
|
51
|
-
|
|
51
|
+
//randomizer that asymptotes at 150% of `HMSH_BLOCK_TIME_MS`
|
|
52
|
+
const streamDuration = enums_1.HMSH_BLOCK_TIME_MS + Math.round((enums_1.HMSH_BLOCK_TIME_MS * Math.random()));
|
|
53
|
+
const result = await this.stream.xreadgroup('GROUP', group, consumer, 'BLOCK', streamDuration, 'STREAMS', stream, '>');
|
|
52
54
|
if (this.isStreamMessage(result)) {
|
|
53
55
|
const [[, messages]] = result;
|
|
54
56
|
for (const [id, message] of messages) {
|
|
@@ -70,7 +72,7 @@ class Router {
|
|
|
70
72
|
if (this.shouldConsume && process.env.NODE_ENV !== 'test') {
|
|
71
73
|
this.logger.error(`stream-consume-message-error`, { err, stream, group, consumer });
|
|
72
74
|
this.errorCount++;
|
|
73
|
-
const timeout = Math.min(enums_1.
|
|
75
|
+
const timeout = Math.min(enums_1.HMSH_GRADUATED_INTERVAL_MS * (2 ** this.errorCount), enums_1.HMSH_MAX_TIMEOUT_MS);
|
|
74
76
|
setTimeout(consume.bind(this), timeout);
|
|
75
77
|
}
|
|
76
78
|
}
|
|
@@ -90,7 +92,7 @@ class Router {
|
|
|
90
92
|
telemetry.startStreamSpan(input, this.role);
|
|
91
93
|
output = await this.execStreamLeg(input, stream, id, callback.bind(this));
|
|
92
94
|
if (output?.status === stream_1.StreamStatus.ERROR) {
|
|
93
|
-
telemetry.setStreamError(`Function Status Code ${output.code || enums_1.
|
|
95
|
+
telemetry.setStreamError(`Function Status Code ${output.code || enums_1.HMSH_CODE_UNKNOWN}`);
|
|
94
96
|
}
|
|
95
97
|
this.errorCount = 0;
|
|
96
98
|
}
|
|
@@ -153,7 +155,7 @@ class Router {
|
|
|
153
155
|
const errorCode = output.code.toString();
|
|
154
156
|
const policy = policies?.[errorCode];
|
|
155
157
|
const maxRetries = policy?.[0];
|
|
156
|
-
const tryCount = Math.min(input.metadata.try || 0, enums_1.
|
|
158
|
+
const tryCount = Math.min(input.metadata.try || 0, enums_1.HMSH_MAX_RETRIES);
|
|
157
159
|
//only possible values for maxRetries are 1, 2, 3
|
|
158
160
|
//only possible values for tryCount are 0, 1, 2
|
|
159
161
|
if (maxRetries > tryCount) {
|
|
@@ -168,7 +170,7 @@ class Router {
|
|
|
168
170
|
error.message = err.message;
|
|
169
171
|
}
|
|
170
172
|
else {
|
|
171
|
-
error.message = enums_1.
|
|
173
|
+
error.message = enums_1.HMSH_STATUS_UNKNOWN;
|
|
172
174
|
}
|
|
173
175
|
if (typeof err.stack === 'string') {
|
|
174
176
|
error.stack = err.stack;
|
|
@@ -178,14 +180,14 @@ class Router {
|
|
|
178
180
|
}
|
|
179
181
|
return {
|
|
180
182
|
status: 'error',
|
|
181
|
-
code: enums_1.
|
|
183
|
+
code: enums_1.HMSH_CODE_UNKNOWN,
|
|
182
184
|
metadata: { ...input.metadata, guid: (0, utils_1.guid)() },
|
|
183
185
|
data: error
|
|
184
186
|
};
|
|
185
187
|
}
|
|
186
188
|
structureUnacknowledgedError(input) {
|
|
187
189
|
const message = 'stream message max delivery count exceeded';
|
|
188
|
-
const code = enums_1.
|
|
190
|
+
const code = enums_1.HMSH_CODE_UNACKED;
|
|
189
191
|
const data = { message, code };
|
|
190
192
|
const output = {
|
|
191
193
|
metadata: { ...input.metadata, guid: (0, utils_1.guid)() },
|
|
@@ -198,9 +200,9 @@ class Router {
|
|
|
198
200
|
return output;
|
|
199
201
|
}
|
|
200
202
|
structureError(input, output) {
|
|
201
|
-
const message = output.data?.message ? output.data?.message.toString() : enums_1.
|
|
203
|
+
const message = output.data?.message ? output.data?.message.toString() : enums_1.HMSH_STATUS_UNKNOWN;
|
|
202
204
|
const statusCode = output.code || output.data?.code;
|
|
203
|
-
const code = isNaN(statusCode) ? enums_1.
|
|
205
|
+
const code = isNaN(statusCode) ? enums_1.HMSH_CODE_UNKNOWN : parseInt(statusCode.toString());
|
|
204
206
|
const data = { message, code };
|
|
205
207
|
if (typeof output.data?.error === 'object') {
|
|
206
208
|
data.error = { ...output.data.error };
|
|
@@ -216,7 +218,7 @@ class Router {
|
|
|
216
218
|
for (const instance of [...Router.instances]) {
|
|
217
219
|
instance.stopConsuming();
|
|
218
220
|
}
|
|
219
|
-
await (0, utils_1.sleepFor)(enums_1.
|
|
221
|
+
await (0, utils_1.sleepFor)(enums_1.HMSH_BLOCK_TIME_MS * 2);
|
|
220
222
|
}
|
|
221
223
|
async stopConsuming() {
|
|
222
224
|
this.shouldConsume = false;
|
|
@@ -236,7 +238,7 @@ class Router {
|
|
|
236
238
|
this.throttle = delayInMillis;
|
|
237
239
|
this.logger.info(`stream-throttle-reset`, { delay: this.throttle, topic: this.topic });
|
|
238
240
|
}
|
|
239
|
-
async claimUnacknowledged(stream, group, consumer, idleTimeMs = this.reclaimDelay, limit = enums_1.
|
|
241
|
+
async claimUnacknowledged(stream, group, consumer, idleTimeMs = this.reclaimDelay, limit = enums_1.HMSH_XPENDING_COUNT) {
|
|
240
242
|
let pendingMessages = [];
|
|
241
243
|
const pendingMessagesInfo = await this.stream.xpending(stream, group, '-', '+', limit); //[[ '1688768134881-0', 'testConsumer1', 1017, 1 ]]
|
|
242
244
|
for (const pendingMessageInfo of pendingMessagesInfo) {
|
|
@@ -265,7 +267,7 @@ class Router {
|
|
|
265
267
|
// ii) corrupt hardware/network/transport/etc
|
|
266
268
|
// 3b) system error: Redis unable to accept `xadd` request
|
|
267
269
|
// 4c) system error: Redis unable to accept `xdel`/`xack` request
|
|
268
|
-
this.logger.error('stream-message-max-delivery-count-exceeded', { id, stream, group, consumer, code: enums_1.
|
|
270
|
+
this.logger.error('stream-message-max-delivery-count-exceeded', { id, stream, group, consumer, code: enums_1.HMSH_CODE_UNACKED, count });
|
|
269
271
|
const streamData = reclaimedMessage[0]?.[1]?.[1];
|
|
270
272
|
//fatal risk point 1 of 3): json is corrupt
|
|
271
273
|
const [err, input] = this.parseStreamData(streamData);
|
|
@@ -24,5 +24,6 @@ declare class IORedisStoreService extends StoreService<RedisClientType, RedisMul
|
|
|
24
24
|
xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
25
25
|
xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
26
26
|
xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
27
|
+
xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
27
28
|
}
|
|
28
29
|
export { IORedisStoreService };
|
|
@@ -105,5 +105,14 @@ class IORedisStoreService extends index_1.StoreService {
|
|
|
105
105
|
throw error;
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
async xlen(key, multi) {
|
|
109
|
+
try {
|
|
110
|
+
return await (multi || this.redisClient).xlen(key);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
108
117
|
}
|
|
109
118
|
exports.IORedisStoreService = IORedisStoreService;
|
|
@@ -26,5 +26,6 @@ declare class RedisStoreService extends StoreService<RedisClientType, RedisMulti
|
|
|
26
26
|
xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
27
27
|
xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
28
28
|
xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
29
|
+
xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
29
30
|
}
|
|
30
31
|
export { RedisStoreService };
|
|
@@ -30,6 +30,7 @@ class RedisStoreService extends index_1.StoreService {
|
|
|
30
30
|
rpush: 'RPUSH',
|
|
31
31
|
xack: 'XACK',
|
|
32
32
|
xdel: 'XDEL',
|
|
33
|
+
xlen: 'XLEN',
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
getMulti() {
|
|
@@ -141,5 +142,20 @@ class RedisStoreService extends index_1.StoreService {
|
|
|
141
142
|
throw error;
|
|
142
143
|
}
|
|
143
144
|
}
|
|
145
|
+
async xlen(key, multi) {
|
|
146
|
+
try {
|
|
147
|
+
if (multi) {
|
|
148
|
+
multi.XLEN(key);
|
|
149
|
+
return multi;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
return await this.redisClient.XLEN(key);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
144
160
|
}
|
|
145
161
|
exports.RedisStoreService = RedisStoreService;
|
|
@@ -11,6 +11,7 @@ import { IdsData, JobStatsRange, StatsType } from '../../types/stats';
|
|
|
11
11
|
import { Transitions } from '../../types/transition';
|
|
12
12
|
import { ReclaimedMessageType } from '../../types/stream';
|
|
13
13
|
import { JobCompletionOptions, JobInterruptOptions } from '../../types/job';
|
|
14
|
+
import { WorkListTaskType } from '../../types/task';
|
|
14
15
|
interface AbstractRedisClient {
|
|
15
16
|
exec(): any;
|
|
16
17
|
}
|
|
@@ -31,6 +32,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
31
32
|
abstract xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
32
33
|
abstract xack(key: string, group: string, id: string, multi?: U): Promise<number | U>;
|
|
33
34
|
abstract xdel(key: string, id: string, multi?: U): Promise<number | U>;
|
|
35
|
+
abstract xlen(key: string, multi?: U): Promise<number | U>;
|
|
34
36
|
constructor(redisClient: T);
|
|
35
37
|
init(namespace: string, appId: string, logger: ILogger): Promise<HotMeshApps>;
|
|
36
38
|
isSuccessful(result: any): boolean;
|
|
@@ -61,12 +63,16 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
61
63
|
activateAppVersion(id: string, version: string): Promise<boolean>;
|
|
62
64
|
registerAppVersion(appId: string, version: string): Promise<any>;
|
|
63
65
|
/**
|
|
64
|
-
* Registers jobId with
|
|
65
|
-
* when originJobId is interrupted
|
|
66
|
-
* list (added via RPUSH)
|
|
67
|
-
* LPOPed items from the list are likewise expired;
|
|
66
|
+
* Registers the job, `jobId`, with `originJobId`. In the future,
|
|
67
|
+
* when `originJobId` is interrupted/expired, the items in the
|
|
68
|
+
* list (added via RPUSH) will be interrupted/expired (removed via LPOPed).
|
|
68
69
|
*/
|
|
69
|
-
|
|
70
|
+
registerJobDependency(originJobId: string, topic: string, jobId: string, gId: string, multi?: U): Promise<any>;
|
|
71
|
+
/**
|
|
72
|
+
* Ensures a `hook signal` is delisted when its parent activity/job
|
|
73
|
+
* is interrupted/expired.
|
|
74
|
+
*/
|
|
75
|
+
registerSignalDependency(jobId: string, signalKey: string, multi?: U): Promise<any>;
|
|
70
76
|
setStats(jobKey: string, jobId: string, dateTime: string, stats: StatsType, appVersion: AppVID, multi?: U): Promise<any>;
|
|
71
77
|
hGetAllResult(result: any): any;
|
|
72
78
|
getJobStats(jobKeys: string[]): Promise<JobStatsRange>;
|
|
@@ -105,15 +111,15 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
105
111
|
* expired at a future date; options indicate whether this
|
|
106
112
|
* is a standard `expire` or an `interrupt`
|
|
107
113
|
*/
|
|
108
|
-
|
|
114
|
+
registerDependenciesForCleanup(jobId: string, deletionTime: number, options: JobCompletionOptions): Promise<void>;
|
|
109
115
|
/**
|
|
110
116
|
* registers a hook activity to be awakened (uses ZSET to
|
|
111
117
|
* store the 'sleep group' and LIST to store the events
|
|
112
118
|
* for the given sleep group. Sleep groups are
|
|
113
119
|
* organized into 'n'-second blocks (LISTS))
|
|
114
120
|
*/
|
|
115
|
-
registerTimeHook(jobId: string, gId: string, activityId: string, type:
|
|
116
|
-
|
|
121
|
+
registerTimeHook(jobId: string, gId: string, activityId: string, type: WorkListTaskType, deletionTime: number, multi?: U): Promise<void>;
|
|
122
|
+
getNextTask(listKey?: string): Promise<[listKey: string, jobId: string, gId: string, activityId: string, type: WorkListTaskType] | boolean>;
|
|
117
123
|
/**
|
|
118
124
|
* when processing time jobs, the target LIST ID returned
|
|
119
125
|
* from the ZSET query can be prefixed to denote what to
|
|
@@ -122,7 +128,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
122
128
|
* generic LIST (lists typically contain target job ids)
|
|
123
129
|
* @param {string} listKey - for example `::INTERRUPT::job123` or `job123`
|
|
124
130
|
*/
|
|
125
|
-
|
|
131
|
+
resolveTaskKeyContext(listKey: string): [('sleep' | 'expire' | 'interrupt' | 'delist'), string];
|
|
126
132
|
/**
|
|
127
133
|
* Interrupts a job and sets sets a job error (410), if 'throw'!=false.
|
|
128
134
|
* This method is called by the engine and not by an activity and is
|
|
@@ -104,7 +104,7 @@ class StoreService {
|
|
|
104
104
|
* check for and process work items in the
|
|
105
105
|
* time and signal task queues.
|
|
106
106
|
*/
|
|
107
|
-
async reserveScoutRole(scoutType, delay = enums_1.
|
|
107
|
+
async reserveScoutRole(scoutType, delay = enums_1.HMSH_SCOUT_INTERVAL_SECONDS) {
|
|
108
108
|
const key = this.mintKey(key_1.KeyType.WORK_ITEMS, { appId: this.appId, scoutType });
|
|
109
109
|
const success = await this.redisClient[this.commands.setnx](key, `${scoutType}:${(0, utils_1.formatISODate)(new Date())}`);
|
|
110
110
|
if (this.isSuccessful(success)) {
|
|
@@ -308,16 +308,35 @@ class StoreService {
|
|
|
308
308
|
return await this.redisClient[this.commands.hset](key, payload);
|
|
309
309
|
}
|
|
310
310
|
/**
|
|
311
|
-
* Registers jobId with
|
|
312
|
-
* when originJobId is interrupted
|
|
313
|
-
* list (added via RPUSH)
|
|
314
|
-
* LPOPed items from the list are likewise expired;
|
|
311
|
+
* Registers the job, `jobId`, with `originJobId`. In the future,
|
|
312
|
+
* when `originJobId` is interrupted/expired, the items in the
|
|
313
|
+
* list (added via RPUSH) will be interrupted/expired (removed via LPOPed).
|
|
315
314
|
*/
|
|
316
|
-
async
|
|
315
|
+
async registerJobDependency(originJobId, topic, jobId, gId, multi) {
|
|
317
316
|
const privateMulti = multi || this.getMulti();
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
317
|
+
const dependencyParams = {
|
|
318
|
+
appId: this.appId,
|
|
319
|
+
jobId: originJobId,
|
|
320
|
+
};
|
|
321
|
+
const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, dependencyParams);
|
|
322
|
+
//tasks have '4' segments
|
|
323
|
+
const expireTask = `expire::${topic}::${gId}::${jobId}`;
|
|
324
|
+
privateMulti[this.commands.rpush](depKey, expireTask);
|
|
325
|
+
if (!multi) {
|
|
326
|
+
return await privateMulti.exec();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Ensures a `hook signal` is delisted when its parent activity/job
|
|
331
|
+
* is interrupted/expired.
|
|
332
|
+
*/
|
|
333
|
+
async registerSignalDependency(jobId, signalKey, multi) {
|
|
334
|
+
const privateMulti = multi || this.getMulti();
|
|
335
|
+
const dependencyParams = { appId: this.appId, jobId };
|
|
336
|
+
const dependencyKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, dependencyParams);
|
|
337
|
+
//tasks have '4' segments
|
|
338
|
+
const delistTask = `delist::signal::${jobId}::${signalKey}`;
|
|
339
|
+
privateMulti[this.commands.rpush](dependencyKey, delistTask);
|
|
321
340
|
if (!multi) {
|
|
322
341
|
return await privateMulti.exec();
|
|
323
342
|
}
|
|
@@ -612,11 +631,11 @@ class StoreService {
|
|
|
612
631
|
}
|
|
613
632
|
async setHookSignal(hook, multi) {
|
|
614
633
|
const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
|
|
615
|
-
const { topic, resolved, jobId } = hook;
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
return await
|
|
634
|
+
const { topic, resolved, jobId } = hook; //`${activityId}::${dad}::${gId}::${jobId}`
|
|
635
|
+
const signalKey = `${topic}:${resolved}`;
|
|
636
|
+
const payload = { [signalKey]: jobId };
|
|
637
|
+
await (multi || this.redisClient)[this.commands.hset](key, payload);
|
|
638
|
+
return await this.registerSignalDependency(jobId.split('::')[3], signalKey, multi);
|
|
620
639
|
}
|
|
621
640
|
async getHookSignal(topic, resolved) {
|
|
622
641
|
const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
|
|
@@ -677,7 +696,7 @@ class StoreService {
|
|
|
677
696
|
* expired at a future date; options indicate whether this
|
|
678
697
|
* is a standard `expire` or an `interrupt`
|
|
679
698
|
*/
|
|
680
|
-
async
|
|
699
|
+
async registerDependenciesForCleanup(jobId, deletionTime, options) {
|
|
681
700
|
const depParams = { appId: this.appId, jobId };
|
|
682
701
|
const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, depParams);
|
|
683
702
|
const context = options.interrupt ? 'INTERRUPT' : 'EXPIRE';
|
|
@@ -700,21 +719,25 @@ class StoreService {
|
|
|
700
719
|
await this.zAdd(zsetKey, deletionTime.toString(), listKey, multi);
|
|
701
720
|
}
|
|
702
721
|
}
|
|
703
|
-
async
|
|
704
|
-
const existing = Boolean(listKey);
|
|
722
|
+
async getNextTask(listKey) {
|
|
705
723
|
const zsetKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId });
|
|
706
724
|
listKey = listKey || await this.zRangeByScore(zsetKey, 0, Date.now());
|
|
707
725
|
if (listKey) {
|
|
708
|
-
|
|
726
|
+
let [pType, pKey] = this.resolveTaskKeyContext(listKey);
|
|
709
727
|
const timeEvent = await this.redisClient[this.commands.lpop](pKey);
|
|
710
728
|
if (timeEvent) {
|
|
711
|
-
//there are
|
|
712
|
-
|
|
729
|
+
//there are 4 time-related task
|
|
730
|
+
//1) sleep (awaken), 2) expire, 3) interrupt, 4) delist
|
|
731
|
+
const [type, activityId, gId, ...jobId] = timeEvent.split('::');
|
|
732
|
+
if (type === 'delist') {
|
|
733
|
+
pType = 'delist';
|
|
734
|
+
}
|
|
713
735
|
return [listKey, jobId.join('::'), gId, activityId, pType];
|
|
714
736
|
}
|
|
715
737
|
await this.redisClient[this.commands.zrem](zsetKey, listKey);
|
|
738
|
+
return true;
|
|
716
739
|
}
|
|
717
|
-
return
|
|
740
|
+
return false;
|
|
718
741
|
}
|
|
719
742
|
/**
|
|
720
743
|
* when processing time jobs, the target LIST ID returned
|
|
@@ -724,7 +747,7 @@ class StoreService {
|
|
|
724
747
|
* generic LIST (lists typically contain target job ids)
|
|
725
748
|
* @param {string} listKey - for example `::INTERRUPT::job123` or `job123`
|
|
726
749
|
*/
|
|
727
|
-
|
|
750
|
+
resolveTaskKeyContext(listKey) {
|
|
728
751
|
if (listKey.startsWith('::INTERRUPT')) {
|
|
729
752
|
return ['interrupt', listKey.split('::')[2]];
|
|
730
753
|
}
|
|
@@ -766,7 +789,7 @@ class StoreService {
|
|
|
766
789
|
this.serializer.resetSymbols(symKeys, symVals, {});
|
|
767
790
|
//persists the standard 410 error (job is `gone`)
|
|
768
791
|
const err = JSON.stringify({
|
|
769
|
-
code: enums_1.
|
|
792
|
+
code: enums_1.HMSH_CODE_INTERRUPT,
|
|
770
793
|
message: options.reason ?? `job [${jobId}] interrupted`,
|
|
771
794
|
job_id: jobId
|
|
772
795
|
});
|
|
@@ -19,5 +19,6 @@ declare class IORedisStreamService extends StreamService<RedisClientType, RedisM
|
|
|
19
19
|
xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
20
20
|
xack(key: string, group: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
21
21
|
xdel(key: string, id: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
22
|
+
xlen(key: string, multi?: RedisMultiType): Promise<number | RedisMultiType>;
|
|
22
23
|
}
|
|
23
24
|
export { IORedisStreamService };
|