@fluidframework/container-runtime 2.1.0-281041 → 2.2.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/CHANGELOG.md +34 -0
- package/README.md +6 -6
- package/api-report/container-runtime.legacy.alpha.api.md +4 -3
- package/container-runtime.test-files.tar +0 -0
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +9 -0
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +0 -14
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +2 -12
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +34 -6
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +177 -74
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +9 -18
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +40 -78
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +0 -6
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +23 -66
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +11 -34
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +9 -52
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js +3 -23
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +2 -6
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +4 -8
- package/dist/gc/gcSummaryStateTracker.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +1 -9
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +3 -25
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/index.d.ts +2 -2
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +2 -7
- package/dist/gc/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +6 -5
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/metadata.d.ts +9 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +6 -1
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +1 -1
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +8 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +34 -2
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +1 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +21 -1
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +35 -14
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +71 -46
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +28 -27
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +143 -112
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +5 -1
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +9 -0
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +0 -14
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +2 -11
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +34 -6
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +178 -75
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +9 -18
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +27 -65
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +0 -6
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +25 -68
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +12 -35
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +9 -52
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js +2 -22
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +2 -6
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +4 -8
- package/lib/gc/gcSummaryStateTracker.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +1 -9
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +3 -24
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/index.d.ts +2 -2
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +2 -2
- package/lib/gc/index.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +6 -5
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/metadata.d.ts +9 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js +4 -0
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +1 -1
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +8 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +34 -2
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +1 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +21 -1
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +35 -14
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +69 -45
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +28 -27
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +144 -113
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +5 -1
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/package.json +23 -32
- package/src/batchTracker.ts +4 -2
- package/src/blobManager/blobManager.ts +9 -0
- package/src/channelCollection.ts +2 -11
- package/src/containerRuntime.ts +214 -100
- package/src/dataStoreContext.ts +29 -93
- package/src/gc/garbageCollection.ts +26 -79
- package/src/gc/gcConfigs.ts +12 -45
- package/src/gc/gcDefinitions.ts +10 -55
- package/src/gc/gcHelpers.ts +10 -8
- package/src/gc/gcSummaryStateTracker.ts +6 -9
- package/src/gc/gcTelemetry.ts +3 -38
- package/src/gc/index.ts +2 -6
- package/src/index.ts +0 -1
- package/src/messageTypes.ts +12 -11
- package/src/metadata.ts +16 -2
- package/src/opLifecycle/batchManager.ts +4 -1
- package/src/opLifecycle/index.ts +6 -1
- package/src/opLifecycle/opGroupingManager.ts +42 -3
- package/src/opLifecycle/outbox.ts +31 -1
- package/src/opLifecycle/remoteMessageProcessor.ts +116 -57
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +199 -177
- package/src/summary/README.md +31 -28
- package/src/summary/summarizerNode/summarizerNode.ts +6 -1
- package/src/summary/summarizerNode/summarizerNodeWithGc.ts +20 -43
- package/src/summary/summaryFormats.md +25 -22
|
@@ -14,23 +14,50 @@ const double_ended_queue_1 = __importDefault(require("double-ended-queue"));
|
|
|
14
14
|
const uuid_1 = require("uuid");
|
|
15
15
|
const metadata_js_1 = require("./metadata.js");
|
|
16
16
|
const index_js_1 = require("./opLifecycle/index.js");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
message) {
|
|
17
|
+
function isEmptyBatchPendingMessage(message) {
|
|
18
|
+
const content = JSON.parse(message.content);
|
|
19
|
+
return content.type === "groupedBatch" && content.contents?.length === 0;
|
|
20
|
+
}
|
|
21
|
+
function buildPendingMessageContent(message) {
|
|
22
22
|
// IMPORTANT: Order matters here, this must match the order of the properties used
|
|
23
23
|
// when submitting the message.
|
|
24
24
|
const { type, contents, compatDetails } = message;
|
|
25
25
|
// Any properties that are not defined, won't be emitted by stringify.
|
|
26
26
|
return JSON.stringify({ type, contents, compatDetails });
|
|
27
27
|
}
|
|
28
|
+
function typesOfKeys(obj) {
|
|
29
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
30
|
+
acc[key] = typeof obj[key];
|
|
31
|
+
return acc;
|
|
32
|
+
}, {});
|
|
33
|
+
}
|
|
34
|
+
function scrubAndStringify(message) {
|
|
35
|
+
// Scrub the whole object in case there are unexpected keys
|
|
36
|
+
const scrubbed = typesOfKeys(message);
|
|
37
|
+
// For these known/expected keys, we can either drill in (for contents)
|
|
38
|
+
// or just use the value as-is (since it's not personal info)
|
|
39
|
+
scrubbed.contents = message.contents && typesOfKeys(message.contents);
|
|
40
|
+
scrubbed.compatDetails = message.compatDetails;
|
|
41
|
+
scrubbed.type = message.type;
|
|
42
|
+
return JSON.stringify(scrubbed);
|
|
43
|
+
}
|
|
28
44
|
function withoutLocalOpMetadata(message) {
|
|
29
45
|
return {
|
|
30
46
|
...message,
|
|
31
47
|
localOpMetadata: undefined,
|
|
32
48
|
};
|
|
33
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the effective batch ID for a pending message.
|
|
52
|
+
* If the batch ID is already present in the message's op metadata, return it.
|
|
53
|
+
* Otherwise, generate a new batch ID using the client ID and batch start CSN.
|
|
54
|
+
* @param pendingMessage - The pending message
|
|
55
|
+
* @returns The effective batch ID
|
|
56
|
+
*/
|
|
57
|
+
function getEffectiveBatchId(pendingMessage) {
|
|
58
|
+
return ((0, metadata_js_1.asBatchMetadata)(pendingMessage.opMetadata)?.batchId ??
|
|
59
|
+
(0, index_js_1.generateBatchId)(pendingMessage.batchInfo.clientId, pendingMessage.batchInfo.batchStartCsn));
|
|
60
|
+
}
|
|
34
61
|
/**
|
|
35
62
|
* PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been
|
|
36
63
|
* acknowledged by the server. It also maintains the batch information for both automatically and manually flushed
|
|
@@ -102,8 +129,6 @@ class PendingStateManager {
|
|
|
102
129
|
this.initialMessages.clear();
|
|
103
130
|
this.pendingMessages.clear();
|
|
104
131
|
});
|
|
105
|
-
// Indicates whether we are processing a batch.
|
|
106
|
-
this.isProcessingBatch = false;
|
|
107
132
|
this.dispose = () => this.disposeOnce.value;
|
|
108
133
|
if (stashedLocalState?.pendingStates) {
|
|
109
134
|
this.initialMessages.push(...stashedLocalState.pendingStates);
|
|
@@ -136,8 +161,8 @@ class PendingStateManager {
|
|
|
136
161
|
content,
|
|
137
162
|
localOpMetadata,
|
|
138
163
|
opMetadata,
|
|
139
|
-
// Note: We only
|
|
140
|
-
|
|
164
|
+
// Note: We only will read this off the first message, but put it on all for simplicity
|
|
165
|
+
batchInfo: { clientId, batchStartCsn, length: batch.length },
|
|
141
166
|
};
|
|
142
167
|
this.pendingMessages.push(pendingMessage);
|
|
143
168
|
}
|
|
@@ -161,7 +186,15 @@ class PendingStateManager {
|
|
|
161
186
|
}
|
|
162
187
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
163
188
|
const nextMessage = this.initialMessages.shift();
|
|
189
|
+
// Nothing to apply if the message is an empty batch.
|
|
190
|
+
// We still need to track it for resubmission.
|
|
164
191
|
try {
|
|
192
|
+
if (isEmptyBatchPendingMessage(nextMessage)) {
|
|
193
|
+
nextMessage.localOpMetadata = { emptyBatch: true }; // equivalent to applyStashedOp for empty batch
|
|
194
|
+
patchbatchInfo(nextMessage); // Back compat
|
|
195
|
+
this.pendingMessages.push(nextMessage);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
165
198
|
// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
|
|
166
199
|
const localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);
|
|
167
200
|
if (!this.stateHandler.isAttached()) {
|
|
@@ -172,7 +205,7 @@ class PendingStateManager {
|
|
|
172
205
|
else {
|
|
173
206
|
nextMessage.localOpMetadata = localOpMetadata;
|
|
174
207
|
// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
|
|
175
|
-
|
|
208
|
+
patchbatchInfo(nextMessage); // Back compat
|
|
176
209
|
this.pendingMessages.push(nextMessage);
|
|
177
210
|
}
|
|
178
211
|
}
|
|
@@ -182,117 +215,113 @@ class PendingStateManager {
|
|
|
182
215
|
}
|
|
183
216
|
}
|
|
184
217
|
/**
|
|
185
|
-
* Processes
|
|
186
|
-
*
|
|
187
|
-
* @param batch - The batch
|
|
188
|
-
* @param
|
|
218
|
+
* Processes an inbound batch of messages - May be local or remote.
|
|
219
|
+
*
|
|
220
|
+
* @param batch - The inbound batch of messages to process. Could be local or remote.
|
|
221
|
+
* @param local - true if we submitted this batch and expect corresponding pending messages
|
|
222
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
223
|
+
*
|
|
224
|
+
* @remarks Closes the container if:
|
|
225
|
+
* - The batchStartCsn doesn't match for local batches
|
|
226
|
+
*/
|
|
227
|
+
processInboundBatch(batch, local) {
|
|
228
|
+
if (local) {
|
|
229
|
+
return this.processPendingLocalBatch(batch);
|
|
230
|
+
}
|
|
231
|
+
// No localOpMetadata for remote messages
|
|
232
|
+
return batch.messages.map((message) => ({ message }));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Processes the incoming batch from the server that was submitted by this client.
|
|
236
|
+
* It verifies that messages are received in the right order and that the batch information is correct.
|
|
237
|
+
* @param batch - The inbound batch (originating from this client) to correlate with the pending local state
|
|
238
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
189
239
|
*/
|
|
190
|
-
processPendingLocalBatch(batch
|
|
191
|
-
|
|
240
|
+
processPendingLocalBatch(batch) {
|
|
241
|
+
this.onLocalBatchBegin(batch);
|
|
242
|
+
// Empty batch
|
|
243
|
+
if (batch.messages.length === 0) {
|
|
244
|
+
(0, internal_1.assert)(batch.emptyBatchSequenceNumber !== undefined, 0x9fb /* Expected sequence number for empty batch */);
|
|
245
|
+
const localOpMetadata = this.processNextPendingMessage(batch.emptyBatchSequenceNumber);
|
|
246
|
+
(0, internal_1.assert)((0, metadata_js_1.asEmptyBatchLocalOpMetadata)(localOpMetadata)?.emptyBatch === true, 0xa20 /* Expected empty batch marker */);
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
return batch.messages.map((message) => ({
|
|
192
250
|
message,
|
|
193
|
-
localOpMetadata: this.
|
|
251
|
+
localOpMetadata: this.processNextPendingMessage(message.sequenceNumber, message),
|
|
194
252
|
}));
|
|
195
253
|
}
|
|
196
254
|
/**
|
|
197
|
-
* Processes
|
|
198
|
-
* the
|
|
199
|
-
* @param message - The message
|
|
200
|
-
* @
|
|
201
|
-
*
|
|
255
|
+
* Processes the pending local copy of message that's been ack'd by the server.
|
|
256
|
+
* @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.
|
|
257
|
+
* @param message - [optional] The entire incoming message, for comparing contents with the pending message for extra validation.
|
|
258
|
+
* @throws DataProcessingError if the pending message content doesn't match the incoming message content.
|
|
259
|
+
* @returns - The localOpMetadata of the next pending message, to be sent to whoever submitted the original message.
|
|
202
260
|
*/
|
|
203
|
-
|
|
204
|
-
// Get the next message from the pending queue. Verify a message exists.
|
|
261
|
+
processNextPendingMessage(sequenceNumber, message) {
|
|
205
262
|
const pendingMessage = this.pendingMessages.peekFront();
|
|
206
263
|
(0, internal_1.assert)(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
|
|
207
|
-
|
|
208
|
-
this.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);
|
|
209
|
-
pendingMessage.sequenceNumber = message.sequenceNumber;
|
|
264
|
+
pendingMessage.sequenceNumber = sequenceNumber;
|
|
210
265
|
this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
|
|
211
266
|
this.pendingMessages.shift();
|
|
212
|
-
|
|
213
|
-
//
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
228
|
-
* @param pendingMessage - The corresponding pendingMessage.
|
|
229
|
-
*/
|
|
230
|
-
maybeProcessBatchBegin(message, batchStartCsn, pendingMessage) {
|
|
231
|
-
if (!this.isProcessingBatch) {
|
|
232
|
-
// Expecting the start of a batch (maybe single-message).
|
|
233
|
-
if (pendingMessage.batchIdContext.batchStartCsn !== batchStartCsn) {
|
|
234
|
-
this.logger?.sendErrorEvent({
|
|
235
|
-
eventName: "BatchClientSequenceNumberMismatch",
|
|
267
|
+
// message is undefined in the Empty Batch case,
|
|
268
|
+
// because we don't have an incoming message to compare and pendingMessage is just a placeholder anyway.
|
|
269
|
+
if (message !== undefined) {
|
|
270
|
+
const messageContent = buildPendingMessageContent(message);
|
|
271
|
+
// Stringified content should match
|
|
272
|
+
if (pendingMessage.content !== messageContent) {
|
|
273
|
+
const pendingContentObj = JSON.parse(pendingMessage.content);
|
|
274
|
+
const incomingContentObj = JSON.parse(messageContent);
|
|
275
|
+
const contentsMatch = pendingContentObj.contents === incomingContentObj.contents ||
|
|
276
|
+
(pendingContentObj.contents !== undefined &&
|
|
277
|
+
incomingContentObj.contents !== undefined &&
|
|
278
|
+
JSON.stringify(pendingContentObj.contents) ===
|
|
279
|
+
JSON.stringify(incomingContentObj.contents));
|
|
280
|
+
this.logger.sendErrorEvent({
|
|
281
|
+
eventName: "unexpectedAckReceived",
|
|
236
282
|
details: {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
messageBatchMetadata: message.metadata?.batch,
|
|
241
|
-
pendingMessageBatchMetadata: pendingMessage.opMetadata?.batch,
|
|
283
|
+
pendingContentScrubbed: scrubAndStringify(pendingContentObj),
|
|
284
|
+
incomingContentScrubbed: scrubAndStringify(incomingContentObj),
|
|
285
|
+
contentsMatch,
|
|
242
286
|
},
|
|
243
|
-
messageDetails: (0, internal_2.extractSafePropertiesFromMessage)(message),
|
|
244
287
|
});
|
|
288
|
+
throw internal_2.DataProcessingError.create("pending local message content mismatch", "unexpectedAckReceived", message);
|
|
245
289
|
}
|
|
246
290
|
}
|
|
247
|
-
|
|
248
|
-
if (message.metadata?.batch) {
|
|
249
|
-
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
250
|
-
(0, internal_1.assert)(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined, 0x16b /* "The pending batch state indicates we are already processing a batch" */);
|
|
251
|
-
// Set the pending batch state indicating we have started processing a batch.
|
|
252
|
-
this.pendingBatchBeginMessage = message;
|
|
253
|
-
this.isProcessingBatch = true;
|
|
254
|
-
}
|
|
291
|
+
return pendingMessage.localOpMetadata;
|
|
255
292
|
}
|
|
256
293
|
/**
|
|
257
|
-
*
|
|
258
|
-
* @param message - The message that is being processed.
|
|
294
|
+
* Check if the incoming batch matches the batch info for the next pending message.
|
|
259
295
|
*/
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
pendingMessagesCount: this.pendingMessagesCount,
|
|
290
|
-
}));
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
// Clear the pending batch state now that we have processed the entire batch.
|
|
294
|
-
this.pendingBatchBeginMessage = undefined;
|
|
295
|
-
this.isProcessingBatch = false;
|
|
296
|
+
onLocalBatchBegin(batch) {
|
|
297
|
+
// Get the next message from the pending queue. Verify a message exists.
|
|
298
|
+
const pendingMessage = this.pendingMessages.peekFront();
|
|
299
|
+
(0, internal_1.assert)(pendingMessage !== undefined, 0xa21 /* No pending message found as we start processing this remote batch */);
|
|
300
|
+
// Note: This could be undefined if this batch became empty on resubmit.
|
|
301
|
+
// In this case the next pending message is an empty batch marker.
|
|
302
|
+
// Empty batches became empty on Resubmit, and submit them and track them in case
|
|
303
|
+
// a different fork of this container also submitted the same batch (and it may not be empty for that fork).
|
|
304
|
+
const firstMessage = batch.messages.length > 0 ? batch.messages[0] : undefined;
|
|
305
|
+
const expectedPendingBatchLength = batch.messages.length === 0 ? 1 : batch.messages.length;
|
|
306
|
+
// We expect the incoming batch to be of the same length, starting at the same clientSequenceNumber,
|
|
307
|
+
// as the batch we originally submitted.
|
|
308
|
+
// We have another later check to compare the message contents, which we'd expect to fail if this check does,
|
|
309
|
+
// so we don't throw here, merely log. In a later release this check may replace that one.
|
|
310
|
+
if (pendingMessage.batchInfo.batchStartCsn !== batch.batchStartCsn ||
|
|
311
|
+
(pendingMessage.batchInfo.length >= 0 && // -1 length is back compat and isn't suitable for this check
|
|
312
|
+
pendingMessage.batchInfo.length !== expectedPendingBatchLength)) {
|
|
313
|
+
this.logger?.sendErrorEvent({
|
|
314
|
+
eventName: "BatchInfoMismatch",
|
|
315
|
+
details: {
|
|
316
|
+
pendingBatchCsn: pendingMessage.batchInfo.batchStartCsn,
|
|
317
|
+
batchStartCsn: batch.batchStartCsn,
|
|
318
|
+
pendingBatchLength: pendingMessage.batchInfo.length,
|
|
319
|
+
batchLength: batch.messages.length,
|
|
320
|
+
pendingMessageBatchMetadata: (0, metadata_js_1.asBatchMetadata)(pendingMessage.opMetadata)?.batch,
|
|
321
|
+
messageBatchMetadata: (0, metadata_js_1.asBatchMetadata)(firstMessage?.metadata)?.batch,
|
|
322
|
+
},
|
|
323
|
+
messageDetails: firstMessage && (0, internal_2.extractSafePropertiesFromMessage)(firstMessage),
|
|
324
|
+
});
|
|
296
325
|
}
|
|
297
326
|
}
|
|
298
327
|
/**
|
|
@@ -318,10 +347,12 @@ class PendingStateManager {
|
|
|
318
347
|
const batchMetadataFlag = (0, metadata_js_1.asBatchMetadata)(pendingMessage.opMetadata)?.batch;
|
|
319
348
|
(0, internal_1.assert)(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
|
|
320
349
|
// The next message starts a batch (possibly single-message), and we'll need its batchId.
|
|
321
|
-
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
350
|
+
const batchId = getEffectiveBatchId(pendingMessage);
|
|
351
|
+
// Resubmit no messages, with the batchId. Will result in another empty batch marker.
|
|
352
|
+
if ((0, metadata_js_1.asEmptyBatchLocalOpMetadata)(pendingMessage.localOpMetadata)?.emptyBatch === true) {
|
|
353
|
+
this.stateHandler.reSubmitBatch([], batchId);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
325
356
|
/**
|
|
326
357
|
* We must preserve the distinct batches on resubmit.
|
|
327
358
|
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
@@ -374,12 +405,12 @@ class PendingStateManager {
|
|
|
374
405
|
}
|
|
375
406
|
}
|
|
376
407
|
exports.PendingStateManager = PendingStateManager;
|
|
377
|
-
/** For back-compat if trying to apply stashed ops that pre-date
|
|
378
|
-
function
|
|
379
|
-
const
|
|
380
|
-
if (
|
|
408
|
+
/** For back-compat if trying to apply stashed ops that pre-date batchInfo */
|
|
409
|
+
function patchbatchInfo(message) {
|
|
410
|
+
const batchInfo = message.batchInfo;
|
|
411
|
+
if (batchInfo === undefined) {
|
|
381
412
|
// Using uuid guarantees uniqueness, retaining existing behavior
|
|
382
|
-
message.
|
|
413
|
+
message.batchInfo = { clientId: (0, uuid_1.v4)(), batchStartCsn: -1, length: -1 };
|
|
383
414
|
}
|
|
384
415
|
}
|
|
385
416
|
//# sourceMappingURL=pendingStateManager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert, Lazy } from \"@fluidframework/core-utils/internal\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tITelemetryLoggerExt,\n\tDataProcessingError,\n\tLoggingError,\n\textractSafePropertiesFromMessage,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport Deque from \"double-ended-queue\";\nimport { v4 as uuid } from \"uuid\";\n\nimport { type InboundSequencedContainerRuntimeMessage } from \"./messageTypes.js\";\nimport { asBatchMetadata, IBatchMetadata } from \"./metadata.js\";\nimport { BatchId, BatchMessage, generateBatchId } from \"./opLifecycle/index.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n *\n * @remarks This is the current serialization format for pending local state when a Container is serialized.\n */\nexport interface IPendingMessage {\n\ttype: \"message\";\n\treferenceSequenceNumber: number;\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n\tsequenceNumber?: number;\n\t/** Info needed to compute the batchId on reconnect */\n\tbatchIdContext: {\n\t\t/** The Batch's original clientId, from when it was first flushed to be submitted */\n\t\tclientId: string;\n\t\t/**\n\t\t * The Batch's original clientSequenceNumber, from when it was first flushed to be submitted\n\t\t *\t@remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)\n\t\t */\n\t\tbatchStartCsn: number;\n\t};\n}\n\ntype Patch<T, U> = U & Omit<T, keyof U>;\n\n/** First version of the type (pre-dates batchIdContext) */\ntype IPendingMessageV0 = Patch<IPendingMessage, { batchIdContext?: undefined }>;\n\n/**\n * Union of all supported schemas for when applying stashed ops\n *\n * @remarks When the format changes, this type should update to reflect all possible schemas.\n */\ntype IPendingMessageFromStash = IPendingMessageV0 | IPendingMessage;\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingMessage[];\n}\n\n/** Info needed to replay/resubmit a pending message */\nexport type PendingMessageResubmitData = Pick<\n\tIPendingMessage,\n\t\"content\" | \"localOpMetadata\" | \"opMetadata\"\n>;\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tclose(error?: ICriticalContainerError): void;\n\tapplyStashedOp(content: string): Promise<unknown>;\n\treSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;\n\tisActiveConnection: () => boolean;\n\tisAttached: () => boolean;\n}\n\n/** Union of keys of T */\ntype KeysOfUnion<T extends object> = T extends T ? keyof T : never;\n/** *Partial* type all possible combinations of properties and values of union T.\n * This loosens typing allowing access to all possible properties without\n * narrowing.\n */\ntype AnyComboFromUnion<T extends object> = { [P in KeysOfUnion<T>]?: T[P] };\n\nfunction buildPendingMessageContent(\n\t// AnyComboFromUnion is needed need to gain access to compatDetails that\n\t// is only defined for some cases.\n\tmessage: AnyComboFromUnion<InboundSequencedContainerRuntimeMessage>,\n): string {\n\t// IMPORTANT: Order matters here, this must match the order of the properties used\n\t// when submitting the message.\n\tconst { type, contents, compatDetails } = message;\n\t// Any properties that are not defined, won't be emitted by stringify.\n\treturn JSON.stringify({ type, contents, compatDetails });\n}\n\nfunction withoutLocalOpMetadata(message: IPendingMessage): IPendingMessage {\n\treturn {\n\t\t...message,\n\t\tlocalOpMetadata: undefined,\n\t};\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n\t/** Messages that will need to be resubmitted if not ack'd before the next reconnection */\n\tprivate readonly pendingMessages = new Deque<IPendingMessage>();\n\t/** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */\n\tprivate readonly initialMessages = new Deque<IPendingMessageFromStash>();\n\n\t/**\n\t * Sequenced local ops that are saved when stashing since pending ops may depend on them\n\t */\n\tprivate savedOps: IPendingMessage[] = [];\n\n\t/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */\n\tprivate negativeCounter: number = -1;\n\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\t// Indicates whether we are processing a batch.\n\tprivate isProcessingBatch: boolean = false;\n\n\t// This stores the first message in the batch that we are processing. This is used to verify that we get\n\t// the correct batch metadata.\n\tprivate pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n\t/** Used to ensure we don't replay ops on the same connection twice */\n\tprivate clientIdFromLastReplay: string | undefined;\n\n\t/**\n\t * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with\n\t * 'hasPendingMessages'.\n\t */\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length + this.initialMessages.length;\n\t}\n\n\t/**\n\t * The minimumPendingMessageSequenceNumber is the minimum of the first pending message and the first initial message.\n\t *\n\t * We need this so that we can properly keep local data and maintain the correct sequence window.\n\t */\n\tpublic get minimumPendingMessageSequenceNumber(): number | undefined {\n\t\treturn this.pendingMessages.peekFront()?.referenceSequenceNumber;\n\t}\n\n\t/**\n\t * Called to check if there are any pending messages in the pending message queue.\n\t * @returns A boolean indicating whether there are messages or not.\n\t */\n\tpublic hasPendingMessages(): boolean {\n\t\treturn this.pendingMessagesCount !== 0;\n\t}\n\n\tpublic getLocalState(snapshotSequenceNumber?: number): IPendingLocalState {\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x2e9 /* \"Must call getLocalState() after applying initial states\" */,\n\t\t);\n\t\t// Using snapshot sequence number to filter ops older than our latest snapshot.\n\t\t// Such ops should not be declared in pending/stashed state. Snapshot seq num will not\n\t\t// be available when the container is not attached. Therefore, no filtering is needed.\n\t\tconst newSavedOps = [...this.savedOps].filter((message) => {\n\t\t\tassert(\n\t\t\t\tmessage.sequenceNumber !== undefined,\n\t\t\t\t0x97c /* saved op should already have a sequence number */,\n\t\t\t);\n\t\t\treturn message.sequenceNumber > (snapshotSequenceNumber ?? 0);\n\t\t});\n\t\tthis.pendingMessages.toArray().forEach((message) => {\n\t\t\tif (\n\t\t\t\tsnapshotSequenceNumber !== undefined &&\n\t\t\t\tmessage.referenceSequenceNumber < snapshotSequenceNumber\n\t\t\t) {\n\t\t\t\tthrow new LoggingError(\"trying to stash ops older than our latest snapshot\");\n\t\t\t}\n\t\t});\n\t\treturn {\n\t\t\tpendingStates: [\n\t\t\t\t...newSavedOps,\n\t\t\t\t...this.pendingMessages.toArray().map(withoutLocalOpMetadata),\n\t\t\t],\n\t\t};\n\t}\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tstashedLocalState: IPendingLocalState | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t) {\n\t\tif (stashedLocalState?.pendingStates) {\n\t\t\tthis.initialMessages.push(...stashedLocalState.pendingStates);\n\t\t}\n\t}\n\n\tpublic get disposed() {\n\t\treturn this.disposeOnce.evaluated;\n\t}\n\tpublic readonly dispose = () => this.disposeOnce.value;\n\n\t/**\n\t * The given batch has been flushed, and needs to be tracked locally until the corresponding\n\t * acks are processed, to ensure it is successfully sent.\n\t * @param batch - The batch that was flushed\n\t * @param clientSequenceNumber - The CSN of the first message in the batch,\n\t * or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)\n\t */\n\tpublic onFlushBatch(batch: BatchMessage[], clientSequenceNumber: number | undefined) {\n\t\t// If we're connected this is the client of the current connection,\n\t\t// otherwise it's the clientId that just disconnected\n\t\t// It's only undefined if we've NEVER connected. This is a tight corner case and we can\n\t\t// simply make up a unique ID in this case.\n\t\tconst clientId = this.stateHandler.clientId() ?? uuid();\n\n\t\t// If the batch was not yet sent, we need to assign a unique batchStartCsn\n\t\t// Use a negative number to distinguish these from real CSNs\n\t\tconst batchStartCsn = clientSequenceNumber ?? this.negativeCounter--;\n\n\t\tfor (const message of batch) {\n\t\t\tconst {\n\t\t\t\tcontents: content = \"\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\tmetadata: opMetadata,\n\t\t\t} = message;\n\t\t\tconst pendingMessage: IPendingMessage = {\n\t\t\t\ttype: \"message\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tcontent,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\topMetadata,\n\t\t\t\t// Note: We only need this on the first message.\n\t\t\t\tbatchIdContext: { clientId, batchStartCsn },\n\t\t\t};\n\t\t\tthis.pendingMessages.push(pendingMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n\t * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n\t */\n\tpublic async applyStashedOpsAt(seqNum?: number) {\n\t\t// apply stashed ops at sequence number\n\t\twhile (!this.initialMessages.isEmpty()) {\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tconst peekMessage = this.initialMessages.peekFront()!;\n\t\t\t\tif (peekMessage.referenceSequenceNumber > seqNum) {\n\t\t\t\t\tbreak; // nothing left to do at this sequence number\n\t\t\t\t}\n\t\t\t\tif (peekMessage.referenceSequenceNumber < seqNum) {\n\t\t\t\t\tthrow new Error(\"loaded from snapshot too recent to apply stashed ops\");\n\t\t\t\t}\n\t\t\t}\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.shift()!;\n\t\t\ttry {\n\t\t\t\t// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n\t\t\t\tconst localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);\n\t\t\t\tif (!this.stateHandler.isAttached()) {\n\t\t\t\t\tif (localOpMetadata !== undefined) {\n\t\t\t\t\t\tthrow new Error(\"Local Op Metadata must be undefined when not attached\");\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t\t\tpatchBatchIdContext(nextMessage); // Back compat\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes the incoming batch from the server. It verifies that messages are received in the right order and\n\t * that the batch information is correct.\n\t * @param batch - The batch that is being processed.\n\t * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch\n\t */\n\tpublic processPendingLocalBatch(\n\t\tbatch: InboundSequencedContainerRuntimeMessage[],\n\t\tbatchStartCsn: number,\n\t): {\n\t\tmessage: InboundSequencedContainerRuntimeMessage;\n\t\tlocalOpMetadata: unknown;\n\t}[] {\n\t\treturn batch.map((message) => ({\n\t\t\tmessage,\n\t\t\tlocalOpMetadata: this.processPendingLocalMessage(message, batchStartCsn),\n\t\t}));\n\t}\n\n\t/**\n\t * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n\t * the batch information was preserved for batch messages.\n\t * @param message - The message that got ack'd and needs to be processed.\n\t * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)\n\t * (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)\n\t */\n\tprivate processPendingLocalMessage(\n\t\tmessage: InboundSequencedContainerRuntimeMessage,\n\t\tbatchStartCsn: number,\n\t): unknown {\n\t\t// Get the next message from the pending queue. Verify a message exists.\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0x169 /* \"No pending message found for this remote message\" */,\n\t\t);\n\n\t\t// This may be the start of a batch.\n\t\tthis.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);\n\n\t\tpendingMessage.sequenceNumber = message.sequenceNumber;\n\t\tthis.savedOps.push(withoutLocalOpMetadata(pendingMessage));\n\n\t\tthis.pendingMessages.shift();\n\n\t\tconst messageContent = buildPendingMessageContent(message);\n\n\t\t// Stringified content should match\n\t\tif (pendingMessage.content !== messageContent) {\n\t\t\tthis.stateHandler.close(\n\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\"pending local message content mismatch\",\n\t\t\t\t\t\"unexpectedAckReceived\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\texpectedMessageType: JSON.parse(pendingMessage.content).type,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Post-processing part - If we are processing a batch then this could be the last message in the batch.\n\t\tthis.maybeProcessBatchEnd(message);\n\n\t\treturn pendingMessage.localOpMetadata;\n\t}\n\n\t/**\n\t * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n\t * @param message - The message that is being processed.\n\t * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)\n\t * @param pendingMessage - The corresponding pendingMessage.\n\t */\n\tprivate maybeProcessBatchBegin(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tbatchStartCsn: number,\n\t\tpendingMessage: IPendingMessage,\n\t) {\n\t\tif (!this.isProcessingBatch) {\n\t\t\t// Expecting the start of a batch (maybe single-message).\n\t\t\tif (pendingMessage.batchIdContext.batchStartCsn !== batchStartCsn) {\n\t\t\t\tthis.logger?.sendErrorEvent({\n\t\t\t\t\teventName: \"BatchClientSequenceNumberMismatch\",\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tprocessingBatch: !!this.pendingBatchBeginMessage,\n\t\t\t\t\t\tpendingBatchCsn: pendingMessage.batchIdContext.batchStartCsn,\n\t\t\t\t\t\tbatchStartCsn,\n\t\t\t\t\t\tmessageBatchMetadata: (message.metadata as any)?.batch,\n\t\t\t\t\t\tpendingMessageBatchMetadata: (pendingMessage.opMetadata as any)?.batch,\n\t\t\t\t\t},\n\t\t\t\t\tmessageDetails: extractSafePropertiesFromMessage(message),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// This message is the first in a batch if the \"batch\" property on the metadata is set to true\n\t\tif ((message.metadata as IBatchMetadata | undefined)?.batch) {\n\t\t\t// We should not already be processing a batch and there should be no pending batch begin message.\n\t\t\tassert(\n\t\t\t\t!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n\t\t\t\t0x16b /* \"The pending batch state indicates we are already processing a batch\" */,\n\t\t\t);\n\n\t\t\t// Set the pending batch state indicating we have started processing a batch.\n\t\t\tthis.pendingBatchBeginMessage = message;\n\t\t\tthis.isProcessingBatch = true;\n\t\t}\n\t}\n\n\t/**\n\t * This message could be the last message in batch. If so, clear batch state since the batch is complete.\n\t * @param message - The message that is being processed.\n\t */\n\tprivate maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n\t\tif (!this.isProcessingBatch) {\n\t\t\treturn;\n\t\t}\n\n\t\t// There should be a pending batch begin message.\n\t\tassert(\n\t\t\tthis.pendingBatchBeginMessage !== undefined,\n\t\t\t0x16d /* \"There is no pending batch begin message\" */,\n\t\t);\n\n\t\tconst batchEndMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\t\tif (this.pendingMessages.isEmpty() || batchEndMetadata === false) {\n\t\t\t// Get the batch begin metadata from the first message in the batch.\n\t\t\tconst batchBeginMetadata = (\n\t\t\t\tthis.pendingBatchBeginMessage.metadata as IBatchMetadata | undefined\n\t\t\t)?.batch;\n\n\t\t\t// There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n\t\t\t// are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n\t\t\tif (this.pendingBatchBeginMessage === message) {\n\t\t\t\tassert(\n\t\t\t\t\tbatchBeginMetadata === undefined,\n\t\t\t\t\t0x16e /* \"Batch with single message should not have batch metadata\" */,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tif (batchBeginMetadata !== true || batchEndMetadata !== false) {\n\t\t\t\t\tthis.stateHandler.close(\n\t\t\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\t\t\"Pending batch inconsistency\", // Formerly known as asserts 0x16f and 0x170\n\t\t\t\t\t\t\t\"processPendingLocalMessage\",\n\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\t\t\t\t\tthis.pendingBatchBeginMessage.clientId === null\n\t\t\t\t\t\t\t\t\t\t? \"null\"\n\t\t\t\t\t\t\t\t\t\t: this.pendingBatchBeginMessage.clientId,\n\t\t\t\t\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t\t\t\t\t\thasBatchStart: batchBeginMetadata === true,\n\t\t\t\t\t\t\t\thasBatchEnd: batchEndMetadata === false,\n\t\t\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t\t\t\tpendingMessagesCount: this.pendingMessagesCount,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Clear the pending batch state now that we have processed the entire batch.\n\t\t\tthis.pendingBatchBeginMessage = undefined;\n\t\t\tthis.isProcessingBatch = false;\n\t\t}\n\t}\n\n\t/**\n\t * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n\t * states in its queue. This includes triggering resubmission of unacked ops.\n\t * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)\n\t */\n\tpublic replayPendingStates() {\n\t\tassert(\n\t\t\tthis.stateHandler.connected(),\n\t\t\t0x172 /* \"The connection state is not consistent with the runtime\" */,\n\t\t);\n\n\t\t// This assert suggests we are about to send same ops twice, which will result in data loss.\n\t\tassert(\n\t\t\tthis.clientIdFromLastReplay !== this.stateHandler.clientId(),\n\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t);\n\t\tthis.clientIdFromLastReplay = this.stateHandler.clientId();\n\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x174 /* \"initial states should be empty before replaying pending\" */,\n\t\t);\n\n\t\tconst initialPendingMessagesCount = this.pendingMessages.length;\n\t\tlet remainingPendingMessagesCount = this.pendingMessages.length;\n\n\t\t// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n\t\t// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n\t\t// which must not be replayed.\n\t\twhile (remainingPendingMessagesCount > 0) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tlet pendingMessage = this.pendingMessages.shift()!;\n\t\t\tremainingPendingMessagesCount--;\n\n\t\t\tconst batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;\n\t\t\tassert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);\n\n\t\t\t// The next message starts a batch (possibly single-message), and we'll need its batchId.\n\t\t\t// We'll find batchId on this message if it was previously generated.\n\t\t\t// Otherwise, generate it now - this is the first time resubmitting this batch.\n\t\t\tconst batchId =\n\t\t\t\tasBatchMetadata(pendingMessage.opMetadata)?.batchId ??\n\t\t\t\tgenerateBatchId(\n\t\t\t\t\tpendingMessage.batchIdContext.clientId,\n\t\t\t\t\tpendingMessage.batchIdContext.batchStartCsn,\n\t\t\t\t);\n\n\t\t\t/**\n\t\t\t * We must preserve the distinct batches on resubmit.\n\t\t\t * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n\t\t\t * either receive the whole batch ack or nothing at all. @see ScheduleManager for how this works.\n\t\t\t */\n\t\t\tif (batchMetadataFlag === undefined) {\n\t\t\t\t// Single-message batch\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tbatchId,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// else: batchMetadataFlag === true (It's a typical multi-message batch)\n\n\t\t\tassert(\n\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t);\n\n\t\t\tconst batch: PendingMessageResubmitData[] = [];\n\n\t\t\t// check is >= because batch end may be last pending message\n\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\tbatch.push({\n\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t});\n\n\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\tassert(\n\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.stateHandler.reSubmitBatch(batch, batchId);\n\t\t}\n\n\t\t// pending ops should no longer depend on previous sequenced local ops after resubmit\n\t\tthis.savedOps = [];\n\n\t\t// We replayPendingStates on read connections too - we expect these to get nack'd though, and to then reconnect\n\t\t// on a write connection and replay again. This filters out the replay that happens on the read connection so\n\t\t// we only see the replays on write connections (that have a chance to go through).\n\t\tif (this.stateHandler.isActiveConnection()) {\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"PendingStatesReplayed\",\n\t\t\t\tcount: initialPendingMessagesCount,\n\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t});\n\t\t}\n\t}\n}\n\n/** For back-compat if trying to apply stashed ops that pre-date batchIdContext */\nfunction patchBatchIdContext(\n\tmessage: IPendingMessageFromStash,\n): asserts message is IPendingMessage {\n\tconst batchIdContext: IPendingMessageFromStash[\"batchIdContext\"] = message.batchIdContext;\n\tif (batchIdContext === undefined) {\n\t\t// Using uuid guarantees uniqueness, retaining existing behavior\n\t\tmessage.batchIdContext = { clientId: uuid(), batchStartCsn: -1 };\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert, Lazy } from \"@fluidframework/core-utils/internal\";\nimport {\n\tITelemetryLoggerExt,\n\tDataProcessingError,\n\tLoggingError,\n\textractSafePropertiesFromMessage,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport Deque from \"double-ended-queue\";\nimport { v4 as uuid } from \"uuid\";\n\nimport {\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype LocalContainerRuntimeMessage,\n} from \"./messageTypes.js\";\nimport { asBatchMetadata, asEmptyBatchLocalOpMetadata } from \"./metadata.js\";\nimport { BatchId, BatchMessage, generateBatchId, InboundBatch } from \"./opLifecycle/index.js\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n *\n * @remarks This is the current serialization format for pending local state when a Container is serialized.\n */\nexport interface IPendingMessage {\n\ttype: \"message\";\n\treferenceSequenceNumber: number;\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n\tsequenceNumber?: number;\n\t/** Info about the batch this pending message belongs to, for validation and for computing the batchId on reconnect */\n\tbatchInfo: {\n\t\t/** The Batch's original clientId, from when it was first flushed to be submitted */\n\t\tclientId: string;\n\t\t/**\n\t\t * The Batch's original clientSequenceNumber, from when it was first flushed to be submitted\n\t\t *\t@remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)\n\t\t */\n\t\tbatchStartCsn: number;\n\t\t/** length of the batch (how many runtime messages here) */\n\t\tlength: number;\n\t};\n}\n\ntype Patch<T, U> = U & Omit<T, keyof U>;\n\n/** First version of the type (pre-dates batchInfo) */\ntype IPendingMessageV0 = Patch<IPendingMessage, { batchInfo?: undefined }>;\n\n/**\n * Union of all supported schemas for when applying stashed ops\n *\n * @remarks When the format changes, this type should update to reflect all possible schemas.\n */\ntype IPendingMessageFromStash = IPendingMessageV0 | IPendingMessage;\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingMessage[];\n}\n\n/** Info needed to replay/resubmit a pending message */\nexport type PendingMessageResubmitData = Pick<\n\tIPendingMessage,\n\t\"content\" | \"localOpMetadata\" | \"opMetadata\"\n>;\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tapplyStashedOp(content: string): Promise<unknown>;\n\treSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;\n\tisActiveConnection: () => boolean;\n\tisAttached: () => boolean;\n}\n\nfunction isEmptyBatchPendingMessage(message: IPendingMessageFromStash): boolean {\n\tconst content = JSON.parse(message.content);\n\treturn content.type === \"groupedBatch\" && content.contents?.length === 0;\n}\n\nfunction buildPendingMessageContent(message: InboundSequencedContainerRuntimeMessage): string {\n\t// IMPORTANT: Order matters here, this must match the order of the properties used\n\t// when submitting the message.\n\tconst { type, contents, compatDetails }: InboundContainerRuntimeMessage = message;\n\t// Any properties that are not defined, won't be emitted by stringify.\n\treturn JSON.stringify({ type, contents, compatDetails });\n}\n\nfunction typesOfKeys<T extends object>(obj: T): Record<keyof T, string> {\n\treturn Object.keys(obj).reduce((acc, key) => {\n\t\tacc[key] = typeof obj[key];\n\t\treturn acc;\n\t}, {}) as Record<keyof T, string>;\n}\n\nfunction scrubAndStringify(\n\tmessage: InboundContainerRuntimeMessage | LocalContainerRuntimeMessage,\n): string {\n\t// Scrub the whole object in case there are unexpected keys\n\tconst scrubbed: Record<string, unknown> = typesOfKeys(message);\n\n\t// For these known/expected keys, we can either drill in (for contents)\n\t// or just use the value as-is (since it's not personal info)\n\tscrubbed.contents = message.contents && typesOfKeys(message.contents);\n\tscrubbed.compatDetails = message.compatDetails;\n\tscrubbed.type = message.type;\n\n\treturn JSON.stringify(scrubbed);\n}\n\nfunction withoutLocalOpMetadata(message: IPendingMessage): IPendingMessage {\n\treturn {\n\t\t...message,\n\t\tlocalOpMetadata: undefined,\n\t};\n}\n\n/**\n * Get the effective batch ID for a pending message.\n * If the batch ID is already present in the message's op metadata, return it.\n * Otherwise, generate a new batch ID using the client ID and batch start CSN.\n * @param pendingMessage - The pending message\n * @returns The effective batch ID\n */\nfunction getEffectiveBatchId(pendingMessage: IPendingMessage): string {\n\treturn (\n\t\tasBatchMetadata(pendingMessage.opMetadata)?.batchId ??\n\t\tgenerateBatchId(pendingMessage.batchInfo.clientId, pendingMessage.batchInfo.batchStartCsn)\n\t);\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n\t/** Messages that will need to be resubmitted if not ack'd before the next reconnection */\n\tprivate readonly pendingMessages = new Deque<IPendingMessage>();\n\t/** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */\n\tprivate readonly initialMessages = new Deque<IPendingMessageFromStash>();\n\n\t/**\n\t * Sequenced local ops that are saved when stashing since pending ops may depend on them\n\t */\n\tprivate savedOps: IPendingMessage[] = [];\n\n\t/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */\n\tprivate negativeCounter: number = -1;\n\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\t/** Used to ensure we don't replay ops on the same connection twice */\n\tprivate clientIdFromLastReplay: string | undefined;\n\n\t/**\n\t * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with\n\t * 'hasPendingMessages'.\n\t */\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length + this.initialMessages.length;\n\t}\n\n\t/**\n\t * The minimumPendingMessageSequenceNumber is the minimum of the first pending message and the first initial message.\n\t *\n\t * We need this so that we can properly keep local data and maintain the correct sequence window.\n\t */\n\tpublic get minimumPendingMessageSequenceNumber(): number | undefined {\n\t\treturn this.pendingMessages.peekFront()?.referenceSequenceNumber;\n\t}\n\n\t/**\n\t * Called to check if there are any pending messages in the pending message queue.\n\t * @returns A boolean indicating whether there are messages or not.\n\t */\n\tpublic hasPendingMessages(): boolean {\n\t\treturn this.pendingMessagesCount !== 0;\n\t}\n\n\tpublic getLocalState(snapshotSequenceNumber?: number): IPendingLocalState {\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x2e9 /* \"Must call getLocalState() after applying initial states\" */,\n\t\t);\n\t\t// Using snapshot sequence number to filter ops older than our latest snapshot.\n\t\t// Such ops should not be declared in pending/stashed state. Snapshot seq num will not\n\t\t// be available when the container is not attached. Therefore, no filtering is needed.\n\t\tconst newSavedOps = [...this.savedOps].filter((message) => {\n\t\t\tassert(\n\t\t\t\tmessage.sequenceNumber !== undefined,\n\t\t\t\t0x97c /* saved op should already have a sequence number */,\n\t\t\t);\n\t\t\treturn message.sequenceNumber > (snapshotSequenceNumber ?? 0);\n\t\t});\n\t\tthis.pendingMessages.toArray().forEach((message) => {\n\t\t\tif (\n\t\t\t\tsnapshotSequenceNumber !== undefined &&\n\t\t\t\tmessage.referenceSequenceNumber < snapshotSequenceNumber\n\t\t\t) {\n\t\t\t\tthrow new LoggingError(\"trying to stash ops older than our latest snapshot\");\n\t\t\t}\n\t\t});\n\t\treturn {\n\t\t\tpendingStates: [\n\t\t\t\t...newSavedOps,\n\t\t\t\t...this.pendingMessages.toArray().map(withoutLocalOpMetadata),\n\t\t\t],\n\t\t};\n\t}\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tstashedLocalState: IPendingLocalState | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t) {\n\t\tif (stashedLocalState?.pendingStates) {\n\t\t\tthis.initialMessages.push(...stashedLocalState.pendingStates);\n\t\t}\n\t}\n\n\tpublic get disposed() {\n\t\treturn this.disposeOnce.evaluated;\n\t}\n\tpublic readonly dispose = () => this.disposeOnce.value;\n\n\t/**\n\t * The given batch has been flushed, and needs to be tracked locally until the corresponding\n\t * acks are processed, to ensure it is successfully sent.\n\t * @param batch - The batch that was flushed\n\t * @param clientSequenceNumber - The CSN of the first message in the batch,\n\t * or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)\n\t */\n\tpublic onFlushBatch(batch: BatchMessage[], clientSequenceNumber: number | undefined) {\n\t\t// If we're connected this is the client of the current connection,\n\t\t// otherwise it's the clientId that just disconnected\n\t\t// It's only undefined if we've NEVER connected. This is a tight corner case and we can\n\t\t// simply make up a unique ID in this case.\n\t\tconst clientId = this.stateHandler.clientId() ?? uuid();\n\n\t\t// If the batch was not yet sent, we need to assign a unique batchStartCsn\n\t\t// Use a negative number to distinguish these from real CSNs\n\t\tconst batchStartCsn = clientSequenceNumber ?? this.negativeCounter--;\n\n\t\tfor (const message of batch) {\n\t\t\tconst {\n\t\t\t\tcontents: content = \"\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\tmetadata: opMetadata,\n\t\t\t} = message;\n\t\t\tconst pendingMessage: IPendingMessage = {\n\t\t\t\ttype: \"message\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tcontent,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\topMetadata,\n\t\t\t\t// Note: We only will read this off the first message, but put it on all for simplicity\n\t\t\t\tbatchInfo: { clientId, batchStartCsn, length: batch.length },\n\t\t\t};\n\t\t\tthis.pendingMessages.push(pendingMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n\t * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n\t */\n\tpublic async applyStashedOpsAt(seqNum?: number) {\n\t\t// apply stashed ops at sequence number\n\t\twhile (!this.initialMessages.isEmpty()) {\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tconst peekMessage = this.initialMessages.peekFront()!;\n\t\t\t\tif (peekMessage.referenceSequenceNumber > seqNum) {\n\t\t\t\t\tbreak; // nothing left to do at this sequence number\n\t\t\t\t}\n\t\t\t\tif (peekMessage.referenceSequenceNumber < seqNum) {\n\t\t\t\t\tthrow new Error(\"loaded from snapshot too recent to apply stashed ops\");\n\t\t\t\t}\n\t\t\t}\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.shift()!;\n\t\t\t// Nothing to apply if the message is an empty batch.\n\t\t\t// We still need to track it for resubmission.\n\t\t\ttry {\n\t\t\t\tif (isEmptyBatchPendingMessage(nextMessage)) {\n\t\t\t\t\tnextMessage.localOpMetadata = { emptyBatch: true }; // equivalent to applyStashedOp for empty batch\n\t\t\t\t\tpatchbatchInfo(nextMessage); // Back compat\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n\t\t\t\tconst localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);\n\t\t\t\tif (!this.stateHandler.isAttached()) {\n\t\t\t\t\tif (localOpMetadata !== undefined) {\n\t\t\t\t\t\tthrow new Error(\"Local Op Metadata must be undefined when not attached\");\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t\t\tpatchbatchInfo(nextMessage); // Back compat\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes an inbound batch of messages - May be local or remote.\n\t *\n\t * @param batch - The inbound batch of messages to process. Could be local or remote.\n\t * @param local - true if we submitted this batch and expect corresponding pending messages\n\t * @returns The inbound batch's messages with localOpMetadata \"zipped\" in.\n\t *\n\t * @remarks Closes the container if:\n\t * - The batchStartCsn doesn't match for local batches\n\t */\n\tpublic processInboundBatch(\n\t\tbatch: InboundBatch,\n\t\tlocal: boolean,\n\t): {\n\t\tmessage: InboundSequencedContainerRuntimeMessage;\n\t\tlocalOpMetadata?: unknown;\n\t}[] {\n\t\tif (local) {\n\t\t\treturn this.processPendingLocalBatch(batch);\n\t\t}\n\n\t\t// No localOpMetadata for remote messages\n\t\treturn batch.messages.map((message) => ({ message }));\n\t}\n\n\t/**\n\t * Processes the incoming batch from the server that was submitted by this client.\n\t * It verifies that messages are received in the right order and that the batch information is correct.\n\t * @param batch - The inbound batch (originating from this client) to correlate with the pending local state\n\t * @returns The inbound batch's messages with localOpMetadata \"zipped\" in.\n\t */\n\tprivate processPendingLocalBatch(batch: InboundBatch): {\n\t\tmessage: InboundSequencedContainerRuntimeMessage;\n\t\tlocalOpMetadata: unknown;\n\t}[] {\n\t\tthis.onLocalBatchBegin(batch);\n\n\t\t// Empty batch\n\t\tif (batch.messages.length === 0) {\n\t\t\tassert(\n\t\t\t\tbatch.emptyBatchSequenceNumber !== undefined,\n\t\t\t\t0x9fb /* Expected sequence number for empty batch */,\n\t\t\t);\n\t\t\tconst localOpMetadata = this.processNextPendingMessage(batch.emptyBatchSequenceNumber);\n\t\t\tassert(\n\t\t\t\tasEmptyBatchLocalOpMetadata(localOpMetadata)?.emptyBatch === true,\n\t\t\t\t0xa20 /* Expected empty batch marker */,\n\t\t\t);\n\t\t\treturn [];\n\t\t}\n\n\t\treturn batch.messages.map((message) => ({\n\t\t\tmessage,\n\t\t\tlocalOpMetadata: this.processNextPendingMessage(message.sequenceNumber, message),\n\t\t}));\n\t}\n\n\t/**\n\t * Processes the pending local copy of message that's been ack'd by the server.\n\t * @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.\n\t * @param message - [optional] The entire incoming message, for comparing contents with the pending message for extra validation.\n\t * @throws DataProcessingError if the pending message content doesn't match the incoming message content.\n\t * @returns - The localOpMetadata of the next pending message, to be sent to whoever submitted the original message.\n\t */\n\tprivate processNextPendingMessage(\n\t\tsequenceNumber: number,\n\t\tmessage?: InboundSequencedContainerRuntimeMessage,\n\t): unknown {\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0x169 /* \"No pending message found for this remote message\" */,\n\t\t);\n\n\t\tpendingMessage.sequenceNumber = sequenceNumber;\n\t\tthis.savedOps.push(withoutLocalOpMetadata(pendingMessage));\n\n\t\tthis.pendingMessages.shift();\n\n\t\t// message is undefined in the Empty Batch case,\n\t\t// because we don't have an incoming message to compare and pendingMessage is just a placeholder anyway.\n\t\tif (message !== undefined) {\n\t\t\tconst messageContent = buildPendingMessageContent(message);\n\n\t\t\t// Stringified content should match\n\t\t\tif (pendingMessage.content !== messageContent) {\n\t\t\t\tconst pendingContentObj = JSON.parse(\n\t\t\t\t\tpendingMessage.content,\n\t\t\t\t) as LocalContainerRuntimeMessage;\n\t\t\t\tconst incomingContentObj = JSON.parse(\n\t\t\t\t\tmessageContent,\n\t\t\t\t) as InboundContainerRuntimeMessage;\n\n\t\t\t\tconst contentsMatch =\n\t\t\t\t\tpendingContentObj.contents === incomingContentObj.contents ||\n\t\t\t\t\t(pendingContentObj.contents !== undefined &&\n\t\t\t\t\t\tincomingContentObj.contents !== undefined &&\n\t\t\t\t\t\tJSON.stringify(pendingContentObj.contents) ===\n\t\t\t\t\t\t\tJSON.stringify(incomingContentObj.contents));\n\n\t\t\t\tthis.logger.sendErrorEvent({\n\t\t\t\t\teventName: \"unexpectedAckReceived\",\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tpendingContentScrubbed: scrubAndStringify(pendingContentObj),\n\t\t\t\t\t\tincomingContentScrubbed: scrubAndStringify(incomingContentObj),\n\t\t\t\t\t\tcontentsMatch,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t\"pending local message content mismatch\",\n\t\t\t\t\t\"unexpectedAckReceived\",\n\t\t\t\t\tmessage,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn pendingMessage.localOpMetadata;\n\t}\n\n\t/**\n\t * Check if the incoming batch matches the batch info for the next pending message.\n\t */\n\tprivate onLocalBatchBegin(batch: InboundBatch) {\n\t\t// Get the next message from the pending queue. Verify a message exists.\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0xa21 /* No pending message found as we start processing this remote batch */,\n\t\t);\n\n\t\t// Note: This could be undefined if this batch became empty on resubmit.\n\t\t// In this case the next pending message is an empty batch marker.\n\t\t// Empty batches became empty on Resubmit, and submit them and track them in case\n\t\t// a different fork of this container also submitted the same batch (and it may not be empty for that fork).\n\t\tconst firstMessage = batch.messages.length > 0 ? batch.messages[0] : undefined;\n\t\tconst expectedPendingBatchLength = batch.messages.length === 0 ? 1 : batch.messages.length;\n\n\t\t// We expect the incoming batch to be of the same length, starting at the same clientSequenceNumber,\n\t\t// as the batch we originally submitted.\n\t\t// We have another later check to compare the message contents, which we'd expect to fail if this check does,\n\t\t// so we don't throw here, merely log. In a later release this check may replace that one.\n\t\tif (\n\t\t\tpendingMessage.batchInfo.batchStartCsn !== batch.batchStartCsn ||\n\t\t\t(pendingMessage.batchInfo.length >= 0 && // -1 length is back compat and isn't suitable for this check\n\t\t\t\tpendingMessage.batchInfo.length !== expectedPendingBatchLength)\n\t\t) {\n\t\t\tthis.logger?.sendErrorEvent({\n\t\t\t\teventName: \"BatchInfoMismatch\",\n\t\t\t\tdetails: {\n\t\t\t\t\tpendingBatchCsn: pendingMessage.batchInfo.batchStartCsn,\n\t\t\t\t\tbatchStartCsn: batch.batchStartCsn,\n\t\t\t\t\tpendingBatchLength: pendingMessage.batchInfo.length,\n\t\t\t\t\tbatchLength: batch.messages.length,\n\t\t\t\t\tpendingMessageBatchMetadata: asBatchMetadata(pendingMessage.opMetadata)?.batch,\n\t\t\t\t\tmessageBatchMetadata: asBatchMetadata(firstMessage?.metadata)?.batch,\n\t\t\t\t},\n\t\t\t\tmessageDetails: firstMessage && extractSafePropertiesFromMessage(firstMessage),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n\t * states in its queue. This includes triggering resubmission of unacked ops.\n\t * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)\n\t */\n\tpublic replayPendingStates() {\n\t\tassert(\n\t\t\tthis.stateHandler.connected(),\n\t\t\t0x172 /* \"The connection state is not consistent with the runtime\" */,\n\t\t);\n\n\t\t// This assert suggests we are about to send same ops twice, which will result in data loss.\n\t\tassert(\n\t\t\tthis.clientIdFromLastReplay !== this.stateHandler.clientId(),\n\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t);\n\t\tthis.clientIdFromLastReplay = this.stateHandler.clientId();\n\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x174 /* \"initial states should be empty before replaying pending\" */,\n\t\t);\n\n\t\tconst initialPendingMessagesCount = this.pendingMessages.length;\n\t\tlet remainingPendingMessagesCount = this.pendingMessages.length;\n\n\t\t// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n\t\t// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n\t\t// which must not be replayed.\n\t\twhile (remainingPendingMessagesCount > 0) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tlet pendingMessage = this.pendingMessages.shift()!;\n\t\t\tremainingPendingMessagesCount--;\n\n\t\t\tconst batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;\n\t\t\tassert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);\n\n\t\t\t// The next message starts a batch (possibly single-message), and we'll need its batchId.\n\t\t\tconst batchId = getEffectiveBatchId(pendingMessage);\n\t\t\t// Resubmit no messages, with the batchId. Will result in another empty batch marker.\n\t\t\tif (asEmptyBatchLocalOpMetadata(pendingMessage.localOpMetadata)?.emptyBatch === true) {\n\t\t\t\tthis.stateHandler.reSubmitBatch([], batchId);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * We must preserve the distinct batches on resubmit.\n\t\t\t * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n\t\t\t * either receive the whole batch ack or nothing at all. @see ScheduleManager for how this works.\n\t\t\t */\n\t\t\tif (batchMetadataFlag === undefined) {\n\t\t\t\t// Single-message batch\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tbatchId,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// else: batchMetadataFlag === true (It's a typical multi-message batch)\n\n\t\t\tassert(\n\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t);\n\n\t\t\tconst batch: PendingMessageResubmitData[] = [];\n\n\t\t\t// check is >= because batch end may be last pending message\n\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\tbatch.push({\n\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t});\n\n\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\tassert(\n\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.stateHandler.reSubmitBatch(batch, batchId);\n\t\t}\n\n\t\t// pending ops should no longer depend on previous sequenced local ops after resubmit\n\t\tthis.savedOps = [];\n\n\t\t// We replayPendingStates on read connections too - we expect these to get nack'd though, and to then reconnect\n\t\t// on a write connection and replay again. This filters out the replay that happens on the read connection so\n\t\t// we only see the replays on write connections (that have a chance to go through).\n\t\tif (this.stateHandler.isActiveConnection()) {\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"PendingStatesReplayed\",\n\t\t\t\tcount: initialPendingMessagesCount,\n\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t});\n\t\t}\n\t}\n}\n\n/** For back-compat if trying to apply stashed ops that pre-date batchInfo */\nfunction patchbatchInfo(\n\tmessage: IPendingMessageFromStash,\n): asserts message is IPendingMessage {\n\tconst batchInfo: IPendingMessageFromStash[\"batchInfo\"] = message.batchInfo;\n\tif (batchInfo === undefined) {\n\t\t// Using uuid guarantees uniqueness, retaining existing behavior\n\t\tmessage.batchInfo = { clientId: uuid(), batchStartCsn: -1, length: -1 };\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarizerNode.d.ts","sourceRoot":"","sources":["../../../src/summary/summarizerNode/summarizerNode.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAGvE,OAAO,EACN,aAAa,EACb,yBAAyB,EACzB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAEN,iBAAiB,EACjB,8BAA8B,EAE9B,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACxF,OAAO,EACN,mBAAmB,EAMnB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EACN,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,2BAA2B,EAC3B,WAAW,EACX,qBAAqB,EAErB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,mBAAoB,SAAQ,eAAe,EAAE,2BAA2B;CAAG;AAE5F;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,YAAW,mBAAmB;IAuBxD,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAEpC,OAAO,CAAC,qBAAqB;IAC7B,8CAA8C;IAC9C,OAAO,CAAC,cAAc,CAAC;IACvB,SAAS,CAAC,gBAAgB,CAAC;IAC3B,oEAAoE;IACpE,SAAS,CAAC,eAAe,CAAC;IA7B3B;;;OAGG;IACH,IAAW,uBAAuB,WAEjC;IAED,SAAS,CAAC,QAAQ,CAAC,QAAQ,8BAAqC;IAChE,SAAS,CAAC,QAAQ,CAAC,gBAAgB,2BAAkC;IACrE,SAAS,CAAC,0BAA0B,EAAE,MAAM,GAAG,SAAS,CAAC;IACzD,OAAO,CAAC,aAAa,CAAuE;IAC5F,OAAO,CAAC,gBAAgB,CAAS;IAEjC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IAE/C;;;OAGG;gBAEF,UAAU,EAAE,oBAAoB,EACf,mBAAmB,EAAE,mBAAmB,EACzD,MAAM,EAAE,qBAAqB,EACrB,qBAAqB,EAAE,MAAM;IACrC,8CAA8C;IACtC,cAAc,CAAC,yBAAa,EAC1B,gBAAgB,CAAC,kCAAsB;IACjD,oEAAoE;IAC1D,eAAe,CAAC,oBAAQ;IAYnC;;;;;;;;;;OAUG;IACI,YAAY,CAClB,uBAAuB,EAAE,MAAM,EAC/B,aAAa,EAAE,oBAAoB,EACnC,sBAAsB,EAAE,MAAM,GAC5B,mBAAmB;IA8CT,SAAS,CACrB,QAAQ,EAAE,OAAO,EACjB,UAAU,GAAE,OAAc,EAC1B,gBAAgB,CAAC,EAAE,iBAAiB,GAClC,OAAO,CAAC,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"summarizerNode.d.ts","sourceRoot":"","sources":["../../../src/summary/summarizerNode/summarizerNode.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAGvE,OAAO,EACN,aAAa,EACb,yBAAyB,EACzB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAEN,iBAAiB,EACjB,8BAA8B,EAE9B,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACxF,OAAO,EACN,mBAAmB,EAMnB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EACN,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,2BAA2B,EAC3B,WAAW,EACX,qBAAqB,EAErB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,mBAAoB,SAAQ,eAAe,EAAE,2BAA2B;CAAG;AAE5F;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,YAAW,mBAAmB;IAuBxD,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAEpC,OAAO,CAAC,qBAAqB;IAC7B,8CAA8C;IAC9C,OAAO,CAAC,cAAc,CAAC;IACvB,SAAS,CAAC,gBAAgB,CAAC;IAC3B,oEAAoE;IACpE,SAAS,CAAC,eAAe,CAAC;IA7B3B;;;OAGG;IACH,IAAW,uBAAuB,WAEjC;IAED,SAAS,CAAC,QAAQ,CAAC,QAAQ,8BAAqC;IAChE,SAAS,CAAC,QAAQ,CAAC,gBAAgB,2BAAkC;IACrE,SAAS,CAAC,0BAA0B,EAAE,MAAM,GAAG,SAAS,CAAC;IACzD,OAAO,CAAC,aAAa,CAAuE;IAC5F,OAAO,CAAC,gBAAgB,CAAS;IAEjC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IAE/C;;;OAGG;gBAEF,UAAU,EAAE,oBAAoB,EACf,mBAAmB,EAAE,mBAAmB,EACzD,MAAM,EAAE,qBAAqB,EACrB,qBAAqB,EAAE,MAAM;IACrC,8CAA8C;IACtC,cAAc,CAAC,yBAAa,EAC1B,gBAAgB,CAAC,kCAAsB;IACjD,oEAAoE;IAC1D,eAAe,CAAC,oBAAQ;IAYnC;;;;;;;;;;OAUG;IACI,YAAY,CAClB,uBAAuB,EAAE,MAAM,EAC/B,aAAa,EAAE,oBAAoB,EACnC,sBAAsB,EAAE,MAAM,GAC5B,mBAAmB;IA8CT,SAAS,CACrB,QAAQ,EAAE,OAAO,EACjB,UAAU,GAAE,OAAc,EAC1B,gBAAgB,CAAC,EAAE,iBAAiB,GAClC,OAAO,CAAC,gBAAgB,CAAC;IA4D5B;;;;;;OAMG;IACI,eAAe,IAAI,qBAAqB;IAI/C;;;;;;;;OAQG;IACH,SAAS,CAAC,mBAAmB,CAAC,mBAAmB,EAAE,OAAO,GAAG,qBAAqB;IA2BlF,OAAO,CAAC,kBAAkB;IA8B1B;;;;OAIG;IACI,eAAe,CAAC,cAAc,EAAE,MAAM;IAQ7C;;;;;;;OAOG;IACH,SAAS,CAAC,mBAAmB,CAC5B,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,WAAW,GAAG,SAAS,EACnC,mBAAmB,EAAE,OAAO;IA2DtB,YAAY;IAUnB;;;;;;OAMG;IACU,oBAAoB,CAChC,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,GACnB,OAAO,CAAC,qBAAqB,CAAC;IA+CjC;;;;;OAKG;IACH,SAAS,CAAC,+BAA+B,CACxC,cAAc,EAAE,MAAM,EACtB,uBAAuB,EAAE,MAAM,GAC7B,IAAI;IA4BP,OAAO,CAAC,wBAAwB;IAQzB,sBAAsB,CAAC,QAAQ,EAAE,aAAa;IAS9C,YAAY,CAAC,EAAE,EAAE,yBAAyB,GAAG,IAAI;IAIjD,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAM/C;;;;OAIG;IACH,SAAS,CAAC,UAAU,IAAI,OAAO;IAI/B,IAAW,aAAa,IAAI,QAAQ,CAAC,WAAW,CAAC,GAAG,SAAS,CAE5D;IAED,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IAEpC,WAAW;IACjB,yBAAyB;IACzB,mBAAmB,EAAE,mBAAmB;IACxC,2CAA2C;IAC3C,EAAE,EAAE,MAAM;IACV;;;;OAIG;IACH,WAAW,EAAE,8BAA8B,EAC3C,MAAM,GAAE,qBAA0B,GAChC,eAAe;IAuBX,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAIxD;;;;;OAKG;IACH,SAAS,CAAC,wBAAwB,CACjC,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,8BAA8B,GACzC,mBAAmB;IAsCtB;;;;;;;;OAQG;IACH,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM;IAoBjE,SAAS,CAAC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW;IAI7D;;OAEG;IACI,mBAAmB,IAAI,OAAO;IAIrC;;OAEG;IACH,SAAS,CAAC,oBAAoB,CAAC,UAAU,EAAE,uBAAuB,GAAG,KAAK;CAW1E;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,WAC5B,mBAAmB,uBACN,mBAAmB,wBAClB,MAAM,2BACH,MAAM,GAAG,SAAS,WACnC,qBAAqB,KAC3B,mBAWD,CAAC"}
|
|
@@ -98,6 +98,10 @@ class SummarizerNode {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
async summarize(fullTree, trackState = true, telemetryContext) {
|
|
101
|
+
// If trackState is false, call summarize internal directly and don't track any state.
|
|
102
|
+
if (!trackState) {
|
|
103
|
+
return this.summarizeInternalFn(fullTree, trackState, telemetryContext);
|
|
104
|
+
}
|
|
101
105
|
// Try to reuse the tree if unchanged
|
|
102
106
|
if (this.canReuseHandle && !fullTree && !this.hasChanged()) {
|
|
103
107
|
const latestSummary = this._latestSummary;
|
|
@@ -132,7 +136,7 @@ class SummarizerNode {
|
|
|
132
136
|
}
|
|
133
137
|
: undefined;
|
|
134
138
|
}
|
|
135
|
-
const result = await this.summarizeInternalFn(fullTree,
|
|
139
|
+
const result = await this.summarizeInternalFn(fullTree, trackState, telemetryContext, incrementalSummaryContext);
|
|
136
140
|
this.wipLocalPaths = { localPath: summarizerNodeUtils_js_1.EscapedPath.create(result.id) };
|
|
137
141
|
if (result.pathPartsForChildren !== undefined) {
|
|
138
142
|
this.wipLocalPaths.additionalPath = summarizerNodeUtils_js_1.EscapedPath.createAndConcat(result.pathPartsForChildren);
|