@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
|
@@ -35,6 +35,7 @@ class Trigger extends Activity {
|
|
|
35
35
|
try {
|
|
36
36
|
this.setLeg(2);
|
|
37
37
|
await this.getState();
|
|
38
|
+
|
|
38
39
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
39
40
|
telemetry.startJobSpan();
|
|
40
41
|
telemetry.startActivitySpan(this.leg);
|
|
@@ -70,7 +71,7 @@ class Trigger extends Activity {
|
|
|
70
71
|
} finally {
|
|
71
72
|
telemetry?.endJobSpan();
|
|
72
73
|
telemetry?.endActivitySpan();
|
|
73
|
-
this.logger.debug('trigger-process-end', { subscribes: this.config.subscribes, jid: this.context.metadata.jid });
|
|
74
|
+
this.logger.debug('trigger-process-end', { subscribes: this.config.subscribes, jid: this.context.metadata.jid, gid: this.context.metadata.gid });
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -108,8 +109,10 @@ class Trigger extends Activity {
|
|
|
108
109
|
this.context = {
|
|
109
110
|
metadata: {
|
|
110
111
|
...this.metadata,
|
|
112
|
+
gid: guid(),
|
|
111
113
|
ngn: this.context.metadata.ngn,
|
|
112
114
|
pj: this.context.metadata.pj,
|
|
115
|
+
pg: this.context.metadata.pg,
|
|
113
116
|
pd: this.context.metadata.pd,
|
|
114
117
|
pa: this.context.metadata.pa,
|
|
115
118
|
app: id,
|
|
@@ -187,6 +190,7 @@ class Trigger extends Activity {
|
|
|
187
190
|
resolvedDepKey,
|
|
188
191
|
this.context.metadata.tpc,
|
|
189
192
|
this.context.metadata.jid,
|
|
193
|
+
this.context.metadata.gid,
|
|
190
194
|
multi,
|
|
191
195
|
);
|
|
192
196
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GenerationalError,
|
|
3
|
+
GetStateError,
|
|
4
|
+
InactiveJobError } from '../../modules/errors';
|
|
2
5
|
import { Activity } from './activity';
|
|
3
6
|
import { CollatorService } from '../collator';
|
|
4
7
|
import { EngineService } from '../engine';
|
|
@@ -29,14 +32,11 @@ class Worker extends Activity {
|
|
|
29
32
|
|
|
30
33
|
//******** INITIAL ENTRY POINT (A) ********//
|
|
31
34
|
async process(): Promise<string> {
|
|
32
|
-
this.logger.debug('worker-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
35
|
+
this.logger.debug('worker-process', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
33
36
|
let telemetry: TelemetryService;
|
|
34
37
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await CollatorService.notarizeEntry(this);
|
|
38
|
-
await this.getState();
|
|
39
|
-
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
38
|
+
await this.verifyEntry();
|
|
39
|
+
|
|
40
40
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
41
41
|
telemetry.startActivitySpan(this.leg);
|
|
42
42
|
this.mapInputData();
|
|
@@ -63,6 +63,9 @@ class Worker extends Activity {
|
|
|
63
63
|
if (error instanceof InactiveJobError) {
|
|
64
64
|
this.logger.error('await-inactive-job-error', { error });
|
|
65
65
|
return;
|
|
66
|
+
} else if (error instanceof GenerationalError) {
|
|
67
|
+
this.logger.info('process-event-generational-job-error', { error });
|
|
68
|
+
return;
|
|
66
69
|
} else if (error instanceof GetStateError) {
|
|
67
70
|
this.logger.error('worker-get-state-error', { error });
|
|
68
71
|
return;
|
|
@@ -73,7 +76,7 @@ class Worker extends Activity {
|
|
|
73
76
|
throw error;
|
|
74
77
|
} finally {
|
|
75
78
|
telemetry?.endActivitySpan();
|
|
76
|
-
this.logger.debug('worker-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
79
|
+
this.logger.debug('worker-process-end', { jid: this.context.metadata.jid, gid: this.context.metadata.gid, aid: this.metadata.aid });
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
|
|
@@ -83,6 +86,7 @@ class Worker extends Activity {
|
|
|
83
86
|
metadata: {
|
|
84
87
|
guid: guid(),
|
|
85
88
|
jid: this.context.metadata.jid,
|
|
89
|
+
gid: this.context.metadata.gid,
|
|
86
90
|
dad: this.metadata.dad,
|
|
87
91
|
aid: this.metadata.aid,
|
|
88
92
|
topic,
|
|
@@ -96,7 +100,7 @@ class Worker extends Activity {
|
|
|
96
100
|
retry: this.config.retry
|
|
97
101
|
};
|
|
98
102
|
}
|
|
99
|
-
return (await this.engine.
|
|
103
|
+
return (await this.engine.router?.publishMessage(topic, streamData, multi)) as string;
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Durable } from '.';
|
|
2
|
-
import { asyncLocalStorage } from '
|
|
2
|
+
import { asyncLocalStorage } from '../../modules/storage';
|
|
3
3
|
import { ClientService as Client } from './client';
|
|
4
4
|
import { WorkflowHandleService } from './handle';
|
|
5
5
|
import { Search } from './search';
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
DurableSleepForError,
|
|
8
8
|
DurableTimeoutError,
|
|
9
9
|
DurableWaitForSignalError} from '../../modules/errors';
|
|
10
|
-
import { asyncLocalStorage } from '
|
|
10
|
+
import { asyncLocalStorage } from '../../modules/storage';
|
|
11
11
|
import { APP_ID, APP_VERSION, getWorkflowYAML } from './factory';
|
|
12
12
|
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
13
13
|
import {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
DurableSleepForError,
|
|
7
7
|
DurableWaitForSignalError } from '../../modules/errors';
|
|
8
8
|
import { KeyService, KeyType } from '../../modules/key';
|
|
9
|
-
import { asyncLocalStorage } from '
|
|
9
|
+
import { asyncLocalStorage } from '../../modules/storage';
|
|
10
10
|
import { ClientService as Client } from './client';
|
|
11
11
|
import { ConnectionService as Connection } from './connection';
|
|
12
12
|
import { DEFAULT_COEFFICIENT } from './factory';
|
package/services/engine/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
STATUS_CODE_SUCCESS,
|
|
5
5
|
STATUS_CODE_PENDING,
|
|
6
6
|
STATUS_CODE_TIMEOUT,
|
|
7
|
-
DURABLE_EXPIRE_SECONDS} from '../../modules/enums';
|
|
7
|
+
DURABLE_EXPIRE_SECONDS } from '../../modules/enums';
|
|
8
8
|
import {
|
|
9
9
|
formatISODate,
|
|
10
10
|
getSubscriptionTopic,
|
|
@@ -23,9 +23,8 @@ import { Trigger } from '../activities/trigger';
|
|
|
23
23
|
import { CompilerService } from '../compiler';
|
|
24
24
|
import { ILogger } from '../logger';
|
|
25
25
|
import { ReporterService } from '../reporter';
|
|
26
|
+
import { Router } from '../router';
|
|
26
27
|
import { SerializerService } from '../serializer';
|
|
27
|
-
import { StoreSignaler } from '../signaler/store';
|
|
28
|
-
import { StreamSignaler } from '../signaler/stream';
|
|
29
28
|
import { StoreService } from '../store';
|
|
30
29
|
import { RedisStoreService as RedisStore } from '../store/clients/redis';
|
|
31
30
|
import { IORedisStoreService as IORedisStore } from '../store/clients/ioredis';
|
|
@@ -84,12 +83,11 @@ class EngineService {
|
|
|
84
83
|
apps: HotMeshApps | null;
|
|
85
84
|
appId: string;
|
|
86
85
|
guid: string;
|
|
86
|
+
router: Router | null;
|
|
87
87
|
store: StoreService<RedisClient, RedisMulti> | null;
|
|
88
88
|
stream: StreamService<RedisClient, RedisMulti> | null;
|
|
89
89
|
subscribe: SubService<RedisClient, RedisMulti> | null;
|
|
90
|
-
|
|
91
|
-
streamSignaler: StreamSignaler | null;
|
|
92
|
-
task: TaskService | null;
|
|
90
|
+
taskService: TaskService | null;
|
|
93
91
|
logger: ILogger;
|
|
94
92
|
cacheMode: CacheMode = 'cache';
|
|
95
93
|
untilVersion: string | null = null;
|
|
@@ -110,9 +108,9 @@ class EngineService {
|
|
|
110
108
|
await instance.initStoreChannel(config.engine.store);
|
|
111
109
|
await instance.initSubChannel(config.engine.sub);
|
|
112
110
|
await instance.initStreamChannel(config.engine.stream);
|
|
113
|
-
instance.
|
|
111
|
+
instance.router = instance.initRouter(config);
|
|
114
112
|
|
|
115
|
-
instance.
|
|
113
|
+
instance.router.consumeMessages(
|
|
116
114
|
instance.stream.mintKey(
|
|
117
115
|
KeyType.STREAMS,
|
|
118
116
|
{ appId: instance.appId },
|
|
@@ -122,12 +120,8 @@ class EngineService {
|
|
|
122
120
|
instance.processStreamMessage.bind(instance)
|
|
123
121
|
);
|
|
124
122
|
|
|
125
|
-
//the storeSignaler service is used by the engine to create `webhooks`
|
|
126
|
-
//todo: unify/move to the task service (it manages all `signal` types)
|
|
127
|
-
instance.storeSignaler = new StoreSignaler(instance.store, logger);
|
|
128
|
-
|
|
129
123
|
//the task service is used by the engine to process `webhooks` and `timehooks`
|
|
130
|
-
instance.
|
|
124
|
+
instance.taskService = new TaskService(instance.store, logger);
|
|
131
125
|
|
|
132
126
|
return instance;
|
|
133
127
|
}
|
|
@@ -181,8 +175,8 @@ class EngineService {
|
|
|
181
175
|
);
|
|
182
176
|
}
|
|
183
177
|
|
|
184
|
-
|
|
185
|
-
return new
|
|
178
|
+
initRouter(config: HotMeshConfig): Router {
|
|
179
|
+
return new Router(
|
|
186
180
|
{
|
|
187
181
|
namespace: this.namespace,
|
|
188
182
|
appId: this.appId,
|
|
@@ -231,21 +225,20 @@ class EngineService {
|
|
|
231
225
|
}
|
|
232
226
|
|
|
233
227
|
async processWebHooks() {
|
|
234
|
-
this.
|
|
228
|
+
this.taskService.processWebHooks((this.hook).bind(this));
|
|
235
229
|
}
|
|
236
230
|
|
|
237
231
|
async processTimeHooks() {
|
|
238
|
-
this.
|
|
232
|
+
this.taskService.processTimeHooks((this.hookTime).bind(this));
|
|
239
233
|
}
|
|
240
234
|
|
|
241
235
|
async throttle(delayInMillis: number) {
|
|
242
|
-
this.
|
|
236
|
+
this.router.setThrottle(delayInMillis);
|
|
243
237
|
}
|
|
244
238
|
|
|
245
239
|
// ************* METADATA/MODEL METHODS *************
|
|
246
240
|
async initActivity(topic: string, data: JobData = {}, context?: JobState): Promise<Await|Cycle|Hook|Signal|Trigger|Worker|Interrupt> {
|
|
247
241
|
const [activityId, schema] = await this.getSchema(topic);
|
|
248
|
-
polyfill
|
|
249
242
|
const ActivityHandler = Activities[polyfill.resolveActivityType(schema.type)];
|
|
250
243
|
if (ActivityHandler) {
|
|
251
244
|
const utc = formatISODate(new Date());
|
|
@@ -329,6 +322,7 @@ class EngineService {
|
|
|
329
322
|
async processStreamMessage(streamData: StreamDataResponse): Promise<void> {
|
|
330
323
|
this.logger.debug('engine-process-stream-message', {
|
|
331
324
|
jid: streamData.metadata.jid,
|
|
325
|
+
gid: streamData.metadata.gid,
|
|
332
326
|
dad: streamData.metadata.dad,
|
|
333
327
|
aid: streamData.metadata.aid,
|
|
334
328
|
status: streamData.status || StreamStatus.SUCCESS,
|
|
@@ -338,41 +332,83 @@ class EngineService {
|
|
|
338
332
|
const context: PartialJobState = {
|
|
339
333
|
metadata: {
|
|
340
334
|
jid: streamData.metadata.jid,
|
|
335
|
+
gid: streamData.metadata.gid,
|
|
341
336
|
dad: streamData.metadata.dad,
|
|
342
337
|
aid: streamData.metadata.aid,
|
|
343
338
|
},
|
|
344
339
|
data: streamData.data,
|
|
345
340
|
};
|
|
346
|
-
if (streamData.type === StreamDataType.TIMEHOOK
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
341
|
+
if (streamData.type === StreamDataType.TIMEHOOK) {
|
|
342
|
+
//TIMEHOOK AWAKEN
|
|
343
|
+
const activityHandler = await this.initActivity(
|
|
344
|
+
`.${streamData.metadata.aid}`,
|
|
345
|
+
context.data,
|
|
346
|
+
context as JobState,
|
|
347
|
+
) as Hook;
|
|
348
|
+
await activityHandler.processTimeHookEvent(streamData.metadata.jid);
|
|
349
|
+
} else if (streamData.type === StreamDataType.WEBHOOK) {
|
|
350
|
+
//WEBHOOK AWAKEN (SIGNAL IN)
|
|
351
|
+
const activityHandler = await this.initActivity(
|
|
352
|
+
`.${streamData.metadata.aid}`,
|
|
353
|
+
context.data,
|
|
354
|
+
context as JobState,
|
|
355
|
+
) as Hook;
|
|
356
|
+
await activityHandler.processWebHookEvent(
|
|
357
|
+
streamData.status,
|
|
358
|
+
streamData.code
|
|
359
|
+
);
|
|
360
|
+
} else if (streamData.type === StreamDataType.TRANSITION) {
|
|
361
|
+
//TRANSITION (ADJACENT ACTIVITY)
|
|
362
|
+
const activityHandler = await this.initActivity(
|
|
363
|
+
`.${streamData.metadata.aid}`,
|
|
364
|
+
context.data,
|
|
365
|
+
context as JobState,
|
|
366
|
+
) as Hook; //todo: `as Activity` (type is more generic)
|
|
367
|
+
await activityHandler.process();
|
|
356
368
|
} else if (streamData.type === StreamDataType.AWAIT) {
|
|
369
|
+
//TRIGGER JOB
|
|
357
370
|
context.metadata = {
|
|
358
371
|
...context.metadata,
|
|
359
372
|
pj: streamData.metadata.jid,
|
|
373
|
+
pg: streamData.metadata.gid,
|
|
360
374
|
pd: streamData.metadata.dad,
|
|
361
375
|
pa: streamData.metadata.aid,
|
|
362
376
|
trc: streamData.metadata.trc,
|
|
363
377
|
spn: streamData.metadata.spn,
|
|
364
378
|
};
|
|
365
|
-
const activityHandler = await this.initActivity(
|
|
379
|
+
const activityHandler = await this.initActivity(
|
|
380
|
+
streamData.metadata.topic,
|
|
381
|
+
streamData.data,
|
|
382
|
+
context as JobState
|
|
383
|
+
) as Trigger;
|
|
366
384
|
await activityHandler.process();
|
|
367
385
|
} else if (streamData.type === StreamDataType.RESULT) {
|
|
368
|
-
|
|
369
|
-
await
|
|
386
|
+
//AWAIT RESULT
|
|
387
|
+
const activityHandler = await this.initActivity(
|
|
388
|
+
`.${context.metadata.aid}`,
|
|
389
|
+
streamData.data,
|
|
390
|
+
context as JobState,
|
|
391
|
+
) as Await;
|
|
392
|
+
await activityHandler.processEvent(
|
|
393
|
+
streamData.status,
|
|
394
|
+
streamData.code,
|
|
395
|
+
);
|
|
370
396
|
} else {
|
|
371
|
-
|
|
372
|
-
await
|
|
397
|
+
//WORKER RESULT
|
|
398
|
+
const activityHandler = await this.initActivity(
|
|
399
|
+
`.${streamData.metadata.aid}`,
|
|
400
|
+
streamData.data,
|
|
401
|
+
context as JobState,
|
|
402
|
+
) as Worker;
|
|
403
|
+
await activityHandler.processEvent(
|
|
404
|
+
streamData.status,
|
|
405
|
+
streamData.code,
|
|
406
|
+
'output'
|
|
407
|
+
);
|
|
373
408
|
}
|
|
374
409
|
this.logger.debug('engine-process-stream-message-end', {
|
|
375
410
|
jid: streamData.metadata.jid,
|
|
411
|
+
gid: streamData.metadata.gid,
|
|
376
412
|
aid: streamData.metadata.aid
|
|
377
413
|
});
|
|
378
414
|
}
|
|
@@ -387,6 +423,7 @@ class EngineService {
|
|
|
387
423
|
metadata: {
|
|
388
424
|
guid: guid(),
|
|
389
425
|
jid: context.metadata.pj,
|
|
426
|
+
gid: context.metadata.pg,
|
|
390
427
|
dad: context.metadata.pd,
|
|
391
428
|
aid: context.metadata.pa,
|
|
392
429
|
trc: context.metadata.trc,
|
|
@@ -406,7 +443,7 @@ class EngineService {
|
|
|
406
443
|
streamData.status = StreamStatus.SUCCESS;
|
|
407
444
|
streamData.code = STATUS_CODE_SUCCESS;
|
|
408
445
|
}
|
|
409
|
-
return (await this.
|
|
446
|
+
return (await this.router?.publishMessage(null, streamData)) as string;
|
|
410
447
|
}
|
|
411
448
|
}
|
|
412
449
|
hasParentJob(context: JobState): boolean {
|
|
@@ -437,7 +474,7 @@ class EngineService {
|
|
|
437
474
|
|
|
438
475
|
// ****************** `HOOK` ACTIVITY RE-ENTRY POINT *****************
|
|
439
476
|
async hook(topic: string, data: JobData, status: StreamStatus = StreamStatus.SUCCESS, code: StreamCode = 200): Promise<string> {
|
|
440
|
-
const hookRule = await this.
|
|
477
|
+
const hookRule = await this.taskService.getHookRule(topic);
|
|
441
478
|
const [aid] = await this.getSchema(`.${hookRule.to}`);
|
|
442
479
|
const streamData: StreamData = {
|
|
443
480
|
type: StreamDataType.WEBHOOK,
|
|
@@ -450,9 +487,9 @@ class EngineService {
|
|
|
450
487
|
},
|
|
451
488
|
data,
|
|
452
489
|
};
|
|
453
|
-
return await this.
|
|
490
|
+
return await this.router.publishMessage(null, streamData) as string;
|
|
454
491
|
}
|
|
455
|
-
async hookTime(jobId: string, activityId: string, type?: 'sleep'|'expire'|'interrupt'): Promise<string | void> {
|
|
492
|
+
async hookTime(jobId: string, gId: string, activityId: string, type?: 'sleep'|'expire'|'interrupt'): Promise<string | void> {
|
|
456
493
|
if (type === 'interrupt') {
|
|
457
494
|
return await this.interrupt(
|
|
458
495
|
activityId, //note: 'activityId' is the actually job topic
|
|
@@ -470,16 +507,17 @@ class EngineService {
|
|
|
470
507
|
metadata: {
|
|
471
508
|
guid: guid(),
|
|
472
509
|
jid: jobId,
|
|
473
|
-
|
|
510
|
+
gid: gId,
|
|
474
511
|
dad,
|
|
512
|
+
aid,
|
|
475
513
|
},
|
|
476
514
|
data: { timestamp: Date.now() },
|
|
477
515
|
};
|
|
478
|
-
await this.
|
|
516
|
+
await this.router.publishMessage(null, streamData);
|
|
479
517
|
}
|
|
480
518
|
async hookAll(hookTopic: string, data: JobData, keyResolver: JobStatsInput, queryFacets: string[] = []): Promise<string[]> {
|
|
481
519
|
const config = await this.getVID();
|
|
482
|
-
const hookRule = await this.
|
|
520
|
+
const hookRule = await this.taskService.getHookRule(hookTopic);
|
|
483
521
|
if (hookRule) {
|
|
484
522
|
const subscriptionTopic = await getSubscriptionTopic(hookRule.to, this.store, config)
|
|
485
523
|
const resolvedQuery = await this.resolveQuery(subscriptionTopic, keyResolver);
|
|
@@ -597,7 +635,7 @@ class EngineService {
|
|
|
597
635
|
}
|
|
598
636
|
}
|
|
599
637
|
async add(streamData: StreamData|StreamDataResponse): Promise<string> {
|
|
600
|
-
return await this.
|
|
638
|
+
return await this.router.publishMessage(null, streamData) as string;
|
|
601
639
|
}
|
|
602
640
|
|
|
603
641
|
registerJobCallback(jobId: string, jobCallback: JobMessageCallback) {
|
|
@@ -632,7 +670,7 @@ class EngineService {
|
|
|
632
670
|
this.pubPermSubs(context, jobOutput, options.emit);
|
|
633
671
|
}
|
|
634
672
|
if (!options.emit) {
|
|
635
|
-
this.
|
|
673
|
+
this.taskService.registerJobForCleanup(
|
|
636
674
|
context.metadata.jid,
|
|
637
675
|
this.resolveExpires(context, options),
|
|
638
676
|
options,
|
|
@@ -647,7 +685,7 @@ class EngineService {
|
|
|
647
685
|
* it will be expired immediately.
|
|
648
686
|
*/
|
|
649
687
|
resolveExpires(context: JobState, options: JobCompletionOptions): number {
|
|
650
|
-
return
|
|
688
|
+
return options.expire ?? context.metadata.expire ?? DURABLE_EXPIRE_SECONDS;
|
|
651
689
|
}
|
|
652
690
|
|
|
653
691
|
|
|
@@ -4,8 +4,8 @@ import { RedisConnection } from '../connector/clients/redis';
|
|
|
4
4
|
import { RedisConnection as IORedisConnection } from '../connector/clients/ioredis';
|
|
5
5
|
import { EngineService } from '../engine';
|
|
6
6
|
import { LoggerService, ILogger } from '../logger';
|
|
7
|
-
import { StreamSignaler } from '../signaler/stream';
|
|
8
7
|
import { QuorumService } from '../quorum';
|
|
8
|
+
import { Router } from '../router';
|
|
9
9
|
import { WorkerService } from '../worker';
|
|
10
10
|
import {
|
|
11
11
|
JobState,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
HotMeshConfig,
|
|
18
18
|
HotMeshManifest } from '../../types/hotmesh';
|
|
19
|
-
import { JobMessageCallback } from '../../types/quorum';
|
|
19
|
+
import { JobMessageCallback, QuorumProfile } from '../../types/quorum';
|
|
20
20
|
import {
|
|
21
21
|
JobStatsInput,
|
|
22
22
|
GetStatsOptions,
|
|
@@ -137,6 +137,9 @@ class HotMeshService {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// ************* COMPILER METHODS *************
|
|
140
|
+
async rollCall(delay?: number): Promise<QuorumProfile[]> {
|
|
141
|
+
return await this.quorum?.rollCall(delay);
|
|
142
|
+
}
|
|
140
143
|
async plan(path: string): Promise<HotMeshManifest> {
|
|
141
144
|
return await this.engine?.plan(path);
|
|
142
145
|
}
|
|
@@ -147,10 +150,6 @@ class HotMeshService {
|
|
|
147
150
|
//activation is a quorum operation
|
|
148
151
|
return await this.quorum?.activate(version, delay);
|
|
149
152
|
}
|
|
150
|
-
async inventory(version: string, delay?: number): Promise<number> {
|
|
151
|
-
//get count of all peers
|
|
152
|
-
return await this.quorum?.inventory(delay);
|
|
153
|
-
}
|
|
154
153
|
|
|
155
154
|
// ************* REPORTER METHODS *************
|
|
156
155
|
async getStats(topic: string, query: JobStatsInput): Promise<StatsResponse> {
|
|
@@ -193,14 +192,14 @@ class HotMeshService {
|
|
|
193
192
|
static async stop() {
|
|
194
193
|
if (!this.disconnecting) {
|
|
195
194
|
this.disconnecting = true;
|
|
196
|
-
await
|
|
195
|
+
await Router.stopConsuming();
|
|
197
196
|
await RedisConnection.disconnectAll();
|
|
198
197
|
await IORedisConnection.disconnectAll();
|
|
199
198
|
}
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
stop() {
|
|
203
|
-
this.engine?.
|
|
202
|
+
this.engine?.taskService.cancelCleanup();
|
|
204
203
|
}
|
|
205
204
|
|
|
206
205
|
async compress(terms: string[]): Promise<boolean> {
|
package/services/quorum/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { RedisClientType as IORedisClientType } from '../../types/ioredisclient'
|
|
|
14
14
|
import {
|
|
15
15
|
QuorumMessage,
|
|
16
16
|
QuorumMessageCallback,
|
|
17
|
+
QuorumProfile,
|
|
17
18
|
SubscriptionCallback,
|
|
18
19
|
ThrottleMessage
|
|
19
20
|
} from '../../types/quorum';
|
|
@@ -26,10 +27,10 @@ const QUORUM_DELAY = 250;
|
|
|
26
27
|
|
|
27
28
|
class QuorumService {
|
|
28
29
|
namespace: string;
|
|
29
|
-
apps: HotMeshApps | null;
|
|
30
30
|
appId: string;
|
|
31
31
|
guid: string;
|
|
32
32
|
engine: EngineService;
|
|
33
|
+
profiles: QuorumProfile[] = [];
|
|
33
34
|
store: StoreService<RedisClient, RedisMulti> | null;
|
|
34
35
|
subscribe: SubService<RedisClient, RedisMulti> | null;
|
|
35
36
|
logger: ILogger;
|
|
@@ -108,9 +109,12 @@ class QuorumService {
|
|
|
108
109
|
if (message.type === 'activate') {
|
|
109
110
|
self.engine.setCacheMode(message.cache_mode, message.until_version);
|
|
110
111
|
} else if (message.type === 'ping') {
|
|
111
|
-
|
|
112
|
+
self.sayPong(self.appId, self.guid, message.originator, message.details);
|
|
112
113
|
} else if (message.type === 'pong' && self.guid === message.originator) {
|
|
113
114
|
self.quorum = self.quorum + 1;
|
|
115
|
+
if (message.profile) {
|
|
116
|
+
self.profiles.push(message.profile);
|
|
117
|
+
}
|
|
114
118
|
} else if (message.type === 'throttle') {
|
|
115
119
|
self.engine.throttle(message.throttle);
|
|
116
120
|
} else if (message.type === 'work') {
|
|
@@ -127,20 +131,38 @@ class QuorumService {
|
|
|
127
131
|
};
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
async sayPong(appId: string, guid: string, originator: string) {
|
|
134
|
+
async sayPong(appId: string, guid: string, originator: string, details = false) {
|
|
135
|
+
let profile: QuorumProfile;
|
|
136
|
+
if (details) {
|
|
137
|
+
profile = {
|
|
138
|
+
engine_id: this.guid,
|
|
139
|
+
namespace: this.namespace,
|
|
140
|
+
app_id: this.appId,
|
|
141
|
+
stream: this.engine.stream.mintKey(KeyType.STREAMS, { appId: this.appId })
|
|
142
|
+
};
|
|
143
|
+
}
|
|
131
144
|
this.store.publish(
|
|
132
145
|
KeyType.QUORUM,
|
|
133
|
-
{
|
|
146
|
+
{
|
|
147
|
+
type: 'pong',
|
|
148
|
+
guid, originator,
|
|
149
|
+
profile,
|
|
150
|
+
},
|
|
134
151
|
appId,
|
|
135
152
|
);
|
|
136
153
|
}
|
|
137
154
|
|
|
138
|
-
async requestQuorum(delay = QUORUM_DELAY): Promise<number> {
|
|
155
|
+
async requestQuorum(delay = QUORUM_DELAY, details = false): Promise<number> {
|
|
139
156
|
const quorum = this.quorum;
|
|
140
157
|
this.quorum = 0;
|
|
158
|
+
this.profiles.length = 0;
|
|
141
159
|
await this.store.publish(
|
|
142
160
|
KeyType.QUORUM,
|
|
143
|
-
{
|
|
161
|
+
{
|
|
162
|
+
type: 'ping',
|
|
163
|
+
originator: this.guid,
|
|
164
|
+
details,
|
|
165
|
+
},
|
|
144
166
|
this.appId,
|
|
145
167
|
);
|
|
146
168
|
await sleepFor(delay);
|
|
@@ -166,12 +188,26 @@ class QuorumService {
|
|
|
166
188
|
|
|
167
189
|
|
|
168
190
|
// ************* COMPILER METHODS *************
|
|
169
|
-
async
|
|
170
|
-
await this.requestQuorum(delay);
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
async rollCall(delay = QUORUM_DELAY): Promise<QuorumProfile[]> {
|
|
192
|
+
await this.requestQuorum(delay, true);
|
|
193
|
+
const targetStreams = [];
|
|
194
|
+
const multi = this.store.getMulti();
|
|
195
|
+
this.profiles.forEach((profile: QuorumProfile) => {
|
|
196
|
+
if (!targetStreams.includes(profile.stream)) {
|
|
197
|
+
targetStreams.push(profile.stream);
|
|
198
|
+
this.store.xlen(profile.stream, multi);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
const stream_depths = await multi.exec() as number[];
|
|
202
|
+
this.profiles.forEach(async (profile: QuorumProfile) => {
|
|
203
|
+
const index = targetStreams.indexOf(profile.stream);
|
|
204
|
+
if (index != -1) {
|
|
205
|
+
profile.stream_depth = Array.isArray(stream_depths[index]) ?
|
|
206
|
+
stream_depths[index][1] :
|
|
207
|
+
stream_depths[index];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return this.profiles;
|
|
175
211
|
}
|
|
176
212
|
async activate(version: string, delay = QUORUM_DELAY): Promise<boolean> {
|
|
177
213
|
version = version.toString();
|
|
@@ -27,8 +27,8 @@ import {
|
|
|
27
27
|
StreamStatus
|
|
28
28
|
} from '../../types/stream';
|
|
29
29
|
|
|
30
|
-
class
|
|
31
|
-
static
|
|
30
|
+
class Router {
|
|
31
|
+
static instances: Set<Router> = new Set();
|
|
32
32
|
appId: string;
|
|
33
33
|
guid: string;
|
|
34
34
|
role: StreamRole;
|
|
@@ -70,7 +70,7 @@ class StreamSignaler {
|
|
|
70
70
|
|
|
71
71
|
async consumeMessages(stream: string, group: string, consumer: string, callback: (streamData: StreamData) => Promise<StreamDataResponse|void>): Promise<void> {
|
|
72
72
|
this.logger.info(`stream-consumer-starting`, { group, consumer, stream });
|
|
73
|
-
|
|
73
|
+
Router.instances.add(this);
|
|
74
74
|
this.shouldConsume = true;
|
|
75
75
|
await this.createGroup(stream, group);
|
|
76
76
|
let lastCheckedPendingMessagesAt = Date.now();
|
|
@@ -254,7 +254,7 @@ class StreamSignaler {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
static async stopConsuming() {
|
|
257
|
-
for (const instance of [...
|
|
257
|
+
for (const instance of [...Router.instances]) {
|
|
258
258
|
instance.stopConsuming();
|
|
259
259
|
}
|
|
260
260
|
await sleepFor(BLOCK_TIME_MS);
|
|
@@ -356,4 +356,4 @@ class StreamSignaler {
|
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
export {
|
|
359
|
+
export { Router };
|
|
@@ -19,7 +19,7 @@ export const MDATA_SYMBOLS = {
|
|
|
19
19
|
KEYS: ['au', 'err', 'l2s']
|
|
20
20
|
},
|
|
21
21
|
JOB: {
|
|
22
|
-
KEYS: ['ngn', 'tpc', 'pj', 'pd', 'pa', 'key', 'app', 'vrs', 'jid', 'aid', 'ts', 'jc', 'ju', 'js', 'err', 'trc']
|
|
22
|
+
KEYS: ['ngn', 'tpc', 'pj', 'pg', 'pd', 'pa', 'key', 'app', 'vrs', 'jid', 'gid', 'aid', 'ts', 'jc', 'ju', 'js', 'err', 'trc']
|
|
23
23
|
},
|
|
24
24
|
JOB_UPDATE: {
|
|
25
25
|
KEYS: ['ju', 'err']
|
|
@@ -131,6 +131,15 @@ class IORedisStoreService extends StoreService<RedisClientType, RedisMultiType>
|
|
|
131
131
|
throw error;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
async xlen(key: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
|
|
136
|
+
try {
|
|
137
|
+
return await (multi || this.redisClient).xlen(key);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
export { IORedisStoreService };
|
|
@@ -42,6 +42,7 @@ class RedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
|
|
|
42
42
|
rpush: 'RPUSH',
|
|
43
43
|
xack: 'XACK',
|
|
44
44
|
xdel: 'XDEL',
|
|
45
|
+
xlen: 'XLEN',
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -167,6 +168,21 @@ class RedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
|
|
|
167
168
|
throw error;
|
|
168
169
|
}
|
|
169
170
|
}
|
|
171
|
+
|
|
172
|
+
async xlen(key: string, multi? : RedisMultiType): Promise<number|RedisMultiType> {
|
|
173
|
+
try {
|
|
174
|
+
if (multi) {
|
|
175
|
+
multi.XLEN(key);
|
|
176
|
+
return multi;
|
|
177
|
+
} else {
|
|
178
|
+
return await this.redisClient.XLEN(key);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.logger.error(`Error getting stream depth: ${key}`, { error });
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
}
|
|
171
187
|
|
|
172
188
|
export { RedisStoreService };
|