@hotmeshio/hotmesh 0.0.41 → 0.0.42
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/build/modules/errors.d.ts +1 -8
- package/build/modules/errors.js +1 -12
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +8 -1
- package/build/services/activities/activity.js +17 -12
- package/build/services/collator/index.d.ts +20 -2
- package/build/services/collator/index.js +41 -7
- package/build/services/durable/factory.d.ts +0 -1
- package/build/services/durable/factory.js +0 -138
- package/build/services/durable/worker.js +0 -15
- package/build/services/durable/workflow.d.ts +0 -9
- package/build/services/durable/workflow.js +0 -29
- package/build/services/engine/index.d.ts +1 -1
- package/build/services/engine/index.js +5 -8
- package/build/services/store/index.d.ts +11 -0
- package/build/services/store/index.js +14 -0
- package/build/types/job.d.ts +1 -0
- package/modules/errors.ts +0 -15
- package/package.json +1 -1
- package/services/activities/activity.ts +30 -15
- package/services/collator/index.ts +41 -8
- package/services/durable/factory.ts +0 -138
- package/services/durable/worker.ts +0 -16
- package/services/durable/workflow.ts +0 -32
- package/services/engine/index.ts +5 -6
- package/services/store/index.ts +15 -0
- package/types/job.ts +1 -0
|
@@ -23,13 +23,6 @@ declare class DurableWaitForSignalError extends Error {
|
|
|
23
23
|
index: number;
|
|
24
24
|
}[]);
|
|
25
25
|
}
|
|
26
|
-
declare class DurableSleepError extends Error {
|
|
27
|
-
code: number;
|
|
28
|
-
duration: number;
|
|
29
|
-
index: number;
|
|
30
|
-
dimension: string;
|
|
31
|
-
constructor(message: string, duration: number, index: number, dimension: string);
|
|
32
|
-
}
|
|
33
26
|
declare class DurableSleepForError extends Error {
|
|
34
27
|
code: number;
|
|
35
28
|
duration: number;
|
|
@@ -86,4 +79,4 @@ declare class CollationError extends Error {
|
|
|
86
79
|
fault: CollationFaultType;
|
|
87
80
|
constructor(status: number, leg: ActivityDuplex, stage: CollationStage, fault?: CollationFaultType);
|
|
88
81
|
}
|
|
89
|
-
export { CollationError, DurableFatalError, DurableIncompleteSignalError, DurableMaxedError, DurableRetryError,
|
|
82
|
+
export { CollationError, DurableFatalError, DurableIncompleteSignalError, DurableMaxedError, DurableRetryError, DurableSleepForError, DurableTimeoutError, DurableWaitForSignalError, DuplicateJobError, ExecActivityError, GenerationalError, GetStateError, InactiveJobError, MapDataError, RegisterTimeoutError, SetStateError, };
|
package/build/modules/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SetStateError = exports.RegisterTimeoutError = exports.MapDataError = exports.InactiveJobError = exports.GetStateError = exports.GenerationalError = exports.ExecActivityError = exports.DuplicateJobError = exports.DurableWaitForSignalError = exports.DurableTimeoutError = exports.DurableSleepForError = exports.
|
|
3
|
+
exports.SetStateError = exports.RegisterTimeoutError = exports.MapDataError = exports.InactiveJobError = exports.GetStateError = exports.GenerationalError = exports.ExecActivityError = exports.DuplicateJobError = exports.DurableWaitForSignalError = exports.DurableTimeoutError = exports.DurableSleepForError = exports.DurableRetryError = exports.DurableMaxedError = exports.DurableIncompleteSignalError = exports.DurableFatalError = exports.CollationError = void 0;
|
|
4
4
|
const enums_1 = require("./enums");
|
|
5
5
|
class GetStateError extends Error {
|
|
6
6
|
constructor(jobId) {
|
|
@@ -34,17 +34,6 @@ class DurableWaitForSignalError extends Error {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
exports.DurableWaitForSignalError = DurableWaitForSignalError;
|
|
37
|
-
/* @deprecated */
|
|
38
|
-
class DurableSleepError extends Error {
|
|
39
|
-
constructor(message, duration, index, dimension) {
|
|
40
|
-
super(message);
|
|
41
|
-
this.duration = duration;
|
|
42
|
-
this.index = index;
|
|
43
|
-
this.dimension = dimension;
|
|
44
|
-
this.code = 595;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
exports.DurableSleepError = DurableSleepError;
|
|
48
37
|
class DurableSleepForError extends Error {
|
|
49
38
|
constructor(message, duration, index, dimension) {
|
|
50
39
|
super(message);
|
package/build/package.json
CHANGED
|
@@ -31,6 +31,11 @@ declare class Activity {
|
|
|
31
31
|
* all aspects of the entry including job and activty state
|
|
32
32
|
*/
|
|
33
33
|
verifyEntry(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Upon entering leg 2 of a duplexed activty, verify
|
|
36
|
+
* all aspects of the re-entry including job and activty state
|
|
37
|
+
*/
|
|
38
|
+
verifyReentry(): Promise<number>;
|
|
34
39
|
processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output'): Promise<void>;
|
|
35
40
|
processPending(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
|
|
36
41
|
processSuccess(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
|
|
@@ -57,7 +62,9 @@ declare class Activity {
|
|
|
57
62
|
getState(): Promise<void>;
|
|
58
63
|
/**
|
|
59
64
|
* if the job is created/deleted/created with the same key,
|
|
60
|
-
* the 'gid' ensures no stale messages
|
|
65
|
+
* the 'gid' ensures no stale messages (such as sleep delays)
|
|
66
|
+
* enter the workstream. Any message with a mismatched gid
|
|
67
|
+
* belongs to a prior job and can safely be ignored/dropped.
|
|
61
68
|
*/
|
|
62
69
|
assertGenerationalId(jobGID: string, msgGID?: string): void;
|
|
63
70
|
initDimensionalAddress(dad: string): void;
|
|
@@ -40,6 +40,17 @@ class Activity {
|
|
|
40
40
|
collator_1.CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
41
41
|
await collator_1.CollatorService.notarizeEntry(this);
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Upon entering leg 2 of a duplexed activty, verify
|
|
45
|
+
* all aspects of the re-entry including job and activty state
|
|
46
|
+
*/
|
|
47
|
+
async verifyReentry() {
|
|
48
|
+
const guid = this.context.metadata.guid;
|
|
49
|
+
this.setLeg(2);
|
|
50
|
+
await this.getState();
|
|
51
|
+
collator_1.CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
52
|
+
return await collator_1.CollatorService.notarizeReentry(this, guid);
|
|
53
|
+
}
|
|
43
54
|
//******** DUPLEX RE-ENTRY POINT ********//
|
|
44
55
|
async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output') {
|
|
45
56
|
this.setLeg(2);
|
|
@@ -54,17 +65,9 @@ class Activity {
|
|
|
54
65
|
this.logger.debug('activity-process-event', { topic: this.config.subtype, jid, aid, status, code });
|
|
55
66
|
let telemetry;
|
|
56
67
|
try {
|
|
57
|
-
await this.
|
|
58
|
-
|
|
59
|
-
const aState = await collator_1.CollatorService.notarizeReentry(this);
|
|
60
|
-
this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(aState);
|
|
68
|
+
const collationKey = await this.verifyReentry();
|
|
69
|
+
this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(collationKey);
|
|
61
70
|
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
62
|
-
let isComplete = collator_1.CollatorService.isActivityComplete(this.context.metadata.js);
|
|
63
|
-
if (isComplete) {
|
|
64
|
-
this.logger.warn('activity-process-event-duplicate', { jid, aid });
|
|
65
|
-
this.logger.debug('activity-process-event-duplicate-resolution', { resolution: 'Increase HotMesh config `reclaimDelay` timeout.' });
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
71
|
telemetry.startActivitySpan(this.leg);
|
|
69
72
|
let multiResponse;
|
|
70
73
|
if (status === stream_1.StreamStatus.PENDING) {
|
|
@@ -306,7 +309,7 @@ class Activity {
|
|
|
306
309
|
telemetry_1.TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
|
|
307
310
|
let { dad, jid } = this.context.metadata;
|
|
308
311
|
const dIds = collator_1.CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad || '');
|
|
309
|
-
//`state` is a
|
|
312
|
+
//`state` is a unidimensional hash; context is a tree
|
|
310
313
|
const [state, status] = await this.store.getState(jid, consumes, dIds);
|
|
311
314
|
this.context = (0, utils_1.restoreHierarchy)(state);
|
|
312
315
|
this.assertGenerationalId(this.context.metadata.gid, gid);
|
|
@@ -316,7 +319,9 @@ class Activity {
|
|
|
316
319
|
}
|
|
317
320
|
/**
|
|
318
321
|
* if the job is created/deleted/created with the same key,
|
|
319
|
-
* the 'gid' ensures no stale messages
|
|
322
|
+
* the 'gid' ensures no stale messages (such as sleep delays)
|
|
323
|
+
* enter the workstream. Any message with a mismatched gid
|
|
324
|
+
* belongs to a prior job and can safely be ignored/dropped.
|
|
320
325
|
*/
|
|
321
326
|
assertGenerationalId(jobGID, msgGID) {
|
|
322
327
|
if (msgGID !== jobGID) {
|
|
@@ -30,7 +30,17 @@ declare class CollatorService {
|
|
|
30
30
|
static authorizeReentry(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
31
31
|
static notarizeEarlyExit(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
32
32
|
static notarizeEarlyCompletion(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* verifies both the concrete and synthetic keys for the activity; concrete keys
|
|
35
|
+
* exist in the original model and are effectively the 'real' keys. In reality,
|
|
36
|
+
* hook activities are atomized during compilation to create a synthetic DAG that
|
|
37
|
+
* is used to track the status of the graph in a distributed environment. The
|
|
38
|
+
* synthetic key represents different dimensional realities and is used to
|
|
39
|
+
* track re-entry overages (it distinguishes between the original and re-entry).
|
|
40
|
+
* The essential challenge is: is this a re-entry that is purposeful in
|
|
41
|
+
* order to induce cycles, or is the re-entry due to a failure in the system?
|
|
42
|
+
*/
|
|
43
|
+
static notarizeReentry(activity: Activity, guid: string, multi?: RedisMulti): Promise<number>;
|
|
34
44
|
static notarizeContinuation(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
35
45
|
static notarizeCompletion(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
36
46
|
static getDigitAtIndex(num: number, targetDigitIndex: number): number | null;
|
|
@@ -38,6 +48,15 @@ declare class CollatorService {
|
|
|
38
48
|
static isDuplicate(num: number, targetDigitIndex: number): boolean;
|
|
39
49
|
static isInactive(num: number): boolean;
|
|
40
50
|
static isPrimed(amount: number, leg: ActivityDuplex): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* During compilation, the graphs are compiled into structures necessary
|
|
53
|
+
* for distributed processing; these are referred to as 'synthetic DAGs',
|
|
54
|
+
* because they are not part of the original graph, but are used to track
|
|
55
|
+
* the status of the graph in a distributed environment. This check ensures
|
|
56
|
+
* that the 'synthetic key' is not a duplicate. (which is different than
|
|
57
|
+
* saying the 'key' is not a duplicate)
|
|
58
|
+
*/
|
|
59
|
+
static verifySyntheticInteger(amount: number): void;
|
|
41
60
|
static verifyInteger(amount: number, leg: ActivityDuplex, stage: CollationStage): void;
|
|
42
61
|
static getDimensionsById(ancestors: string[], dad: string): Record<string, string>;
|
|
43
62
|
/**
|
|
@@ -72,7 +91,6 @@ declare class CollatorService {
|
|
|
72
91
|
*
|
|
73
92
|
*/
|
|
74
93
|
static bindAncestorArray(graphs: HotMeshGraph[]): void;
|
|
75
|
-
static isActivityComplete(status: number): boolean;
|
|
76
94
|
/**
|
|
77
95
|
* All activities exist on a dimensional plane. Zero
|
|
78
96
|
* is the default. A value of
|
|
@@ -67,11 +67,28 @@ class CollatorService {
|
|
|
67
67
|
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1000001 - decrement, this.getDimensionalAddress(activity), multi);
|
|
68
68
|
}
|
|
69
69
|
;
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* verifies both the concrete and synthetic keys for the activity; concrete keys
|
|
72
|
+
* exist in the original model and are effectively the 'real' keys. In reality,
|
|
73
|
+
* hook activities are atomized during compilation to create a synthetic DAG that
|
|
74
|
+
* is used to track the status of the graph in a distributed environment. The
|
|
75
|
+
* synthetic key represents different dimensional realities and is used to
|
|
76
|
+
* track re-entry overages (it distinguishes between the original and re-entry).
|
|
77
|
+
* The essential challenge is: is this a re-entry that is purposeful in
|
|
78
|
+
* order to induce cycles, or is the re-entry due to a failure in the system?
|
|
79
|
+
*/
|
|
80
|
+
static async notarizeReentry(activity, guid, multi) {
|
|
81
|
+
const jid = activity.context.metadata.jid;
|
|
82
|
+
const localMulti = multi || activity.store.getMulti();
|
|
71
83
|
//increment by 1_000_000 (indicates re-entry and is used to drive the 'dimensional address' for adjacent activities (minus 1))
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
await activity.store.collate(jid, activity.metadata.aid, 1000000, this.getDimensionalAddress(activity, true), localMulti);
|
|
85
|
+
await activity.store.collateSynthetic(jid, guid, 1000000, localMulti);
|
|
86
|
+
const [_amountConcrete, _amountSynthetic] = await localMulti.exec();
|
|
87
|
+
const amountConcrete = Array.isArray(_amountConcrete) ? _amountConcrete[1] : _amountConcrete;
|
|
88
|
+
const amountSynthetic = Array.isArray(_amountSynthetic) ? _amountSynthetic[1] : _amountSynthetic;
|
|
89
|
+
this.verifyInteger(amountConcrete, 2, 'enter');
|
|
90
|
+
this.verifySyntheticInteger(amountSynthetic);
|
|
91
|
+
return amountConcrete;
|
|
75
92
|
}
|
|
76
93
|
;
|
|
77
94
|
static async notarizeContinuation(activity, multi) {
|
|
@@ -119,6 +136,26 @@ class CollatorService {
|
|
|
119
136
|
this.getDigitAtIndex(amount, 1) < 9;
|
|
120
137
|
}
|
|
121
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* During compilation, the graphs are compiled into structures necessary
|
|
141
|
+
* for distributed processing; these are referred to as 'synthetic DAGs',
|
|
142
|
+
* because they are not part of the original graph, but are used to track
|
|
143
|
+
* the status of the graph in a distributed environment. This check ensures
|
|
144
|
+
* that the 'synthetic key' is not a duplicate. (which is different than
|
|
145
|
+
* saying the 'key' is not a duplicate)
|
|
146
|
+
*/
|
|
147
|
+
static verifySyntheticInteger(amount) {
|
|
148
|
+
const samount = amount.toString();
|
|
149
|
+
const isCompletedValue = parseInt(samount[samount.length - 1], 10);
|
|
150
|
+
if (isCompletedValue > 0) {
|
|
151
|
+
//already done error (ack/delete clearly failed; this is a duplicate)
|
|
152
|
+
throw new errors_1.CollationError(amount, 2, 'enter', collator_1.CollationFaultType.INACTIVE);
|
|
153
|
+
}
|
|
154
|
+
else if (amount >= 2000000) {
|
|
155
|
+
//duplicate synthetic key (todo: need to resolve/fix this!!)
|
|
156
|
+
throw new errors_1.CollationError(amount, 2, 'enter', collator_1.CollationFaultType.DUPLICATE);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
122
159
|
static verifyInteger(amount, leg, stage) {
|
|
123
160
|
let faultType;
|
|
124
161
|
if (leg === 1 && stage === 'enter') {
|
|
@@ -222,9 +259,6 @@ class CollatorService {
|
|
|
222
259
|
dfs(startingNode, []);
|
|
223
260
|
});
|
|
224
261
|
}
|
|
225
|
-
static isActivityComplete(status) {
|
|
226
|
-
return (status - 0) <= 0;
|
|
227
|
-
}
|
|
228
262
|
/**
|
|
229
263
|
* All activities exist on a dimensional plane. Zero
|
|
230
264
|
* is the default. A value of
|
|
@@ -10,7 +10,6 @@ exports.DEFAULT_COEFFICIENT = exports.APP_ID = exports.APP_VERSION = exports.get
|
|
|
10
10
|
* ERROR CODES:
|
|
11
11
|
* 594: waitforsignal
|
|
12
12
|
* 592: sleepFor
|
|
13
|
-
* 595: sleep (deprecated)
|
|
14
13
|
* 596, 597, 598: fatal
|
|
15
14
|
* 599: retry
|
|
16
15
|
*/
|
|
@@ -125,19 +124,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
125
124
|
maps:
|
|
126
125
|
index: '{$self.output.data.index}'
|
|
127
126
|
signals: '{$self.output.data.signals}'
|
|
128
|
-
595:
|
|
129
|
-
schema:
|
|
130
|
-
type: object
|
|
131
|
-
properties:
|
|
132
|
-
duration:
|
|
133
|
-
type: number
|
|
134
|
-
description: sleep duration in seconds
|
|
135
|
-
index:
|
|
136
|
-
type: number
|
|
137
|
-
description: the current index
|
|
138
|
-
maps:
|
|
139
|
-
duration: '{$self.output.data.duration}'
|
|
140
|
-
index: '{$self.output.data.index}'
|
|
141
127
|
592:
|
|
142
128
|
schema:
|
|
143
129
|
type: object
|
|
@@ -232,19 +218,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
232
218
|
maps:
|
|
233
219
|
index: '{$self.output.data.index}'
|
|
234
220
|
signals: '{$self.output.data.signals}'
|
|
235
|
-
595:
|
|
236
|
-
schema:
|
|
237
|
-
type: object
|
|
238
|
-
properties:
|
|
239
|
-
duration:
|
|
240
|
-
type: number
|
|
241
|
-
description: sleep duration in seconds
|
|
242
|
-
index:
|
|
243
|
-
type: number
|
|
244
|
-
description: the current index
|
|
245
|
-
maps:
|
|
246
|
-
duration: '{$self.output.data.duration}'
|
|
247
|
-
index: '{$self.output.data.index}'
|
|
248
221
|
592:
|
|
249
222
|
schema:
|
|
250
223
|
type: object
|
|
@@ -323,57 +296,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
323
296
|
maps:
|
|
324
297
|
duration: '{siga1.output.data.duration}'
|
|
325
298
|
|
|
326
|
-
siga595:
|
|
327
|
-
title: Signal In - Sleep before trying again
|
|
328
|
-
type: await
|
|
329
|
-
topic: ${app}.sleep.execute
|
|
330
|
-
input:
|
|
331
|
-
schema:
|
|
332
|
-
type: object
|
|
333
|
-
properties:
|
|
334
|
-
duration:
|
|
335
|
-
type: number
|
|
336
|
-
index:
|
|
337
|
-
type: number
|
|
338
|
-
workflowId:
|
|
339
|
-
type: string
|
|
340
|
-
parentWorkflowId:
|
|
341
|
-
type: string
|
|
342
|
-
originJobId:
|
|
343
|
-
type: string
|
|
344
|
-
maps:
|
|
345
|
-
duration: '{sigw1.output.data.duration}'
|
|
346
|
-
index: '{sigw1.output.data.index}'
|
|
347
|
-
parentWorkflowId:
|
|
348
|
-
'@pipe':
|
|
349
|
-
- ['{$job.metadata.jid}', '-s']
|
|
350
|
-
- ['{@string.concat}']
|
|
351
|
-
originJobId:
|
|
352
|
-
'@pipe':
|
|
353
|
-
- ['{t1.output.data.originJobId}', '{t1.output.data.originJobId}', '{$job.metadata.jid}']
|
|
354
|
-
- ['{@conditional.ternary}']
|
|
355
|
-
|
|
356
|
-
workflowId:
|
|
357
|
-
'@pipe':
|
|
358
|
-
- ['-', '{$job.metadata.jid}', '-$sleep', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
359
|
-
- ['{@string.concat}']
|
|
360
|
-
output:
|
|
361
|
-
schema:
|
|
362
|
-
type: object
|
|
363
|
-
properties:
|
|
364
|
-
done:
|
|
365
|
-
type: boolean
|
|
366
|
-
maps:
|
|
367
|
-
done: '{sigw1.output.data.done}'
|
|
368
|
-
|
|
369
|
-
sigc595:
|
|
370
|
-
title: Signal In - Goto Activity siga1
|
|
371
|
-
type: cycle
|
|
372
|
-
ancestor: siga1
|
|
373
|
-
input:
|
|
374
|
-
maps:
|
|
375
|
-
duration: '{siga1.output.data.duration}'
|
|
376
|
-
|
|
377
299
|
siga592:
|
|
378
300
|
title: Signal In - Sleep For a duration and then cycle/goto
|
|
379
301
|
type: hook
|
|
@@ -467,56 +389,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
467
389
|
maps:
|
|
468
390
|
duration: '{a1.output.data.duration}'
|
|
469
391
|
|
|
470
|
-
a595:
|
|
471
|
-
title: Sleep before trying again
|
|
472
|
-
type: await
|
|
473
|
-
topic: ${app}.sleep.execute
|
|
474
|
-
input:
|
|
475
|
-
schema:
|
|
476
|
-
type: object
|
|
477
|
-
properties:
|
|
478
|
-
duration:
|
|
479
|
-
type: number
|
|
480
|
-
index:
|
|
481
|
-
type: number
|
|
482
|
-
workflowId:
|
|
483
|
-
type: string
|
|
484
|
-
parentWorkflowId:
|
|
485
|
-
type: string
|
|
486
|
-
originJobId:
|
|
487
|
-
type: string
|
|
488
|
-
maps:
|
|
489
|
-
duration: '{w1.output.data.duration}'
|
|
490
|
-
index: '{w1.output.data.index}'
|
|
491
|
-
parentWorkflowId:
|
|
492
|
-
'@pipe':
|
|
493
|
-
- ['{$job.metadata.jid}', '-s']
|
|
494
|
-
- ['{@string.concat}']
|
|
495
|
-
originJobId:
|
|
496
|
-
'@pipe':
|
|
497
|
-
- ['{t1.output.data.originJobId}', '{t1.output.data.originJobId}', '{$job.metadata.jid}']
|
|
498
|
-
- ['{@conditional.ternary}']
|
|
499
|
-
workflowId:
|
|
500
|
-
'@pipe':
|
|
501
|
-
- ['-', '{$job.metadata.jid}', '-$sleep-', '{w1.output.data.index}']
|
|
502
|
-
- ['{@string.concat}']
|
|
503
|
-
output:
|
|
504
|
-
schema:
|
|
505
|
-
type: object
|
|
506
|
-
properties:
|
|
507
|
-
done:
|
|
508
|
-
type: boolean
|
|
509
|
-
maps:
|
|
510
|
-
done: '{w1.output.data.done}'
|
|
511
|
-
|
|
512
|
-
c595:
|
|
513
|
-
title: Goto Activity a1
|
|
514
|
-
type: cycle
|
|
515
|
-
ancestor: a1
|
|
516
|
-
input:
|
|
517
|
-
maps:
|
|
518
|
-
duration: '{a1.output.data.duration}'
|
|
519
|
-
|
|
520
392
|
a592:
|
|
521
393
|
title: Sleep For a duration and then cycle/goto
|
|
522
394
|
type: hook
|
|
@@ -574,9 +446,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
574
446
|
- to: siga594
|
|
575
447
|
conditions:
|
|
576
448
|
code: 594
|
|
577
|
-
- to: siga595
|
|
578
|
-
conditions:
|
|
579
|
-
code: 595
|
|
580
449
|
- to: siga592
|
|
581
450
|
conditions:
|
|
582
451
|
code: 592
|
|
@@ -585,8 +454,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
585
454
|
code: 599
|
|
586
455
|
siga594:
|
|
587
456
|
- to: sigc594
|
|
588
|
-
siga595:
|
|
589
|
-
- to: sigc595
|
|
590
457
|
siga592:
|
|
591
458
|
- to: sigc592
|
|
592
459
|
siga599:
|
|
@@ -597,9 +464,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
597
464
|
- to: a594
|
|
598
465
|
conditions:
|
|
599
466
|
code: 594
|
|
600
|
-
- to: a595
|
|
601
|
-
conditions:
|
|
602
|
-
code: 595
|
|
603
467
|
- to: a592
|
|
604
468
|
conditions:
|
|
605
469
|
code: 592
|
|
@@ -611,8 +475,6 @@ const getWorkflowYAML = (app, version) => {
|
|
|
611
475
|
code: [200, 598, 597, 596]
|
|
612
476
|
a594:
|
|
613
477
|
- to: c594
|
|
614
|
-
a595:
|
|
615
|
-
- to: c595
|
|
616
478
|
a592:
|
|
617
479
|
- to: c592
|
|
618
480
|
a599:
|
|
@@ -187,21 +187,6 @@ class WorkerService {
|
|
|
187
187
|
catch (err) {
|
|
188
188
|
//not an error...just a trigger to sleep
|
|
189
189
|
if (err instanceof errors_1.DurableSleepForError) {
|
|
190
|
-
return {
|
|
191
|
-
status: stream_1.StreamStatus.SUCCESS,
|
|
192
|
-
code: err.code,
|
|
193
|
-
metadata: { ...data.metadata },
|
|
194
|
-
data: {
|
|
195
|
-
code: err.code,
|
|
196
|
-
message: JSON.stringify({ duration: err.duration, index: err.index, dimension: err.dimension }),
|
|
197
|
-
duration: err.duration,
|
|
198
|
-
index: err.index,
|
|
199
|
-
dimension: err.dimension
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
//deprecated format; not an error...just a trigger to sleep
|
|
203
|
-
}
|
|
204
|
-
else if (err instanceof errors_1.DurableSleepError) {
|
|
205
190
|
return {
|
|
206
191
|
status: stream_1.StreamStatus.SUCCESS,
|
|
207
192
|
code: err.code,
|
|
@@ -112,15 +112,6 @@ export declare class WorkflowService {
|
|
|
112
112
|
* @returns {Promise<number>}
|
|
113
113
|
*/
|
|
114
114
|
static sleepFor(duration: string): Promise<number>;
|
|
115
|
-
/**
|
|
116
|
-
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
117
|
-
* upon reentry, the function will traverse prior execution paths up
|
|
118
|
-
* until the sleep command and then resume execution from that point.
|
|
119
|
-
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
120
|
-
* @returns {Promise<number>}
|
|
121
|
-
* @deprecated - use `sleepFor` instead
|
|
122
|
-
*/
|
|
123
|
-
static sleep(duration: string): Promise<number>;
|
|
124
115
|
/**
|
|
125
116
|
* Waits for a signal to awaken
|
|
126
117
|
* @param {string[]} signals - the signals to wait for
|
|
@@ -338,35 +338,6 @@ class WorkflowService {
|
|
|
338
338
|
}
|
|
339
339
|
return seconds;
|
|
340
340
|
}
|
|
341
|
-
/**
|
|
342
|
-
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
343
|
-
* upon reentry, the function will traverse prior execution paths up
|
|
344
|
-
* until the sleep command and then resume execution from that point.
|
|
345
|
-
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
346
|
-
* @returns {Promise<number>}
|
|
347
|
-
* @deprecated - use `sleepFor` instead
|
|
348
|
-
*/
|
|
349
|
-
static async sleep(duration) {
|
|
350
|
-
const seconds = (0, ms_1.default)(duration) / 1000;
|
|
351
|
-
const store = storage_1.asyncLocalStorage.getStore();
|
|
352
|
-
const COUNTER = store.get('counter');
|
|
353
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
354
|
-
const workflowId = store.get('workflowId');
|
|
355
|
-
const workflowTopic = store.get('workflowTopic');
|
|
356
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
357
|
-
const namespace = store.get('namespace');
|
|
358
|
-
const sleepJobId = `-${workflowId}-$sleep${workflowDimension}-${execIndex}`;
|
|
359
|
-
try {
|
|
360
|
-
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
361
|
-
await hotMeshClient.getState(`${hotMeshClient.appId}.sleep.execute`, sleepJobId);
|
|
362
|
-
//if no error is thrown, we've already slept, return the delay
|
|
363
|
-
return seconds;
|
|
364
|
-
}
|
|
365
|
-
catch (e) {
|
|
366
|
-
// spawn a new sleep job if error code 595 is thrown by the worker)
|
|
367
|
-
throw new errors_1.DurableSleepError(workflowId, seconds, execIndex, workflowDimension);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
341
|
/**
|
|
371
342
|
* Waits for a signal to awaken
|
|
372
343
|
* @param {string[]} signals - the signals to wait for
|
|
@@ -66,7 +66,7 @@ declare class EngineService {
|
|
|
66
66
|
interrupt(topic: string, jobId: string, options?: JobInterruptOptions): Promise<string>;
|
|
67
67
|
scrub(jobId: string): Promise<void>;
|
|
68
68
|
hook(topic: string, data: JobData, status?: StreamStatus, code?: StreamCode): Promise<string>;
|
|
69
|
-
hookTime(jobId: string, gId: string,
|
|
69
|
+
hookTime(jobId: string, gId: string, topicOrActivity: string, type?: WorkListTaskType): Promise<string | void>;
|
|
70
70
|
hookAll(hookTopic: string, data: JobData, keyResolver: JobStatsInput, queryFacets?: string[]): Promise<string[]>;
|
|
71
71
|
pub(topic: string, data: JobData, context?: JobState): Promise<string>;
|
|
72
72
|
sub(topic: string, callback: JobMessageCallback): Promise<void>;
|
|
@@ -226,6 +226,7 @@ class EngineService {
|
|
|
226
226
|
});
|
|
227
227
|
const context = {
|
|
228
228
|
metadata: {
|
|
229
|
+
guid: streamData.metadata.guid,
|
|
229
230
|
jid: streamData.metadata.jid,
|
|
230
231
|
gid: streamData.metadata.gid,
|
|
231
232
|
dad: streamData.metadata.dad,
|
|
@@ -353,15 +354,11 @@ class EngineService {
|
|
|
353
354
|
};
|
|
354
355
|
return await this.router.publishMessage(null, streamData);
|
|
355
356
|
}
|
|
356
|
-
async hookTime(jobId, gId,
|
|
357
|
-
if (type === 'interrupt') {
|
|
358
|
-
return await this.interrupt(
|
|
359
|
-
jobId, { suppress: true, expire: 1 });
|
|
357
|
+
async hookTime(jobId, gId, topicOrActivity, type) {
|
|
358
|
+
if (type === 'interrupt' || type === 'expire') {
|
|
359
|
+
return await this.interrupt(topicOrActivity, jobId, { suppress: true, expire: 1 });
|
|
360
360
|
}
|
|
361
|
-
|
|
362
|
-
return await this.store.expireJob(jobId, 1);
|
|
363
|
-
}
|
|
364
|
-
const [aid, ...dimensions] = activityId.split(',');
|
|
361
|
+
const [aid, ...dimensions] = topicOrActivity.split(',');
|
|
365
362
|
const dad = `,${dimensions.join(',')}`;
|
|
366
363
|
const streamData = {
|
|
367
364
|
type: stream_1.StreamDataType.TIMEHOOK,
|
|
@@ -86,7 +86,18 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
86
86
|
*/
|
|
87
87
|
getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
|
|
88
88
|
getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
|
|
89
|
+
/**
|
|
90
|
+
* collate is a generic method for incrementing a value in a hash
|
|
91
|
+
* in order to track their progress during processing.
|
|
92
|
+
*/
|
|
89
93
|
collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, multi?: U): Promise<number>;
|
|
94
|
+
/**
|
|
95
|
+
* synthentic collation affects those activities in the graph
|
|
96
|
+
* that represent the synthetic DAG that was materialized during compilation;
|
|
97
|
+
* Synthetic targeting ensures that re-entry due to failure can be distinguished from
|
|
98
|
+
* purposeful re-entry.
|
|
99
|
+
*/
|
|
100
|
+
collateSynthetic(jobId: string, guid: string, amount: number, multi?: U): Promise<number>;
|
|
90
101
|
setStateNX(jobId: string, appId: string): Promise<boolean>;
|
|
91
102
|
getSchema(activityId: string, appVersion: AppVID): Promise<ActivityType>;
|
|
92
103
|
getSchemas(appVersion: AppVID): Promise<Record<string, ActivityType>>;
|
|
@@ -485,6 +485,10 @@ class StoreService {
|
|
|
485
485
|
throw new errors_1.GetStateError(jobId);
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* collate is a generic method for incrementing a value in a hash
|
|
490
|
+
* in order to track their progress during processing.
|
|
491
|
+
*/
|
|
488
492
|
async collate(jobId, activityId, amount, dIds, multi) {
|
|
489
493
|
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
490
494
|
const collationKey = `${activityId}/output/metadata/as`; //activity state
|
|
@@ -497,6 +501,16 @@ class StoreService {
|
|
|
497
501
|
const targetId = Object.keys(hashData)[0];
|
|
498
502
|
return await (multi || this.redisClient)[this.commands.hincrbyfloat](jobKey, targetId, amount);
|
|
499
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* synthentic collation affects those activities in the graph
|
|
506
|
+
* that represent the synthetic DAG that was materialized during compilation;
|
|
507
|
+
* Synthetic targeting ensures that re-entry due to failure can be distinguished from
|
|
508
|
+
* purposeful re-entry.
|
|
509
|
+
*/
|
|
510
|
+
async collateSynthetic(jobId, guid, amount, multi) {
|
|
511
|
+
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
512
|
+
return await (multi || this.redisClient)[this.commands.hincrbyfloat](jobKey, guid, amount);
|
|
513
|
+
}
|
|
500
514
|
async setStateNX(jobId, appId) {
|
|
501
515
|
const hashKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
|
|
502
516
|
const result = await this.redisClient[this.commands.hsetnx](hashKey, ':', '1');
|
package/build/types/job.d.ts
CHANGED
package/modules/errors.ts
CHANGED
|
@@ -45,20 +45,6 @@ class DurableWaitForSignalError extends Error {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/* @deprecated */
|
|
49
|
-
class DurableSleepError extends Error {
|
|
50
|
-
code: number;
|
|
51
|
-
duration: number; //seconds
|
|
52
|
-
index: number; //execution order in the workflow
|
|
53
|
-
dimension: string; //hook dimension (e.g., ',0,1,0') (uses empty string for `null`)
|
|
54
|
-
constructor(message: string, duration: number, index: number, dimension: string) {
|
|
55
|
-
super(message);
|
|
56
|
-
this.duration = duration;
|
|
57
|
-
this.index = index;
|
|
58
|
-
this.dimension = dimension;
|
|
59
|
-
this.code = 595;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
48
|
class DurableSleepForError extends Error {
|
|
63
49
|
code: number;
|
|
64
50
|
duration: number; //seconds
|
|
@@ -175,7 +161,6 @@ export {
|
|
|
175
161
|
DurableIncompleteSignalError,
|
|
176
162
|
DurableMaxedError,
|
|
177
163
|
DurableRetryError,
|
|
178
|
-
DurableSleepError,
|
|
179
164
|
DurableSleepForError,
|
|
180
165
|
DurableTimeoutError,
|
|
181
166
|
DurableWaitForSignalError,
|
package/package.json
CHANGED
|
@@ -90,6 +90,22 @@ class Activity {
|
|
|
90
90
|
await CollatorService.notarizeEntry(this);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Upon entering leg 2 of a duplexed activty, verify
|
|
95
|
+
* all aspects of the re-entry including job and activty state
|
|
96
|
+
*/
|
|
97
|
+
async verifyReentry(): Promise<number> {
|
|
98
|
+
const guid = this.context.metadata.guid;
|
|
99
|
+
this.setLeg(2);
|
|
100
|
+
await this.getState();
|
|
101
|
+
CollatorService.assertJobActive(
|
|
102
|
+
this.context.metadata.js,
|
|
103
|
+
this.context.metadata.jid,
|
|
104
|
+
this.metadata.aid
|
|
105
|
+
);
|
|
106
|
+
return await CollatorService.notarizeReentry(this, guid);
|
|
107
|
+
}
|
|
108
|
+
|
|
93
109
|
//******** DUPLEX RE-ENTRY POINT ********//
|
|
94
110
|
async processEvent(status: StreamStatus = StreamStatus.SUCCESS, code: StreamCode = 200, type: 'hook' | 'output' = 'output'): Promise<void> {
|
|
95
111
|
this.setLeg(2);
|
|
@@ -103,23 +119,20 @@ class Activity {
|
|
|
103
119
|
this.code = code;
|
|
104
120
|
this.logger.debug('activity-process-event', { topic: this.config.subtype, jid, aid, status, code });
|
|
105
121
|
let telemetry: TelemetryService;
|
|
106
|
-
try {
|
|
107
|
-
await this.getState();
|
|
108
|
-
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
109
|
-
const aState = await CollatorService.notarizeReentry(this);
|
|
110
|
-
this.adjacentIndex = CollatorService.getDimensionalIndex(aState);
|
|
111
|
-
|
|
112
|
-
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
113
|
-
let isComplete = CollatorService.isActivityComplete(this.context.metadata.js);
|
|
114
122
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.logger.debug('activity-process-event-duplicate-resolution', { resolution: 'Increase HotMesh config `reclaimDelay` timeout.' });
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
123
|
+
try {
|
|
124
|
+
const collationKey = await this.verifyReentry();
|
|
120
125
|
|
|
126
|
+
this.adjacentIndex = CollatorService.getDimensionalIndex(collationKey);
|
|
127
|
+
telemetry = new TelemetryService(
|
|
128
|
+
this.engine.appId,
|
|
129
|
+
this.config,
|
|
130
|
+
this.metadata,
|
|
131
|
+
this.context,
|
|
132
|
+
);
|
|
121
133
|
telemetry.startActivitySpan(this.leg);
|
|
122
134
|
let multiResponse: MultiResponseFlags;
|
|
135
|
+
|
|
123
136
|
if (status === StreamStatus.PENDING) {
|
|
124
137
|
multiResponse = await this.processPending(telemetry, type);
|
|
125
138
|
} else if (status === StreamStatus.SUCCESS) {
|
|
@@ -384,7 +397,7 @@ class Activity {
|
|
|
384
397
|
TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
|
|
385
398
|
let { dad, jid } = this.context.metadata;
|
|
386
399
|
const dIds = CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad || '');
|
|
387
|
-
//`state` is a
|
|
400
|
+
//`state` is a unidimensional hash; context is a tree
|
|
388
401
|
const [state, status] = await this.store.getState(jid, consumes, dIds);
|
|
389
402
|
this.context = restoreHierarchy(state) as JobState;
|
|
390
403
|
this.assertGenerationalId(this.context.metadata.gid, gid);
|
|
@@ -395,7 +408,9 @@ class Activity {
|
|
|
395
408
|
|
|
396
409
|
/**
|
|
397
410
|
* if the job is created/deleted/created with the same key,
|
|
398
|
-
* the 'gid' ensures no stale messages
|
|
411
|
+
* the 'gid' ensures no stale messages (such as sleep delays)
|
|
412
|
+
* enter the workstream. Any message with a mismatched gid
|
|
413
|
+
* belongs to a prior job and can safely be ignored/dropped.
|
|
399
414
|
*/
|
|
400
415
|
assertGenerationalId(jobGID: string, msgGID?: string) {
|
|
401
416
|
if (msgGID !== jobGID) {
|
|
@@ -78,11 +78,28 @@ class CollatorService {
|
|
|
78
78
|
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1_000_001 - decrement, this.getDimensionalAddress(activity), multi);
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
/**
|
|
82
|
+
* verifies both the concrete and synthetic keys for the activity; concrete keys
|
|
83
|
+
* exist in the original model and are effectively the 'real' keys. In reality,
|
|
84
|
+
* hook activities are atomized during compilation to create a synthetic DAG that
|
|
85
|
+
* is used to track the status of the graph in a distributed environment. The
|
|
86
|
+
* synthetic key represents different dimensional realities and is used to
|
|
87
|
+
* track re-entry overages (it distinguishes between the original and re-entry).
|
|
88
|
+
* The essential challenge is: is this a re-entry that is purposeful in
|
|
89
|
+
* order to induce cycles, or is the re-entry due to a failure in the system?
|
|
90
|
+
*/
|
|
91
|
+
static async notarizeReentry(activity: Activity, guid: string, multi?: RedisMulti): Promise<number> {
|
|
92
|
+
const jid = activity.context.metadata.jid;
|
|
93
|
+
const localMulti = multi || activity.store.getMulti();
|
|
82
94
|
//increment by 1_000_000 (indicates re-entry and is used to drive the 'dimensional address' for adjacent activities (minus 1))
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
await activity.store.collate(jid, activity.metadata.aid, 1_000_000, this.getDimensionalAddress(activity, true), localMulti);
|
|
96
|
+
await activity.store.collateSynthetic(jid, guid, 1_000_000, localMulti);
|
|
97
|
+
const [_amountConcrete, _amountSynthetic] = await localMulti.exec();
|
|
98
|
+
const amountConcrete = Array.isArray(_amountConcrete) ? _amountConcrete[1] : _amountConcrete;
|
|
99
|
+
const amountSynthetic = Array.isArray(_amountSynthetic) ? _amountSynthetic[1] : _amountSynthetic;
|
|
100
|
+
this.verifyInteger(amountConcrete as number, 2, 'enter');
|
|
101
|
+
this.verifySyntheticInteger(amountSynthetic as number);
|
|
102
|
+
return amountConcrete as number;
|
|
86
103
|
};
|
|
87
104
|
|
|
88
105
|
static async notarizeContinuation(activity: Activity, multi?: RedisMulti): Promise<number> {
|
|
@@ -134,6 +151,26 @@ class CollatorService {
|
|
|
134
151
|
}
|
|
135
152
|
}
|
|
136
153
|
|
|
154
|
+
/**
|
|
155
|
+
* During compilation, the graphs are compiled into structures necessary
|
|
156
|
+
* for distributed processing; these are referred to as 'synthetic DAGs',
|
|
157
|
+
* because they are not part of the original graph, but are used to track
|
|
158
|
+
* the status of the graph in a distributed environment. This check ensures
|
|
159
|
+
* that the 'synthetic key' is not a duplicate. (which is different than
|
|
160
|
+
* saying the 'key' is not a duplicate)
|
|
161
|
+
*/
|
|
162
|
+
static verifySyntheticInteger(amount: number): void {
|
|
163
|
+
const samount = amount.toString();
|
|
164
|
+
const isCompletedValue = parseInt(samount[samount.length - 1], 10);
|
|
165
|
+
if (isCompletedValue > 0) {
|
|
166
|
+
//already done error (ack/delete clearly failed; this is a duplicate)
|
|
167
|
+
throw new CollationError(amount, 2, 'enter', CollationFaultType.INACTIVE);
|
|
168
|
+
} else if (amount >= 2_000_000) {
|
|
169
|
+
//duplicate synthetic key (todo: need to resolve/fix this!!)
|
|
170
|
+
throw new CollationError(amount, 2, 'enter', CollationFaultType.DUPLICATE);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
137
174
|
static verifyInteger(amount: number, leg: ActivityDuplex, stage: CollationStage): void {
|
|
138
175
|
let faultType: CollationFaultType | undefined;
|
|
139
176
|
if (leg === 1 && stage === 'enter') {
|
|
@@ -239,10 +276,6 @@ class CollatorService {
|
|
|
239
276
|
});
|
|
240
277
|
}
|
|
241
278
|
|
|
242
|
-
static isActivityComplete(status: number): boolean {
|
|
243
|
-
return (status - 0) <= 0;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
279
|
/**
|
|
247
280
|
* All activities exist on a dimensional plane. Zero
|
|
248
281
|
* is the default. A value of
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* ERROR CODES:
|
|
8
8
|
* 594: waitforsignal
|
|
9
9
|
* 592: sleepFor
|
|
10
|
-
* 595: sleep (deprecated)
|
|
11
10
|
* 596, 597, 598: fatal
|
|
12
11
|
* 599: retry
|
|
13
12
|
*/
|
|
@@ -122,19 +121,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
122
121
|
maps:
|
|
123
122
|
index: '{$self.output.data.index}'
|
|
124
123
|
signals: '{$self.output.data.signals}'
|
|
125
|
-
595:
|
|
126
|
-
schema:
|
|
127
|
-
type: object
|
|
128
|
-
properties:
|
|
129
|
-
duration:
|
|
130
|
-
type: number
|
|
131
|
-
description: sleep duration in seconds
|
|
132
|
-
index:
|
|
133
|
-
type: number
|
|
134
|
-
description: the current index
|
|
135
|
-
maps:
|
|
136
|
-
duration: '{$self.output.data.duration}'
|
|
137
|
-
index: '{$self.output.data.index}'
|
|
138
124
|
592:
|
|
139
125
|
schema:
|
|
140
126
|
type: object
|
|
@@ -229,19 +215,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
229
215
|
maps:
|
|
230
216
|
index: '{$self.output.data.index}'
|
|
231
217
|
signals: '{$self.output.data.signals}'
|
|
232
|
-
595:
|
|
233
|
-
schema:
|
|
234
|
-
type: object
|
|
235
|
-
properties:
|
|
236
|
-
duration:
|
|
237
|
-
type: number
|
|
238
|
-
description: sleep duration in seconds
|
|
239
|
-
index:
|
|
240
|
-
type: number
|
|
241
|
-
description: the current index
|
|
242
|
-
maps:
|
|
243
|
-
duration: '{$self.output.data.duration}'
|
|
244
|
-
index: '{$self.output.data.index}'
|
|
245
218
|
592:
|
|
246
219
|
schema:
|
|
247
220
|
type: object
|
|
@@ -320,57 +293,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
320
293
|
maps:
|
|
321
294
|
duration: '{siga1.output.data.duration}'
|
|
322
295
|
|
|
323
|
-
siga595:
|
|
324
|
-
title: Signal In - Sleep before trying again
|
|
325
|
-
type: await
|
|
326
|
-
topic: ${app}.sleep.execute
|
|
327
|
-
input:
|
|
328
|
-
schema:
|
|
329
|
-
type: object
|
|
330
|
-
properties:
|
|
331
|
-
duration:
|
|
332
|
-
type: number
|
|
333
|
-
index:
|
|
334
|
-
type: number
|
|
335
|
-
workflowId:
|
|
336
|
-
type: string
|
|
337
|
-
parentWorkflowId:
|
|
338
|
-
type: string
|
|
339
|
-
originJobId:
|
|
340
|
-
type: string
|
|
341
|
-
maps:
|
|
342
|
-
duration: '{sigw1.output.data.duration}'
|
|
343
|
-
index: '{sigw1.output.data.index}'
|
|
344
|
-
parentWorkflowId:
|
|
345
|
-
'@pipe':
|
|
346
|
-
- ['{$job.metadata.jid}', '-s']
|
|
347
|
-
- ['{@string.concat}']
|
|
348
|
-
originJobId:
|
|
349
|
-
'@pipe':
|
|
350
|
-
- ['{t1.output.data.originJobId}', '{t1.output.data.originJobId}', '{$job.metadata.jid}']
|
|
351
|
-
- ['{@conditional.ternary}']
|
|
352
|
-
|
|
353
|
-
workflowId:
|
|
354
|
-
'@pipe':
|
|
355
|
-
- ['-', '{$job.metadata.jid}', '-$sleep', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
356
|
-
- ['{@string.concat}']
|
|
357
|
-
output:
|
|
358
|
-
schema:
|
|
359
|
-
type: object
|
|
360
|
-
properties:
|
|
361
|
-
done:
|
|
362
|
-
type: boolean
|
|
363
|
-
maps:
|
|
364
|
-
done: '{sigw1.output.data.done}'
|
|
365
|
-
|
|
366
|
-
sigc595:
|
|
367
|
-
title: Signal In - Goto Activity siga1
|
|
368
|
-
type: cycle
|
|
369
|
-
ancestor: siga1
|
|
370
|
-
input:
|
|
371
|
-
maps:
|
|
372
|
-
duration: '{siga1.output.data.duration}'
|
|
373
|
-
|
|
374
296
|
siga592:
|
|
375
297
|
title: Signal In - Sleep For a duration and then cycle/goto
|
|
376
298
|
type: hook
|
|
@@ -464,56 +386,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
464
386
|
maps:
|
|
465
387
|
duration: '{a1.output.data.duration}'
|
|
466
388
|
|
|
467
|
-
a595:
|
|
468
|
-
title: Sleep before trying again
|
|
469
|
-
type: await
|
|
470
|
-
topic: ${app}.sleep.execute
|
|
471
|
-
input:
|
|
472
|
-
schema:
|
|
473
|
-
type: object
|
|
474
|
-
properties:
|
|
475
|
-
duration:
|
|
476
|
-
type: number
|
|
477
|
-
index:
|
|
478
|
-
type: number
|
|
479
|
-
workflowId:
|
|
480
|
-
type: string
|
|
481
|
-
parentWorkflowId:
|
|
482
|
-
type: string
|
|
483
|
-
originJobId:
|
|
484
|
-
type: string
|
|
485
|
-
maps:
|
|
486
|
-
duration: '{w1.output.data.duration}'
|
|
487
|
-
index: '{w1.output.data.index}'
|
|
488
|
-
parentWorkflowId:
|
|
489
|
-
'@pipe':
|
|
490
|
-
- ['{$job.metadata.jid}', '-s']
|
|
491
|
-
- ['{@string.concat}']
|
|
492
|
-
originJobId:
|
|
493
|
-
'@pipe':
|
|
494
|
-
- ['{t1.output.data.originJobId}', '{t1.output.data.originJobId}', '{$job.metadata.jid}']
|
|
495
|
-
- ['{@conditional.ternary}']
|
|
496
|
-
workflowId:
|
|
497
|
-
'@pipe':
|
|
498
|
-
- ['-', '{$job.metadata.jid}', '-$sleep-', '{w1.output.data.index}']
|
|
499
|
-
- ['{@string.concat}']
|
|
500
|
-
output:
|
|
501
|
-
schema:
|
|
502
|
-
type: object
|
|
503
|
-
properties:
|
|
504
|
-
done:
|
|
505
|
-
type: boolean
|
|
506
|
-
maps:
|
|
507
|
-
done: '{w1.output.data.done}'
|
|
508
|
-
|
|
509
|
-
c595:
|
|
510
|
-
title: Goto Activity a1
|
|
511
|
-
type: cycle
|
|
512
|
-
ancestor: a1
|
|
513
|
-
input:
|
|
514
|
-
maps:
|
|
515
|
-
duration: '{a1.output.data.duration}'
|
|
516
|
-
|
|
517
389
|
a592:
|
|
518
390
|
title: Sleep For a duration and then cycle/goto
|
|
519
391
|
type: hook
|
|
@@ -571,9 +443,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
571
443
|
- to: siga594
|
|
572
444
|
conditions:
|
|
573
445
|
code: 594
|
|
574
|
-
- to: siga595
|
|
575
|
-
conditions:
|
|
576
|
-
code: 595
|
|
577
446
|
- to: siga592
|
|
578
447
|
conditions:
|
|
579
448
|
code: 592
|
|
@@ -582,8 +451,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
582
451
|
code: 599
|
|
583
452
|
siga594:
|
|
584
453
|
- to: sigc594
|
|
585
|
-
siga595:
|
|
586
|
-
- to: sigc595
|
|
587
454
|
siga592:
|
|
588
455
|
- to: sigc592
|
|
589
456
|
siga599:
|
|
@@ -594,9 +461,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
594
461
|
- to: a594
|
|
595
462
|
conditions:
|
|
596
463
|
code: 594
|
|
597
|
-
- to: a595
|
|
598
|
-
conditions:
|
|
599
|
-
code: 595
|
|
600
464
|
- to: a592
|
|
601
465
|
conditions:
|
|
602
466
|
code: 592
|
|
@@ -608,8 +472,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
608
472
|
code: [200, 598, 597, 596]
|
|
609
473
|
a594:
|
|
610
474
|
- to: c594
|
|
611
|
-
a595:
|
|
612
|
-
- to: c595
|
|
613
475
|
a592:
|
|
614
476
|
- to: c592
|
|
615
477
|
a599:
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
DurableIncompleteSignalError,
|
|
4
4
|
DurableMaxedError,
|
|
5
5
|
DurableRetryError,
|
|
6
|
-
DurableSleepError,
|
|
7
6
|
DurableSleepForError,
|
|
8
7
|
DurableTimeoutError,
|
|
9
8
|
DurableWaitForSignalError} from '../../modules/errors';
|
|
@@ -249,21 +248,6 @@ export class WorkerService {
|
|
|
249
248
|
}
|
|
250
249
|
} as StreamDataResponse;
|
|
251
250
|
|
|
252
|
-
//deprecated format; not an error...just a trigger to sleep
|
|
253
|
-
} else if (err instanceof DurableSleepError) {
|
|
254
|
-
return {
|
|
255
|
-
status: StreamStatus.SUCCESS,
|
|
256
|
-
code: err.code,
|
|
257
|
-
metadata: { ...data.metadata },
|
|
258
|
-
data: {
|
|
259
|
-
code: err.code,
|
|
260
|
-
message: JSON.stringify({ duration: err.duration, index: err.index, dimension: err.dimension }),
|
|
261
|
-
duration: err.duration,
|
|
262
|
-
index: err.index,
|
|
263
|
-
dimension: err.dimension
|
|
264
|
-
}
|
|
265
|
-
} as StreamDataResponse;
|
|
266
|
-
|
|
267
251
|
//not an error...just a trigger to wait for a signal
|
|
268
252
|
} else if (err instanceof DurableWaitForSignalError) {
|
|
269
253
|
return {
|
|
@@ -2,7 +2,6 @@ import ms from 'ms';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
DurableIncompleteSignalError,
|
|
5
|
-
DurableSleepError,
|
|
6
5
|
DurableSleepForError,
|
|
7
6
|
DurableWaitForSignalError } from '../../modules/errors';
|
|
8
7
|
import { KeyService, KeyType } from '../../modules/key';
|
|
@@ -376,37 +375,6 @@ export class WorkflowService {
|
|
|
376
375
|
return seconds;
|
|
377
376
|
}
|
|
378
377
|
|
|
379
|
-
/**
|
|
380
|
-
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
381
|
-
* upon reentry, the function will traverse prior execution paths up
|
|
382
|
-
* until the sleep command and then resume execution from that point.
|
|
383
|
-
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
384
|
-
* @returns {Promise<number>}
|
|
385
|
-
* @deprecated - use `sleepFor` instead
|
|
386
|
-
*/
|
|
387
|
-
static async sleep(duration: string): Promise<number> {
|
|
388
|
-
const seconds = ms(duration) / 1000;
|
|
389
|
-
|
|
390
|
-
const store = asyncLocalStorage.getStore();
|
|
391
|
-
const COUNTER = store.get('counter');
|
|
392
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
393
|
-
const workflowId = store.get('workflowId');
|
|
394
|
-
const workflowTopic = store.get('workflowTopic');
|
|
395
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
396
|
-
const namespace = store.get('namespace');
|
|
397
|
-
const sleepJobId = `-${workflowId}-$sleep${workflowDimension}-${execIndex}`;
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
401
|
-
await hotMeshClient.getState(`${hotMeshClient.appId}.sleep.execute`, sleepJobId);
|
|
402
|
-
//if no error is thrown, we've already slept, return the delay
|
|
403
|
-
return seconds;
|
|
404
|
-
} catch (e) {
|
|
405
|
-
// spawn a new sleep job if error code 595 is thrown by the worker)
|
|
406
|
-
throw new DurableSleepError(workflowId, seconds, execIndex, workflowDimension);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
378
|
/**
|
|
411
379
|
* Waits for a signal to awaken
|
|
412
380
|
* @param {string[]} signals - the signals to wait for
|
package/services/engine/index.ts
CHANGED
|
@@ -332,6 +332,7 @@ class EngineService {
|
|
|
332
332
|
});
|
|
333
333
|
const context: PartialJobState = {
|
|
334
334
|
metadata: {
|
|
335
|
+
guid: streamData.metadata.guid,
|
|
335
336
|
jid: streamData.metadata.jid,
|
|
336
337
|
gid: streamData.metadata.gid,
|
|
337
338
|
dad: streamData.metadata.dad,
|
|
@@ -490,17 +491,15 @@ class EngineService {
|
|
|
490
491
|
};
|
|
491
492
|
return await this.router.publishMessage(null, streamData) as string;
|
|
492
493
|
}
|
|
493
|
-
async hookTime(jobId: string, gId: string,
|
|
494
|
-
if (type === 'interrupt') {
|
|
494
|
+
async hookTime(jobId: string, gId: string, topicOrActivity: string, type?: WorkListTaskType): Promise<string | void> {
|
|
495
|
+
if (type === 'interrupt' || type === 'expire') {
|
|
495
496
|
return await this.interrupt(
|
|
496
|
-
|
|
497
|
+
topicOrActivity,
|
|
497
498
|
jobId,
|
|
498
499
|
{ suppress: true, expire: 1 },
|
|
499
500
|
);
|
|
500
|
-
} else if (type === 'expire') {
|
|
501
|
-
return await this.store.expireJob(jobId, 1);
|
|
502
501
|
}
|
|
503
|
-
const [aid, ...dimensions] =
|
|
502
|
+
const [aid, ...dimensions] = topicOrActivity.split(',');
|
|
504
503
|
const dad = `,${dimensions.join(',')}`;
|
|
505
504
|
const streamData: StreamData = {
|
|
506
505
|
type: StreamDataType.TIMEHOOK,
|
package/services/store/index.ts
CHANGED
|
@@ -586,6 +586,10 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
586
586
|
}
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
+
/**
|
|
590
|
+
* collate is a generic method for incrementing a value in a hash
|
|
591
|
+
* in order to track their progress during processing.
|
|
592
|
+
*/
|
|
589
593
|
async collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, multi? : U): Promise<number> {
|
|
590
594
|
const jobKey = this.mintKey(KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
591
595
|
const collationKey = `${activityId}/output/metadata/as`; //activity state
|
|
@@ -600,6 +604,17 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
600
604
|
return await (multi || this.redisClient)[this.commands.hincrbyfloat](jobKey, targetId, amount);
|
|
601
605
|
}
|
|
602
606
|
|
|
607
|
+
/**
|
|
608
|
+
* synthentic collation affects those activities in the graph
|
|
609
|
+
* that represent the synthetic DAG that was materialized during compilation;
|
|
610
|
+
* Synthetic targeting ensures that re-entry due to failure can be distinguished from
|
|
611
|
+
* purposeful re-entry.
|
|
612
|
+
*/
|
|
613
|
+
async collateSynthetic(jobId: string, guid: string, amount: number, multi? : U): Promise<number> {
|
|
614
|
+
const jobKey = this.mintKey(KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
615
|
+
return await (multi || this.redisClient)[this.commands.hincrbyfloat](jobKey, guid, amount);
|
|
616
|
+
}
|
|
617
|
+
|
|
603
618
|
async setStateNX(jobId: string, appId: string): Promise<boolean> {
|
|
604
619
|
const hashKey = this.mintKey(KeyType.JOB_STATE, { appId, jobId });
|
|
605
620
|
const result = await this.redisClient[this.commands.hsetnx](hashKey, ':', '1');
|
package/types/job.ts
CHANGED
|
@@ -8,6 +8,7 @@ type ActivityData = {
|
|
|
8
8
|
|
|
9
9
|
type JobMetadata = {
|
|
10
10
|
key?: string; //job_key
|
|
11
|
+
guid?: string; //system assigned guid that corresponds to the transition message guid that spawned reentry
|
|
11
12
|
gid: string; //system assigned guid; ensured created/deleted/created jobs are unique
|
|
12
13
|
jid: string; //job_id (jid+dad+aid) is composite key for activity
|
|
13
14
|
dad: string; //dimensional address for the activity (,0,0,1)
|