@hotmeshio/hotmesh 0.0.36 → 0.0.38
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 +11 -11
- package/build/modules/enums.d.ts +1 -0
- package/build/modules/enums.js +3 -1
- package/build/modules/errors.d.ts +9 -1
- package/build/modules/errors.js +12 -1
- package/build/modules/key.d.ts +20 -19
- package/build/modules/key.js +20 -20
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +10 -0
- package/build/services/activities/activity.js +28 -3
- package/build/services/activities/await.js +10 -9
- package/build/services/activities/cycle.js +10 -9
- package/build/services/activities/hook.d.ts +7 -1
- package/build/services/activities/hook.js +61 -44
- package/build/services/activities/interrupt.js +10 -9
- package/build/services/activities/signal.js +7 -7
- package/build/services/activities/trigger.js +4 -2
- package/build/services/activities/worker.js +9 -8
- package/build/services/durable/meshos.js +2 -2
- package/build/services/durable/worker.js +2 -2
- package/build/services/durable/workflow.js +17 -17
- package/build/services/engine/index.d.ts +5 -7
- package/build/services/engine/index.js +53 -47
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +6 -7
- package/build/services/quorum/index.d.ts +6 -6
- package/build/services/quorum/index.js +47 -11
- package/build/services/{signaler/stream.d.ts → router/index.d.ts} +3 -3
- package/build/services/{signaler/stream.js → router/index.js} +6 -6
- package/build/services/serializer/index.js +1 -1
- 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 +10 -4
- package/build/services/store/index.js +21 -10
- 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 +13 -4
- package/build/services/task/index.js +115 -17
- package/build/services/telemetry/index.js +6 -6
- package/build/services/worker/index.d.ts +4 -3
- package/build/services/worker/index.js +32 -8
- package/build/types/job.d.ts +2 -0
- package/build/types/quorum.d.ts +11 -1
- package/build/types/redisclient.d.ts +1 -0
- package/build/types/stream.d.ts +1 -0
- package/modules/enums.ts +3 -0
- package/modules/errors.ts +18 -0
- package/modules/key.ts +21 -20
- package/package.json +1 -1
- package/services/activities/activity.ts +44 -4
- package/services/activities/await.ts +14 -10
- package/services/activities/cycle.ts +14 -10
- package/services/activities/hook.ts +70 -47
- package/services/activities/interrupt.ts +13 -10
- package/services/activities/signal.ts +11 -8
- package/services/activities/trigger.ts +5 -1
- package/services/activities/worker.ts +13 -9
- package/services/durable/meshos.ts +1 -1
- package/services/durable/worker.ts +1 -1
- package/services/durable/workflow.ts +1 -1
- package/services/engine/index.ts +82 -44
- package/services/hotmesh/index.ts +7 -8
- package/services/quorum/index.ts +48 -12
- package/services/{signaler/stream.ts → router/index.ts} +5 -5
- package/services/serializer/index.ts +1 -1
- package/services/store/clients/ioredis.ts +9 -0
- package/services/store/clients/redis.ts +16 -0
- package/services/store/index.ts +27 -12
- 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 +120 -21
- package/services/telemetry/index.ts +6 -6
- package/services/worker/index.ts +37 -7
- package/types/job.ts +2 -0
- package/types/quorum.ts +15 -4
- package/types/redisclient.ts +1 -0
- package/types/stream.ts +6 -5
- package/build/services/signaler/store.d.ts +0 -15
- package/build/services/signaler/store.js +0 -68
- package/services/signaler/store.ts +0 -76
- /package/build/{services/durable/asyncLocalStorage.d.ts → modules/storage.d.ts} +0 -0
- /package/build/{services/durable/asyncLocalStorage.js → modules/storage.js} +0 -0
- /package/{services/durable/asyncLocalStorage.ts → modules/storage.ts} +0 -0
|
@@ -99,10 +99,19 @@ class StoreService {
|
|
|
99
99
|
invalidateCache() {
|
|
100
100
|
this.cache.invalidate();
|
|
101
101
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
/**
|
|
103
|
+
* At any given time only a single engine will
|
|
104
|
+
* check for and process work items in the
|
|
105
|
+
* time and signal task queues.
|
|
106
|
+
*/
|
|
107
|
+
async reserveScoutRole(scoutType, delay = enums_1.SCOUT_INTERVAL_SECONDS) {
|
|
108
|
+
const key = this.mintKey(key_1.KeyType.WORK_ITEMS, { appId: this.appId, scoutType });
|
|
109
|
+
const success = await this.redisClient[this.commands.setnx](key, `${scoutType}:${(0, utils_1.formatISODate)(new Date())}`);
|
|
110
|
+
if (this.isSuccessful(success)) {
|
|
111
|
+
await this.redisClient[this.commands.expire](key, delay - 1);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
106
115
|
}
|
|
107
116
|
async getSettings(bCreate = false) {
|
|
108
117
|
let settings = this.cache?.getSettings();
|
|
@@ -304,11 +313,11 @@ class StoreService {
|
|
|
304
313
|
* list (added via RPUSH) are LPOPed. If origin was expired, then
|
|
305
314
|
* LPOPed items from the list are likewise expired;
|
|
306
315
|
*/
|
|
307
|
-
async setDependency(originJobId, topic, jobId, multi) {
|
|
316
|
+
async setDependency(originJobId, topic, jobId, gId, multi) {
|
|
308
317
|
const privateMulti = multi || this.getMulti();
|
|
309
318
|
const depParams = { appId: this.appId, jobId: originJobId };
|
|
310
319
|
const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, depParams);
|
|
311
|
-
privateMulti[this.commands.rpush](depKey, `expire::${topic}::${jobId}`);
|
|
320
|
+
privateMulti[this.commands.rpush](depKey, `expire::${topic}::${gId}::${jobId}`);
|
|
312
321
|
if (!multi) {
|
|
313
322
|
return await privateMulti.exec();
|
|
314
323
|
}
|
|
@@ -682,9 +691,9 @@ class StoreService {
|
|
|
682
691
|
* for the given sleep group. Sleep groups are
|
|
683
692
|
* organized into 'n'-second blocks (LISTS))
|
|
684
693
|
*/
|
|
685
|
-
async registerTimeHook(jobId, activityId, type, deletionTime, multi) {
|
|
694
|
+
async registerTimeHook(jobId, gId, activityId, type, deletionTime, multi) {
|
|
686
695
|
const listKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId, timeValue: deletionTime });
|
|
687
|
-
const timeEvent = `${type}::${activityId}::${jobId}`;
|
|
696
|
+
const timeEvent = `${type}::${activityId}::${gId}::${jobId}`;
|
|
688
697
|
const len = await (multi || this.redisClient)[this.commands.rpush](listKey, timeEvent);
|
|
689
698
|
if (multi || len === 1) {
|
|
690
699
|
const zsetKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId });
|
|
@@ -692,6 +701,7 @@ class StoreService {
|
|
|
692
701
|
}
|
|
693
702
|
}
|
|
694
703
|
async getNextTimeJob(listKey) {
|
|
704
|
+
const existing = Boolean(listKey);
|
|
695
705
|
const zsetKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId });
|
|
696
706
|
listKey = listKey || await this.zRangeByScore(zsetKey, 0, Date.now());
|
|
697
707
|
if (listKey) {
|
|
@@ -699,11 +709,12 @@ class StoreService {
|
|
|
699
709
|
const timeEvent = await this.redisClient[this.commands.lpop](pKey);
|
|
700
710
|
if (timeEvent) {
|
|
701
711
|
//there are 3 time-related event triggers: sleep, expire, interrupt
|
|
702
|
-
const [_type, activityId, ...jobId] = timeEvent.split('::');
|
|
703
|
-
return [listKey, jobId.join('::'), activityId, pType];
|
|
712
|
+
const [_type, activityId, gId, ...jobId] = timeEvent.split('::');
|
|
713
|
+
return [listKey, jobId.join('::'), gId, activityId, pType];
|
|
704
714
|
}
|
|
705
715
|
await this.redisClient[this.commands.zrem](zsetKey, listKey);
|
|
706
716
|
}
|
|
717
|
+
return existing;
|
|
707
718
|
}
|
|
708
719
|
/**
|
|
709
720
|
* when processing time jobs, the target LIST ID returned
|
|
@@ -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 };
|
|
@@ -25,18 +25,18 @@ class IORedisStreamService extends index_1.StreamService {
|
|
|
25
25
|
try {
|
|
26
26
|
return (await this.redisClient.xgroup(command, key, groupName, id, mkStream)) === 'OK';
|
|
27
27
|
}
|
|
28
|
-
catch (
|
|
28
|
+
catch (error) {
|
|
29
29
|
this.logger.info(`Consumer group not created with MKSTREAM for key: ${key} and group: ${groupName}`);
|
|
30
|
-
throw
|
|
30
|
+
throw error;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
34
|
try {
|
|
35
35
|
return (await this.redisClient.xgroup(command, key, groupName, id)) === 'OK';
|
|
36
36
|
}
|
|
37
|
-
catch (
|
|
37
|
+
catch (error) {
|
|
38
38
|
this.logger.info(`Consumer group not created for key: ${key} and group: ${groupName}`);
|
|
39
|
-
throw
|
|
39
|
+
throw error;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -44,9 +44,9 @@ class IORedisStreamService extends index_1.StreamService {
|
|
|
44
44
|
try {
|
|
45
45
|
return await (multi || this.redisClient).xadd(key, id, messageId, messageValue);
|
|
46
46
|
}
|
|
47
|
-
catch (
|
|
48
|
-
this.logger.error(`Error publishing 'xadd'; key: ${key}`,
|
|
49
|
-
throw
|
|
47
|
+
catch (error) {
|
|
48
|
+
this.logger.error(`Error publishing 'xadd'; key: ${key}`, { error });
|
|
49
|
+
throw error;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
async xreadgroup(command, groupName, consumerName, blockOption, blockTime, streamsOption, streamName, id) {
|
|
@@ -56,9 +56,9 @@ class IORedisStreamService extends index_1.StreamService {
|
|
|
56
56
|
// @ts-ignore
|
|
57
57
|
blockOption, blockTime, streamsOption, streamName, id);
|
|
58
58
|
}
|
|
59
|
-
catch (
|
|
60
|
-
this.logger.error(`Error reading stream data [Stream ${streamName}] [Group ${groupName}]`,
|
|
61
|
-
throw
|
|
59
|
+
catch (error) {
|
|
60
|
+
this.logger.error(`Error reading stream data [Stream ${streamName}] [Group ${groupName}]`, { error });
|
|
61
|
+
throw error;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
async xpending(key, group, start, end, count, consumer) {
|
|
@@ -75,40 +75,49 @@ class IORedisStreamService extends index_1.StreamService {
|
|
|
75
75
|
try {
|
|
76
76
|
return await this.redisClient.call('XPENDING', ...args);
|
|
77
77
|
}
|
|
78
|
-
catch (
|
|
79
|
-
this.logger.error('err, args',
|
|
78
|
+
catch (error) {
|
|
79
|
+
this.logger.error('err, args', { error }, args);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
catch (
|
|
83
|
-
this.logger.error(`Error in retrieving pending messages for [stream ${key}], [group ${group}]`,
|
|
84
|
-
throw
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.logger.error(`Error in retrieving pending messages for [stream ${key}], [group ${group}]`, { error });
|
|
84
|
+
throw error;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
async xclaim(key, group, consumer, minIdleTime, id, ...args) {
|
|
88
88
|
try {
|
|
89
89
|
return await this.redisClient.xclaim(key, group, consumer, minIdleTime, id, ...args);
|
|
90
90
|
}
|
|
91
|
-
catch (
|
|
92
|
-
this.logger.error(`Error in claiming message with id: ${id} in group: ${group} for key: ${key}`,
|
|
93
|
-
throw
|
|
91
|
+
catch (error) {
|
|
92
|
+
this.logger.error(`Error in claiming message with id: ${id} in group: ${group} for key: ${key}`, { error });
|
|
93
|
+
throw error;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
async xack(key, group, id, multi) {
|
|
97
97
|
try {
|
|
98
98
|
return await (multi || this.redisClient).xack(key, group, id);
|
|
99
99
|
}
|
|
100
|
-
catch (
|
|
101
|
-
this.logger.error(`Error in acknowledging messages in group: ${group} for key: ${key}`,
|
|
102
|
-
throw
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.logger.error(`Error in acknowledging messages in group: ${group} for key: ${key}`, { error });
|
|
102
|
+
throw error;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
async xdel(key, id, multi) {
|
|
106
106
|
try {
|
|
107
107
|
return await (multi || this.redisClient).xdel(key, id);
|
|
108
108
|
}
|
|
109
|
-
catch (
|
|
110
|
-
this.logger.error(`Error in deleting messages with id: ${id} for key: ${key}`,
|
|
111
|
-
throw
|
|
109
|
+
catch (error) {
|
|
110
|
+
this.logger.error(`Error in deleting messages with id: ${id} for key: ${key}`, { error });
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async xlen(key, multi) {
|
|
115
|
+
try {
|
|
116
|
+
return await (multi || this.redisClient).xlen(key);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
120
|
+
throw error;
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
}
|
|
@@ -19,5 +19,6 @@ declare class RedisStreamService extends StreamService<RedisClientType, RedisMul
|
|
|
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 { RedisStreamService };
|
|
@@ -115,5 +115,20 @@ class RedisStreamService extends index_1.StreamService {
|
|
|
115
115
|
throw err;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
async xlen(key, multi) {
|
|
119
|
+
try {
|
|
120
|
+
if (multi) {
|
|
121
|
+
multi.XLEN(key);
|
|
122
|
+
return multi;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
return await this.redisClient.XLEN(key);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
118
133
|
}
|
|
119
134
|
exports.RedisStreamService = RedisStreamService;
|
|
@@ -17,5 +17,6 @@ declare abstract class StreamService<T, U> {
|
|
|
17
17
|
abstract xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
18
18
|
abstract xack(key: string, group: string, id: string, multi?: U): Promise<number | U>;
|
|
19
19
|
abstract xdel(key: string, id: string, multi?: U): Promise<number | U>;
|
|
20
|
+
abstract xlen(key: string, multi?: U): Promise<number | U>;
|
|
20
21
|
}
|
|
21
22
|
export { StreamService };
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { ILogger } from '../logger';
|
|
3
3
|
import { StoreService } from '../store';
|
|
4
|
-
import { HookInterface } from '../../types/hook';
|
|
5
|
-
import { JobCompletionOptions } from '../../types/job';
|
|
4
|
+
import { HookInterface, HookRule } from '../../types/hook';
|
|
5
|
+
import { JobCompletionOptions, JobState } from '../../types/job';
|
|
6
6
|
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
7
7
|
declare class TaskService {
|
|
8
8
|
store: StoreService<RedisClient, RedisMulti>;
|
|
9
9
|
logger: ILogger;
|
|
10
10
|
cleanupTimeout: NodeJS.Timeout | null;
|
|
11
|
+
isScout: boolean;
|
|
11
12
|
constructor(store: StoreService<RedisClient, RedisMulti>, logger: ILogger);
|
|
12
13
|
processWebHooks(hookEventCallback: HookInterface): Promise<void>;
|
|
13
14
|
enqueueWorkItems(keys: string[]): Promise<void>;
|
|
14
15
|
registerJobForCleanup(jobId: string, inSeconds: number, options: JobCompletionOptions): Promise<void>;
|
|
15
|
-
registerTimeHook(jobId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt', inSeconds?: number, multi?: RedisMulti): Promise<void>;
|
|
16
|
-
|
|
16
|
+
registerTimeHook(jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt', inSeconds?: number, multi?: RedisMulti): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Should this engine instance play the role of 'scout' for the quorum.
|
|
19
|
+
*/
|
|
20
|
+
shouldScout(): Promise<boolean>;
|
|
21
|
+
processTimeHooks(timeEventCallback: (jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt') => Promise<void>, listKey?: string): Promise<void>;
|
|
17
22
|
cancelCleanup(): void;
|
|
23
|
+
getHookRule(topic: string): Promise<HookRule | undefined>;
|
|
24
|
+
registerWebHook(topic: string, context: JobState, dad: string, multi?: RedisMulti): Promise<string>;
|
|
25
|
+
processWebHookSignal(topic: string, data: Record<string, unknown>): Promise<[string, string, string, string] | undefined>;
|
|
26
|
+
deleteWebHookSignal(topic: string, data: Record<string, unknown>): Promise<number>;
|
|
18
27
|
}
|
|
19
28
|
export { TaskService };
|
|
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TaskService = void 0;
|
|
4
4
|
const enums_1 = require("../../modules/enums");
|
|
5
5
|
const utils_1 = require("../../modules/utils");
|
|
6
|
+
const pipe_1 = require("../pipe");
|
|
6
7
|
class TaskService {
|
|
7
8
|
constructor(store, logger) {
|
|
8
9
|
this.cleanupTimeout = null;
|
|
10
|
+
this.isScout = false;
|
|
9
11
|
this.logger = logger;
|
|
10
12
|
this.store = store;
|
|
11
13
|
}
|
|
@@ -36,29 +38,61 @@ class TaskService {
|
|
|
36
38
|
await this.store.registerExpireJob(jobId, expireTimeSlot, options);
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
|
-
async registerTimeHook(jobId, activityId, type, inSeconds = enums_1.FIDELITY_SECONDS, multi) {
|
|
41
|
+
async registerTimeHook(jobId, gId, activityId, type, inSeconds = enums_1.FIDELITY_SECONDS, multi) {
|
|
40
42
|
const awakenTimeSlot = Math.floor((Date.now() + (inSeconds * 1000)) / (enums_1.FIDELITY_SECONDS * 1000)) * (enums_1.FIDELITY_SECONDS * 1000); //n second awaken groups
|
|
41
|
-
await this.store.registerTimeHook(jobId, activityId, type, awakenTimeSlot, multi);
|
|
43
|
+
await this.store.registerTimeHook(jobId, gId, activityId, type, awakenTimeSlot, multi);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Should this engine instance play the role of 'scout' for the quorum.
|
|
47
|
+
*/
|
|
48
|
+
async shouldScout() {
|
|
49
|
+
const wasScout = this.isScout;
|
|
50
|
+
const isScout = wasScout || (this.isScout = await this.store.reserveScoutRole('time'));
|
|
51
|
+
if (isScout) {
|
|
52
|
+
if (!wasScout) {
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
this.isScout = false;
|
|
55
|
+
}, enums_1.SCOUT_INTERVAL_SECONDS * 1000);
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
42
60
|
}
|
|
43
61
|
async processTimeHooks(timeEventCallback, listKey) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
if (await this.shouldScout()) {
|
|
63
|
+
try {
|
|
64
|
+
const timeJob = await this.store.getNextTimeJob(listKey);
|
|
65
|
+
if (Array.isArray(timeJob)) {
|
|
66
|
+
//a queue had a job; try again immediately
|
|
67
|
+
const [listKey, jobId, gId, activityId, type] = timeJob;
|
|
68
|
+
await timeEventCallback(jobId, gId, activityId, type);
|
|
69
|
+
await (0, utils_1.sleepFor)(0);
|
|
70
|
+
this.processTimeHooks(timeEventCallback, listKey);
|
|
71
|
+
}
|
|
72
|
+
else if (timeJob) {
|
|
73
|
+
//a queue was just emptied; try again immediately
|
|
74
|
+
await (0, utils_1.sleepFor)(0);
|
|
75
|
+
this.processTimeHooks(timeEventCallback);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
//all queues are empty; sleep before checking
|
|
79
|
+
let sleep = (0, utils_1.XSleepFor)(enums_1.FIDELITY_SECONDS * 1000);
|
|
80
|
+
this.cleanupTimeout = sleep.timerId;
|
|
81
|
+
await sleep.promise;
|
|
82
|
+
this.processTimeHooks(timeEventCallback);
|
|
83
|
+
}
|
|
51
84
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
await sleep.promise;
|
|
56
|
-
this.processTimeHooks(timeEventCallback);
|
|
85
|
+
catch (err) {
|
|
86
|
+
//todo: retry connect to redis
|
|
87
|
+
this.logger.error('task-process-timehooks-error', err);
|
|
57
88
|
}
|
|
58
89
|
}
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
90
|
+
else {
|
|
91
|
+
//didn't get the scout role; try again in 'one-ish' minutes
|
|
92
|
+
let sleep = (0, utils_1.XSleepFor)(enums_1.SCOUT_INTERVAL_SECONDS * 1000 * 2 * Math.random());
|
|
93
|
+
this.cleanupTimeout = sleep.timerId;
|
|
94
|
+
await sleep.promise;
|
|
95
|
+
this.processTimeHooks(timeEventCallback);
|
|
62
96
|
}
|
|
63
97
|
}
|
|
64
98
|
cancelCleanup() {
|
|
@@ -67,5 +101,69 @@ class TaskService {
|
|
|
67
101
|
this.cleanupTimeout = undefined;
|
|
68
102
|
}
|
|
69
103
|
}
|
|
104
|
+
async getHookRule(topic) {
|
|
105
|
+
const rules = await this.store.getHookRules();
|
|
106
|
+
return rules?.[topic]?.[0];
|
|
107
|
+
}
|
|
108
|
+
async registerWebHook(topic, context, dad, multi) {
|
|
109
|
+
const hookRule = await this.getHookRule(topic);
|
|
110
|
+
if (hookRule) {
|
|
111
|
+
const mapExpression = hookRule.conditions.match[0].expected;
|
|
112
|
+
const resolved = pipe_1.Pipe.resolve(mapExpression, context);
|
|
113
|
+
const jobId = context.metadata.jid;
|
|
114
|
+
const gId = context.metadata.gid;
|
|
115
|
+
const activityId = hookRule.to;
|
|
116
|
+
const hook = {
|
|
117
|
+
topic,
|
|
118
|
+
resolved,
|
|
119
|
+
jobId: `${activityId}::${dad}::${gId}::${jobId}`,
|
|
120
|
+
};
|
|
121
|
+
await this.store.setHookSignal(hook, multi);
|
|
122
|
+
return jobId;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
throw new Error('signaler.registerWebHook:error: hook rule not found');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async processWebHookSignal(topic, data) {
|
|
129
|
+
const hookRule = await this.getHookRule(topic);
|
|
130
|
+
if (hookRule) {
|
|
131
|
+
//NOTE: both formats are supported by the mapping engine:
|
|
132
|
+
// `$self.hook.data` OR `$hook.data`
|
|
133
|
+
const context = { $self: { hook: { data } }, $hook: { data } };
|
|
134
|
+
const mapExpression = hookRule.conditions.match[0].actual;
|
|
135
|
+
const resolved = pipe_1.Pipe.resolve(mapExpression, context);
|
|
136
|
+
const hookSignalId = await this.store.getHookSignal(topic, resolved);
|
|
137
|
+
if (!hookSignalId) {
|
|
138
|
+
//messages can be double-processed; not an issue; return undefined
|
|
139
|
+
//users can also provide a bogus topic; not an issue; return undefined
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
//`aid` is part of composit key, but the hook `topic` is its public interface;
|
|
143
|
+
// this means that a new version of the graph can be deployed and the
|
|
144
|
+
// topic can be re-mapped to a different activity id. Outside callers
|
|
145
|
+
// can adhere to the unchanged contract (calling the same topic),
|
|
146
|
+
// while the internal system can be updated in real time as necessary.
|
|
147
|
+
const [_aid, dad, gid, ...jid] = hookSignalId.split('::');
|
|
148
|
+
return [jid.join('::'), hookRule.to, dad, gid];
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
throw new Error('signal-not-found');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async deleteWebHookSignal(topic, data) {
|
|
155
|
+
const hookRule = await this.getHookRule(topic);
|
|
156
|
+
if (hookRule) {
|
|
157
|
+
//NOTE: both formats are supported by the mapping engine:
|
|
158
|
+
// `$self.hook.data` OR `$hook.data`
|
|
159
|
+
const context = { $self: { hook: { data } }, $hook: { data } };
|
|
160
|
+
const mapExpression = hookRule.conditions.match[0].actual;
|
|
161
|
+
const resolved = pipe_1.Pipe.resolve(mapExpression, context);
|
|
162
|
+
return await this.store.deleteHookSignal(topic, resolved);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
throw new Error('signaler.process:error: hook rule not found');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
70
168
|
}
|
|
71
169
|
exports.TaskService = TaskService;
|
|
@@ -93,17 +93,17 @@ class TelemetryService {
|
|
|
93
93
|
return result;
|
|
94
94
|
}, {})
|
|
95
95
|
};
|
|
96
|
-
this.span
|
|
96
|
+
this.span?.setAttributes(namespacedAtts);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
setActivityAttributes(attributes) {
|
|
100
|
-
this.span
|
|
100
|
+
this.span?.setAttributes(attributes);
|
|
101
101
|
}
|
|
102
102
|
setStreamAttributes(attributes) {
|
|
103
|
-
this.span
|
|
103
|
+
this.span?.setAttributes(attributes);
|
|
104
104
|
}
|
|
105
105
|
setJobAttributes(attributes) {
|
|
106
|
-
this.jobSpan
|
|
106
|
+
this.jobSpan?.setAttributes(attributes);
|
|
107
107
|
}
|
|
108
108
|
endJobSpan() {
|
|
109
109
|
this.endSpan(this.jobSpan);
|
|
@@ -174,10 +174,10 @@ class TelemetryService {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
setActivityError(message) {
|
|
177
|
-
this.span
|
|
177
|
+
this.span?.setStatus({ code: telemetry_1.SpanStatusCode.ERROR, message });
|
|
178
178
|
}
|
|
179
179
|
setStreamError(message) {
|
|
180
|
-
this.span
|
|
180
|
+
this.span?.setStatus({ code: telemetry_1.SpanStatusCode.ERROR, message });
|
|
181
181
|
}
|
|
182
182
|
/**
|
|
183
183
|
* Adds the paths (HGET) necessary to restore telemetry state for an activity
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ILogger } from "../logger";
|
|
2
|
-
import {
|
|
2
|
+
import { Router } from "../router";
|
|
3
3
|
import { StoreService } from '../store';
|
|
4
4
|
import { StreamService } from '../stream';
|
|
5
5
|
import { SubService } from '../sub';
|
|
@@ -15,7 +15,7 @@ declare class WorkerService {
|
|
|
15
15
|
store: StoreService<RedisClient, RedisMulti> | null;
|
|
16
16
|
stream: StreamService<RedisClient, RedisMulti> | null;
|
|
17
17
|
subscribe: SubService<RedisClient, RedisMulti> | null;
|
|
18
|
-
|
|
18
|
+
router: Router | null;
|
|
19
19
|
logger: ILogger;
|
|
20
20
|
reporting: boolean;
|
|
21
21
|
static init(namespace: string, appId: string, guid: string, config: HotMeshConfig, logger: ILogger): Promise<WorkerService[]>;
|
|
@@ -23,8 +23,9 @@ declare class WorkerService {
|
|
|
23
23
|
initStoreChannel(service: WorkerService, store: RedisClient): Promise<void>;
|
|
24
24
|
initSubChannel(service: WorkerService, sub: RedisClient): Promise<void>;
|
|
25
25
|
initStreamChannel(service: WorkerService, stream: RedisClient): Promise<void>;
|
|
26
|
-
|
|
26
|
+
initRouter(worker: HotMeshWorker, logger: ILogger): Router;
|
|
27
27
|
subscriptionHandler(): SubscriptionCallback;
|
|
28
|
+
sayPong(appId: string, guid: string, originator: string, details?: boolean): Promise<void>;
|
|
28
29
|
throttle(delayInMillis: number): Promise<void>;
|
|
29
30
|
}
|
|
30
31
|
export { WorkerService };
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WorkerService = void 0;
|
|
4
4
|
const key_1 = require("../../modules/key");
|
|
5
|
-
const
|
|
5
|
+
const router_1 = require("../router");
|
|
6
6
|
const redis_1 = require("../store/clients/redis");
|
|
7
7
|
const ioredis_1 = require("../store/clients/ioredis");
|
|
8
8
|
const redis_2 = require("../stream/clients/redis");
|
|
9
9
|
const ioredis_2 = require("../stream/clients/ioredis");
|
|
10
10
|
const ioredis_3 = require("../sub/clients/ioredis");
|
|
11
11
|
const redis_3 = require("../sub/clients/redis");
|
|
12
|
-
const
|
|
12
|
+
const stream_1 = require("../../types/stream");
|
|
13
13
|
const utils_1 = require("../../modules/utils");
|
|
14
14
|
const connector_1 = require("../connector");
|
|
15
15
|
class WorkerService {
|
|
@@ -35,9 +35,9 @@ class WorkerService {
|
|
|
35
35
|
await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId, service.topic);
|
|
36
36
|
await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId, service.guid);
|
|
37
37
|
await service.initStreamChannel(service, worker.stream);
|
|
38
|
-
service.
|
|
38
|
+
service.router = service.initRouter(worker, logger);
|
|
39
39
|
const key = service.stream.mintKey(key_1.KeyType.STREAMS, { appId: service.appId, topic: worker.topic });
|
|
40
|
-
await service.
|
|
40
|
+
await service.router.consumeMessages(key, 'WORKER', service.guid, worker.callback);
|
|
41
41
|
services.push(service);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -78,12 +78,12 @@ class WorkerService {
|
|
|
78
78
|
}
|
|
79
79
|
await service.stream.init(service.namespace, service.appId, service.logger);
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
return new
|
|
81
|
+
initRouter(worker, logger) {
|
|
82
|
+
return new router_1.Router({
|
|
83
83
|
namespace: this.namespace,
|
|
84
84
|
appId: this.appId,
|
|
85
85
|
guid: this.guid,
|
|
86
|
-
role:
|
|
86
|
+
role: stream_1.StreamRole.WORKER,
|
|
87
87
|
topic: worker.topic,
|
|
88
88
|
reclaimDelay: worker.reclaimDelay,
|
|
89
89
|
reclaimCount: worker.reclaimCount,
|
|
@@ -96,10 +96,34 @@ class WorkerService {
|
|
|
96
96
|
if (message.type === 'throttle') {
|
|
97
97
|
self.throttle(message.throttle);
|
|
98
98
|
}
|
|
99
|
+
else if (message.type === 'ping') {
|
|
100
|
+
self.sayPong(self.appId, self.guid, message.originator, message.details);
|
|
101
|
+
}
|
|
99
102
|
};
|
|
100
103
|
}
|
|
104
|
+
async sayPong(appId, guid, originator, details = false) {
|
|
105
|
+
let profile;
|
|
106
|
+
if (details) {
|
|
107
|
+
const params = {
|
|
108
|
+
appId: this.appId,
|
|
109
|
+
topic: this.topic,
|
|
110
|
+
};
|
|
111
|
+
profile = {
|
|
112
|
+
engine_id: this.guid,
|
|
113
|
+
namespace: this.namespace,
|
|
114
|
+
app_id: this.appId,
|
|
115
|
+
worker_topic: this.topic,
|
|
116
|
+
stream: this.stream.mintKey(key_1.KeyType.STREAMS, params),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
this.store.publish(key_1.KeyType.QUORUM, {
|
|
120
|
+
type: 'pong',
|
|
121
|
+
guid, originator,
|
|
122
|
+
profile,
|
|
123
|
+
}, appId);
|
|
124
|
+
}
|
|
101
125
|
async throttle(delayInMillis) {
|
|
102
|
-
this.
|
|
126
|
+
this.router.setThrottle(delayInMillis);
|
|
103
127
|
}
|
|
104
128
|
}
|
|
105
129
|
exports.WorkerService = WorkerService;
|
package/build/types/job.d.ts
CHANGED
package/build/types/quorum.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { JobOutput } from "./job";
|
|
2
|
+
export interface QuorumProfile {
|
|
3
|
+
namespace: string;
|
|
4
|
+
app_id: string;
|
|
5
|
+
engine_id: string;
|
|
6
|
+
worker_topic?: string;
|
|
7
|
+
stream?: string;
|
|
8
|
+
stream_depth?: number;
|
|
9
|
+
}
|
|
2
10
|
export interface PingMessage {
|
|
3
11
|
type: 'ping';
|
|
4
12
|
originator: string;
|
|
13
|
+
details?: boolean;
|
|
5
14
|
}
|
|
6
15
|
export interface WorkMessage {
|
|
7
16
|
type: 'work';
|
|
@@ -13,8 +22,9 @@ export interface CronMessage {
|
|
|
13
22
|
}
|
|
14
23
|
export interface PongMessage {
|
|
15
24
|
type: 'pong';
|
|
16
|
-
originator: string;
|
|
17
25
|
guid: string;
|
|
26
|
+
originator: string;
|
|
27
|
+
profile?: QuorumProfile;
|
|
18
28
|
}
|
|
19
29
|
export interface ActivateMessage {
|
|
20
30
|
type: 'activate';
|
|
@@ -4,6 +4,7 @@ interface RedisMultiType {
|
|
|
4
4
|
XADD(key: string, id: string, fields: any): this;
|
|
5
5
|
XACK(key: string, group: string, id: string): this;
|
|
6
6
|
XDEL(key: string, id: string): this;
|
|
7
|
+
XLEN(key: string): this;
|
|
7
8
|
HDEL(key: string, itemId: string): this;
|
|
8
9
|
HGET(key: string, itemId: string): this;
|
|
9
10
|
HGETALL(key: string): this;
|
package/build/types/stream.d.ts
CHANGED