@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
|
@@ -10,9 +10,8 @@ const utils_1 = require("../../modules/utils");
|
|
|
10
10
|
const activities_1 = __importDefault(require("../activities"));
|
|
11
11
|
const compiler_1 = require("../compiler");
|
|
12
12
|
const reporter_1 = require("../reporter");
|
|
13
|
+
const router_1 = require("../router");
|
|
13
14
|
const serializer_1 = require("../serializer");
|
|
14
|
-
const store_1 = require("../signaler/store");
|
|
15
|
-
const stream_1 = require("../signaler/stream");
|
|
16
15
|
const redis_1 = require("../store/clients/redis");
|
|
17
16
|
const ioredis_1 = require("../store/clients/ioredis");
|
|
18
17
|
const redis_2 = require("../stream/clients/redis");
|
|
@@ -20,7 +19,7 @@ const ioredis_2 = require("../stream/clients/ioredis");
|
|
|
20
19
|
const ioredis_3 = require("../sub/clients/ioredis");
|
|
21
20
|
const redis_3 = require("../sub/clients/redis");
|
|
22
21
|
const task_1 = require("../task");
|
|
23
|
-
const
|
|
22
|
+
const stream_1 = require("../../types/stream");
|
|
24
23
|
class EngineService {
|
|
25
24
|
constructor() {
|
|
26
25
|
this.cacheMode = 'cache';
|
|
@@ -40,13 +39,10 @@ class EngineService {
|
|
|
40
39
|
await instance.initStoreChannel(config.engine.store);
|
|
41
40
|
await instance.initSubChannel(config.engine.sub);
|
|
42
41
|
await instance.initStreamChannel(config.engine.stream);
|
|
43
|
-
instance.
|
|
44
|
-
instance.
|
|
45
|
-
//the storeSignaler service is used by the engine to create `webhooks`
|
|
46
|
-
//todo: unify/move to the task service (it manages all `signal` types)
|
|
47
|
-
instance.storeSignaler = new store_1.StoreSignaler(instance.store, logger);
|
|
42
|
+
instance.router = instance.initRouter(config);
|
|
43
|
+
instance.router.consumeMessages(instance.stream.mintKey(key_1.KeyType.STREAMS, { appId: instance.appId }), 'ENGINE', instance.guid, instance.processStreamMessage.bind(instance));
|
|
48
44
|
//the task service is used by the engine to process `webhooks` and `timehooks`
|
|
49
|
-
instance.
|
|
45
|
+
instance.taskService = new task_1.TaskService(instance.store, logger);
|
|
50
46
|
return instance;
|
|
51
47
|
}
|
|
52
48
|
}
|
|
@@ -84,12 +80,12 @@ class EngineService {
|
|
|
84
80
|
}
|
|
85
81
|
await this.stream.init(this.namespace, this.appId, this.logger);
|
|
86
82
|
}
|
|
87
|
-
|
|
88
|
-
return new
|
|
83
|
+
initRouter(config) {
|
|
84
|
+
return new router_1.Router({
|
|
89
85
|
namespace: this.namespace,
|
|
90
86
|
appId: this.appId,
|
|
91
87
|
guid: this.guid,
|
|
92
|
-
role:
|
|
88
|
+
role: stream_1.StreamRole.ENGINE,
|
|
93
89
|
reclaimDelay: config.engine.reclaimDelay,
|
|
94
90
|
reclaimCount: config.engine.reclaimCount,
|
|
95
91
|
}, this.stream, this.store, this.logger);
|
|
@@ -128,18 +124,17 @@ class EngineService {
|
|
|
128
124
|
}
|
|
129
125
|
}
|
|
130
126
|
async processWebHooks() {
|
|
131
|
-
this.
|
|
127
|
+
this.taskService.processWebHooks((this.hook).bind(this));
|
|
132
128
|
}
|
|
133
129
|
async processTimeHooks() {
|
|
134
|
-
this.
|
|
130
|
+
this.taskService.processTimeHooks((this.hookTime).bind(this));
|
|
135
131
|
}
|
|
136
132
|
async throttle(delayInMillis) {
|
|
137
|
-
this.
|
|
133
|
+
this.router.setThrottle(delayInMillis);
|
|
138
134
|
}
|
|
139
135
|
// ************* METADATA/MODEL METHODS *************
|
|
140
136
|
async initActivity(topic, data = {}, context) {
|
|
141
137
|
const [activityId, schema] = await this.getSchema(topic);
|
|
142
|
-
utils_1.polyfill;
|
|
143
138
|
const ActivityHandler = activities_1.default[utils_1.polyfill.resolveActivityType(schema.type)];
|
|
144
139
|
if (ActivityHandler) {
|
|
145
140
|
const utc = (0, utils_1.formatISODate)(new Date());
|
|
@@ -222,37 +217,43 @@ class EngineService {
|
|
|
222
217
|
async processStreamMessage(streamData) {
|
|
223
218
|
this.logger.debug('engine-process-stream-message', {
|
|
224
219
|
jid: streamData.metadata.jid,
|
|
220
|
+
gid: streamData.metadata.gid,
|
|
225
221
|
dad: streamData.metadata.dad,
|
|
226
222
|
aid: streamData.metadata.aid,
|
|
227
|
-
status: streamData.status ||
|
|
223
|
+
status: streamData.status || stream_1.StreamStatus.SUCCESS,
|
|
228
224
|
code: streamData.code || 200,
|
|
229
225
|
type: streamData.type,
|
|
230
226
|
});
|
|
231
227
|
const context = {
|
|
232
228
|
metadata: {
|
|
233
229
|
jid: streamData.metadata.jid,
|
|
230
|
+
gid: streamData.metadata.gid,
|
|
234
231
|
dad: streamData.metadata.dad,
|
|
235
232
|
aid: streamData.metadata.aid,
|
|
236
233
|
},
|
|
237
234
|
data: streamData.data,
|
|
238
235
|
};
|
|
239
|
-
if (streamData.type ===
|
|
236
|
+
if (streamData.type === stream_1.StreamDataType.TIMEHOOK) {
|
|
237
|
+
//TIMEHOOK AWAKEN
|
|
240
238
|
const activityHandler = await this.initActivity(`.${streamData.metadata.aid}`, context.data, context);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
239
|
+
await activityHandler.processTimeHookEvent(streamData.metadata.jid);
|
|
240
|
+
}
|
|
241
|
+
else if (streamData.type === stream_1.StreamDataType.WEBHOOK) {
|
|
242
|
+
//WEBHOOK AWAKEN (SIGNAL IN)
|
|
243
|
+
const activityHandler = await this.initActivity(`.${streamData.metadata.aid}`, context.data, context);
|
|
244
|
+
await activityHandler.processWebHookEvent(streamData.status, streamData.code);
|
|
245
|
+
}
|
|
246
|
+
else if (streamData.type === stream_1.StreamDataType.TRANSITION) {
|
|
247
|
+
//TRANSITION (ADJACENT ACTIVITY)
|
|
248
|
+
const activityHandler = await this.initActivity(`.${streamData.metadata.aid}`, context.data, context); //todo: `as Activity` (type is more generic)
|
|
249
|
+
await activityHandler.process();
|
|
251
250
|
}
|
|
252
|
-
else if (streamData.type ===
|
|
251
|
+
else if (streamData.type === stream_1.StreamDataType.AWAIT) {
|
|
252
|
+
//TRIGGER JOB
|
|
253
253
|
context.metadata = {
|
|
254
254
|
...context.metadata,
|
|
255
255
|
pj: streamData.metadata.jid,
|
|
256
|
+
pg: streamData.metadata.gid,
|
|
256
257
|
pd: streamData.metadata.dad,
|
|
257
258
|
pa: streamData.metadata.aid,
|
|
258
259
|
trc: streamData.metadata.trc,
|
|
@@ -261,16 +262,19 @@ class EngineService {
|
|
|
261
262
|
const activityHandler = await this.initActivity(streamData.metadata.topic, streamData.data, context);
|
|
262
263
|
await activityHandler.process();
|
|
263
264
|
}
|
|
264
|
-
else if (streamData.type ===
|
|
265
|
+
else if (streamData.type === stream_1.StreamDataType.RESULT) {
|
|
266
|
+
//AWAIT RESULT
|
|
265
267
|
const activityHandler = await this.initActivity(`.${context.metadata.aid}`, streamData.data, context);
|
|
266
268
|
await activityHandler.processEvent(streamData.status, streamData.code);
|
|
267
269
|
}
|
|
268
270
|
else {
|
|
271
|
+
//WORKER RESULT
|
|
269
272
|
const activityHandler = await this.initActivity(`.${streamData.metadata.aid}`, streamData.data, context);
|
|
270
273
|
await activityHandler.processEvent(streamData.status, streamData.code, 'output');
|
|
271
274
|
}
|
|
272
275
|
this.logger.debug('engine-process-stream-message-end', {
|
|
273
276
|
jid: streamData.metadata.jid,
|
|
277
|
+
gid: streamData.metadata.gid,
|
|
274
278
|
aid: streamData.metadata.aid
|
|
275
279
|
});
|
|
276
280
|
}
|
|
@@ -284,28 +288,29 @@ class EngineService {
|
|
|
284
288
|
metadata: {
|
|
285
289
|
guid: (0, utils_1.guid)(),
|
|
286
290
|
jid: context.metadata.pj,
|
|
291
|
+
gid: context.metadata.pg,
|
|
287
292
|
dad: context.metadata.pd,
|
|
288
293
|
aid: context.metadata.pa,
|
|
289
294
|
trc: context.metadata.trc,
|
|
290
295
|
spn,
|
|
291
296
|
},
|
|
292
|
-
type:
|
|
297
|
+
type: stream_1.StreamDataType.RESULT,
|
|
293
298
|
data: jobOutput.data,
|
|
294
299
|
};
|
|
295
300
|
if (error && error.code) {
|
|
296
|
-
streamData.status =
|
|
301
|
+
streamData.status = stream_1.StreamStatus.ERROR;
|
|
297
302
|
streamData.data = error;
|
|
298
303
|
streamData.code = error.code;
|
|
299
304
|
}
|
|
300
305
|
else if (emit) {
|
|
301
|
-
streamData.status =
|
|
306
|
+
streamData.status = stream_1.StreamStatus.PENDING;
|
|
302
307
|
streamData.code = enums_1.STATUS_CODE_PENDING;
|
|
303
308
|
}
|
|
304
309
|
else {
|
|
305
|
-
streamData.status =
|
|
310
|
+
streamData.status = stream_1.StreamStatus.SUCCESS;
|
|
306
311
|
streamData.code = enums_1.STATUS_CODE_SUCCESS;
|
|
307
312
|
}
|
|
308
|
-
return (await this.
|
|
313
|
+
return (await this.router?.publishMessage(null, streamData));
|
|
309
314
|
}
|
|
310
315
|
}
|
|
311
316
|
hasParentJob(context) {
|
|
@@ -332,11 +337,11 @@ class EngineService {
|
|
|
332
337
|
await this.store.scrub(jobId);
|
|
333
338
|
}
|
|
334
339
|
// ****************** `HOOK` ACTIVITY RE-ENTRY POINT *****************
|
|
335
|
-
async hook(topic, data, status =
|
|
336
|
-
const hookRule = await this.
|
|
340
|
+
async hook(topic, data, status = stream_1.StreamStatus.SUCCESS, code = 200) {
|
|
341
|
+
const hookRule = await this.taskService.getHookRule(topic);
|
|
337
342
|
const [aid] = await this.getSchema(`.${hookRule.to}`);
|
|
338
343
|
const streamData = {
|
|
339
|
-
type:
|
|
344
|
+
type: stream_1.StreamDataType.WEBHOOK,
|
|
340
345
|
status,
|
|
341
346
|
code,
|
|
342
347
|
metadata: {
|
|
@@ -346,9 +351,9 @@ class EngineService {
|
|
|
346
351
|
},
|
|
347
352
|
data,
|
|
348
353
|
};
|
|
349
|
-
return await this.
|
|
354
|
+
return await this.router.publishMessage(null, streamData);
|
|
350
355
|
}
|
|
351
|
-
async hookTime(jobId, activityId, type) {
|
|
356
|
+
async hookTime(jobId, gId, activityId, type) {
|
|
352
357
|
if (type === 'interrupt') {
|
|
353
358
|
return await this.interrupt(activityId, //note: 'activityId' is the actually job topic
|
|
354
359
|
jobId, { suppress: true, expire: 1 });
|
|
@@ -360,20 +365,21 @@ class EngineService {
|
|
|
360
365
|
const [aid, ...dimensions] = activityId.split(',');
|
|
361
366
|
const dad = `,${dimensions.join(',')}`;
|
|
362
367
|
const streamData = {
|
|
363
|
-
type:
|
|
368
|
+
type: stream_1.StreamDataType.TIMEHOOK,
|
|
364
369
|
metadata: {
|
|
365
370
|
guid: (0, utils_1.guid)(),
|
|
366
371
|
jid: jobId,
|
|
367
|
-
|
|
372
|
+
gid: gId,
|
|
368
373
|
dad,
|
|
374
|
+
aid,
|
|
369
375
|
},
|
|
370
376
|
data: { timestamp: Date.now() },
|
|
371
377
|
};
|
|
372
|
-
await this.
|
|
378
|
+
await this.router.publishMessage(null, streamData);
|
|
373
379
|
}
|
|
374
380
|
async hookAll(hookTopic, data, keyResolver, queryFacets = []) {
|
|
375
381
|
const config = await this.getVID();
|
|
376
|
-
const hookRule = await this.
|
|
382
|
+
const hookRule = await this.taskService.getHookRule(hookTopic);
|
|
377
383
|
if (hookRule) {
|
|
378
384
|
const subscriptionTopic = await (0, utils_1.getSubscriptionTopic)(hookRule.to, this.store, config);
|
|
379
385
|
const resolvedQuery = await this.resolveQuery(subscriptionTopic, keyResolver);
|
|
@@ -485,7 +491,7 @@ class EngineService {
|
|
|
485
491
|
}
|
|
486
492
|
}
|
|
487
493
|
async add(streamData) {
|
|
488
|
-
return await this.
|
|
494
|
+
return await this.router.publishMessage(null, streamData);
|
|
489
495
|
}
|
|
490
496
|
registerJobCallback(jobId, jobCallback) {
|
|
491
497
|
this.jobCallbacks[jobId] = jobCallback;
|
|
@@ -510,7 +516,7 @@ class EngineService {
|
|
|
510
516
|
this.pubPermSubs(context, jobOutput, options.emit);
|
|
511
517
|
}
|
|
512
518
|
if (!options.emit) {
|
|
513
|
-
this.
|
|
519
|
+
this.taskService.registerJobForCleanup(context.metadata.jid, this.resolveExpires(context, options), options);
|
|
514
520
|
}
|
|
515
521
|
return msgId;
|
|
516
522
|
}
|
|
@@ -520,7 +526,7 @@ class EngineService {
|
|
|
520
526
|
* it will be expired immediately.
|
|
521
527
|
*/
|
|
522
528
|
resolveExpires(context, options) {
|
|
523
|
-
return
|
|
529
|
+
return options.expire ?? context.metadata.expire ?? enums_1.DURABLE_EXPIRE_SECONDS;
|
|
524
530
|
}
|
|
525
531
|
// ****** GET JOB STATE/COLLATION STATUS BY ID *********
|
|
526
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>;
|
|
@@ -7,8 +7,8 @@ const redis_1 = require("../connector/clients/redis");
|
|
|
7
7
|
const ioredis_1 = require("../connector/clients/ioredis");
|
|
8
8
|
const engine_1 = require("../engine");
|
|
9
9
|
const logger_1 = require("../logger");
|
|
10
|
-
const stream_1 = require("../signaler/stream");
|
|
11
10
|
const quorum_1 = require("../quorum");
|
|
11
|
+
const router_1 = require("../router");
|
|
12
12
|
const worker_1 = require("../worker");
|
|
13
13
|
const connector_1 = require("../connector");
|
|
14
14
|
class HotMeshService {
|
|
@@ -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);
|
|
@@ -141,13 +140,13 @@ class HotMeshService {
|
|
|
141
140
|
static async stop() {
|
|
142
141
|
if (!this.disconnecting) {
|
|
143
142
|
this.disconnecting = true;
|
|
144
|
-
await
|
|
143
|
+
await router_1.Router.stopConsuming();
|
|
145
144
|
await redis_1.RedisConnection.disconnectAll();
|
|
146
145
|
await ioredis_1.RedisConnection.disconnectAll();
|
|
147
146
|
}
|
|
148
147
|
}
|
|
149
148
|
stop() {
|
|
150
|
-
this.engine?.
|
|
149
|
+
this.engine?.taskService.cancelCleanup();
|
|
151
150
|
}
|
|
152
151
|
async compress(terms) {
|
|
153
152
|
return await this.engine?.compress(terms);
|
|
@@ -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();
|
|
@@ -4,8 +4,8 @@ import { StoreService } from '../store';
|
|
|
4
4
|
import { StreamService } from '../stream';
|
|
5
5
|
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
6
6
|
import { ReclaimedMessageType, StreamConfig, StreamData, StreamDataResponse, StreamRole } from '../../types/stream';
|
|
7
|
-
declare class
|
|
8
|
-
static
|
|
7
|
+
declare class Router {
|
|
8
|
+
static instances: Set<Router>;
|
|
9
9
|
appId: string;
|
|
10
10
|
guid: string;
|
|
11
11
|
role: StreamRole;
|
|
@@ -40,4 +40,4 @@ declare class StreamSignaler {
|
|
|
40
40
|
expireUnacknowledged(reclaimedMessage: ReclaimedMessageType, stream: string, group: string, consumer: string, id: string, count: number): Promise<void>;
|
|
41
41
|
parseStreamData(str: string): [undefined, StreamData] | [Error];
|
|
42
42
|
}
|
|
43
|
-
export {
|
|
43
|
+
export { Router };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.Router = void 0;
|
|
4
4
|
const enums_1 = require("../../modules/enums");
|
|
5
5
|
const key_1 = require("../../modules/key");
|
|
6
6
|
const utils_1 = require("../../modules/utils");
|
|
7
7
|
const telemetry_1 = require("../telemetry");
|
|
8
8
|
const stream_1 = require("../../types/stream");
|
|
9
|
-
class
|
|
9
|
+
class Router {
|
|
10
10
|
constructor(config, stream, store, logger) {
|
|
11
11
|
this.throttle = 0;
|
|
12
12
|
this.errorCount = 0;
|
|
@@ -35,7 +35,7 @@ class StreamSignaler {
|
|
|
35
35
|
}
|
|
36
36
|
async consumeMessages(stream, group, consumer, callback) {
|
|
37
37
|
this.logger.info(`stream-consumer-starting`, { group, consumer, stream });
|
|
38
|
-
|
|
38
|
+
Router.instances.add(this);
|
|
39
39
|
this.shouldConsume = true;
|
|
40
40
|
await this.createGroup(stream, group);
|
|
41
41
|
let lastCheckedPendingMessagesAt = Date.now();
|
|
@@ -213,7 +213,7 @@ class StreamSignaler {
|
|
|
213
213
|
};
|
|
214
214
|
}
|
|
215
215
|
static async stopConsuming() {
|
|
216
|
-
for (const instance of [...
|
|
216
|
+
for (const instance of [...Router.instances]) {
|
|
217
217
|
instance.stopConsuming();
|
|
218
218
|
}
|
|
219
219
|
await (0, utils_1.sleepFor)(enums_1.BLOCK_TIME_MS);
|
|
@@ -311,5 +311,5 @@ class StreamSignaler {
|
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
|
-
exports.
|
|
315
|
-
|
|
314
|
+
exports.Router = Router;
|
|
315
|
+
Router.instances = new Set();
|
|
@@ -12,7 +12,7 @@ exports.MDATA_SYMBOLS = {
|
|
|
12
12
|
KEYS: ['au', 'err', 'l2s']
|
|
13
13
|
},
|
|
14
14
|
JOB: {
|
|
15
|
-
KEYS: ['ngn', 'tpc', 'pj', 'pd', 'pa', 'key', 'app', 'vrs', 'jid', 'aid', 'ts', 'jc', 'ju', 'js', 'err', 'trc']
|
|
15
|
+
KEYS: ['ngn', 'tpc', 'pj', 'pg', 'pd', 'pa', 'key', 'app', 'vrs', 'jid', 'gid', 'aid', 'ts', 'jc', 'ju', 'js', 'err', 'trc']
|
|
16
16
|
},
|
|
17
17
|
JOB_UPDATE: {
|
|
18
18
|
KEYS: ['ju', 'err']
|
|
@@ -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;
|
|
@@ -31,6 +31,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
31
31
|
abstract xclaim(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): Promise<ReclaimedMessageType>;
|
|
32
32
|
abstract xack(key: string, group: string, id: string, multi?: U): Promise<number | U>;
|
|
33
33
|
abstract xdel(key: string, id: string, multi?: U): Promise<number | U>;
|
|
34
|
+
abstract xlen(key: string, multi?: U): Promise<number | U>;
|
|
34
35
|
constructor(redisClient: T);
|
|
35
36
|
init(namespace: string, appId: string, logger: ILogger): Promise<HotMeshApps>;
|
|
36
37
|
isSuccessful(result: any): boolean;
|
|
@@ -39,7 +40,12 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
39
40
|
zRangeByScore(key: string, score: number | string, value: string | number): Promise<string | null>;
|
|
40
41
|
mintKey(type: KeyType, params: KeyStoreParams): string;
|
|
41
42
|
invalidateCache(): void;
|
|
42
|
-
|
|
43
|
+
/**
|
|
44
|
+
* At any given time only a single engine will
|
|
45
|
+
* check for and process work items in the
|
|
46
|
+
* time and signal task queues.
|
|
47
|
+
*/
|
|
48
|
+
reserveScoutRole(scoutType: 'time' | 'signal', delay?: number): Promise<boolean>;
|
|
43
49
|
getSettings(bCreate?: boolean): Promise<HotMeshSettings>;
|
|
44
50
|
setSettings(manifest: HotMeshSettings): Promise<any>;
|
|
45
51
|
reserveSymbolRange(target: string, size: number, type: 'JOB' | 'ACTIVITY'): Promise<[number, number, Symbols]>;
|
|
@@ -61,7 +67,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
61
67
|
* list (added via RPUSH) are LPOPed. If origin was expired, then
|
|
62
68
|
* LPOPed items from the list are likewise expired;
|
|
63
69
|
*/
|
|
64
|
-
setDependency(originJobId: string, topic: string, jobId: string, multi?: U): Promise<any>;
|
|
70
|
+
setDependency(originJobId: string, topic: string, jobId: string, gId: string, multi?: U): Promise<any>;
|
|
65
71
|
setStats(jobKey: string, jobId: string, dateTime: string, stats: StatsType, appVersion: AppVID, multi?: U): Promise<any>;
|
|
66
72
|
hGetAllResult(result: any): any;
|
|
67
73
|
getJobStats(jobKeys: string[]): Promise<JobStatsRange>;
|
|
@@ -107,8 +113,8 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
107
113
|
* for the given sleep group. Sleep groups are
|
|
108
114
|
* organized into 'n'-second blocks (LISTS))
|
|
109
115
|
*/
|
|
110
|
-
registerTimeHook(jobId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt', deletionTime: number, multi?: U): Promise<void>;
|
|
111
|
-
getNextTimeJob(listKey?: string): Promise<[listKey: string, jobId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt'] |
|
|
116
|
+
registerTimeHook(jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt', deletionTime: number, multi?: U): Promise<void>;
|
|
117
|
+
getNextTimeJob(listKey?: string): Promise<[listKey: string, jobId: string, gId: string, activityId: string, type: 'sleep' | 'expire' | 'interrupt'] | boolean>;
|
|
112
118
|
/**
|
|
113
119
|
* when processing time jobs, the target LIST ID returned
|
|
114
120
|
* from the ZSET query can be prefixed to denote what to
|