@langchain/langgraph 1.3.7 → 1.4.1
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/dist/channels/base.cjs +78 -2
- package/dist/channels/base.cjs.map +1 -1
- package/dist/channels/base.d.cts +35 -2
- package/dist/channels/base.d.cts.map +1 -1
- package/dist/channels/base.d.ts +35 -2
- package/dist/channels/base.d.ts.map +1 -1
- package/dist/channels/base.js +77 -4
- package/dist/channels/base.js.map +1 -1
- package/dist/channels/delta.cjs +136 -0
- package/dist/channels/delta.cjs.map +1 -0
- package/dist/channels/delta.d.cts +99 -0
- package/dist/channels/delta.d.cts.map +1 -0
- package/dist/channels/delta.d.ts +99 -0
- package/dist/channels/delta.d.ts.map +1 -0
- package/dist/channels/delta.js +136 -0
- package/dist/channels/delta.js.map +1 -0
- package/dist/channels/index.cjs +5 -0
- package/dist/channels/index.d.cts +3 -2
- package/dist/channels/index.d.ts +3 -2
- package/dist/channels/index.js +3 -2
- package/dist/constants.cjs +63 -4
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +33 -2
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.ts +33 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +59 -5
- package/dist/constants.js.map +1 -1
- package/dist/errors.cjs +128 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +86 -1
- package/dist/errors.d.cts.map +1 -1
- package/dist/errors.d.ts +86 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +123 -1
- package/dist/errors.js.map +1 -1
- package/dist/func/index.cjs +9 -2
- package/dist/func/index.cjs.map +1 -1
- package/dist/func/index.d.cts +14 -0
- package/dist/func/index.d.cts.map +1 -1
- package/dist/func/index.d.ts +14 -0
- package/dist/func/index.d.ts.map +1 -1
- package/dist/func/index.js +9 -2
- package/dist/func/index.js.map +1 -1
- package/dist/graph/graph.cjs +44 -7
- package/dist/graph/graph.cjs.map +1 -1
- package/dist/graph/graph.d.cts +24 -2
- package/dist/graph/graph.d.cts.map +1 -1
- package/dist/graph/graph.d.ts +24 -2
- package/dist/graph/graph.d.ts.map +1 -1
- package/dist/graph/graph.js +44 -7
- package/dist/graph/graph.js.map +1 -1
- package/dist/graph/index.d.ts +3 -3
- package/dist/graph/messages_reducer.cjs +55 -0
- package/dist/graph/messages_reducer.cjs.map +1 -1
- package/dist/graph/messages_reducer.d.cts +28 -1
- package/dist/graph/messages_reducer.d.cts.map +1 -1
- package/dist/graph/messages_reducer.d.ts +28 -1
- package/dist/graph/messages_reducer.d.ts.map +1 -1
- package/dist/graph/messages_reducer.js +56 -2
- package/dist/graph/messages_reducer.js.map +1 -1
- package/dist/graph/state.cjs +208 -12
- package/dist/graph/state.cjs.map +1 -1
- package/dist/graph/state.d.cts +193 -17
- package/dist/graph/state.d.cts.map +1 -1
- package/dist/graph/state.d.ts +193 -17
- package/dist/graph/state.d.ts.map +1 -1
- package/dist/graph/state.js +210 -14
- package/dist/graph/state.js.map +1 -1
- package/dist/graph/zod/schema.cjs +5 -0
- package/dist/graph/zod/schema.cjs.map +1 -1
- package/dist/graph/zod/schema.d.cts.map +1 -1
- package/dist/graph/zod/schema.d.ts.map +1 -1
- package/dist/graph/zod/schema.js +5 -0
- package/dist/graph/zod/schema.js.map +1 -1
- package/dist/index.cjs +11 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -8
- package/dist/index.d.ts +11 -8
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/prebuilt/react_agent_executor.d.cts +1 -1
- package/dist/prebuilt/tool_node.cjs +69 -6
- package/dist/prebuilt/tool_node.cjs.map +1 -1
- package/dist/prebuilt/tool_node.d.cts +55 -3
- package/dist/prebuilt/tool_node.d.cts.map +1 -1
- package/dist/prebuilt/tool_node.d.ts +55 -3
- package/dist/prebuilt/tool_node.d.ts.map +1 -1
- package/dist/prebuilt/tool_node.js +69 -6
- package/dist/prebuilt/tool_node.js.map +1 -1
- package/dist/pregel/algo.cjs +182 -21
- package/dist/pregel/algo.cjs.map +1 -1
- package/dist/pregel/algo.d.cts +1 -1
- package/dist/pregel/algo.d.cts.map +1 -1
- package/dist/pregel/algo.d.ts +1 -1
- package/dist/pregel/algo.d.ts.map +1 -1
- package/dist/pregel/algo.js +185 -25
- package/dist/pregel/algo.js.map +1 -1
- package/dist/pregel/call.cjs +2 -1
- package/dist/pregel/call.cjs.map +1 -1
- package/dist/pregel/call.js +2 -1
- package/dist/pregel/call.js.map +1 -1
- package/dist/pregel/index.cjs +15 -3
- package/dist/pregel/index.cjs.map +1 -1
- package/dist/pregel/index.d.cts.map +1 -1
- package/dist/pregel/index.d.ts.map +1 -1
- package/dist/pregel/index.js +17 -5
- package/dist/pregel/index.js.map +1 -1
- package/dist/pregel/loop.cjs +362 -41
- package/dist/pregel/loop.cjs.map +1 -1
- package/dist/pregel/loop.js +365 -44
- package/dist/pregel/loop.js.map +1 -1
- package/dist/pregel/messages-v2.cjs +1 -1
- package/dist/pregel/messages-v2.js +1 -1
- package/dist/pregel/messages.cjs +1 -1
- package/dist/pregel/messages.js +1 -1
- package/dist/pregel/read.cjs +15 -5
- package/dist/pregel/read.cjs.map +1 -1
- package/dist/pregel/read.d.cts +9 -0
- package/dist/pregel/read.d.cts.map +1 -1
- package/dist/pregel/read.d.ts +9 -0
- package/dist/pregel/read.d.ts.map +1 -1
- package/dist/pregel/read.js +15 -5
- package/dist/pregel/read.js.map +1 -1
- package/dist/pregel/remote-run-stream.cjs +107 -0
- package/dist/pregel/remote-run-stream.cjs.map +1 -0
- package/dist/pregel/remote-run-stream.d.cts +33 -0
- package/dist/pregel/remote-run-stream.d.cts.map +1 -0
- package/dist/pregel/remote-run-stream.d.ts +33 -0
- package/dist/pregel/remote-run-stream.d.ts.map +1 -0
- package/dist/pregel/remote-run-stream.js +107 -0
- package/dist/pregel/remote-run-stream.js.map +1 -0
- package/dist/pregel/remote.cjs +61 -1
- package/dist/pregel/remote.cjs.map +1 -1
- package/dist/pregel/remote.d.cts +17 -0
- package/dist/pregel/remote.d.cts.map +1 -1
- package/dist/pregel/remote.d.ts +17 -0
- package/dist/pregel/remote.d.ts.map +1 -1
- package/dist/pregel/remote.js +61 -1
- package/dist/pregel/remote.js.map +1 -1
- package/dist/pregel/replay.cjs +62 -0
- package/dist/pregel/replay.cjs.map +1 -0
- package/dist/pregel/replay.js +62 -0
- package/dist/pregel/replay.js.map +1 -0
- package/dist/pregel/retry.cjs +8 -6
- package/dist/pregel/retry.cjs.map +1 -1
- package/dist/pregel/retry.js +8 -6
- package/dist/pregel/retry.js.map +1 -1
- package/dist/pregel/runnable_types.d.cts +20 -0
- package/dist/pregel/runnable_types.d.cts.map +1 -1
- package/dist/pregel/runnable_types.d.ts +20 -0
- package/dist/pregel/runnable_types.d.ts.map +1 -1
- package/dist/pregel/runner.cjs +48 -7
- package/dist/pregel/runner.cjs.map +1 -1
- package/dist/pregel/runner.js +50 -9
- package/dist/pregel/runner.js.map +1 -1
- package/dist/pregel/runtime.cjs +64 -0
- package/dist/pregel/runtime.cjs.map +1 -0
- package/dist/pregel/runtime.d.cts +57 -0
- package/dist/pregel/runtime.d.cts.map +1 -0
- package/dist/pregel/runtime.d.ts +57 -0
- package/dist/pregel/runtime.d.ts.map +1 -0
- package/dist/pregel/runtime.js +64 -0
- package/dist/pregel/runtime.js.map +1 -0
- package/dist/pregel/stream.cjs +2 -0
- package/dist/pregel/stream.cjs.map +1 -1
- package/dist/pregel/stream.js +2 -0
- package/dist/pregel/stream.js.map +1 -1
- package/dist/pregel/timeout.cjs +216 -0
- package/dist/pregel/timeout.cjs.map +1 -0
- package/dist/pregel/timeout.js +216 -0
- package/dist/pregel/timeout.js.map +1 -0
- package/dist/pregel/types.cjs +3 -1
- package/dist/pregel/types.cjs.map +1 -1
- package/dist/pregel/types.d.cts +13 -0
- package/dist/pregel/types.d.cts.map +1 -1
- package/dist/pregel/types.d.ts +14 -1
- package/dist/pregel/types.d.ts.map +1 -1
- package/dist/pregel/types.js +3 -1
- package/dist/pregel/types.js.map +1 -1
- package/dist/pregel/utils/config.cjs +18 -2
- package/dist/pregel/utils/config.cjs.map +1 -1
- package/dist/pregel/utils/config.d.cts +15 -1
- package/dist/pregel/utils/config.d.cts.map +1 -1
- package/dist/pregel/utils/config.d.ts +15 -1
- package/dist/pregel/utils/config.d.ts.map +1 -1
- package/dist/pregel/utils/config.js +18 -2
- package/dist/pregel/utils/config.js.map +1 -1
- package/dist/pregel/utils/index.cjs +1 -0
- package/dist/pregel/utils/index.cjs.map +1 -1
- package/dist/pregel/utils/index.d.cts +6 -1
- package/dist/pregel/utils/index.d.cts.map +1 -1
- package/dist/pregel/utils/index.d.ts +6 -1
- package/dist/pregel/utils/index.d.ts.map +1 -1
- package/dist/pregel/utils/index.js +1 -0
- package/dist/pregel/utils/index.js.map +1 -1
- package/dist/pregel/utils/timeout.cjs +34 -0
- package/dist/pregel/utils/timeout.cjs.map +1 -0
- package/dist/pregel/utils/timeout.d.cts +45 -0
- package/dist/pregel/utils/timeout.d.cts.map +1 -0
- package/dist/pregel/utils/timeout.d.ts +45 -0
- package/dist/pregel/utils/timeout.d.ts.map +1 -0
- package/dist/pregel/utils/timeout.js +34 -0
- package/dist/pregel/utils/timeout.js.map +1 -0
- package/dist/state/schema.cjs +12 -2
- package/dist/state/schema.cjs.map +1 -1
- package/dist/state/schema.d.cts.map +1 -1
- package/dist/state/schema.d.ts.map +1 -1
- package/dist/state/schema.js +12 -2
- package/dist/state/schema.js.map +1 -1
- package/dist/web.cjs +11 -0
- package/dist/web.d.cts +11 -8
- package/dist/web.d.ts +11 -8
- package/dist/web.js +5 -3
- package/package.json +5 -5
package/dist/pregel/loop.js
CHANGED
|
@@ -1,18 +1,78 @@
|
|
|
1
|
-
import { CONFIG_KEY_CHECKPOINT_ID, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_READ, CONFIG_KEY_RESUME_MAP, CONFIG_KEY_RESUMING, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_STREAM, INPUT, INTERRUPT, NULL_TASK_ID, PUSH, RESUME, _isSend, isCommand } from "../constants.js";
|
|
1
|
+
import { CONFIG_KEY_CHECKPOINT_ID, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_READ, CONFIG_KEY_REPLAY_STATE, CONFIG_KEY_RESUME_MAP, CONFIG_KEY_RESUMING, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, PUSH, RESUME, _isSend, isCommand } from "../constants.js";
|
|
2
2
|
import { EmptyInputError, GraphInterrupt, isGraphInterrupt } from "../errors.js";
|
|
3
|
-
import { createCheckpoint,
|
|
3
|
+
import { channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, isDeltaChannel } from "../channels/base.js";
|
|
4
4
|
import { gatherIterator, gatherIteratorSync, prefixGenerator } from "../utils.js";
|
|
5
5
|
import { isXXH3 } from "../hash.js";
|
|
6
6
|
import { mapCommand, mapInput, mapOutputUpdates, mapOutputValues, readChannels } from "./io.js";
|
|
7
7
|
import { getNewChannelVersions, patchConfigurable } from "./utils/index.js";
|
|
8
|
-
import { _applyWrites, _prepareNextTasks, _prepareSingleTask, increment, sanitizeUntrackedValuesInSend, shouldInterrupt } from "./algo.js";
|
|
8
|
+
import { _applyWrites, _prepareNextTasks, _prepareNodeErrorHandlerTask, _prepareSingleTask, increment, sanitizeUntrackedValuesInSend, shouldInterrupt } from "./algo.js";
|
|
9
9
|
import { mapDebugCheckpoint, mapDebugTaskResults, mapDebugTasks, printStepTasks } from "./debug.js";
|
|
10
|
+
import { ReplayState } from "./replay.js";
|
|
10
11
|
import { createDuplexStream } from "./stream.js";
|
|
11
12
|
import { AsyncBatchedStore, BaseCache, WRITES_IDX_MAP, copyCheckpoint, emptyCheckpoint } from "@langchain/langgraph-checkpoint";
|
|
13
|
+
import { v4 } from "uuid";
|
|
14
|
+
import { BaseMessage } from "@langchain/core/messages";
|
|
12
15
|
//#region src/pregel/loop.ts
|
|
13
16
|
const INPUT_DONE = Symbol.for("INPUT_DONE");
|
|
14
17
|
const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
|
|
15
18
|
const DEFAULT_LOOP_LIMIT = 25;
|
|
19
|
+
/**
|
|
20
|
+
* Recursively assign a stable UUID to any {@link BaseMessage} (in a value, an
|
|
21
|
+
* array, or an object's values) that is missing an `id`. Used so DeltaChannel
|
|
22
|
+
* writes — replayed on every read — reconstruct identical message identities.
|
|
23
|
+
*/
|
|
24
|
+
function ensureMessageIds(value) {
|
|
25
|
+
if (value == null || typeof value !== "object") return;
|
|
26
|
+
if (BaseMessage.isInstance(value)) {
|
|
27
|
+
const msg = value;
|
|
28
|
+
if (msg.id == null) {
|
|
29
|
+
msg.id = v4();
|
|
30
|
+
if (msg.lc_kwargs != null) msg.lc_kwargs.id = msg.id;
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
for (const item of value) ensureMessageIds(item);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Split a serialized checkpoint namespace into its path segments.
|
|
41
|
+
*
|
|
42
|
+
* Checkpoint namespaces are stored as a single string whose nested levels are
|
|
43
|
+
* joined by {@link CHECKPOINT_NAMESPACE_SEPARATOR} (e.g. `"parent|child"`).
|
|
44
|
+
* The root namespace — represented as `undefined` or the empty string — maps
|
|
45
|
+
* to an empty array.
|
|
46
|
+
*
|
|
47
|
+
* @param ns - The serialized checkpoint namespace, or `undefined`.
|
|
48
|
+
* @returns The namespace as an array of path segments (`[]` for the root).
|
|
49
|
+
*/
|
|
50
|
+
function checkpointNamespaceFromNs(ns) {
|
|
51
|
+
if (ns === void 0 || ns === "") return [];
|
|
52
|
+
return ns.split("|");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Find the most deeply nested namespace recorded in a checkpoint map.
|
|
56
|
+
*
|
|
57
|
+
* The checkpoint map ({@link CONFIG_KEY_CHECKPOINT_MAP}) associates every
|
|
58
|
+
* namespace seen on a thread with its checkpoint id. Because nested namespaces
|
|
59
|
+
* are built by appending segments to their parent, a deeper namespace always
|
|
60
|
+
* yields a longer key — so the longest non-empty key is the deepest one.
|
|
61
|
+
*
|
|
62
|
+
* Used by the loop's `#interruptStreamNamespace()` during subgraph
|
|
63
|
+
* time-travel: interrupt events must be emitted against the active (deepest)
|
|
64
|
+
* subgraph namespace rather than the root graph.
|
|
65
|
+
*
|
|
66
|
+
* @param map - The checkpoint map (namespace -> checkpoint id), or `undefined`.
|
|
67
|
+
* @returns The deepest namespace as path segments, or `[]` when the map is
|
|
68
|
+
* absent, empty, or only contains the root namespace.
|
|
69
|
+
*/
|
|
70
|
+
function deepestCheckpointMapNamespace(map) {
|
|
71
|
+
if (!map) return [];
|
|
72
|
+
let deepest = "";
|
|
73
|
+
for (const key of Object.keys(map)) if (key !== "" && key.length > deepest.length) deepest = key;
|
|
74
|
+
return checkpointNamespaceFromNs(deepest);
|
|
75
|
+
}
|
|
16
76
|
var AsyncBatchedCache = class extends BaseCache {
|
|
17
77
|
cache;
|
|
18
78
|
queue = Promise.resolve();
|
|
@@ -49,6 +109,15 @@ var PregelLoop = class PregelLoop {
|
|
|
49
109
|
channels;
|
|
50
110
|
checkpoint;
|
|
51
111
|
checkpointIdSaved;
|
|
112
|
+
/**
|
|
113
|
+
* Exit-mode accumulator of DeltaChannel writes across the whole run, as
|
|
114
|
+
* `[step, taskId, channel, value]`. `undefined` outside "exit" durability.
|
|
115
|
+
*/
|
|
116
|
+
_exitDeltaWrites;
|
|
117
|
+
/** Whether a real checkpoint was loaded from the saver at initialization. */
|
|
118
|
+
_hasPersistedParent = false;
|
|
119
|
+
/** The checkpointConfig as captured at initialization (anchor for exit writes). */
|
|
120
|
+
_initialCheckpointConfig;
|
|
52
121
|
checkpointConfig;
|
|
53
122
|
checkpointMetadata;
|
|
54
123
|
checkpointNamespace;
|
|
@@ -64,10 +133,18 @@ var PregelLoop = class PregelLoop {
|
|
|
64
133
|
prevCheckpointConfig;
|
|
65
134
|
updatedChannels;
|
|
66
135
|
status = "pending";
|
|
136
|
+
/**
|
|
137
|
+
* Run-scoped control surface for cooperative draining. Populated from the
|
|
138
|
+
* run config. When `control.drainRequested` is true, the loop stops at the
|
|
139
|
+
* next superstep boundary instead of dispatching more tasks.
|
|
140
|
+
*/
|
|
141
|
+
control;
|
|
67
142
|
tasks = {};
|
|
68
143
|
stream;
|
|
69
144
|
checkpointerPromises = /* @__PURE__ */ new Set();
|
|
70
145
|
isNested;
|
|
146
|
+
/** True when an explicit checkpoint_id targets the latest saved checkpoint. */
|
|
147
|
+
resumeAtHead;
|
|
71
148
|
_checkpointerChainedPromise = Promise.resolve();
|
|
72
149
|
/**
|
|
73
150
|
* Track a checkpointer promise, removing it from the set on success.
|
|
@@ -107,6 +184,9 @@ var PregelLoop = class PregelLoop {
|
|
|
107
184
|
const runIdMatchesPrevious = !this.isNested && this.config.metadata?.run_id !== void 0 && this.checkpointMetadata?.run_id !== void 0 && this.config.metadata.run_id === this.checkpointMetadata?.run_id;
|
|
108
185
|
return hasChannelVersions && (configIsResuming || inputIsNullOrUndefined || inputIsCommandResuming || inputIsResuming || runIdMatchesPrevious);
|
|
109
186
|
}
|
|
187
|
+
get isReplaying() {
|
|
188
|
+
return !this.skipDoneTasks;
|
|
189
|
+
}
|
|
110
190
|
constructor(params) {
|
|
111
191
|
this.input = params.input;
|
|
112
192
|
this.checkpointer = params.checkpointer;
|
|
@@ -122,6 +202,7 @@ var PregelLoop = class PregelLoop {
|
|
|
122
202
|
this.config = params.config;
|
|
123
203
|
this.checkpointConfig = params.checkpointConfig;
|
|
124
204
|
this.isNested = params.isNested;
|
|
205
|
+
this.resumeAtHead = params.resumeAtHead;
|
|
125
206
|
this.manager = params.manager;
|
|
126
207
|
this.outputKeys = params.outputKeys;
|
|
127
208
|
this.streamKeys = params.streamKeys;
|
|
@@ -137,6 +218,11 @@ var PregelLoop = class PregelLoop {
|
|
|
137
218
|
this.durability = params.durability;
|
|
138
219
|
this.debug = params.debug;
|
|
139
220
|
this.triggerToNodes = params.triggerToNodes;
|
|
221
|
+
this.control = this.config.control;
|
|
222
|
+
this._exitDeltaWrites = this.durability === "exit" && this.checkpointer != null ? [] : void 0;
|
|
223
|
+
this._hasPersistedParent = params.hasPersistedParent ?? false;
|
|
224
|
+
this._initialCheckpointConfig = params.checkpointConfig;
|
|
225
|
+
this.checkpointIdSaved = params.checkpoint.id;
|
|
140
226
|
}
|
|
141
227
|
static async initialize(params) {
|
|
142
228
|
let { config, stream } = params;
|
|
@@ -147,15 +233,24 @@ var PregelLoop = class PregelLoop {
|
|
|
147
233
|
if (scratchpad.subgraphCounter > 0) config = patchConfigurable(config, { [CONFIG_KEY_CHECKPOINT_NS]: [config.configurable[CONFIG_KEY_CHECKPOINT_NS], scratchpad.subgraphCounter.toString()].join("|") });
|
|
148
234
|
scratchpad.subgraphCounter += 1;
|
|
149
235
|
}
|
|
236
|
+
const requestedCheckpointId = config.configurable?.checkpoint_id;
|
|
150
237
|
const isNested = CONFIG_KEY_READ in (config.configurable ?? {});
|
|
151
238
|
if (!isNested && config.configurable?.checkpoint_ns !== void 0 && config.configurable?.checkpoint_ns !== "") config = patchConfigurable(config, {
|
|
152
239
|
checkpoint_ns: "",
|
|
153
240
|
checkpoint_id: void 0
|
|
154
241
|
});
|
|
155
242
|
let checkpointConfig = config;
|
|
156
|
-
if (config.configurable?.["checkpoint_map"] !== void 0 && config.configurable?.["checkpoint_map"]?.[config.configurable?.checkpoint_ns]) checkpointConfig = patchConfigurable(config, { checkpoint_id: config.configurable[CONFIG_KEY_CHECKPOINT_MAP][config.configurable?.checkpoint_ns] });
|
|
157
|
-
const checkpointNamespace = config.configurable?.checkpoint_ns
|
|
158
|
-
|
|
243
|
+
if (config.configurable?.checkpoint_id === void 0 && config.configurable?.["checkpoint_map"] !== void 0 && config.configurable?.["checkpoint_map"]?.[config.configurable?.checkpoint_ns]) checkpointConfig = patchConfigurable(config, { checkpoint_id: config.configurable[CONFIG_KEY_CHECKPOINT_MAP][config.configurable?.checkpoint_ns] });
|
|
244
|
+
const checkpointNamespace = checkpointNamespaceFromNs(config.configurable?.checkpoint_ns);
|
|
245
|
+
let saved;
|
|
246
|
+
if (!params.checkpointer) saved = void 0;
|
|
247
|
+
else if (checkpointConfig.configurable?.["checkpoint_id"]) saved = await params.checkpointer.getTuple(checkpointConfig);
|
|
248
|
+
else if (config.configurable?.["__pregel_replay_state"]) {
|
|
249
|
+
saved = await config.configurable[CONFIG_KEY_REPLAY_STATE].getCheckpoint(config.configurable?.["checkpoint_ns"] ?? "", params.checkpointer, checkpointConfig);
|
|
250
|
+
if (config.configurable) delete config.configurable[CONFIG_KEY_RESUMING];
|
|
251
|
+
} else saved = await params.checkpointer.getTuple(checkpointConfig);
|
|
252
|
+
const hasPersistedParent = saved !== void 0;
|
|
253
|
+
if (!saved) saved = {
|
|
159
254
|
config,
|
|
160
255
|
checkpoint: emptyCheckpoint(),
|
|
161
256
|
metadata: {
|
|
@@ -177,8 +272,21 @@ var PregelLoop = class PregelLoop {
|
|
|
177
272
|
const prevCheckpointConfig = saved.parentConfig;
|
|
178
273
|
const checkpoint = copyCheckpoint(saved.checkpoint);
|
|
179
274
|
const checkpointMetadata = { ...saved.metadata };
|
|
180
|
-
|
|
181
|
-
const
|
|
275
|
+
let checkpointPendingWrites = saved.pendingWrites ?? [];
|
|
276
|
+
const currentCheckpointNamespace = config.configurable?.checkpoint_ns;
|
|
277
|
+
const checkpointMap = config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP];
|
|
278
|
+
if (typeof currentCheckpointNamespace === "string" && currentCheckpointNamespace !== "" && typeof checkpointMap === "object" && checkpointMap !== null && currentCheckpointNamespace in checkpointMap && checkpointPendingWrites.length > 0) checkpointPendingWrites = checkpointPendingWrites.filter(([, channel]) => channel !== RESUME);
|
|
279
|
+
let resumeAtHead = false;
|
|
280
|
+
const threadId = checkpointConfig.configurable?.thread_id;
|
|
281
|
+
const checkpointNs = checkpointConfig.configurable?.checkpoint_ns ?? "";
|
|
282
|
+
if (params.checkpointer && requestedCheckpointId && typeof threadId === "string") resumeAtHead = (await params.checkpointer.getTuple({ configurable: {
|
|
283
|
+
thread_id: threadId,
|
|
284
|
+
checkpoint_ns: checkpointNs
|
|
285
|
+
} }))?.config.configurable?.checkpoint_id === requestedCheckpointId && checkpointMetadata.source !== "update" && checkpointMetadata.source !== "fork";
|
|
286
|
+
const channels = await channelsFromCheckpoint(params.channelSpecs, checkpoint, {
|
|
287
|
+
saver: params.checkpointer,
|
|
288
|
+
config: checkpointConfig
|
|
289
|
+
});
|
|
182
290
|
const step = (checkpointMetadata.step ?? 0) + 1;
|
|
183
291
|
const stop = step + (config.recursionLimit ?? DEFAULT_LOOP_LIMIT) + 1;
|
|
184
292
|
const checkpointPreviousVersions = { ...checkpoint.channel_versions };
|
|
@@ -195,6 +303,7 @@ var PregelLoop = class PregelLoop {
|
|
|
195
303
|
checkpointNamespace,
|
|
196
304
|
channels,
|
|
197
305
|
isNested,
|
|
306
|
+
resumeAtHead,
|
|
198
307
|
manager: params.manager,
|
|
199
308
|
skipDoneTasks,
|
|
200
309
|
step,
|
|
@@ -211,7 +320,8 @@ var PregelLoop = class PregelLoop {
|
|
|
211
320
|
interruptBefore: params.interruptBefore,
|
|
212
321
|
durability: params.durability,
|
|
213
322
|
debug: params.debug,
|
|
214
|
-
triggerToNodes: params.triggerToNodes
|
|
323
|
+
triggerToNodes: params.triggerToNodes,
|
|
324
|
+
hasPersistedParent
|
|
215
325
|
});
|
|
216
326
|
}
|
|
217
327
|
_checkpointerPutAfterPrevious(input) {
|
|
@@ -250,6 +360,10 @@ var PregelLoop = class PregelLoop {
|
|
|
250
360
|
c,
|
|
251
361
|
v
|
|
252
362
|
]);
|
|
363
|
+
for (const [c, v] of writesToSave) {
|
|
364
|
+
const channel = this.channels[c];
|
|
365
|
+
if (channel != null && isDeltaChannel(channel)) ensureMessageIds(v);
|
|
366
|
+
}
|
|
253
367
|
const config = patchConfigurable(this.checkpointConfig, {
|
|
254
368
|
[CONFIG_KEY_CHECKPOINT_NS]: this.config.configurable?.checkpoint_ns ?? "",
|
|
255
369
|
[CONFIG_KEY_CHECKPOINT_ID]: this.checkpoint.id
|
|
@@ -309,7 +423,8 @@ var PregelLoop = class PregelLoop {
|
|
|
309
423
|
/**
|
|
310
424
|
* Execute a single iteration of the Pregel loop.
|
|
311
425
|
* Returns true if more iterations are needed.
|
|
312
|
-
* @param params
|
|
426
|
+
* @param params - The input keys to use for the tick.
|
|
427
|
+
* @returns True if more iterations are needed, false otherwise.
|
|
313
428
|
*/
|
|
314
429
|
async tick(params) {
|
|
315
430
|
if (this.store && !this.store.isRunning) await this.store?.start();
|
|
@@ -320,13 +435,23 @@ var PregelLoop = class PregelLoop {
|
|
|
320
435
|
this.status = "interrupt_before";
|
|
321
436
|
throw new GraphInterrupt();
|
|
322
437
|
} else if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
|
|
323
|
-
const
|
|
324
|
-
|
|
438
|
+
const finishTaskList = Object.values(this.tasks);
|
|
439
|
+
const writes = finishTaskList.flatMap((t) => t.writes);
|
|
440
|
+
this.updatedChannels = _applyWrites(this.checkpoint, this.channels, finishTaskList, this.checkpointerGetNextVersion, this.triggerToNodes);
|
|
325
441
|
const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, writes, this.channels), "values"));
|
|
442
|
+
if (this._exitDeltaWrites !== void 0) for (const [tid, ch, v] of this.checkpointPendingWrites) {
|
|
443
|
+
const channel = this.channels[ch];
|
|
444
|
+
if (channel != null && isDeltaChannel(channel)) this._exitDeltaWrites.push([
|
|
445
|
+
this.step,
|
|
446
|
+
tid,
|
|
447
|
+
ch,
|
|
448
|
+
v
|
|
449
|
+
]);
|
|
450
|
+
}
|
|
326
451
|
this.checkpointPendingWrites = [];
|
|
327
452
|
await this._putCheckpoint({ source: "loop" });
|
|
328
453
|
this._emitValuesWithCheckpointMeta(valuesOutput);
|
|
329
|
-
if (shouldInterrupt(this.checkpoint, this.interruptAfter,
|
|
454
|
+
if (shouldInterrupt(this.checkpoint, this.interruptAfter, finishTaskList)) {
|
|
330
455
|
this.status = "interrupt_after";
|
|
331
456
|
throw new GraphInterrupt();
|
|
332
457
|
}
|
|
@@ -346,30 +471,40 @@ var PregelLoop = class PregelLoop {
|
|
|
346
471
|
triggerToNodes: this.triggerToNodes,
|
|
347
472
|
updatedChannels: this.updatedChannels
|
|
348
473
|
});
|
|
349
|
-
|
|
350
|
-
if (
|
|
474
|
+
let taskList = Object.values(this.tasks);
|
|
475
|
+
if (this.checkpointer && (this.stream.modes.has("checkpoints") || this.stream.modes.has("debug"))) this._emit(await gatherIterator(prefixGenerator(mapDebugCheckpoint(this.checkpointConfig, this.channels, this.streamKeys, this.checkpointMetadata, taskList, this.checkpointPendingWrites, this.prevCheckpointConfig, this.outputKeys), "checkpoints")));
|
|
476
|
+
if (taskList.length === 0) {
|
|
351
477
|
this.status = "done";
|
|
352
478
|
return false;
|
|
353
479
|
}
|
|
480
|
+
if (this.control != null && this.control.drainRequested) {
|
|
481
|
+
this.status = "draining";
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
354
484
|
if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {
|
|
355
485
|
for (const [tid, k, v] of this.checkpointPendingWrites) {
|
|
356
|
-
if (k === "__error__" || k === "__interrupt__" || k === "__resume__") continue;
|
|
357
|
-
const task =
|
|
486
|
+
if (k === "__error__" || k === "__error_source_node__" || k === "__interrupt__" || k === "__resume__") continue;
|
|
487
|
+
const task = taskList.find((t) => t.id === tid);
|
|
358
488
|
if (task) task.writes.push([k, v]);
|
|
359
489
|
}
|
|
360
|
-
|
|
490
|
+
this._resumeErrorHandlersIfApplicable();
|
|
491
|
+
taskList = Object.values(this.tasks);
|
|
492
|
+
for (const task of taskList) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
|
|
361
493
|
}
|
|
362
|
-
if (
|
|
363
|
-
if (shouldInterrupt(this.checkpoint, this.interruptBefore,
|
|
494
|
+
if (taskList.every((task) => task.writes.length > 0)) return this.tick({ inputKeys });
|
|
495
|
+
if (shouldInterrupt(this.checkpoint, this.interruptBefore, taskList)) {
|
|
364
496
|
this.status = "interrupt_before";
|
|
365
497
|
throw new GraphInterrupt();
|
|
366
498
|
}
|
|
367
|
-
|
|
368
|
-
|
|
499
|
+
if (this.stream.modes.has("tasks") || this.stream.modes.has("debug")) {
|
|
500
|
+
const debugOutput = await gatherIterator(prefixGenerator(mapDebugTasks(taskList), "tasks"));
|
|
501
|
+
this._emit(debugOutput);
|
|
502
|
+
}
|
|
369
503
|
return true;
|
|
370
504
|
}
|
|
371
505
|
async finishAndHandleError(error) {
|
|
372
506
|
if (this.durability === "exit" && (!this.isNested || typeof error !== "undefined" || this.checkpointNamespace.every((part) => !part.includes(":")))) {
|
|
507
|
+
await this._putExitDeltaWrites();
|
|
373
508
|
this._putCheckpoint(this.checkpointMetadata);
|
|
374
509
|
this._flushPendingWrites();
|
|
375
510
|
}
|
|
@@ -380,7 +515,7 @@ var PregelLoop = class PregelLoop {
|
|
|
380
515
|
this.updatedChannels = _applyWrites(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion, this.triggerToNodes);
|
|
381
516
|
this._emitValuesWithCheckpointMeta(gatherIteratorSync(prefixGenerator(mapOutputValues(this.outputKeys, Object.values(this.tasks).flatMap((t) => t.writes), this.channels), "values")));
|
|
382
517
|
}
|
|
383
|
-
if (isGraphInterrupt(error) && !error.interrupts.length) this._emit([["updates", { [INTERRUPT]: [] }], ["values", { [INTERRUPT]: [] }]]);
|
|
518
|
+
if (isGraphInterrupt(error) && !error.interrupts.length) this._emit([["updates", { [INTERRUPT]: [] }], ["values", { [INTERRUPT]: [] }]], this.#interruptStreamNamespace());
|
|
384
519
|
}
|
|
385
520
|
return suppress;
|
|
386
521
|
}
|
|
@@ -407,7 +542,7 @@ var PregelLoop = class PregelLoop {
|
|
|
407
542
|
this.toInterrupt.push(pushed);
|
|
408
543
|
return;
|
|
409
544
|
}
|
|
410
|
-
this._emit(gatherIteratorSync(prefixGenerator(mapDebugTasks([pushed]), "tasks")));
|
|
545
|
+
if (this.stream.modes.has("tasks") || this.stream.modes.has("debug")) this._emit(gatherIteratorSync(prefixGenerator(mapDebugTasks([pushed]), "tasks")));
|
|
411
546
|
if (this.debug) printStepTasks(this.step, [pushed]);
|
|
412
547
|
this.tasks[pushed.id] = pushed;
|
|
413
548
|
if (this.skipDoneTasks) this._matchWrites({ [pushed.id]: pushed });
|
|
@@ -415,6 +550,74 @@ var PregelLoop = class PregelLoop {
|
|
|
415
550
|
for (const { task } of tasks) this._outputWrites(task.id, task.writes, true);
|
|
416
551
|
return pushed;
|
|
417
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Returns the name of the error handler node registered for `nodeName`, or
|
|
555
|
+
* `undefined` if none is configured.
|
|
556
|
+
*/
|
|
557
|
+
getErrorHandlerNode(nodeName) {
|
|
558
|
+
return this.nodes[nodeName]?.errorHandlerNode;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Whether `nodeName` is itself an auto-generated error handler node.
|
|
562
|
+
*/
|
|
563
|
+
isErrorHandlerNode(nodeName) {
|
|
564
|
+
return this.nodes[nodeName]?.isErrorHandler === true;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Schedule a node-level error handler task for a task that failed after its
|
|
568
|
+
* retry policy was exhausted. Prepares the handler task (injecting a
|
|
569
|
+
* {@link NodeError}), registers it so the runner executes it within the
|
|
570
|
+
* current step, and returns it (or `undefined` if no handler applies).
|
|
571
|
+
*
|
|
572
|
+
* The failure provenance (`ERROR` + `ERROR_SOURCE_NODE`) is checkpointed by
|
|
573
|
+
* the runner via {@link PregelLoop#putWrites} so handlers observe the same
|
|
574
|
+
* context after a resume.
|
|
575
|
+
*/
|
|
576
|
+
scheduleErrorHandler(failedTask, error) {
|
|
577
|
+
const handlerNode = this.getErrorHandlerNode(String(failedTask.name));
|
|
578
|
+
if (!handlerNode) return void 0;
|
|
579
|
+
const handlerTask = _prepareNodeErrorHandlerTask(failedTask, handlerNode, error, this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, failedTask.config ?? this.config, {
|
|
580
|
+
step: this.step,
|
|
581
|
+
checkpointer: this.checkpointer,
|
|
582
|
+
manager: this.manager,
|
|
583
|
+
store: this.store,
|
|
584
|
+
stream: this.stream
|
|
585
|
+
});
|
|
586
|
+
if (handlerTask === void 0) return void 0;
|
|
587
|
+
this.tasks[handlerTask.id] = handlerTask;
|
|
588
|
+
this._emit(gatherIteratorSync(prefixGenerator(mapDebugTasks([handlerTask]), "tasks")));
|
|
589
|
+
if (this.debug) printStepTasks(this.step, [handlerTask]);
|
|
590
|
+
return handlerTask;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* On resume, re-schedule error handlers for tasks that failed in a prior run
|
|
594
|
+
* but had not finished being handled. Scans pending writes for
|
|
595
|
+
* `ERROR_SOURCE_NODE` markers (paired with `ERROR`), marks the originating
|
|
596
|
+
* task as done (so the runner won't re-run it), and prepares a fresh handler
|
|
597
|
+
* task so the runner picks it up.
|
|
598
|
+
*/
|
|
599
|
+
_resumeErrorHandlersIfApplicable() {
|
|
600
|
+
const failed = /* @__PURE__ */ new Map();
|
|
601
|
+
for (const [tid, chan] of this.checkpointPendingWrites) {
|
|
602
|
+
if (chan !== "__error_source_node__") continue;
|
|
603
|
+
const errorWrite = this.checkpointPendingWrites.find(([t, c]) => t === tid && c === "__error__");
|
|
604
|
+
if (errorWrite === void 0) continue;
|
|
605
|
+
const value = errorWrite[2];
|
|
606
|
+
const error = new Error(value?.message ?? String(value));
|
|
607
|
+
if (value?.name) error.name = value.name;
|
|
608
|
+
failed.set(tid, error);
|
|
609
|
+
}
|
|
610
|
+
for (const [tid, error] of failed) {
|
|
611
|
+
const task = this.tasks[tid];
|
|
612
|
+
if (task === void 0) continue;
|
|
613
|
+
if (!this.getErrorHandlerNode(String(task.name))) continue;
|
|
614
|
+
if (task.writes.length === 0) task.writes.push([ERROR, {
|
|
615
|
+
message: error.message,
|
|
616
|
+
name: error.name
|
|
617
|
+
}]);
|
|
618
|
+
this.scheduleErrorHandler(task, error);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
418
621
|
_suppressInterrupt(e) {
|
|
419
622
|
return isGraphInterrupt(e) && !this.isNested;
|
|
420
623
|
}
|
|
@@ -443,20 +646,24 @@ var PregelLoop = class PregelLoop {
|
|
|
443
646
|
writes: nullWrites,
|
|
444
647
|
triggers: []
|
|
445
648
|
}], this.checkpointerGetNextVersion, this.triggerToNodes);
|
|
446
|
-
const
|
|
447
|
-
|
|
649
|
+
const inputIsCommand = isCommand(this.input);
|
|
650
|
+
const isCommandUpdateOrGoto = inputIsCommand && nullWrites.length > 0;
|
|
651
|
+
const isTimeTraveling = this.isReplaying && (this.isNested && configurable?.["checkpoint_ns"] !== void 0 && configurable?.["checkpoint_ns"] !== "" && configurable?.["checkpoint_map"] !== void 0 && configurable["checkpoint_ns"] in configurable["checkpoint_map"] || !(inputIsCommand && this.input.resume != null || configurable?.["__pregel_resuming"] === true || this.resumeAtHead));
|
|
652
|
+
if (isTimeTraveling) this.checkpointPendingWrites = this.checkpointPendingWrites.filter((w) => w[1] !== RESUME);
|
|
653
|
+
const cachedIsResuming = this.isResuming;
|
|
654
|
+
if (cachedIsResuming || isCommandUpdateOrGoto) {
|
|
655
|
+
const interruptSeen = { ...this.checkpoint.versions_seen[INTERRUPT] };
|
|
448
656
|
for (const channelName in this.channels) {
|
|
449
657
|
if (!Object.prototype.hasOwnProperty.call(this.channels, channelName)) continue;
|
|
450
|
-
if (this.checkpoint.channel_versions[channelName] !== void 0)
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
658
|
+
if (this.checkpoint.channel_versions[channelName] !== void 0) interruptSeen[channelName] = this.checkpoint.channel_versions[channelName];
|
|
659
|
+
}
|
|
660
|
+
this.checkpoint.versions_seen[INTERRUPT] = interruptSeen;
|
|
661
|
+
if (isTimeTraveling && this.checkpointMetadata.source !== "update" && this.checkpointMetadata.source !== "fork") {
|
|
662
|
+
this.checkpointPendingWrites = this.checkpointPendingWrites.filter((w) => w[1] !== INTERRUPT);
|
|
663
|
+
await this._putCheckpoint({ source: "fork" });
|
|
457
664
|
}
|
|
458
665
|
const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, true, this.channels), "values"));
|
|
459
|
-
if (
|
|
666
|
+
if (cachedIsResuming) this.input = INPUT_RESUMING;
|
|
460
667
|
else if (isCommandUpdateOrGoto) {
|
|
461
668
|
await this._putCheckpoint({ source: "input" });
|
|
462
669
|
this.input = INPUT_DONE;
|
|
@@ -471,17 +678,47 @@ var PregelLoop = class PregelLoop {
|
|
|
471
678
|
writes: inputWrites,
|
|
472
679
|
triggers: []
|
|
473
680
|
}]), this.checkpointerGetNextVersion, this.triggerToNodes);
|
|
681
|
+
const deltaInput = inputWrites.filter(([c]) => {
|
|
682
|
+
const channel = this.channels[c];
|
|
683
|
+
return channel != null && isDeltaChannel(channel);
|
|
684
|
+
});
|
|
685
|
+
if (deltaInput.length > 0) {
|
|
686
|
+
if (this._exitDeltaWrites !== void 0) for (const [c, v] of deltaInput) this._exitDeltaWrites.push([
|
|
687
|
+
this.step,
|
|
688
|
+
NULL_TASK_ID,
|
|
689
|
+
c,
|
|
690
|
+
v
|
|
691
|
+
]);
|
|
692
|
+
else if (this.checkpointer != null) this.putWrites(NULL_TASK_ID, deltaInput);
|
|
693
|
+
}
|
|
474
694
|
await this._putCheckpoint({ source: "input" });
|
|
475
695
|
this.input = INPUT_DONE;
|
|
476
696
|
} else if (!("__pregel_resuming" in (this.config.configurable ?? {}))) throw new EmptyInputError(`Received no input writes for ${JSON.stringify(inputKeys, null, 2)}`);
|
|
477
697
|
else this.input = INPUT_DONE;
|
|
478
698
|
}
|
|
479
|
-
if (!this.isNested)
|
|
699
|
+
if (!this.isNested) {
|
|
700
|
+
let replayState;
|
|
701
|
+
if (isTimeTraveling) {
|
|
702
|
+
let replayCheckpointId = this.checkpoint.id;
|
|
703
|
+
if ((this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork") && this.prevCheckpointConfig) replayCheckpointId = this.prevCheckpointConfig.configurable?.["checkpoint_id"] ?? replayCheckpointId;
|
|
704
|
+
replayState = new ReplayState(replayCheckpointId);
|
|
705
|
+
}
|
|
706
|
+
this.config = patchConfigurable(this.config, {
|
|
707
|
+
[CONFIG_KEY_RESUMING]: this.isResuming,
|
|
708
|
+
[CONFIG_KEY_REPLAY_STATE]: replayState
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
#interruptStreamNamespace() {
|
|
713
|
+
const ns = this.checkpointNamespace;
|
|
714
|
+
if (!(ns.length === 0 || ns.length === 1 && ns[0] === "") || this.config.configurable?.["__pregel_stream"] === void 0) return ns;
|
|
715
|
+
const deepest = deepestCheckpointMapNamespace(this.config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP]);
|
|
716
|
+
return deepest.length > 0 ? deepest : ns;
|
|
480
717
|
}
|
|
481
|
-
_emit(values) {
|
|
718
|
+
_emit(values, namespace = this.checkpointNamespace) {
|
|
482
719
|
for (const [mode, payload] of values) {
|
|
483
720
|
if (this.stream.modes.has(mode)) this.stream.push([
|
|
484
|
-
|
|
721
|
+
namespace,
|
|
485
722
|
mode,
|
|
486
723
|
payload
|
|
487
724
|
]);
|
|
@@ -494,7 +731,7 @@ var PregelLoop = class PregelLoop {
|
|
|
494
731
|
else return "task";
|
|
495
732
|
})();
|
|
496
733
|
this.stream.push([
|
|
497
|
-
|
|
734
|
+
namespace,
|
|
498
735
|
"debug",
|
|
499
736
|
{
|
|
500
737
|
step,
|
|
@@ -564,15 +801,99 @@ var PregelLoop = class PregelLoop {
|
|
|
564
801
|
}
|
|
565
802
|
};
|
|
566
803
|
};
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
804
|
+
let newCounters;
|
|
805
|
+
if (!exiting) {
|
|
806
|
+
const prevCounters = this.checkpointMetadata.counters_since_delta_snapshot ?? {};
|
|
807
|
+
newCounters = {};
|
|
808
|
+
const updated = this.updatedChannels ?? /* @__PURE__ */ new Set();
|
|
809
|
+
for (const chName in this.channels) {
|
|
810
|
+
if (!Object.prototype.hasOwnProperty.call(this.channels, chName)) continue;
|
|
811
|
+
if (!isDeltaChannel(this.channels[chName])) continue;
|
|
812
|
+
const [u, s] = prevCounters[chName] ?? [0, 0];
|
|
813
|
+
newCounters[chName] = [updated.has(chName) ? u + 1 : u, s + 1];
|
|
814
|
+
}
|
|
815
|
+
this.checkpointMetadata = {
|
|
816
|
+
...inputMetadata,
|
|
817
|
+
step: this.step,
|
|
818
|
+
parents: this.config.configurable?.["checkpoint_map"] ?? {}
|
|
819
|
+
};
|
|
820
|
+
} else newCounters = { ...this.checkpointMetadata.counters_since_delta_snapshot ?? {} };
|
|
821
|
+
const channelsToSnapshot = doCheckpoint ? deltaChannelsToSnapshot(this.channels, newCounters) : /* @__PURE__ */ new Set();
|
|
822
|
+
this.checkpoint = createCheckpoint(this.checkpoint, doCheckpoint ? this.channels : void 0, this.step, {
|
|
823
|
+
id: exiting ? this.checkpoint.id : void 0,
|
|
824
|
+
channelsToSnapshot,
|
|
825
|
+
updatedChannels: this.updatedChannels,
|
|
826
|
+
getNextVersion: doCheckpoint ? (current) => this.checkpointerGetNextVersion(current) : void 0
|
|
827
|
+
});
|
|
828
|
+
for (const k of channelsToSnapshot) newCounters[k] = [0, 0];
|
|
829
|
+
const nonZero = {};
|
|
830
|
+
for (const k in newCounters) {
|
|
831
|
+
if (!Object.prototype.hasOwnProperty.call(newCounters, k)) continue;
|
|
832
|
+
const [u, s] = newCounters[k];
|
|
833
|
+
if (u !== 0 || s !== 0) nonZero[k] = [u, s];
|
|
834
|
+
}
|
|
835
|
+
if (Object.keys(nonZero).length > 0) this.checkpointMetadata.counters_since_delta_snapshot = nonZero;
|
|
836
|
+
else delete this.checkpointMetadata.counters_since_delta_snapshot;
|
|
573
837
|
if (doCheckpoint) storeCheckpoint(this.checkpoint);
|
|
574
838
|
if (!exiting) this.step += 1;
|
|
575
839
|
}
|
|
840
|
+
/**
|
|
841
|
+
* Stage the exit-mode accumulator of DeltaChannel writes so the final
|
|
842
|
+
* checkpoint can be reconstructed. In "exit" durability per-step writes are
|
|
843
|
+
* not persisted, so delta writes are accumulated across the run and anchored
|
|
844
|
+
* here — under the saved parent, or a freshly-created stub when this is a
|
|
845
|
+
* first run with no persisted parent. Channels that will snapshot in the
|
|
846
|
+
* final checkpoint are excluded (their full value lives in `channel_values`).
|
|
847
|
+
*
|
|
848
|
+
* Must run BEFORE the final `_putCheckpoint` so the stub branch can adjust
|
|
849
|
+
* `checkpointConfig` to anchor the final checkpoint on the stub.
|
|
850
|
+
*/
|
|
851
|
+
async _putExitDeltaWrites() {
|
|
852
|
+
if (this._exitDeltaWrites === void 0 || this._exitDeltaWrites.length === 0 || this.checkpointer == null || this._initialCheckpointConfig === void 0) return;
|
|
853
|
+
const counters = this.checkpointMetadata.counters_since_delta_snapshot ?? {};
|
|
854
|
+
const channelsToSnapshot = deltaChannelsToSnapshot(this.channels, counters);
|
|
855
|
+
const pending = this._exitDeltaWrites.filter(([, , ch]) => !channelsToSnapshot.has(ch));
|
|
856
|
+
if (pending.length === 0) return;
|
|
857
|
+
let anchorConfig;
|
|
858
|
+
if (this._hasPersistedParent) anchorConfig = this._initialCheckpointConfig;
|
|
859
|
+
else {
|
|
860
|
+
const stubCp = emptyCheckpoint();
|
|
861
|
+
stubCp.id = this.checkpointIdSaved ?? stubCp.id;
|
|
862
|
+
stubCp.ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
863
|
+
const stubPutConfig = patchConfigurable(this._initialCheckpointConfig, { [CONFIG_KEY_CHECKPOINT_ID]: void 0 });
|
|
864
|
+
anchorConfig = patchConfigurable(this._initialCheckpointConfig, { [CONFIG_KEY_CHECKPOINT_ID]: stubCp.id });
|
|
865
|
+
this._trackCheckpointerPromise(this.checkpointer.put(stubPutConfig, stubCp, {
|
|
866
|
+
source: "loop",
|
|
867
|
+
step: -2,
|
|
868
|
+
parents: {}
|
|
869
|
+
}, {}));
|
|
870
|
+
this.checkpointConfig = anchorConfig;
|
|
871
|
+
}
|
|
872
|
+
const anchorWriteConfig = patchConfigurable(anchorConfig, {
|
|
873
|
+
[CONFIG_KEY_CHECKPOINT_NS]: this.config.configurable?.checkpoint_ns ?? "",
|
|
874
|
+
[CONFIG_KEY_CHECKPOINT_ID]: anchorConfig.configurable?.[CONFIG_KEY_CHECKPOINT_ID]
|
|
875
|
+
});
|
|
876
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
877
|
+
const order = [];
|
|
878
|
+
for (const [step, tid, ch, v] of pending) {
|
|
879
|
+
const key = `${step}\u0000${tid}`;
|
|
880
|
+
let group = grouped.get(key);
|
|
881
|
+
if (group === void 0) {
|
|
882
|
+
group = [];
|
|
883
|
+
grouped.set(key, group);
|
|
884
|
+
order.push({
|
|
885
|
+
key,
|
|
886
|
+
step,
|
|
887
|
+
tid
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
group.push([ch, v]);
|
|
891
|
+
}
|
|
892
|
+
for (const { key, step, tid } of order) {
|
|
893
|
+
const synthTid = `${String(step).padStart(8, "0")}-${tid}`;
|
|
894
|
+
this._trackCheckpointerPromise(this.checkpointer.putWrites(anchorWriteConfig, grouped.get(key), synthTid));
|
|
895
|
+
}
|
|
896
|
+
}
|
|
576
897
|
_flushPendingWrites() {
|
|
577
898
|
if (this.checkpointer == null) return;
|
|
578
899
|
if (this.checkpointPendingWrites.length === 0) return;
|