@langchain/langgraph 1.4.21 → 1.5.0
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 +113 -8
- package/dist/channels/base.cjs.map +1 -1
- package/dist/channels/base.d.cts +26 -4
- package/dist/channels/base.d.cts.map +1 -1
- package/dist/channels/base.d.ts +26 -4
- package/dist/channels/base.d.ts.map +1 -1
- package/dist/channels/base.js +110 -10
- package/dist/channels/base.js.map +1 -1
- package/dist/channels/index.cjs +2 -0
- package/dist/channels/index.d.cts +2 -2
- package/dist/channels/index.d.ts +2 -2
- package/dist/channels/index.js +2 -2
- package/dist/constants.cjs +14 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +13 -1
- package/dist/constants.js.map +1 -1
- package/dist/graph/index.cjs +1 -1
- package/dist/graph/index.js +1 -1
- package/dist/graph/messages_reducer.cjs +7 -0
- package/dist/graph/messages_reducer.cjs.map +1 -1
- package/dist/graph/messages_reducer.js +7 -1
- package/dist/graph/messages_reducer.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/pregel/algo.cjs +46 -45
- package/dist/pregel/algo.cjs.map +1 -1
- package/dist/pregel/algo.d.cts.map +1 -1
- package/dist/pregel/algo.d.ts.map +1 -1
- package/dist/pregel/algo.js +47 -47
- package/dist/pregel/algo.js.map +1 -1
- package/dist/pregel/checkpoint.cjs +70 -0
- package/dist/pregel/checkpoint.cjs.map +1 -0
- package/dist/pregel/checkpoint.js +67 -0
- package/dist/pregel/checkpoint.js.map +1 -0
- package/dist/pregel/index.cjs +87 -34
- 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 +91 -38
- package/dist/pregel/index.js.map +1 -1
- package/dist/pregel/loop.cjs +325 -109
- package/dist/pregel/loop.cjs.map +1 -1
- package/dist/pregel/loop.js +330 -115
- package/dist/pregel/loop.js.map +1 -1
- package/dist/state/prebuilt/messages.cjs +1 -1
- package/dist/state/prebuilt/messages.js +1 -1
- package/dist/web.cjs +1 -1
- package/dist/web.js +1 -1
- package/package.json +5 -5
package/dist/pregel/loop.js
CHANGED
|
@@ -1,40 +1,116 @@
|
|
|
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, _isOverwriteValue, _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, Overwrite, PUSH, RESUME, _getOverwriteValue, _isOverwriteValue, _isSend, isCommand } from "../constants.js";
|
|
2
2
|
import { EmptyInputError, GraphInterrupt, isGraphInterrupt } from "../errors.js";
|
|
3
|
-
import { DELTA_WRITES_VERSIONED, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, exitDeltaTaskId, isDeltaChannel, isDeltaWritesVersioned } from "../channels/base.js";
|
|
3
|
+
import { DELTA_WRITES_VERSIONED, channelsFromCheckpoint, createCheckpoint, deltaChannelWrites, deltaChannelsToSnapshot, deltaChannelsWithPendingWrites, exitDeltaLateTaskId, exitDeltaTaskId, isDeltaChannel, isDeltaWritesVersioned } from "../channels/base.js";
|
|
4
4
|
import { gatherIterator, gatherIteratorSync, prefixGenerator } from "../utils.js";
|
|
5
|
+
import { advanceDeltaCounters, checkpointSuperseded } from "./checkpoint.js";
|
|
5
6
|
import { isXXH3 } from "../hash.js";
|
|
6
7
|
import { mapCommand, mapInput, mapOutputUpdates, mapOutputValues, readChannels } from "./io.js";
|
|
7
8
|
import { getNewChannelVersions, patchConfigurable } from "./utils/index.js";
|
|
8
|
-
import { _applyWrites, _prepareNextTasks, _prepareNodeErrorHandlerTask, _prepareSingleTask, increment, sanitizeUntrackedValuesInSend, shouldInterrupt } from "./algo.js";
|
|
9
|
+
import { _applyWrites, _prepareNextTasks, _prepareNodeErrorHandlerTask, _prepareSingleTask, increment, sanitizeUntrackedValuesInSend, shouldInterrupt, taskPathStr } from "./algo.js";
|
|
9
10
|
import { mapDebugCheckpoint, mapDebugTaskResults, mapDebugTasks, printStepTasks } from "./debug.js";
|
|
11
|
+
import { isMessagesDeltaReducer } from "../graph/messages_reducer.js";
|
|
10
12
|
import { ReplayState } from "./replay.js";
|
|
11
13
|
import { createDuplexStream } from "./stream.js";
|
|
12
|
-
import { AsyncBatchedStore, BaseCache, WRITES_IDX_MAP, copyCheckpoint, emptyCheckpoint } from "@langchain/langgraph-checkpoint";
|
|
14
|
+
import { AsyncBatchedStore, BaseCache, WRITES_IDX_MAP, copyCheckpoint, emptyCheckpoint, uuid5 } from "@langchain/langgraph-checkpoint";
|
|
13
15
|
import { v4 } from "@langchain/core/utils/uuid";
|
|
14
|
-
import { BaseMessage } from "@langchain/core/messages";
|
|
16
|
+
import { BaseMessage, coerceMessageLikeToMessage } from "@langchain/core/messages";
|
|
15
17
|
//#region src/pregel/loop.ts
|
|
16
18
|
const INPUT_DONE = Symbol.for("INPUT_DONE");
|
|
17
19
|
const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
|
|
18
20
|
const DEFAULT_LOOP_LIMIT = 25;
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
22
|
+
* Give the messages written to DeltaChannels stable ids, and return the writes
|
|
23
|
+
* in `writes` that go to a DeltaChannel.
|
|
24
|
+
*
|
|
25
|
+
* A DeltaChannel replays its saved writes on every read, and its reducer may
|
|
26
|
+
* not assign missing ids (`messagesDeltaReducer` doesn't), so a message saved
|
|
27
|
+
* without an id comes back without one, or with a new one on every read, and
|
|
28
|
+
* nothing can target it by id, for example `RemoveMessage`.
|
|
29
|
+
*
|
|
30
|
+
* In a channel whose reducer is {@link messagesDeltaReducer}, every
|
|
31
|
+
* message-like it accepts (a message, a plain object, a string, a
|
|
32
|
+
* `[role, content]` pair; alone, in an array or in an `Overwrite`) gets an id.
|
|
33
|
+
* A plain message object stays plain, with the id on a copy, except in an
|
|
34
|
+
* `Overwrite`, whose value the channel keeps as it is, so there it becomes a
|
|
35
|
+
* message. Other DeltaChannels may hold other data, so there only message
|
|
36
|
+
* objects get ids. Anything that can't become a message is left as it is, so
|
|
37
|
+
* it fails where it always has, when the reducer reads it.
|
|
38
|
+
*
|
|
39
|
+
* A write that changes is replaced in `writes` by a new `[channel, value]`
|
|
40
|
+
* pair: arrays and pairs the caller passed in are never changed, only the ids
|
|
41
|
+
* of message objects.
|
|
23
42
|
*/
|
|
24
|
-
function
|
|
25
|
-
|
|
43
|
+
function ensureDeltaMessageIds(channels, writes) {
|
|
44
|
+
const deltaWrites = [];
|
|
45
|
+
for (let i = 0; i < writes.length; i += 1) {
|
|
46
|
+
const [channelName, value] = writes[i];
|
|
47
|
+
const channel = channels[String(channelName)];
|
|
48
|
+
if (!isDeltaChannel(channel)) continue;
|
|
49
|
+
if (isMessagesDeltaReducer(channel.reducer)) {
|
|
50
|
+
const newValue = withMessageIds(value);
|
|
51
|
+
if (newValue !== value) writes[i] = [channelName, newValue];
|
|
52
|
+
} else stampMessageIds(value);
|
|
53
|
+
deltaWrites.push(writes[i]);
|
|
54
|
+
}
|
|
55
|
+
return deltaWrites;
|
|
56
|
+
}
|
|
57
|
+
function withMessageIds(value) {
|
|
58
|
+
const [isOverwrite, overwriteValue] = _getOverwriteValue(value);
|
|
59
|
+
if (!isOverwrite) return Array.isArray(value) ? mapItems(value, writtenMessageWithId) : writtenMessageWithId(value);
|
|
60
|
+
if (!Array.isArray(overwriteValue)) return value;
|
|
61
|
+
const items = mapItems(overwriteValue, messageWithId);
|
|
62
|
+
return items === overwriteValue ? value : new Overwrite(items);
|
|
63
|
+
}
|
|
64
|
+
function mapItems(items, fn) {
|
|
65
|
+
const mapped = items.map(fn);
|
|
66
|
+
return mapped.every((item, i) => item === items[i]) ? items : mapped;
|
|
67
|
+
}
|
|
68
|
+
function writtenMessageWithId(item) {
|
|
69
|
+
if (!isPlainObject(item)) return messageWithId(item);
|
|
70
|
+
try {
|
|
71
|
+
if (coerceMessageLikeToMessage(item).id) return item;
|
|
72
|
+
} catch {
|
|
73
|
+
return item;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
...item,
|
|
77
|
+
id: v4()
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function messageWithId(item) {
|
|
81
|
+
if (BaseMessage.isInstance(item)) {
|
|
82
|
+
stampMessageIds(item);
|
|
83
|
+
return item;
|
|
84
|
+
}
|
|
85
|
+
let message;
|
|
86
|
+
try {
|
|
87
|
+
message = coerceMessageLikeToMessage(item);
|
|
88
|
+
} catch {
|
|
89
|
+
return item;
|
|
90
|
+
}
|
|
91
|
+
if (!message.id) setMessageId(message);
|
|
92
|
+
return message;
|
|
93
|
+
}
|
|
94
|
+
function isPlainObject(item) {
|
|
95
|
+
if (typeof item !== "object" || item === null) return false;
|
|
96
|
+
const proto = Object.getPrototypeOf(item);
|
|
97
|
+
return (proto === Object.prototype || proto === null) && !("lc" in item);
|
|
98
|
+
}
|
|
99
|
+
function stampMessageIds(value) {
|
|
26
100
|
if (BaseMessage.isInstance(value)) {
|
|
27
|
-
|
|
28
|
-
if (msg.id == null) {
|
|
29
|
-
msg.id = v4();
|
|
30
|
-
if (msg.lc_kwargs != null) msg.lc_kwargs.id = msg.id;
|
|
31
|
-
}
|
|
101
|
+
if (value.id == null) setMessageId(value);
|
|
32
102
|
return;
|
|
33
103
|
}
|
|
34
104
|
if (Array.isArray(value)) {
|
|
35
|
-
for (const item of value)
|
|
105
|
+
for (const item of value) stampMessageIds(item);
|
|
36
106
|
return;
|
|
37
107
|
}
|
|
108
|
+
const [isOverwrite, overwriteValue] = _getOverwriteValue(value);
|
|
109
|
+
if (isOverwrite) stampMessageIds(overwriteValue);
|
|
110
|
+
}
|
|
111
|
+
function setMessageId(message) {
|
|
112
|
+
message.id = v4();
|
|
113
|
+
if (message.lc_kwargs != null) message.lc_kwargs.id = message.id;
|
|
38
114
|
}
|
|
39
115
|
/**
|
|
40
116
|
* Split a serialized checkpoint namespace into its path segments.
|
|
@@ -111,20 +187,65 @@ var PregelLoop = class PregelLoop {
|
|
|
111
187
|
checkpointIdSaved;
|
|
112
188
|
/**
|
|
113
189
|
* Exit-mode accumulator of DeltaChannel writes across the whole run, as
|
|
114
|
-
* `[step, taskId, channel, value]
|
|
190
|
+
* `[step, taskId, taskPath, channel, value]` — see `_putExitDeltaWrites`
|
|
191
|
+
* for how they are ordered. `undefined` outside "exit" durability.
|
|
115
192
|
*/
|
|
116
193
|
_exitDeltaWrites;
|
|
117
194
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
195
|
+
* The `(taskId, channel)` pairs whose delta writes are stored on the loaded
|
|
196
|
+
* checkpoint, which a resume addressed by `checkpoint_id` can rerun.
|
|
197
|
+
* Captured in the constructor, before this run's Command processing adds
|
|
198
|
+
* writes (mirrors Python's `_first`). Used by the exit-mode accumulator to
|
|
199
|
+
* skip the loaded writes on the first superstep — they are already stored
|
|
200
|
+
* on the checkpoint.
|
|
201
|
+
*/
|
|
202
|
+
_storedDeltaWrites = /* @__PURE__ */ new Set();
|
|
203
|
+
/**
|
|
204
|
+
* This run's `Command` delta writes (`NULL_TASK_ID`), kept apart from the
|
|
205
|
+
* `NULL_TASK_ID` writes loaded with the checkpoint, which the first
|
|
206
|
+
* superstep skips. Written by the input handler; replayed into the
|
|
207
|
+
* exit-mode accumulator at the first superstep.
|
|
208
|
+
*/
|
|
209
|
+
_exitCommandWrites = [];
|
|
210
|
+
/**
|
|
211
|
+
* The step of the first superstep this run completed (the loaded
|
|
212
|
+
* checkpoint's own), or `null` before it ticks. The exit-mode accumulator
|
|
213
|
+
* keeps that superstep's real task paths so its writes interleave with the
|
|
214
|
+
* loaded ones as live execution did; later supersteps get `~~`-prefixed
|
|
215
|
+
* synthetic paths (see `_putExitDeltaWrites`).
|
|
216
|
+
*/
|
|
217
|
+
_exitFirstStep = null;
|
|
218
|
+
/**
|
|
219
|
+
* DeltaChannels that must snapshot at the next checkpoint, whatever their
|
|
220
|
+
* cadence counters say:
|
|
221
|
+
* - an Overwrite arrived since the last checkpoint, so sparse replay has to
|
|
222
|
+
* start from the post-overwrite value;
|
|
223
|
+
* - the checkpoint this run starts from has pending writes to them; see
|
|
224
|
+
* {@link deltaChannelsWithPendingWrites}.
|
|
225
|
+
* Cleared once the channel snapshots.
|
|
123
226
|
*/
|
|
124
|
-
|
|
227
|
+
_deltaChannelsForcedSnapshot = /* @__PURE__ */ new Set();
|
|
228
|
+
/**
|
|
229
|
+
* Set by `_first` for a resume: the writes it loaded with the checkpoint,
|
|
230
|
+
* which the end of the superstep checks against the ones handed back to
|
|
231
|
+
* their tasks.
|
|
232
|
+
*/
|
|
233
|
+
_resumeLoadedWrites = [];
|
|
234
|
+
/** Tasks a resume handed loaded writes back to. */
|
|
235
|
+
_reappliedTaskIds = /* @__PURE__ */ new Set();
|
|
236
|
+
/**
|
|
237
|
+
* An update or fork checkpoint addressed by `checkpoint_id` that the thread
|
|
238
|
+
* has moved past, so it may already have children.
|
|
239
|
+
*/
|
|
240
|
+
addressedCheckpointSuperseded;
|
|
125
241
|
/** Whether a real checkpoint was loaded from the saver at initialization. */
|
|
126
242
|
_hasPersistedParent = false;
|
|
127
243
|
/**
|
|
244
|
+
* Whether initialization loaded the thread's latest checkpoint, not one a
|
|
245
|
+
* `checkpoint_id` addressed, so nothing has been built on it yet.
|
|
246
|
+
*/
|
|
247
|
+
_loadedLatest = false;
|
|
248
|
+
/**
|
|
128
249
|
* Whether this branch carries {@link DELTA_WRITES_VERSIONED}: a graph with
|
|
129
250
|
* a DeltaChannel on a new thread, or a loaded checkpoint that has it. Every
|
|
130
251
|
* checkpoint this loop writes inherits it.
|
|
@@ -162,6 +283,14 @@ var PregelLoop = class PregelLoop {
|
|
|
162
283
|
resumeAtHead;
|
|
163
284
|
_checkpointerChainedPromise = Promise.resolve();
|
|
164
285
|
/**
|
|
286
|
+
* The DeltaChannel writes this loop sent to the saver since the last
|
|
287
|
+
* checkpoint save was scheduled. That save waits for them, and a rejection
|
|
288
|
+
* skips it and every later save: a DeltaChannel is rebuilt from its writes
|
|
289
|
+
* along the parent chain, so a checkpoint saved past a missing write or
|
|
290
|
+
* parent reads back short for good.
|
|
291
|
+
*/
|
|
292
|
+
_pendingDeltaWrites = [];
|
|
293
|
+
/**
|
|
165
294
|
* Track a checkpointer promise, removing it from the set on success.
|
|
166
295
|
* Failed promises are kept so that Promise.all() in the finally block
|
|
167
296
|
* of _streamIterator can surface the error.
|
|
@@ -175,6 +304,7 @@ var PregelLoop = class PregelLoop {
|
|
|
175
304
|
}, (error) => {
|
|
176
305
|
throw error;
|
|
177
306
|
});
|
|
307
|
+
tracked.catch(() => {});
|
|
178
308
|
this.checkpointerPromises.add(tracked);
|
|
179
309
|
}
|
|
180
310
|
/**
|
|
@@ -222,12 +352,14 @@ var PregelLoop = class PregelLoop {
|
|
|
222
352
|
this.checkpointPreviousVersions = params.checkpointPreviousVersions;
|
|
223
353
|
this.channels = params.channels;
|
|
224
354
|
this.checkpointPendingWrites = params.checkpointPendingWrites;
|
|
355
|
+
for (const [tid, ch] of this.checkpointPendingWrites) if (tid !== "00000000-0000-0000-0000-000000000000" && isDeltaChannel(this.channels[ch])) this._storedDeltaWrites.add(`${tid}\u0000${ch}`);
|
|
225
356
|
this.step = params.step;
|
|
226
357
|
this.stop = params.stop;
|
|
227
358
|
this.config = params.config;
|
|
228
359
|
this.checkpointConfig = params.checkpointConfig;
|
|
229
360
|
this.isNested = params.isNested;
|
|
230
361
|
this.resumeAtHead = params.resumeAtHead;
|
|
362
|
+
this.addressedCheckpointSuperseded = params.addressedCheckpointSuperseded;
|
|
231
363
|
this.manager = params.manager;
|
|
232
364
|
this.lifecycleEvents = params.hasGraphCallbacks ? [] : void 0;
|
|
233
365
|
this.outputKeys = params.outputKeys;
|
|
@@ -247,6 +379,7 @@ var PregelLoop = class PregelLoop {
|
|
|
247
379
|
this.control = this.config.control;
|
|
248
380
|
this._exitDeltaWrites = this.durability === "exit" && this.checkpointer != null ? [] : void 0;
|
|
249
381
|
this._hasPersistedParent = params.hasPersistedParent ?? false;
|
|
382
|
+
this._loadedLatest = params.loadedLatest ?? false;
|
|
250
383
|
this._deltaWritesVersioned = params.deltaWritesVersioned ?? false;
|
|
251
384
|
this._initialCheckpointConfig = params.checkpointConfig;
|
|
252
385
|
this.checkpointIdSaved = params.checkpoint.id;
|
|
@@ -260,7 +393,6 @@ var PregelLoop = class PregelLoop {
|
|
|
260
393
|
if (scratchpad.subgraphCounter > 0) config = patchConfigurable(config, { [CONFIG_KEY_CHECKPOINT_NS]: [config.configurable[CONFIG_KEY_CHECKPOINT_NS], scratchpad.subgraphCounter.toString()].join("|") });
|
|
261
394
|
scratchpad.subgraphCounter += 1;
|
|
262
395
|
}
|
|
263
|
-
const requestedCheckpointId = config.configurable?.checkpoint_id;
|
|
264
396
|
const isNested = CONFIG_KEY_READ in (config.configurable ?? {});
|
|
265
397
|
if (!isNested && config.configurable?.checkpoint_ns !== void 0 && config.configurable?.checkpoint_ns !== "") config = patchConfigurable(config, {
|
|
266
398
|
checkpoint_ns: "",
|
|
@@ -270,13 +402,18 @@ var PregelLoop = class PregelLoop {
|
|
|
270
402
|
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] });
|
|
271
403
|
const checkpointNamespace = checkpointNamespaceFromNs(config.configurable?.checkpoint_ns);
|
|
272
404
|
let saved;
|
|
405
|
+
let loadedLatest = false;
|
|
273
406
|
if (!params.checkpointer) saved = void 0;
|
|
274
407
|
else if (checkpointConfig.configurable?.["checkpoint_id"]) saved = await params.checkpointer.getTuple(checkpointConfig);
|
|
275
408
|
else if (config.configurable?.["__pregel_replay_state"]) {
|
|
276
409
|
saved = await config.configurable[CONFIG_KEY_REPLAY_STATE].getCheckpoint(config.configurable?.["checkpoint_ns"] ?? "", params.checkpointer, checkpointConfig);
|
|
277
410
|
if (config.configurable) delete config.configurable[CONFIG_KEY_RESUMING];
|
|
278
|
-
} else
|
|
411
|
+
} else {
|
|
412
|
+
saved = await params.checkpointer.getTuple(checkpointConfig);
|
|
413
|
+
loadedLatest = true;
|
|
414
|
+
}
|
|
279
415
|
const hasPersistedParent = saved !== void 0;
|
|
416
|
+
const addressedConfig = checkpointConfig;
|
|
280
417
|
if (!saved) saved = {
|
|
281
418
|
config,
|
|
282
419
|
checkpoint: emptyCheckpoint(),
|
|
@@ -304,12 +441,13 @@ var PregelLoop = class PregelLoop {
|
|
|
304
441
|
const checkpointMap = config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP];
|
|
305
442
|
if (typeof currentCheckpointNamespace === "string" && currentCheckpointNamespace !== "" && typeof checkpointMap === "object" && checkpointMap !== null && currentCheckpointNamespace in checkpointMap && checkpointPendingWrites.length > 0) checkpointPendingWrites = checkpointPendingWrites.filter(([, channel]) => channel !== RESUME);
|
|
306
443
|
let resumeAtHead = false;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
444
|
+
let addressedCheckpointSuperseded = false;
|
|
445
|
+
if (params.checkpointer && hasPersistedParent) {
|
|
446
|
+
const superseded = await checkpointSuperseded(params.checkpointer, addressedConfig, saved);
|
|
447
|
+
const isUpdateOrFork = checkpointMetadata.source === "update" || checkpointMetadata.source === "fork";
|
|
448
|
+
resumeAtHead = addressedConfig.configurable?.checkpoint_id !== void 0 && !superseded && !isUpdateOrFork;
|
|
449
|
+
addressedCheckpointSuperseded = superseded && isUpdateOrFork;
|
|
450
|
+
}
|
|
313
451
|
const deltaWritesVersioned = Object.values(params.channelSpecs).some(isDeltaChannel) && (!hasPersistedParent || isDeltaWritesVersioned(saved.metadata));
|
|
314
452
|
const channels = await channelsFromCheckpoint(params.channelSpecs, checkpoint, {
|
|
315
453
|
saver: params.checkpointer,
|
|
@@ -333,6 +471,7 @@ var PregelLoop = class PregelLoop {
|
|
|
333
471
|
channels,
|
|
334
472
|
isNested,
|
|
335
473
|
resumeAtHead,
|
|
474
|
+
addressedCheckpointSuperseded,
|
|
336
475
|
manager: params.manager,
|
|
337
476
|
hasGraphCallbacks: params.hasGraphCallbacks,
|
|
338
477
|
skipDoneTasks,
|
|
@@ -352,11 +491,14 @@ var PregelLoop = class PregelLoop {
|
|
|
352
491
|
debug: params.debug,
|
|
353
492
|
triggerToNodes: params.triggerToNodes,
|
|
354
493
|
hasPersistedParent,
|
|
494
|
+
loadedLatest,
|
|
355
495
|
deltaWritesVersioned
|
|
356
496
|
});
|
|
357
497
|
}
|
|
358
498
|
_checkpointerPutAfterPrevious(input) {
|
|
359
|
-
|
|
499
|
+
const pendingDeltaWrites = this._pendingDeltaWrites;
|
|
500
|
+
this._pendingDeltaWrites = [];
|
|
501
|
+
this._checkpointerChainedPromise = this._checkpointerChainedPromise.then(() => Promise.all(pendingDeltaWrites)).then(() => {
|
|
360
502
|
return this.checkpointer?.put(input.config, input.checkpoint, input.metadata, input.newVersions);
|
|
361
503
|
});
|
|
362
504
|
this._trackCheckpointerPromise(this._checkpointerChainedPromise);
|
|
@@ -365,10 +507,13 @@ var PregelLoop = class PregelLoop {
|
|
|
365
507
|
* Put writes for a task, to be read by the next tick.
|
|
366
508
|
* @param taskId
|
|
367
509
|
* @param writes
|
|
510
|
+
* @param cached The writes were served from the node cache: stream them as
|
|
511
|
+
* cached and don't write them to the cache again.
|
|
368
512
|
*/
|
|
369
|
-
putWrites(taskId, writes) {
|
|
513
|
+
putWrites(taskId, writes, cached = false) {
|
|
370
514
|
let writesCopy = writes;
|
|
371
515
|
if (writesCopy.length === 0) return;
|
|
516
|
+
const deltaWrites = ensureDeltaMessageIds(this.channels, writes);
|
|
372
517
|
if (writesCopy.every(([key]) => key in WRITES_IDX_MAP)) writesCopy = Array.from(new Map(writesCopy.map((w) => [w[0], w])).values());
|
|
373
518
|
let hasUntrackedChannels = false;
|
|
374
519
|
for (const key in this.channels) if (Object.prototype.hasOwnProperty.call(this.channels, key)) {
|
|
@@ -391,17 +536,18 @@ var PregelLoop = class PregelLoop {
|
|
|
391
536
|
c,
|
|
392
537
|
v
|
|
393
538
|
]);
|
|
394
|
-
for (const [c, v] of writesToSave) {
|
|
395
|
-
const channel = this.channels[c];
|
|
396
|
-
if (channel != null && isDeltaChannel(channel)) ensureMessageIds(v);
|
|
397
|
-
}
|
|
398
539
|
const config = patchConfigurable(this.checkpointConfig, {
|
|
399
540
|
[CONFIG_KEY_CHECKPOINT_NS]: this.config.configurable?.checkpoint_ns ?? "",
|
|
400
541
|
[CONFIG_KEY_CHECKPOINT_ID]: this.checkpoint.id
|
|
401
542
|
});
|
|
402
|
-
if (this.durability !== "exit" && this.checkpointer != null)
|
|
403
|
-
|
|
404
|
-
|
|
543
|
+
if (this.durability !== "exit" && this.checkpointer != null) {
|
|
544
|
+
const task = this.tasks[taskId];
|
|
545
|
+
const saved = this.checkpointer.putWrites(config, writesToSave, taskId, task != null ? taskPathStr(task.path) : "");
|
|
546
|
+
this._trackCheckpointerPromise(saved);
|
|
547
|
+
if (deltaWrites.length > 0) this._pendingDeltaWrites.push(saved);
|
|
548
|
+
}
|
|
549
|
+
if (this.tasks) this._outputWrites(taskId, writesCopy, cached);
|
|
550
|
+
if (cached || !writes.length || !this.cache || !this.tasks) return;
|
|
405
551
|
const task = this.tasks[taskId];
|
|
406
552
|
if (task == null || task.cache_key == null) return;
|
|
407
553
|
if (writes[0][0] === "__error__" || writes[0][0] === "__interrupt__") return;
|
|
@@ -411,6 +557,10 @@ var PregelLoop = class PregelLoop {
|
|
|
411
557
|
ttl: task.cache_key.ttl
|
|
412
558
|
}]);
|
|
413
559
|
}
|
|
560
|
+
/** Streams the writes reapplied to tasks that finished in a previous run. */
|
|
561
|
+
_outputCachedWrites(tasks) {
|
|
562
|
+
for (const task of tasks) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
|
|
563
|
+
}
|
|
414
564
|
_outputWrites(taskId, writes, cached = false) {
|
|
415
565
|
const task = this.tasks[taskId];
|
|
416
566
|
if (task !== void 0) {
|
|
@@ -469,19 +619,37 @@ var PregelLoop = class PregelLoop {
|
|
|
469
619
|
const finishTaskList = Object.values(this.tasks);
|
|
470
620
|
const writes = finishTaskList.flatMap((t) => t.writes);
|
|
471
621
|
this.updatedChannels = _applyWrites(this.checkpoint, this.channels, finishTaskList, this.checkpointerGetNextVersion, this.triggerToNodes);
|
|
472
|
-
for (const [ch, v] of writes)
|
|
473
|
-
|
|
474
|
-
|
|
622
|
+
for (const [ch, v] of deltaChannelWrites(this.channels, writes)) if (_isOverwriteValue(v)) this._deltaChannelsForcedSnapshot.add(ch);
|
|
623
|
+
if (this._resumeLoadedWrites.length > 0) {
|
|
624
|
+
for (const ch of deltaChannelsWithPendingWrites(this.channels, this._resumeLoadedWrites.filter(([tid]) => !this._reappliedTaskIds.has(tid)))) this._deltaChannelsForcedSnapshot.add(ch);
|
|
625
|
+
this._resumeLoadedWrites = [];
|
|
475
626
|
}
|
|
476
627
|
const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, writes, this.channels), "values"));
|
|
477
|
-
if (this._exitDeltaWrites !== void 0)
|
|
478
|
-
const
|
|
479
|
-
if (
|
|
480
|
-
this.step
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
628
|
+
if (this._exitDeltaWrites !== void 0) {
|
|
629
|
+
const first = this._exitFirstStep === null;
|
|
630
|
+
if (first) {
|
|
631
|
+
this._exitFirstStep = this.step;
|
|
632
|
+
for (const [ch, v] of this._exitCommandWrites) this._exitDeltaWrites.push([
|
|
633
|
+
this.step,
|
|
634
|
+
NULL_TASK_ID,
|
|
635
|
+
"",
|
|
636
|
+
ch,
|
|
637
|
+
v
|
|
638
|
+
]);
|
|
639
|
+
}
|
|
640
|
+
for (const [tid, ch, v] of this.checkpointPendingWrites) {
|
|
641
|
+
const channel = this.channels[ch];
|
|
642
|
+
if (channel == null || !isDeltaChannel(channel)) continue;
|
|
643
|
+
if (first && (tid === "00000000-0000-0000-0000-000000000000" || this._storedDeltaWrites.has(`${tid}\u0000${ch}`))) continue;
|
|
644
|
+
const task = this.tasks[tid];
|
|
645
|
+
this._exitDeltaWrites.push([
|
|
646
|
+
this.step,
|
|
647
|
+
tid,
|
|
648
|
+
task != null ? taskPathStr(task.path) : "",
|
|
649
|
+
ch,
|
|
650
|
+
v
|
|
651
|
+
]);
|
|
652
|
+
}
|
|
485
653
|
}
|
|
486
654
|
this.checkpointPendingWrites = [];
|
|
487
655
|
await this._putCheckpoint({ source: "loop" });
|
|
@@ -513,25 +681,24 @@ var PregelLoop = class PregelLoop {
|
|
|
513
681
|
this.status = "done";
|
|
514
682
|
return false;
|
|
515
683
|
}
|
|
516
|
-
if (this.control != null && this.control.drainRequested) {
|
|
517
|
-
this.status = "draining";
|
|
518
|
-
return false;
|
|
519
|
-
}
|
|
520
684
|
if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {
|
|
521
|
-
|
|
522
|
-
if (k === "__error__" || k === "__error_source_node__" || k === "__interrupt__" || k === "__resume__") continue;
|
|
523
|
-
const task = taskList.find((t) => t.id === tid);
|
|
524
|
-
if (task) task.writes.push([k, v]);
|
|
525
|
-
}
|
|
685
|
+
this._reapplyWritesToSucceededNodes(this.tasks);
|
|
526
686
|
this._resumeErrorHandlersIfApplicable();
|
|
527
687
|
taskList = Object.values(this.tasks);
|
|
528
|
-
for (const task of taskList) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
|
|
529
688
|
}
|
|
530
|
-
if (taskList.every((task) => task.writes.length > 0))
|
|
689
|
+
if (taskList.every((task) => task.writes.length > 0)) {
|
|
690
|
+
this._outputCachedWrites(taskList);
|
|
691
|
+
return this.tick({ inputKeys });
|
|
692
|
+
}
|
|
531
693
|
if (shouldInterrupt(this.checkpoint, this.interruptBefore, taskList)) {
|
|
532
694
|
this.status = "interrupt_before";
|
|
533
695
|
throw new GraphInterrupt();
|
|
534
696
|
}
|
|
697
|
+
if (this.control != null && this.control.drainRequested) {
|
|
698
|
+
this.status = "draining";
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
this._outputCachedWrites(taskList);
|
|
535
702
|
if (this.stream.modes.has("tasks") || this.stream.modes.has("debug")) {
|
|
536
703
|
const debugOutput = await gatherIterator(prefixGenerator(mapDebugTasks(taskList), "tasks"));
|
|
537
704
|
this._emit(debugOutput);
|
|
@@ -583,7 +750,7 @@ var PregelLoop = class PregelLoop {
|
|
|
583
750
|
this.tasks[pushed.id] = pushed;
|
|
584
751
|
if (this.skipDoneTasks) this._matchWrites({ [pushed.id]: pushed });
|
|
585
752
|
const tasks = await this._matchCachedWrites();
|
|
586
|
-
for (const { task } of tasks) this.
|
|
753
|
+
for (const { task } of tasks) this.putWrites(task.id, task.writes, true);
|
|
587
754
|
return pushed;
|
|
588
755
|
}
|
|
589
756
|
/**
|
|
@@ -667,33 +834,53 @@ var PregelLoop = class PregelLoop {
|
|
|
667
834
|
}
|
|
668
835
|
async _first(inputKeys) {
|
|
669
836
|
const { configurable } = this.config;
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
837
|
+
const inputIsCommand = isCommand(this.input);
|
|
838
|
+
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));
|
|
839
|
+
const isUpdateOrFork = this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork";
|
|
840
|
+
const forks = isTimeTraveling && (inputIsCommand || this.isResuming) && (!isUpdateOrFork || this.addressedCheckpointSuperseded);
|
|
841
|
+
const carried = [];
|
|
842
|
+
const reapplies = this.isResuming && this.skipDoneTasks;
|
|
843
|
+
this._resumeLoadedWrites = reapplies ? this.checkpointPendingWrites.filter(([tid]) => tid !== NULL_TASK_ID) : [];
|
|
844
|
+
this._reappliedTaskIds = /* @__PURE__ */ new Set();
|
|
845
|
+
this._deltaChannelsForcedSnapshot = reapplies ? /* @__PURE__ */ new Set() : deltaChannelsWithPendingWrites(this.channels, this.checkpointPendingWrites);
|
|
846
|
+
if (inputIsCommand) {
|
|
847
|
+
const command = this.input;
|
|
848
|
+
const hasResume = command.resume != null;
|
|
849
|
+
if (command.resume != null && typeof command.resume === "object" && Object.keys(command.resume).every(isXXH3)) {
|
|
673
850
|
this.config.configurable ??= {};
|
|
674
|
-
this.config.configurable[CONFIG_KEY_RESUME_MAP] =
|
|
851
|
+
this.config.configurable[CONFIG_KEY_RESUME_MAP] = command.resume;
|
|
675
852
|
}
|
|
676
853
|
if (hasResume && this.checkpointer == null) throw new Error("Cannot use Command(resume=...) without checkpointer");
|
|
677
854
|
const writes = {};
|
|
678
|
-
for (const [tid, key, value] of mapCommand(
|
|
855
|
+
for (const [tid, key, value] of mapCommand(command, this.checkpointPendingWrites)) {
|
|
679
856
|
writes[tid] ??= [];
|
|
680
857
|
writes[tid].push([key, value]);
|
|
681
858
|
}
|
|
682
859
|
if (Object.keys(writes).length === 0) throw new EmptyInputError("Received empty Command input");
|
|
683
|
-
for (const [tid, ws] of Object.entries(writes))
|
|
860
|
+
for (const [tid, ws] of Object.entries(writes)) if (forks) {
|
|
861
|
+
ensureDeltaMessageIds(this.channels, ws);
|
|
862
|
+
for (const [c, v] of ws) carried.push([
|
|
863
|
+
tid,
|
|
864
|
+
c,
|
|
865
|
+
v
|
|
866
|
+
]);
|
|
867
|
+
} else {
|
|
868
|
+
this.putWrites(tid, ws);
|
|
869
|
+
if (this._exitDeltaWrites !== void 0 && tid === "00000000-0000-0000-0000-000000000000") this._exitCommandWrites.push(...deltaChannelWrites(this.channels, ws));
|
|
870
|
+
}
|
|
871
|
+
for (const ch of deltaChannelsWithPendingWrites(this.channels, carried)) this._deltaChannelsForcedSnapshot.add(ch);
|
|
684
872
|
}
|
|
685
|
-
const nullWrites =
|
|
873
|
+
const nullWrites = [...this.checkpointPendingWrites ?? [], ...carried].filter((w) => w[0] === NULL_TASK_ID).map((w) => w.slice(1));
|
|
686
874
|
if (nullWrites.length > 0) _applyWrites(this.checkpoint, this.channels, [{
|
|
687
875
|
name: INPUT,
|
|
688
876
|
writes: nullWrites,
|
|
689
877
|
triggers: []
|
|
690
878
|
}], this.checkpointerGetNextVersion, this.triggerToNodes);
|
|
691
|
-
const inputIsCommand = isCommand(this.input);
|
|
692
879
|
const isCommandUpdateOrGoto = inputIsCommand && nullWrites.length > 0;
|
|
693
|
-
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));
|
|
694
880
|
if (isTimeTraveling) this.checkpointPendingWrites = this.checkpointPendingWrites.filter((w) => w[1] !== RESUME);
|
|
695
|
-
const
|
|
696
|
-
if (
|
|
881
|
+
const nullResume = (configurable?.[CONFIG_KEY_SCRATCHPAD])?.nullResume;
|
|
882
|
+
if (nullResume !== void 0 && !forks) this.putWrites(NULL_TASK_ID, [[RESUME, nullResume]]);
|
|
883
|
+
let replayBound;
|
|
697
884
|
const cachedIsResuming = this.isResuming;
|
|
698
885
|
if (cachedIsResuming || isCommandUpdateOrGoto) {
|
|
699
886
|
const interruptSeen = { ...this.checkpoint.versions_seen[INTERRUPT] };
|
|
@@ -702,9 +889,11 @@ var PregelLoop = class PregelLoop {
|
|
|
702
889
|
if (this.checkpoint.channel_versions[channelName] !== void 0) interruptSeen[channelName] = this.checkpoint.channel_versions[channelName];
|
|
703
890
|
}
|
|
704
891
|
this.checkpoint.versions_seen[INTERRUPT] = interruptSeen;
|
|
705
|
-
if (
|
|
892
|
+
if (forks) {
|
|
893
|
+
if (isUpdateOrFork && this.prevCheckpointConfig) replayBound = this.prevCheckpointConfig;
|
|
706
894
|
this.checkpointPendingWrites = this.checkpointPendingWrites.filter((w) => w[1] !== INTERRUPT);
|
|
707
895
|
await this._putCheckpoint({ source: "fork" });
|
|
896
|
+
if (nullResume !== void 0) this.putWrites(NULL_TASK_ID, [[RESUME, nullResume]]);
|
|
708
897
|
}
|
|
709
898
|
const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, true, this.channels), "values"));
|
|
710
899
|
if (cachedIsResuming) this.input = INPUT_RESUMING;
|
|
@@ -716,25 +905,24 @@ var PregelLoop = class PregelLoop {
|
|
|
716
905
|
} else {
|
|
717
906
|
const inputWrites = await gatherIterator(mapInput(inputKeys, this.input));
|
|
718
907
|
if (inputWrites.length > 0) {
|
|
908
|
+
const deltaInput = ensureDeltaMessageIds(this.channels, inputWrites);
|
|
719
909
|
const discardTasks = _prepareNextTasks(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.config, true, { step: this.step });
|
|
720
910
|
this.updatedChannels = _applyWrites(this.checkpoint, this.channels, Object.values(discardTasks).concat([{
|
|
721
911
|
name: INPUT,
|
|
722
912
|
writes: inputWrites,
|
|
723
913
|
triggers: []
|
|
724
914
|
}]), this.checkpointerGetNextVersion, this.triggerToNodes);
|
|
725
|
-
const deltaInput
|
|
726
|
-
const channel = this.channels[c];
|
|
727
|
-
return channel != null && isDeltaChannel(channel);
|
|
728
|
-
});
|
|
729
|
-
for (const [c, v] of deltaInput) if (_isOverwriteValue(v)) this._deltaChannelsWithOverwrite.add(c);
|
|
915
|
+
for (const [c, v] of deltaInput) if (_isOverwriteValue(v)) this._deltaChannelsForcedSnapshot.add(c);
|
|
730
916
|
if (deltaInput.length > 0) {
|
|
731
917
|
if (this._exitDeltaWrites !== void 0) for (const [c, v] of deltaInput) this._exitDeltaWrites.push([
|
|
732
918
|
this.step,
|
|
733
919
|
NULL_TASK_ID,
|
|
920
|
+
"",
|
|
734
921
|
c,
|
|
735
922
|
v
|
|
736
923
|
]);
|
|
737
|
-
else if (this.checkpointer != null) this.putWrites(
|
|
924
|
+
else if (this.checkpointer != null) if (this._hasPersistedParent && this._loadedLatest) this.putWrites(uuid5(INPUT, this.checkpoint.id), deltaInput);
|
|
925
|
+
else for (const [c] of deltaInput) this._deltaChannelsForcedSnapshot.add(c);
|
|
738
926
|
}
|
|
739
927
|
await this._putCheckpoint({ source: "input" });
|
|
740
928
|
this.input = INPUT_DONE;
|
|
@@ -745,7 +933,8 @@ var PregelLoop = class PregelLoop {
|
|
|
745
933
|
let replayState;
|
|
746
934
|
if (isTimeTraveling) {
|
|
747
935
|
let replayCheckpointId = this.checkpoint.id;
|
|
748
|
-
if (
|
|
936
|
+
if (replayBound !== void 0) replayCheckpointId = replayBound.configurable?.["checkpoint_id"] ?? replayCheckpointId;
|
|
937
|
+
else if ((this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork") && this.prevCheckpointConfig) replayCheckpointId = this.prevCheckpointConfig.configurable?.["checkpoint_id"] ?? replayCheckpointId;
|
|
749
938
|
replayState = new ReplayState(replayCheckpointId);
|
|
750
939
|
}
|
|
751
940
|
this.config = patchConfigurable(this.config, {
|
|
@@ -855,15 +1044,7 @@ var PregelLoop = class PregelLoop {
|
|
|
855
1044
|
};
|
|
856
1045
|
let newCounters;
|
|
857
1046
|
if (!exiting) {
|
|
858
|
-
|
|
859
|
-
newCounters = {};
|
|
860
|
-
const updated = this.updatedChannels ?? /* @__PURE__ */ new Set();
|
|
861
|
-
for (const chName in this.channels) {
|
|
862
|
-
if (!Object.prototype.hasOwnProperty.call(this.channels, chName)) continue;
|
|
863
|
-
if (!isDeltaChannel(this.channels[chName])) continue;
|
|
864
|
-
const [u, s] = prevCounters[chName] ?? [0, 0];
|
|
865
|
-
newCounters[chName] = [updated.has(chName) ? u + 1 : u, s + 1];
|
|
866
|
-
}
|
|
1047
|
+
newCounters = advanceDeltaCounters(this.channels, this.updatedChannels ?? /* @__PURE__ */ new Set(), this.checkpointMetadata.counters_since_delta_snapshot);
|
|
867
1048
|
this.checkpointMetadata = {
|
|
868
1049
|
...inputMetadata,
|
|
869
1050
|
step: this.step,
|
|
@@ -872,16 +1053,17 @@ var PregelLoop = class PregelLoop {
|
|
|
872
1053
|
} else newCounters = { ...this.checkpointMetadata.counters_since_delta_snapshot ?? {} };
|
|
873
1054
|
if (this._deltaWritesVersioned) this.checkpointMetadata[DELTA_WRITES_VERSIONED] = true;
|
|
874
1055
|
const channelsToSnapshot = doCheckpoint ? deltaChannelsToSnapshot(this.channels, newCounters, this._deltaWritesVersioned ? this.checkpoint.channel_versions : void 0) : /* @__PURE__ */ new Set();
|
|
875
|
-
if (doCheckpoint) for (const ch of this.
|
|
1056
|
+
if (doCheckpoint) for (const ch of this._deltaChannelsForcedSnapshot) channelsToSnapshot.add(ch);
|
|
876
1057
|
this.checkpoint = createCheckpoint(this.checkpoint, doCheckpoint ? this.channels : void 0, this.step, {
|
|
877
1058
|
id: exiting ? this.checkpoint.id : void 0,
|
|
878
1059
|
channelsToSnapshot,
|
|
879
|
-
|
|
880
|
-
getNextVersion: doCheckpoint ? (current) => this.checkpointerGetNextVersion(current) : void 0
|
|
1060
|
+
storedVersions: this.checkpointPreviousVersions,
|
|
1061
|
+
getNextVersion: doCheckpoint ? (current) => this.checkpointerGetNextVersion(current) : void 0,
|
|
1062
|
+
triggerToNodes: this.triggerToNodes
|
|
881
1063
|
});
|
|
882
1064
|
for (const k of channelsToSnapshot) {
|
|
883
1065
|
newCounters[k] = [0, 0];
|
|
884
|
-
this.
|
|
1066
|
+
this._deltaChannelsForcedSnapshot.delete(k);
|
|
885
1067
|
}
|
|
886
1068
|
const nonZero = {};
|
|
887
1069
|
for (const k in newCounters) {
|
|
@@ -907,10 +1089,11 @@ var PregelLoop = class PregelLoop {
|
|
|
907
1089
|
*/
|
|
908
1090
|
async _putExitDeltaWrites() {
|
|
909
1091
|
if (this._exitDeltaWrites === void 0 || this._exitDeltaWrites.length === 0 || this.checkpointer == null || this._initialCheckpointConfig === void 0) return;
|
|
1092
|
+
if (this._hasPersistedParent && !this._loadedLatest) for (const [, , , ch] of this._exitDeltaWrites) this._deltaChannelsForcedSnapshot.add(ch);
|
|
910
1093
|
const counters = this.checkpointMetadata.counters_since_delta_snapshot ?? {};
|
|
911
1094
|
const channelsToSnapshot = deltaChannelsToSnapshot(this.channels, counters, this._deltaWritesVersioned ? this.checkpoint.channel_versions : void 0);
|
|
912
|
-
for (const ch of this.
|
|
913
|
-
const pending = this._exitDeltaWrites.filter(([, , ch]) => !channelsToSnapshot.has(ch));
|
|
1095
|
+
for (const ch of this._deltaChannelsForcedSnapshot) channelsToSnapshot.add(ch);
|
|
1096
|
+
const pending = this._exitDeltaWrites.filter(([, , , ch]) => !channelsToSnapshot.has(ch));
|
|
914
1097
|
if (pending.length === 0) return;
|
|
915
1098
|
let anchorConfig;
|
|
916
1099
|
if (this._hasPersistedParent) anchorConfig = this._initialCheckpointConfig;
|
|
@@ -920,11 +1103,16 @@ var PregelLoop = class PregelLoop {
|
|
|
920
1103
|
stubCp.ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
921
1104
|
const stubPutConfig = patchConfigurable(this._initialCheckpointConfig, { [CONFIG_KEY_CHECKPOINT_ID]: void 0 });
|
|
922
1105
|
anchorConfig = patchConfigurable(this._initialCheckpointConfig, { [CONFIG_KEY_CHECKPOINT_ID]: stubCp.id });
|
|
923
|
-
this.
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1106
|
+
this._checkpointerPutAfterPrevious({
|
|
1107
|
+
config: stubPutConfig,
|
|
1108
|
+
checkpoint: stubCp,
|
|
1109
|
+
metadata: {
|
|
1110
|
+
source: "loop",
|
|
1111
|
+
step: -2,
|
|
1112
|
+
parents: {}
|
|
1113
|
+
},
|
|
1114
|
+
newVersions: {}
|
|
1115
|
+
});
|
|
928
1116
|
this.checkpointConfig = anchorConfig;
|
|
929
1117
|
}
|
|
930
1118
|
const anchorWriteConfig = patchConfigurable(anchorConfig, {
|
|
@@ -933,23 +1121,36 @@ var PregelLoop = class PregelLoop {
|
|
|
933
1121
|
});
|
|
934
1122
|
const grouped = /* @__PURE__ */ new Map();
|
|
935
1123
|
const order = [];
|
|
936
|
-
for (const [step, tid, ch, v] of pending) {
|
|
937
|
-
|
|
1124
|
+
for (const [step, tid, path, ch, v] of pending) {
|
|
1125
|
+
let synthTid;
|
|
1126
|
+
let synthPath;
|
|
1127
|
+
if (tid === "00000000-0000-0000-0000-000000000000") {
|
|
1128
|
+
synthTid = exitDeltaTaskId(step, tid);
|
|
1129
|
+
synthPath = "";
|
|
1130
|
+
} else if (step === this._exitFirstStep) {
|
|
1131
|
+
synthTid = exitDeltaTaskId(step, tid);
|
|
1132
|
+
synthPath = path;
|
|
1133
|
+
} else {
|
|
1134
|
+
synthTid = exitDeltaLateTaskId(step, tid);
|
|
1135
|
+
synthPath = `~~${taskPathStr(step)}${path}`;
|
|
1136
|
+
}
|
|
1137
|
+
const key = `${synthTid}\u0000${synthPath}`;
|
|
938
1138
|
let group = grouped.get(key);
|
|
939
1139
|
if (group === void 0) {
|
|
940
1140
|
group = [];
|
|
941
1141
|
grouped.set(key, group);
|
|
942
1142
|
order.push({
|
|
943
1143
|
key,
|
|
944
|
-
|
|
945
|
-
|
|
1144
|
+
tid: synthTid,
|
|
1145
|
+
path: synthPath
|
|
946
1146
|
});
|
|
947
1147
|
}
|
|
948
1148
|
group.push([ch, v]);
|
|
949
1149
|
}
|
|
950
|
-
for (const { key,
|
|
951
|
-
const
|
|
952
|
-
this._trackCheckpointerPromise(
|
|
1150
|
+
for (const { key, tid, path } of order) {
|
|
1151
|
+
const saved = this.checkpointer.putWrites(anchorWriteConfig, grouped.get(key), tid, path);
|
|
1152
|
+
this._trackCheckpointerPromise(saved);
|
|
1153
|
+
this._pendingDeltaWrites.push(saved);
|
|
953
1154
|
}
|
|
954
1155
|
}
|
|
955
1156
|
_flushPendingWrites() {
|
|
@@ -964,18 +1165,32 @@ var PregelLoop = class PregelLoop {
|
|
|
964
1165
|
byTask[tid] ??= [];
|
|
965
1166
|
byTask[tid].push([key, value]);
|
|
966
1167
|
}
|
|
967
|
-
for (const [tid, ws] of Object.entries(byTask))
|
|
1168
|
+
for (const [tid, ws] of Object.entries(byTask)) {
|
|
1169
|
+
const task = this.tasks[tid];
|
|
1170
|
+
this._trackCheckpointerPromise(this.checkpointer.putWrites(config, ws, tid, task != null ? taskPathStr(task.path) : ""));
|
|
1171
|
+
}
|
|
968
1172
|
}
|
|
969
|
-
|
|
1173
|
+
/**
|
|
1174
|
+
* Hands the writes loaded with the checkpoint back to the finished tasks
|
|
1175
|
+
* among `tasks`. A failed or interrupted task keeps empty writes, so it
|
|
1176
|
+
* reruns or goes to its error handler.
|
|
1177
|
+
*/
|
|
1178
|
+
_reapplyWritesToSucceededNodes(tasks) {
|
|
970
1179
|
for (const [tid, k, v] of this.checkpointPendingWrites) {
|
|
971
|
-
if (k === "__error__" || k === "__interrupt__" || k === "__resume__") continue;
|
|
972
|
-
const task =
|
|
973
|
-
if (task)
|
|
1180
|
+
if (k === "__error__" || k === "__error_source_node__" || k === "__interrupt__" || k === "__resume__") continue;
|
|
1181
|
+
const task = tasks[tid];
|
|
1182
|
+
if (task) {
|
|
1183
|
+
task.writes.push([k, v]);
|
|
1184
|
+
this._reappliedTaskIds.add(tid);
|
|
1185
|
+
}
|
|
974
1186
|
}
|
|
1187
|
+
}
|
|
1188
|
+
_matchWrites(tasks) {
|
|
1189
|
+
this._reapplyWritesToSucceededNodes(tasks);
|
|
975
1190
|
for (const task of Object.values(tasks)) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
|
|
976
1191
|
}
|
|
977
1192
|
};
|
|
978
1193
|
//#endregion
|
|
979
|
-
export { PregelLoop };
|
|
1194
|
+
export { PregelLoop, ensureDeltaMessageIds };
|
|
980
1195
|
|
|
981
1196
|
//# sourceMappingURL=loop.js.map
|