@lostgradient/weft 0.24.1 → 0.24.2
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 +1 -1
- package/dist/core/engine/bulk-operations-purge.d.ts +1 -1
- package/dist/core/engine/bulk-operations-purge.js +3 -2
- package/dist/core/engine/bulk-operations-retry.d.ts +16 -1
- package/dist/core/engine/bulk-operations-retry.js +19 -6
- package/dist/core/engine/bulk-operations.d.ts +3 -2
- package/dist/core/engine/bulk-operations.js +2 -2
- package/dist/core/engine/callback-creators-bundles.js +1 -1
- package/dist/core/engine/callback-creators-schedule.js +2 -2
- package/dist/core/engine/child-workflow.d.ts +13 -1
- package/dist/core/engine/child-workflow.js +22 -4
- package/dist/core/engine/generation-codec.d.ts +22 -0
- package/dist/core/engine/generation-codec.js +2 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/lifecycle/recovery-isolation.d.ts +20 -0
- package/dist/core/engine/lifecycle/recovery-isolation.js +26 -0
- package/dist/core/engine/lifecycle/resume-body.d.ts +44 -0
- package/dist/core/engine/lifecycle/resume-body.js +211 -0
- package/dist/core/engine/lifecycle/resume.d.ts +46 -3
- package/dist/core/engine/lifecycle/resume.js +21 -210
- package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +68 -2
- package/dist/core/engine/lifecycle/standalone-claim-acquire.js +14 -4
- package/dist/core/engine/lifecycle/start-commit.d.ts +2 -0
- package/dist/core/engine/lifecycle/start-commit.js +16 -8
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +8 -2
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +11 -3
- package/dist/core/engine/lifecycle/start-precondition-attribution.d.ts +10 -4
- package/dist/core/engine/lifecycle/start-schedule-timing.d.ts +11 -0
- package/dist/core/engine/lifecycle/start-schedule-timing.js +21 -0
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.d.ts +90 -11
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +24 -5
- package/dist/core/engine/lifecycle/start.d.ts +40 -3
- package/dist/core/engine/lifecycle/start.js +19 -26
- package/dist/core/engine/lifecycle/transition.d.ts +9 -0
- package/dist/core/engine/lifecycle/transition.js +20 -25
- package/dist/core/engine/lifecycle.d.ts +2 -1
- package/dist/core/engine/lifecycle.js +2 -1
- package/dist/core/engine/schedule-overlap.js +2 -1
- package/dist/core/engine/schedule-run.d.ts +10 -0
- package/dist/core/engine/schedule-run.js +1 -1
- package/dist/core/engine/schedules.d.ts +11 -2
- package/dist/core/engine/termination/complete.js +1 -1
- package/dist/core/engine/termination/suspend.js +3 -0
- package/dist/core/engine/validation/schedule.d.ts +25 -1
- package/dist/core/engine/validation/schedule.js +4 -10
- package/dist/core/engine/validation.js +10 -17
- package/dist/core/engine/workflow-claim-reclaim-target.js +6 -6
- package/dist/core/engine/workflow-claim-registry.d.ts +79 -75
- package/dist/core/engine/workflow-claim-registry.js +15 -2
- package/dist/core/engine/workflow-generation-fence.d.ts +74 -0
- package/dist/core/engine/workflow-generation-fence.js +19 -0
- package/dist/core/start-workflow-validation.d.ts +15 -0
- package/dist/core/start-workflow-validation.js +11 -1
- package/dist/core/workflow-identifiers.d.ts +37 -0
- package/dist/core/workflow-identifiers.js +19 -1
- package/dist/http.js +2 -2
- package/dist/indexeddb.js +1 -1
- package/dist/server/fleet-event-feed.js +6 -6
- package/dist/server/rest-binding.d.ts +11 -0
- package/dist/server/runtime/task-dispatch-envelope.d.ts +32 -5
- package/dist/server/runtime/task-dispatch-envelope.js +23 -1
- package/dist/server/runtime/task-dispatch.d.ts +3 -1
- package/dist/server/runtime/task-dispatch.js +9 -8
- package/dist/server/task-ledger-codec.d.ts +19 -0
- package/dist/server/task-ledger-codec.js +3 -0
- package/dist/server/task-ledger.d.ts +1 -1
- package/dist/server/task-ledger.js +1 -0
- package/dist/storage/bun-sql.js +132 -99
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/generation-keys.d.ts +52 -0
- package/dist/storage/generation-keys.js +4 -0
- package/dist/storage/index.d.ts +76 -65
- package/dist/storage/interface.d.ts +24 -232
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +1 -0
- package/dist/storage/lease-keys.d.ts +48 -0
- package/dist/storage/lease-keys.js +8 -0
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +3 -3
- package/dist/storage/node-sqlite.js +132 -99
- package/dist/storage/postgres.js +3 -3
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/signal-keys.d.ts +23 -0
- package/dist/storage/signal-keys.js +9 -0
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +2 -2
- package/dist/storage/workflow-lifecycle-keys.d.ts +128 -0
- package/dist/storage/workflow-lifecycle-keys.js +46 -0
- package/dist/storage/workflow-record-keys.d.ts +98 -0
- package/dist/storage/workflow-record-keys.js +47 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ export declare function purgeWorkflow(internals: EngineInternals, state: Workflo
|
|
|
21
21
|
* `onTerminalConflict: 'start-new'` restart path prepends them to the create batch
|
|
22
22
|
* so purge-and-recreate land as one atomic unit (no window where the prior run is
|
|
23
23
|
* gone but the new one has not committed). Keep this the single source of truth
|
|
24
|
-
* for "what a purge deletes" — do not fork the delete-set.
|
|
24
|
+
* for "what a purge deletes" — do not fork the delete-set. (`wf-gen:<id>` is deliberately excluded; WFT-153, see {@link buildBaseWorkflowDeleteKeys}.)
|
|
25
25
|
*/
|
|
26
26
|
export declare function collectWorkflowPurgeDeleteOperations(internals: EngineInternals, state: WorkflowState): Promise<BatchOperation[]>;
|
|
27
27
|
/**
|
|
@@ -20,6 +20,7 @@ import { decodeScheduleRunMetadata } from "./schedule-run-metadata.js";
|
|
|
20
20
|
import { createTerminalCleanupTimerId } from "./state-utilities.js";
|
|
21
21
|
import { buildExternalTerminalRotationFragment } from "./storage-io.js";
|
|
22
22
|
import { decodeWorkflowState, isTerminalWorkflowStatus } from "./validation.js";
|
|
23
|
+
import { foldWorkflowGenerationBumpForPurge } from "./workflow-generation-fence.js";
|
|
23
24
|
import { buildWorkflowVisibilityIndexTransition } from "./workflow-indexes.js";
|
|
24
25
|
import { getWorkflowRetentionDeadline } from "./workflow-retention-deadline.js";
|
|
25
26
|
export const TERMINAL_CLEANUP_DELAY_MS = 60000;
|
|
@@ -127,8 +128,8 @@ async function shouldPurgeWorkflowState(internals, state, expiredOnly, now) {
|
|
|
127
128
|
return deadline !== null && deadline <= now;
|
|
128
129
|
}
|
|
129
130
|
export async function purgeWorkflow(internals, state, cleanupWaiters) {
|
|
130
|
-
const deleteOperations = await collectWorkflowPurgeDeleteOperations(internals, state), rotation = await buildExternalTerminalRotationFragment(internals, state.id), operations = [...deleteOperations, ...
|
|
131
|
-
await commitFencedEngineWrite(internals, null, operations,
|
|
131
|
+
const deleteOperations = await collectWorkflowPurgeDeleteOperations(internals, state), rotation = await buildExternalTerminalRotationFragment(internals, state.id), fenced = await foldWorkflowGenerationBumpForPurge(internals, state.id, rotation), operations = [...deleteOperations, ...fenced.operations];
|
|
132
|
+
await commitFencedEngineWrite(internals, null, operations, fenced.conditions, () => {
|
|
132
133
|
return Error(`Purge commit for workflow "${state.id}" lost its precondition.`);
|
|
133
134
|
});
|
|
134
135
|
clearPurgedWorkflowInMemoryState(internals, state.id, cleanupWaiters);
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import type { BulkOperationDryRunResult, BulkOperationOptions, BulkRetryFailedResult, ListFilter } from '../types.ts';
|
|
2
2
|
import type { EngineInternals } from './internals.ts';
|
|
3
|
+
import { type LifecycleCallbacks } from './lifecycle.ts';
|
|
3
4
|
/**
|
|
4
5
|
* `bulkOperations.ts`'s `retryFailedAll()` implementation, split into its
|
|
5
6
|
* own module (with the rest of the retry-failed cluster below) to keep
|
|
6
7
|
* `bulk-operations.ts` under the 800-line implementation-file ceiling —
|
|
7
8
|
* the same reason `bulk-operations-purge.ts` was split out earlier.
|
|
8
9
|
*/
|
|
9
|
-
export declare function runBulkFailedWorkflowRetry(internals: EngineInternals, filter: ListFilter, options?: BulkOperationOptions
|
|
10
|
+
export declare function runBulkFailedWorkflowRetry(internals: EngineInternals, filter: ListFilter, options?: BulkOperationOptions,
|
|
11
|
+
/**
|
|
12
|
+
* Threaded down from `Engine#retryFailedAll()`'s own `#createLifecycleCallbacks()`
|
|
13
|
+
* (WFT-95 issue 2 follow-up). Needed only by `retryFailedWorkflow()`'s
|
|
14
|
+
* checkpoint-absent fallback, which must call the internal `startWorkflow()`
|
|
15
|
+
* directly (see its doc comment) rather than the public `engine.start()`.
|
|
16
|
+
* Sourced this way — as a parameter, rather than this module importing
|
|
17
|
+
* `createLifecycleCallbacks` from `callback-creators-core.ts` itself — to avoid
|
|
18
|
+
* introducing a `bulk-operations-retry.ts` -> `callback-creators-core.ts` ->
|
|
19
|
+
* `termination.ts` -> `termination/complete.ts` -> `bulk-operations.ts` ->
|
|
20
|
+
* `bulk-operations-retry.ts` import cycle. Optional so direct-internals test
|
|
21
|
+
* harnesses that never reach the checkpoint-absent fallback (e.g.
|
|
22
|
+
* `bulk-operations-retry-direct.test.ts`) do not need to construct one.
|
|
23
|
+
*/
|
|
24
|
+
callbacks?: LifecycleCallbacks): Promise<BulkRetryFailedResult | BulkOperationDryRunResult>;
|
|
@@ -19,8 +19,13 @@ import {
|
|
|
19
19
|
validateBulkConfirmation,
|
|
20
20
|
withBulkAuditEvent
|
|
21
21
|
} from "./bulk-operations-shared.js";
|
|
22
|
+
import { ensureWorkflowCatalogReady, isWorkflowCatalogReady } from "./catalog-readiness.js";
|
|
22
23
|
import { resolveExecutableRegistrationForRetry } from "./dynamic-source-execution.js";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
assertLeaseHeldForEngineWork,
|
|
26
|
+
commitFencedEngineWriteAllowingPreconditionFailure
|
|
27
|
+
} from "./fenced-write.js";
|
|
28
|
+
import { startWorkflow } from "./lifecycle.js";
|
|
24
29
|
import { buildCatalogEntryRevisionCondition } from "./lifecycle/start-commit.js";
|
|
25
30
|
import { createTerminalCleanupTimerId } from "./state-utilities.js";
|
|
26
31
|
import { loadWorkflowState, runSerializedWorkflowStateWrite } from "./storage-io.js";
|
|
@@ -33,7 +38,7 @@ import {
|
|
|
33
38
|
import { buildWorkflowConcurrencyStartOperations } from "./workflow-concurrency.js";
|
|
34
39
|
import { buildWorkflowVisibilityIndexTransition } from "./workflow-indexes.js";
|
|
35
40
|
const FAILED_WORKFLOW_STATUSES = ["failed"], CHECKPOINT_RETRY_CONCURRENCY_ADMISSION_MAX_ATTEMPTS = 5;
|
|
36
|
-
export async function runBulkFailedWorkflowRetry(internals, filter, options = {}) {
|
|
41
|
+
export async function runBulkFailedWorkflowRetry(internals, filter, options = {}, callbacks) {
|
|
37
42
|
options = normalizeBulkOperationOptions(options);
|
|
38
43
|
assertScopedBulkWorkflowFilter(filter);
|
|
39
44
|
const actionableFilter = buildActionableBulkWorkflowFilter(internals, filter, FAILED_WORKFLOW_STATUSES), preparation = await prepareBulkOperation(internals, "retry-failed", actionableFilter, filter, {}, options);
|
|
@@ -43,7 +48,7 @@ export async function runBulkFailedWorkflowRetry(internals, filter, options = {}
|
|
|
43
48
|
const bulkConcurrency = resolveBulkOperationConcurrency(options);
|
|
44
49
|
let retried = 0;
|
|
45
50
|
const errors = [], retryResults = await runBulkWorkflowPool(preparation.workflowIds, bulkConcurrency, async (workflowId) => {
|
|
46
|
-
await retryFailedWorkflow(internals, workflowId);
|
|
51
|
+
await retryFailedWorkflow(internals, workflowId, callbacks);
|
|
47
52
|
return { status: "retried" };
|
|
48
53
|
});
|
|
49
54
|
for (const retryResult of retryResults) {
|
|
@@ -58,7 +63,7 @@ export async function runBulkFailedWorkflowRetry(internals, filter, options = {}
|
|
|
58
63
|
return result;
|
|
59
64
|
return withBulkAuditEvent(internals, preparation, options, result, retried);
|
|
60
65
|
}
|
|
61
|
-
async function retryFailedWorkflow(internals, workflowId) {
|
|
66
|
+
async function retryFailedWorkflow(internals, workflowId, callbacks) {
|
|
62
67
|
const state = await loadWorkflowState(internals, workflowId);
|
|
63
68
|
if (state === null)
|
|
64
69
|
throw Error("Workflow no longer exists");
|
|
@@ -69,11 +74,19 @@ async function retryFailedWorkflow(internals, workflowId) {
|
|
|
69
74
|
await internals.engine.resume(workflowId);
|
|
70
75
|
return;
|
|
71
76
|
}
|
|
72
|
-
|
|
77
|
+
const restartOptions = {
|
|
73
78
|
id: workflowId,
|
|
74
79
|
onTerminalConflict: "start-new",
|
|
75
80
|
...state.tags !== void 0 ? { tags: state.tags } : {}
|
|
76
|
-
}
|
|
81
|
+
};
|
|
82
|
+
if (callbacks === void 0) {
|
|
83
|
+
await internals.engine.start(state.type, state.input, restartOptions);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
assertLeaseHeldForEngineWork(internals);
|
|
87
|
+
if (!isWorkflowCatalogReady(internals.engine))
|
|
88
|
+
await ensureWorkflowCatalogReady(internals.engine);
|
|
89
|
+
await startWorkflow(internals, state.type, state.input, restartOptions, void 0, callbacks, void 0, void 0, "bulk-retry-only");
|
|
77
90
|
}
|
|
78
91
|
async function reactivateFailedWorkflowFromCheckpoint(internals, state) {
|
|
79
92
|
const reactivated = await runSerializedWorkflowStateWrite(internals, state.id, async () => reactivateFailedWorkflowFromCheckpointSerialized(internals, state.id));
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { BulkCancelResult, BulkDeleteResult, BulkOperationCommitOptions, BulkOperationDryRunOptions, BulkOperationDryRunResult, BulkOperationOptions, BulkRetryFailedResult, BulkSignalAllCommitOptions, BulkSignalAllDryRunOptions, BulkSignalResult, BulkTagResult, ListFilter, PurgeResult } from '../types.ts';
|
|
2
2
|
import { type CleanupWaiters } from './bulk-operations-purge.ts';
|
|
3
3
|
import type { EngineInternals } from './internals.ts';
|
|
4
|
+
import type { LifecycleCallbacks } from './lifecycle.ts';
|
|
4
5
|
export { purgeInternal, TERMINAL_CLEANUP_DELAY_MS } from './bulk-operations-purge.ts';
|
|
5
6
|
export declare function purge(internals: EngineInternals, filter: ListFilter | undefined, cleanupWaiters: CleanupWaiters): Promise<PurgeResult>;
|
|
6
7
|
export declare function cancelAll(internals: EngineInternals, filter: ListFilter, options: BulkOperationDryRunOptions): Promise<BulkOperationDryRunResult>;
|
|
7
8
|
export declare function cancelAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationCommitOptions): Promise<BulkCancelResult>;
|
|
8
9
|
export declare function cancelAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationDryRunOptions | BulkOperationCommitOptions): Promise<BulkCancelResult | BulkOperationDryRunResult>;
|
|
9
10
|
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options: BulkOperationDryRunOptions): Promise<BulkOperationDryRunResult>;
|
|
10
|
-
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationCommitOptions): Promise<BulkRetryFailedResult>;
|
|
11
|
-
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationDryRunOptions | BulkOperationCommitOptions): Promise<BulkRetryFailedResult | BulkOperationDryRunResult>;
|
|
11
|
+
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationCommitOptions, callbacks?: LifecycleCallbacks): Promise<BulkRetryFailedResult>;
|
|
12
|
+
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationDryRunOptions | BulkOperationCommitOptions, callbacks?: LifecycleCallbacks): Promise<BulkRetryFailedResult | BulkOperationDryRunResult>;
|
|
12
13
|
export declare function signalAll(internals: EngineInternals, filter: ListFilter, name: string, payload: unknown, options: BulkSignalAllDryRunOptions): Promise<BulkOperationDryRunResult>;
|
|
13
14
|
export declare function signalAll(internals: EngineInternals, filter: ListFilter, name: string, payload: unknown, options: BulkSignalAllCommitOptions): Promise<BulkSignalResult>;
|
|
14
15
|
export declare function signalAll(internals: EngineInternals, filter: ListFilter, name: string, payload?: unknown, options?: BulkOperationCommitOptions): Promise<BulkSignalResult>;
|
|
@@ -64,9 +64,9 @@ async function runBulkCancellation(internals, filter, options = {}) {
|
|
|
64
64
|
export async function cancelAll(internals, filter, options = {}) {
|
|
65
65
|
return runBulkCancellation(internals, filter, options);
|
|
66
66
|
}
|
|
67
|
-
export async function retryFailedAll(internals, filter, options = {}) {
|
|
67
|
+
export async function retryFailedAll(internals, filter, options = {}, callbacks) {
|
|
68
68
|
assertLeaseHeldForEngineWork(internals);
|
|
69
|
-
return runBulkFailedWorkflowRetry(internals, filter, options);
|
|
69
|
+
return runBulkFailedWorkflowRetry(internals, filter, options, callbacks);
|
|
70
70
|
}
|
|
71
71
|
export async function signalAll(internals, filter, name, payload, maybeOptions) {
|
|
72
72
|
const options = normalizeBulkOperationOptions(maybeOptions ?? {});
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
export function createChildWorkflowOperationCallbacks(engine) {
|
|
59
59
|
return {
|
|
60
60
|
runOperationWithResult: (workflowId, operation, execute) => runOperationWithResultForEngine(engine, workflowId, operation, execute),
|
|
61
|
-
start: (type, input, options) => startWorkflow(getInternals(engine), type, input, options, void 0, createLifecycleCallbacks(engine)),
|
|
61
|
+
start: (type, input, options, skipAdmissionIdCheck) => startWorkflow(getInternals(engine), type, input, options, void 0, createLifecycleCallbacks(engine), void 0, void 0, skipAdmissionIdCheck),
|
|
62
62
|
loadWorkflowState: (workflowId) => loadWorkflowState(getInternals(engine), workflowId),
|
|
63
63
|
getHandle: (workflowId) => engine.getHandle(workflowId),
|
|
64
64
|
getComposedWorkflowInterceptor: () => getComposedWorkflowInterceptor(getInternals(engine))
|
|
@@ -20,8 +20,8 @@ import { feedOperationResult } from "./strategy-helpers.js";
|
|
|
20
20
|
import { ensureTerminalCleanupTracked, failWorkflow, handleCleanupError } from "./termination.js";
|
|
21
21
|
export function createScheduleCallbacks(engine) {
|
|
22
22
|
return {
|
|
23
|
-
startWorkflow: async (type, input, options, additionalStartOperations, revisionOverride) => {
|
|
24
|
-
await startWorkflow(getInternals(engine), type, input, options, additionalStartOperations, createLifecycleCallbacks(engine), void 0, revisionOverride);
|
|
23
|
+
startWorkflow: async (type, input, options, additionalStartOperations, revisionOverride, skipAdmissionIdCheck) => {
|
|
24
|
+
await startWorkflow(getInternals(engine), type, input, options, additionalStartOperations, createLifecycleCallbacks(engine), void 0, revisionOverride, skipAdmissionIdCheck);
|
|
25
25
|
},
|
|
26
26
|
loadWorkflowState: (workflowId) => loadWorkflowState(getInternals(engine), workflowId),
|
|
27
27
|
cancelWorkflow: (workflowId) => engine.cancel(workflowId),
|
|
@@ -8,7 +8,19 @@ type ChildWorkflowOperation = Extract<ContextOperationRequest, {
|
|
|
8
8
|
}>;
|
|
9
9
|
export type ChildWorkflowOperationCallbacks = {
|
|
10
10
|
runOperationWithResult: (workflowId: string, operation: ChildWorkflowOperation, execute: () => Promise<unknown>) => Promise<void>;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* `skipAdmissionIdCheck` (WFT-95, internal only) is threaded straight through
|
|
13
|
+
* to `startWorkflow`'s parameter of the same name — see its doc comment. It
|
|
14
|
+
* is NEVER part of the public `StartOptions` a caller passes; only
|
|
15
|
+
* `dispatchChildWorkflowStart`'s crash-reattach retry sets it, and only
|
|
16
|
+
* after confirming a matching persisted child record already exists for
|
|
17
|
+
* `childWorkflowId`. This retry always passes the literal `'reattach-only'`,
|
|
18
|
+
* not `true`: the confirming read here is separate from
|
|
19
|
+
* `startWorkflow`'s own atomic re-check, so `'reattach-only'` fences the
|
|
20
|
+
* bypass to that re-check actually finding a match to reattach to (see
|
|
21
|
+
* `startWorkflow`'s doc comment for the full TOCTOU race this closes).
|
|
22
|
+
*/
|
|
23
|
+
start: (type: string, input: unknown, options?: StartOptions, skipAdmissionIdCheck?: boolean | 'reattach-only') => Promise<WorkflowHandle>;
|
|
12
24
|
loadWorkflowState: (workflowId: string) => Promise<WorkflowState | null>;
|
|
13
25
|
getHandle: (workflowId: string) => WorkflowHandle;
|
|
14
26
|
getComposedWorkflowInterceptor: () => ComposedWorkflowInterceptor | null;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assertOnTerminalConflictUnsupported,
|
|
3
|
+
StartWorkflowValidationError
|
|
4
|
+
} from "../start-workflow-validation.js";
|
|
5
|
+
import { isReservedWorkflowIdLiteral } from "../workflow-identifiers.js";
|
|
2
6
|
import { stageAtomicWorkflowCommitSideEffects } from "./checkpoint-side-effects.js";
|
|
3
7
|
import {
|
|
4
8
|
buildChildCancellationOperations,
|
|
@@ -50,6 +54,18 @@ async function resolveCollisionChildHandle(childWorkflowId, operation, execution
|
|
|
50
54
|
throw Error(`Child workflow id collision for "${childWorkflowId}" does not match the requested child workflow`, { cause: collisionError });
|
|
51
55
|
return callbacks.getHandle(childWorkflowId);
|
|
52
56
|
}
|
|
57
|
+
async function reattachLegacyReservedChildOrRethrow(childWorkflowId, operation, context, callbacks, originalError) {
|
|
58
|
+
const existingState = await callbacks.loadWorkflowState(childWorkflowId);
|
|
59
|
+
if (!existingState || !existingChildMatchesRequest(existingState, operation, context.pendingExecutionStateOwnerId ?? void 0, context.pendingParentWorkflowId, context.pendingParentWorkflowExecutionToken))
|
|
60
|
+
throw originalError;
|
|
61
|
+
try {
|
|
62
|
+
return await callbacks.start(operation.workflowType, operation.input, { id: childWorkflowId }, "reattach-only");
|
|
63
|
+
} catch (retryError) {
|
|
64
|
+
if (!(retryError instanceof WorkflowAlreadyExistsError))
|
|
65
|
+
throw retryError;
|
|
66
|
+
return resolveCollisionChildHandle(childWorkflowId, operation, context.pendingExecutionStateOwnerId ?? void 0, context.pendingParentWorkflowId, context.pendingParentWorkflowExecutionToken, retryError, callbacks);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
53
69
|
async function dispatchChildWorkflowStart(internals, childWorkflowId, operation, context, callbacks) {
|
|
54
70
|
assertOnTerminalConflictUnsupported(operation.options, "ctx.startChild");
|
|
55
71
|
applyPendingChildExecutionContext(internals, context);
|
|
@@ -58,9 +74,11 @@ async function dispatchChildWorkflowStart(internals, childWorkflowId, operation,
|
|
|
58
74
|
id: childWorkflowId
|
|
59
75
|
});
|
|
60
76
|
} catch (error) {
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
if (error instanceof WorkflowAlreadyExistsError)
|
|
78
|
+
return resolveCollisionChildHandle(childWorkflowId, operation, context.pendingExecutionStateOwnerId ?? void 0, context.pendingParentWorkflowId, context.pendingParentWorkflowExecutionToken, error, callbacks);
|
|
79
|
+
if (error instanceof StartWorkflowValidationError && isReservedWorkflowIdLiteral(childWorkflowId))
|
|
80
|
+
return reattachLegacyReservedChildOrRethrow(childWorkflowId, operation, context, callbacks, error);
|
|
81
|
+
throw error;
|
|
64
82
|
} finally {
|
|
65
83
|
clearPendingChildExecutionContext(internals, context);
|
|
66
84
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure codec for the durable per-workflow-id generation counter
|
|
3
|
+
* (`wf-gen:<id>`, WFT-153).
|
|
4
|
+
*
|
|
5
|
+
* Reuses `lease-codec.ts`'s `encodeEpoch`/`decodeEpoch` under
|
|
6
|
+
* generation-specific names rather than reimplementing them: both are
|
|
7
|
+
* permanently-retained monotonic uint64 counters minted the same way
|
|
8
|
+
* (`(observed ?? 0) + 1`, never a literal), so sharing one encoder/decoder
|
|
9
|
+
* keeps their on-disk representation byte-identical and their validity range
|
|
10
|
+
* ([1, MAX_SAFE_INTEGER)) — fail-closed on corruption — in lockstep by
|
|
11
|
+
* construction rather than by convention.
|
|
12
|
+
*
|
|
13
|
+
* @module core/engine/generation-codec
|
|
14
|
+
*/
|
|
15
|
+
import { decodeEpoch, encodeEpoch } from './lease-codec.ts';
|
|
16
|
+
/** Encode an 8-byte big-endian uint64 generation. See `lease-codec.ts#encodeEpoch`. */
|
|
17
|
+
export declare const encodeGeneration: typeof encodeEpoch;
|
|
18
|
+
/**
|
|
19
|
+
* Decode an 8-byte big-endian uint64 generation, or `null` when the stored
|
|
20
|
+
* value is not a usable generation. See `lease-codec.ts#decodeEpoch`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const decodeGeneration: typeof decodeEpoch;
|
|
@@ -847,7 +847,7 @@ export class Engine extends EventTarget {
|
|
|
847
847
|
return cancelAllWorkflows(getInternals(this), filter, options);
|
|
848
848
|
}
|
|
849
849
|
async retryFailedAll(filter, options) {
|
|
850
|
-
return retryFailedAllWorkflows(getInternals(this), filter, options);
|
|
850
|
+
return retryFailedAllWorkflows(getInternals(this), filter, options, this.#createLifecycleCallbacks());
|
|
851
851
|
}
|
|
852
852
|
async signalAll(filter, name, payloadOrOptions, options) {
|
|
853
853
|
if (options === void 0)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `recoverAll()`'s per-entry error isolation — split out of `transition.ts`,
|
|
3
|
+
* which has no headroom under this repository's 500-line implementation-file
|
|
4
|
+
* ceiling for the WFT-134 claim-release wrapping `recoverEntryOrIsolateFailure`
|
|
5
|
+
* needed (mirrors `resume-body.ts`'s same split). This file's own job is just
|
|
6
|
+
* the branch-matching: given the error `resume()` rejected with, either
|
|
7
|
+
* isolate it to just this workflow (returning `null`) or rethrow it.
|
|
8
|
+
*
|
|
9
|
+
* @module core/engine/lifecycle/recovery-isolation
|
|
10
|
+
*/
|
|
11
|
+
import type { WorkflowHandle } from '../handles.ts';
|
|
12
|
+
import type { LifecycleCallbacks, RecoverAllOptions } from './shared.ts';
|
|
13
|
+
/**
|
|
14
|
+
* Isolate one `recoverAll()` entry's failure to just this workflow when it
|
|
15
|
+
* matches a known-recoverable error, committing the matching `failWorkflowForXxx`
|
|
16
|
+
* fenced write; rethrows anything else, including an opted-in
|
|
17
|
+
* `VersionMismatchError` throw. See `transition.ts`'s `recoverEntryOrIsolateFailure`
|
|
18
|
+
* for the caller-side claim-release wrapping around this.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isolateRecoveryFailure(workflowId: string, callbacks: LifecycleCallbacks, options: RecoverAllOptions | undefined, error: unknown): Promise<WorkflowHandle | null>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RegExpExtensionDecodeError } from "../../codec/extension-codec.js";
|
|
2
|
+
import { VersionMismatchError } from "../../versioning.js";
|
|
3
|
+
import { DynamicWorkflowSourceUnavailableError } from "../dynamic-source-errors.js";
|
|
4
|
+
import { WorkflowClaimUnavailableError } from "../lease-errors.js";
|
|
5
|
+
import { WorkflowRevisionUnavailableError } from "../revision-errors.js";
|
|
6
|
+
export async function isolateRecoveryFailure(workflowId, callbacks, options, error) {
|
|
7
|
+
if (error instanceof RegExpExtensionDecodeError) {
|
|
8
|
+
await callbacks.failWorkflowForCheckpointDecodeError(workflowId, error);
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
if (error instanceof VersionMismatchError && options?.versionMismatchPolicy !== "throw") {
|
|
12
|
+
await callbacks.failWorkflowForVersionMismatch(workflowId, error);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (error instanceof WorkflowClaimUnavailableError)
|
|
16
|
+
return null;
|
|
17
|
+
if (error instanceof WorkflowRevisionUnavailableError) {
|
|
18
|
+
await callbacks.failWorkflowForRevisionUnavailable(workflowId, error);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (error instanceof DynamicWorkflowSourceUnavailableError) {
|
|
22
|
+
await callbacks.failWorkflowForUnavailableDynamicSource(workflowId, error);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `resumeWorkflowFromStorage()`'s guts — split out of `resume.ts`, which has
|
|
3
|
+
* no headroom under the repository's 500-line implementation-file ceiling
|
|
4
|
+
* for the WFT-134 claim-release wrapping added around this logic (mirrors
|
|
5
|
+
* `resume-generation-guard.ts`'s same split). `resume.ts` itself is now just
|
|
6
|
+
* the public entry point: load/validate state, acquire a claim if needed,
|
|
7
|
+
* and call {@link performResumeAfterClaimAcquired} below inside the
|
|
8
|
+
* claim-release `try`/`catch`.
|
|
9
|
+
*
|
|
10
|
+
* @module core/engine/lifecycle/resume-body
|
|
11
|
+
*/
|
|
12
|
+
import { type EventHeadRecord } from '../../event-log.ts';
|
|
13
|
+
import type { Checkpoint, WorkflowState } from '../../types.ts';
|
|
14
|
+
import { type WorkflowVersionTuple } from '../../workflow-version-tuple.ts';
|
|
15
|
+
import { type WorkflowHandle } from '../handles.ts';
|
|
16
|
+
import type { EngineInternals } from '../internals.ts';
|
|
17
|
+
import { deriveResumeGeneration } from './resume-generation-guard.ts';
|
|
18
|
+
import { type LifecycleCallbacks, type RecoverAllOptions, type RegistrationEntry } from './shared.ts';
|
|
19
|
+
export type SerializedResumeArgs = {
|
|
20
|
+
workflowId: string;
|
|
21
|
+
resumeCheckpoint: Checkpoint;
|
|
22
|
+
serializedCheckpoint: Uint8Array;
|
|
23
|
+
registeredVersionTuple: WorkflowVersionTuple;
|
|
24
|
+
restoredHead: EventHeadRecord;
|
|
25
|
+
workflowStartHeaders: Map<string, string> | undefined;
|
|
26
|
+
registration: RegistrationEntry;
|
|
27
|
+
/**
|
|
28
|
+
* The EXACT revision `registration` resolved against, NOT `state.revision`
|
|
29
|
+
* re-read independently (WFT-19 round 5): a legacy record with one registered
|
|
30
|
+
* candidate has `state.revision === undefined` even though the resolver
|
|
31
|
+
* resolved it — caching `state.revision` here would disable exact-revision lookup.
|
|
32
|
+
*/
|
|
33
|
+
resolvedRevision: string | undefined;
|
|
34
|
+
expectedGeneration: ReturnType<typeof deriveResumeGeneration>;
|
|
35
|
+
callbacks: LifecycleCallbacks;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Everything `resumeWorkflowFromStorage` does once a claim (if any) is
|
|
39
|
+
* acquired: load tracking/checkpoint/registration state, prepare the resume,
|
|
40
|
+
* and either relaunch inline or in a worker. `resumeWorkflowFromStorage`
|
|
41
|
+
* itself just wraps a call to this in the WFT-134 claim-release
|
|
42
|
+
* `try`/`catch`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function performResumeAfterClaimAcquired(internals: EngineInternals, workflowId: string, state: WorkflowState, dispatchResumedEvent: boolean, callbacks: LifecycleCallbacks, onRecoveredWorkflow?: RecoverAllOptions['onRecoveredWorkflow']): Promise<WorkflowHandle>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { KEYS } from "../../../storage/interface.js";
|
|
2
|
+
import { deserializeCheckpoint, serializeCheckpoint } from "../../checkpoint.js";
|
|
3
|
+
import { encode } from "../../codec.js";
|
|
4
|
+
import { Context, setContextWorkflowInterceptor } from "../../context.js";
|
|
5
|
+
import { EventLog } from "../../event-log.js";
|
|
6
|
+
import { WorkflowResumedEvent } from "../../events.js";
|
|
7
|
+
import { buildTimerBatchOperations } from "../../scheduler.js";
|
|
8
|
+
import { createCancelHandlerRegistration, resetCancelHandlers } from "../cancel-handlers.js";
|
|
9
|
+
import { rememberCommittedCheckpointBytes } from "../checkpoint-commit-snapshots.js";
|
|
10
|
+
import { rehydrateChildCancellationHandlers } from "../child-workflow-cancellation.js";
|
|
11
|
+
import { resolveExecutableRegistrationOrRenamedNotFound } from "../dynamic-source-execution.js";
|
|
12
|
+
import { commitFencedEngineWrite } from "../fenced-write.js";
|
|
13
|
+
import { getWorkflowExecutionStartedAt } from "../handles.js";
|
|
14
|
+
import { loadWorkflowState } from "../storage-io.js";
|
|
15
|
+
import { getComposedWorkflowInterceptor } from "../strategy-helpers.js";
|
|
16
|
+
import { buildWorkflowVisibilityIndexTransition } from "../workflow-indexes.js";
|
|
17
|
+
import { prepareResumeState } from "./persist.js";
|
|
18
|
+
import {
|
|
19
|
+
reprovideRecoveredServices,
|
|
20
|
+
workflowServicesResolverInfoFromState
|
|
21
|
+
} from "./recovered-services.js";
|
|
22
|
+
import { assertSameGeneration, deriveResumeGeneration } from "./resume-generation-guard.js";
|
|
23
|
+
import {
|
|
24
|
+
enforceHistoryPolicyBeforeReplay,
|
|
25
|
+
loadTerminalCleanupTrackedState,
|
|
26
|
+
loadWorkflowStartHeaders,
|
|
27
|
+
setWorkflowStartHeaders
|
|
28
|
+
} from "./shared.js";
|
|
29
|
+
async function prepareRecoveredServicesOrFail(internals, state, callbacks, resolverInfo) {
|
|
30
|
+
return reprovideRecoveredServices(internals, state, callbacks.failWorkflowForUnavailableServices, callbacks.handleCleanupError, callbacks.dispatchEvent, resolverInfo);
|
|
31
|
+
}
|
|
32
|
+
async function runRecoveredWorkflowHookOrFail(internals, state, handle, resolverInfo, onRecoveredWorkflow, callbacks) {
|
|
33
|
+
const info = {
|
|
34
|
+
...resolverInfo,
|
|
35
|
+
handle,
|
|
36
|
+
launchOptions: resolverInfo.launchOptions ?? { id: state.id },
|
|
37
|
+
services: internals.workflowServices.get(state.id)
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
await onRecoveredWorkflow(info);
|
|
41
|
+
return !1;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const reason = error instanceof Error ? error.message : String(error), hookError = Error(`Recovery hook failed for workflow "${state.id}": ${reason}`);
|
|
44
|
+
try {
|
|
45
|
+
await callbacks.failWorkflowForRecoveryHook(state.id, hookError);
|
|
46
|
+
} catch (commitError) {
|
|
47
|
+
callbacks.handleCleanupError("onRecoveredWorkflow", commitError, state.id);
|
|
48
|
+
}
|
|
49
|
+
return !0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function prepareRecoveredWorkflowOrFail(internals, state, callbacks, onRecoveredWorkflow) {
|
|
53
|
+
const resolverInfo = onRecoveredWorkflow === void 0 ? void 0 : await workflowServicesResolverInfoFromState(internals, state), handle = callbacks.getHandle(state.id);
|
|
54
|
+
if (await prepareRecoveredServicesOrFail(internals, state, callbacks, resolverInfo))
|
|
55
|
+
return { handle, shouldStop: !0 };
|
|
56
|
+
if (onRecoveredWorkflow === void 0 || resolverInfo === void 0)
|
|
57
|
+
return { handle, shouldStop: !1 };
|
|
58
|
+
const shouldStop = await runRecoveredWorkflowHookOrFail(internals, state, handle, resolverInfo, onRecoveredWorkflow, callbacks);
|
|
59
|
+
return { handle, shouldStop };
|
|
60
|
+
}
|
|
61
|
+
function assertResumeNotTerminating(internals, workflowId) {
|
|
62
|
+
if (internals.terminalizingWorkflows.has(workflowId))
|
|
63
|
+
throw Error(`Cannot resume workflow "${workflowId}": termination is in progress`);
|
|
64
|
+
}
|
|
65
|
+
function commitSerializedResumeState(internals, args) {
|
|
66
|
+
const {
|
|
67
|
+
workflowId,
|
|
68
|
+
resumeCheckpoint,
|
|
69
|
+
serializedCheckpoint,
|
|
70
|
+
registeredVersionTuple,
|
|
71
|
+
restoredHead,
|
|
72
|
+
callbacks
|
|
73
|
+
} = args;
|
|
74
|
+
internals.checkpoints.set(workflowId, resumeCheckpoint);
|
|
75
|
+
rememberCommittedCheckpointBytes(internals, workflowId, serializedCheckpoint);
|
|
76
|
+
internals.workflowVersionTuples.set(workflowId, registeredVersionTuple);
|
|
77
|
+
internals.eventLogHeads.set(workflowId, restoredHead);
|
|
78
|
+
setWorkflowStartHeaders(internals, workflowId, args.workflowStartHeaders, callbacks);
|
|
79
|
+
internals.parkedInlineWorkflows.delete(workflowId);
|
|
80
|
+
}
|
|
81
|
+
async function relaunchInlineWorkflowAfterResume(internals, latestState, args) {
|
|
82
|
+
const { workflowId, resumeCheckpoint, registration, resolvedRevision, callbacks } = args, accumulatedResults = new Map(resumeCheckpoint.accumulatedResults), workflowAbort = new AbortController;
|
|
83
|
+
internals.workflowTypeByWorkflowId.set(workflowId, {
|
|
84
|
+
type: latestState.type,
|
|
85
|
+
revision: resolvedRevision
|
|
86
|
+
});
|
|
87
|
+
resetCancelHandlers(internals, workflowId);
|
|
88
|
+
await rehydrateChildCancellationHandlers(internals, workflowId, callbacks);
|
|
89
|
+
const context = new Context({
|
|
90
|
+
workflowId,
|
|
91
|
+
...latestState.workflowExecutionToken !== void 0 && {
|
|
92
|
+
workflowExecutionToken: latestState.workflowExecutionToken
|
|
93
|
+
},
|
|
94
|
+
workflowType: latestState.type,
|
|
95
|
+
startedAt: getWorkflowExecutionStartedAt(latestState),
|
|
96
|
+
abortController: workflowAbort,
|
|
97
|
+
getNow: internals.options.getNow,
|
|
98
|
+
resolveWorkflowType: callbacks.resolveWorkflowTypeTarget,
|
|
99
|
+
executionStateOwnerId: latestState.executionStateOwnerId ?? workflowId,
|
|
100
|
+
accumulatedResults,
|
|
101
|
+
locals: resumeCheckpoint.locals,
|
|
102
|
+
searchAttributes: resumeCheckpoint.searchAttributes,
|
|
103
|
+
registerCancelHandler: createCancelHandlerRegistration(internals, workflowId),
|
|
104
|
+
...registration.searchAttributes && {
|
|
105
|
+
searchAttributeSchema: registration.searchAttributes
|
|
106
|
+
},
|
|
107
|
+
sleepReferenceTime: resumeCheckpoint.createdAt,
|
|
108
|
+
...latestState.executionDeadline !== void 0 && {
|
|
109
|
+
deadline: latestState.executionDeadline
|
|
110
|
+
},
|
|
111
|
+
services: internals.workflowServices.get(workflowId),
|
|
112
|
+
...internals.options.onLog != null && { logSink: internals.options.onLog }
|
|
113
|
+
});
|
|
114
|
+
setContextWorkflowInterceptor(context, getComposedWorkflowInterceptor(internals));
|
|
115
|
+
if (internals.options.development)
|
|
116
|
+
context.explain(!0);
|
|
117
|
+
const generator = registration.handler(context, latestState.input), inlineStrategy = internals.inlineStrategy;
|
|
118
|
+
inlineStrategy.adoptWorkflow(workflowId, generator, context, workflowAbort);
|
|
119
|
+
inlineStrategy.continueWorkflow(workflowId, void 0);
|
|
120
|
+
}
|
|
121
|
+
async function relaunchWorkerWorkflowAfterResume(internals, latestState, args) {
|
|
122
|
+
const { workflowId, resumeCheckpoint, workflowStartHeaders, resolvedRevision, callbacks } = args;
|
|
123
|
+
internals.workflowTypeByWorkflowId.set(workflowId, {
|
|
124
|
+
type: latestState.type,
|
|
125
|
+
revision: resolvedRevision
|
|
126
|
+
});
|
|
127
|
+
resetCancelHandlers(internals, workflowId);
|
|
128
|
+
await rehydrateChildCancellationHandlers(internals, workflowId, callbacks);
|
|
129
|
+
const serialized = serializeCheckpoint(resumeCheckpoint);
|
|
130
|
+
internals.strategy.startWorkflow({
|
|
131
|
+
workflowId,
|
|
132
|
+
...latestState.workflowExecutionToken !== void 0 && {
|
|
133
|
+
workflowExecutionToken: latestState.workflowExecutionToken
|
|
134
|
+
},
|
|
135
|
+
workflowType: latestState.type,
|
|
136
|
+
input: latestState.input,
|
|
137
|
+
checkpoint: serialized,
|
|
138
|
+
nestingDepth: internals.workflowNestingDepths.get(workflowId) ?? 0,
|
|
139
|
+
executionStateOwnerId: latestState.executionStateOwnerId ?? workflowId,
|
|
140
|
+
...latestState.executionDeadline !== void 0 && {
|
|
141
|
+
deadline: latestState.executionDeadline
|
|
142
|
+
},
|
|
143
|
+
...workflowStartHeaders !== void 0 && workflowStartHeaders.size > 0 && {
|
|
144
|
+
headers: [...workflowStartHeaders]
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function reactivateSuspendedWorkflowState(internals, state) {
|
|
149
|
+
if (state.status !== "suspended")
|
|
150
|
+
return;
|
|
151
|
+
const previousState = { ...state };
|
|
152
|
+
state.status = "running";
|
|
153
|
+
state.updatedAt = internals.options.getNow();
|
|
154
|
+
await commitFencedEngineWrite(internals, state.id, [
|
|
155
|
+
{ type: "put", key: KEYS.workflow(state.id), value: encode(state) },
|
|
156
|
+
...buildWorkflowVisibilityIndexTransition(state.id, previousState, state).batchOps,
|
|
157
|
+
...state.executionDeadline !== void 0 ? buildTimerBatchOperations({
|
|
158
|
+
id: `deadline:${state.id}`,
|
|
159
|
+
workflowId: state.id,
|
|
160
|
+
fireAt: state.executionDeadline,
|
|
161
|
+
kind: "execution-deadline"
|
|
162
|
+
}) : []
|
|
163
|
+
], [], () => Error(`Resume of workflow "${state.id}" lost its CAS race.`));
|
|
164
|
+
}
|
|
165
|
+
async function performSerializedResume(internals, args) {
|
|
166
|
+
const { workflowId } = args;
|
|
167
|
+
assertResumeNotTerminating(internals, workflowId);
|
|
168
|
+
const latestState = await loadWorkflowState(internals, workflowId);
|
|
169
|
+
assertResumeNotTerminating(internals, workflowId);
|
|
170
|
+
if (!latestState)
|
|
171
|
+
throw Error(`Workflow "${workflowId}" not found in storage`);
|
|
172
|
+
if (latestState.status !== "running" && latestState.status !== "suspended")
|
|
173
|
+
throw Error(`Cannot resume workflow "${workflowId}": status is "${latestState.status}", expected "running" or "suspended"`);
|
|
174
|
+
assertSameGeneration(workflowId, latestState, args.expectedGeneration);
|
|
175
|
+
await reactivateSuspendedWorkflowState(internals, latestState);
|
|
176
|
+
commitSerializedResumeState(internals, args);
|
|
177
|
+
if (internals.inlineStrategy) {
|
|
178
|
+
await relaunchInlineWorkflowAfterResume(internals, latestState, args);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await relaunchWorkerWorkflowAfterResume(internals, latestState, args);
|
|
182
|
+
}
|
|
183
|
+
export async function performResumeAfterClaimAcquired(internals, workflowId, state, dispatchResumedEvent, callbacks, onRecoveredWorkflow) {
|
|
184
|
+
await loadTerminalCleanupTrackedState(internals, workflowId, callbacks);
|
|
185
|
+
const checkpointBytes = await internals.storage.get(KEYS.checkpoint(workflowId));
|
|
186
|
+
if (!checkpointBytes)
|
|
187
|
+
throw Error(`Checkpoint not found for workflow "${workflowId}"`);
|
|
188
|
+
const checkpoint = deserializeCheckpoint(checkpointBytes), { entry: registration, revision: resolvedRevision } = await resolveExecutableRegistrationOrRenamedNotFound((type) => callbacks.resolveExecutableRegistrationForRevision(type, state.revision), state.type, () => Error(`No workflow registered with name "${state.type}" (needed to resume "${workflowId}")`)), preparedResumeState = await prepareResumeState(internals, workflowId, state, checkpoint, checkpointBytes, registration, callbacks), resumeCheckpoint = preparedResumeState.checkpoint, registeredVersionTuple = preparedResumeState.versionTuple, restoredHead = await new EventLog(internals.storage, workflowId).loadHead();
|
|
189
|
+
if (await enforceHistoryPolicyBeforeReplay(internals, workflowId, restoredHead, callbacks))
|
|
190
|
+
return callbacks.getHandle(workflowId);
|
|
191
|
+
const workflowStartHeaders = await loadWorkflowStartHeaders(internals, workflowId, callbacks), { handle, shouldStop } = await prepareRecoveredWorkflowOrFail(internals, state, callbacks, onRecoveredWorkflow);
|
|
192
|
+
if (shouldStop)
|
|
193
|
+
return handle;
|
|
194
|
+
await callbacks.runSerializedWorkflowStateWrite(workflowId, () => performSerializedResume(internals, {
|
|
195
|
+
workflowId,
|
|
196
|
+
resumeCheckpoint,
|
|
197
|
+
serializedCheckpoint: preparedResumeState.serializedCheckpoint,
|
|
198
|
+
registeredVersionTuple,
|
|
199
|
+
restoredHead,
|
|
200
|
+
workflowStartHeaders,
|
|
201
|
+
registration,
|
|
202
|
+
resolvedRevision,
|
|
203
|
+
expectedGeneration: deriveResumeGeneration(state),
|
|
204
|
+
callbacks
|
|
205
|
+
}));
|
|
206
|
+
if (dispatchResumedEvent)
|
|
207
|
+
callbacks.dispatchEvent(new WorkflowResumedEvent(workflowId, resumeCheckpoint.step));
|
|
208
|
+
if (internals.inlineStrategy)
|
|
209
|
+
callbacks.swallowPromiseRejection(callbacks.processPendingUpdatesAfterInlineAdvance(workflowId));
|
|
210
|
+
return handle;
|
|
211
|
+
}
|