@fluidframework/container-runtime 2.0.0-internal.5.4.0 → 2.0.0-internal.6.1.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 +75 -0
- package/dist/batchTracker.d.ts +2 -1
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +4 -1
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +61 -26
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.js +10 -2
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +26 -11
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +189 -131
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.js +8 -2
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +24 -26
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +20 -4
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +107 -53
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaManagerProxyBase.d.ts +35 -0
- package/dist/deltaManagerProxyBase.d.ts.map +1 -0
- package/dist/deltaManagerProxyBase.js +77 -0
- package/dist/deltaManagerProxyBase.js.map +1 -0
- package/dist/deltaManagerSummarizerProxy.d.ts +1 -1
- package/dist/deltaManagerSummarizerProxy.d.ts.map +1 -1
- package/dist/deltaManagerSummarizerProxy.js +2 -2
- package/dist/deltaManagerSummarizerProxy.js.map +1 -1
- package/dist/gc/garbageCollection.js +17 -20
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.js +13 -11
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcHelpers.js +4 -6
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +4 -6
- package/dist/gc/gcSummaryStateTracker.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +55 -37
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/id-compressor/idCompressor.js +49 -51
- package/dist/id-compressor/idCompressor.js.map +1 -1
- package/dist/id-compressor/idRange.js +2 -2
- package/dist/id-compressor/idRange.js.map +1 -1
- package/dist/id-compressor/sessionIdNormalizer.js +11 -16
- package/dist/id-compressor/sessionIdNormalizer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.js +10 -6
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/opCompressor.js +6 -1
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +11 -9
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +13 -5
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSplitter.js +10 -6
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.js +1 -2
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/opProperties.js +1 -2
- package/dist/opProperties.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +2 -2
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +22 -22
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.js +14 -10
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/orderedClientElection.d.ts +2 -1
- package/dist/summary/orderedClientElection.d.ts.map +1 -1
- package/dist/summary/orderedClientElection.js +17 -18
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/runningSummarizer.d.ts.map +1 -1
- package/dist/summary/runningSummarizer.js +35 -57
- package/dist/summary/runningSummarizer.js.map +1 -1
- package/dist/summary/summarizer.js +4 -7
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summarizerClientElection.js +5 -9
- package/dist/summary/summarizerClientElection.js.map +1 -1
- package/dist/summary/summarizerHeuristics.js +8 -12
- package/dist/summary/summarizerHeuristics.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +22 -15
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -4
- package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js +17 -16
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/dist/summary/summaryCollection.js +3 -5
- package/dist/summary/summaryCollection.js.map +1 -1
- package/dist/summary/summaryFormat.js +1 -2
- package/dist/summary/summaryFormat.js.map +1 -1
- package/dist/summary/summaryGenerator.d.ts.map +1 -1
- package/dist/summary/summaryGenerator.js +62 -21
- package/dist/summary/summaryGenerator.js.map +1 -1
- package/dist/summary/summaryManager.js +3 -5
- package/dist/summary/summaryManager.js.map +1 -1
- package/lib/batchTracker.d.ts +2 -1
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +4 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +61 -26
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.js +10 -2
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +26 -11
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +189 -131
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.js +8 -2
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +24 -26
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +20 -4
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +107 -53
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaManagerProxyBase.d.ts +35 -0
- package/lib/deltaManagerProxyBase.d.ts.map +1 -0
- package/lib/deltaManagerProxyBase.js +73 -0
- package/lib/deltaManagerProxyBase.js.map +1 -0
- package/lib/deltaManagerSummarizerProxy.d.ts +1 -1
- package/lib/deltaManagerSummarizerProxy.d.ts.map +1 -1
- package/lib/deltaManagerSummarizerProxy.js +1 -1
- package/lib/deltaManagerSummarizerProxy.js.map +1 -1
- package/lib/gc/garbageCollection.js +17 -20
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.js +13 -11
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcHelpers.js +4 -6
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +4 -6
- package/lib/gc/gcSummaryStateTracker.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +55 -37
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/id-compressor/idCompressor.js +49 -51
- package/lib/id-compressor/idCompressor.js.map +1 -1
- package/lib/id-compressor/idRange.js +2 -2
- package/lib/id-compressor/idRange.js.map +1 -1
- package/lib/id-compressor/sessionIdNormalizer.js +11 -16
- package/lib/id-compressor/sessionIdNormalizer.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.js +10 -6
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/opCompressor.js +6 -1
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +11 -9
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +13 -5
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSplitter.js +10 -6
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.js +1 -2
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/opProperties.js +1 -2
- package/lib/opProperties.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +2 -2
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +22 -22
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.js +14 -10
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/orderedClientElection.d.ts +2 -1
- package/lib/summary/orderedClientElection.d.ts.map +1 -1
- package/lib/summary/orderedClientElection.js +17 -18
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/runningSummarizer.d.ts.map +1 -1
- package/lib/summary/runningSummarizer.js +35 -57
- package/lib/summary/runningSummarizer.js.map +1 -1
- package/lib/summary/summarizer.js +4 -7
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summarizerClientElection.js +5 -9
- package/lib/summary/summarizerClientElection.js.map +1 -1
- package/lib/summary/summarizerHeuristics.js +8 -12
- package/lib/summary/summarizerHeuristics.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +22 -15
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -4
- package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js +17 -16
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/summary/summaryCollection.js +3 -5
- package/lib/summary/summaryCollection.js.map +1 -1
- package/lib/summary/summaryFormat.js +1 -2
- package/lib/summary/summaryFormat.js.map +1 -1
- package/lib/summary/summaryGenerator.d.ts.map +1 -1
- package/lib/summary/summaryGenerator.js +62 -21
- package/lib/summary/summaryGenerator.js.map +1 -1
- package/lib/summary/summaryManager.js +3 -5
- package/lib/summary/summaryManager.js.map +1 -1
- package/package.json +19 -19
- package/src/batchTracker.ts +2 -1
- package/src/blobManager.ts +43 -2
- package/src/containerRuntime.ts +95 -67
- package/src/dataStore.ts +7 -1
- package/src/dataStoreContext.ts +1 -2
- package/src/dataStores.ts +95 -55
- package/src/deltaManagerProxyBase.ts +111 -0
- package/src/deltaManagerSummarizerProxy.ts +2 -1
- package/src/gc/gcTelemetry.ts +1 -2
- package/src/index.ts +0 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +12 -15
- package/src/summary/orderedClientElection.ts +2 -1
- package/src/summary/runningSummarizer.ts +9 -31
- package/src/summary/summaryGenerator.ts +0 -1
|
@@ -38,9 +38,9 @@ class PendingStateManager {
|
|
|
38
38
|
this.dispose = () => this.disposeOnce.value;
|
|
39
39
|
/**
|
|
40
40
|
* Convert old local state format to the new format (IPendingMessageOld to IPendingMessageNew)
|
|
41
|
-
* ! TODO: Remove this conversion in "2.0.0-internal.7.0.0"
|
|
41
|
+
* ! TODO: Remove this conversion in "2.0.0-internal.7.0.0" (AB#4763)
|
|
42
42
|
*/
|
|
43
|
-
if (initialLocalState
|
|
43
|
+
if (initialLocalState?.pendingStates) {
|
|
44
44
|
for (const initialState of initialLocalState.pendingStates) {
|
|
45
45
|
let messageContent = initialState.content;
|
|
46
46
|
if (initialState.messageType !== undefined &&
|
|
@@ -52,7 +52,10 @@ class PendingStateManager {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
// Note: this object may contain "messageType" prop, but it should not be easily accesible due to interface being used
|
|
55
|
-
this.initialMessages.push(
|
|
55
|
+
this.initialMessages.push({
|
|
56
|
+
...initialState,
|
|
57
|
+
content: messageContent,
|
|
58
|
+
});
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
}
|
|
@@ -71,17 +74,17 @@ class PendingStateManager {
|
|
|
71
74
|
if (!this.pendingMessages.isEmpty()) {
|
|
72
75
|
return {
|
|
73
76
|
pendingStates: this.pendingMessages.toArray().map((message) => {
|
|
74
|
-
|
|
75
|
-
const
|
|
77
|
+
let content = message.content;
|
|
78
|
+
const parsedContent = JSON.parse(content);
|
|
76
79
|
// IdAllocations need their localOpMetadata stashed in the contents
|
|
77
80
|
// of the op to correctly resume the session when processing stashed ops
|
|
78
|
-
if (
|
|
79
|
-
|
|
81
|
+
if (parsedContent.type === containerRuntime_1.ContainerMessageType.IdAllocation) {
|
|
82
|
+
parsedContent.contents.stashedState = message.localOpMetadata;
|
|
83
|
+
content = JSON.stringify(parsedContent);
|
|
80
84
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
localOpMetadata: undefined });
|
|
85
|
+
// delete localOpMetadata since it may not be serializable
|
|
86
|
+
// and will be regenerated by applyStashedOp()
|
|
87
|
+
return { ...message, content, localOpMetadata: undefined };
|
|
85
88
|
}),
|
|
86
89
|
};
|
|
87
90
|
}
|
|
@@ -166,9 +169,8 @@ class PendingStateManager {
|
|
|
166
169
|
* @param message - The message that is being processed.
|
|
167
170
|
*/
|
|
168
171
|
maybeProcessBatchBegin(message) {
|
|
169
|
-
var _a;
|
|
170
172
|
// This message is the first in a batch if the "batch" property on the metadata is set to true
|
|
171
|
-
if (
|
|
173
|
+
if (message.metadata?.batch) {
|
|
172
174
|
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
173
175
|
(0, common_utils_1.assert)(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined, 0x16b /* "The pending batch state indicates we are already processing a batch" */);
|
|
174
176
|
// Set the pending batch state indicating we have started processing a batch.
|
|
@@ -181,16 +183,15 @@ class PendingStateManager {
|
|
|
181
183
|
* @param message - The message that is being processed.
|
|
182
184
|
*/
|
|
183
185
|
maybeProcessBatchEnd(message) {
|
|
184
|
-
var _a, _b;
|
|
185
186
|
if (!this.isProcessingBatch) {
|
|
186
187
|
return;
|
|
187
188
|
}
|
|
188
189
|
// There should be a pending batch begin message.
|
|
189
190
|
(0, common_utils_1.assert)(this.pendingBatchBeginMessage !== undefined, 0x16d /* "There is no pending batch begin message" */);
|
|
190
|
-
const batchEndMetadata =
|
|
191
|
+
const batchEndMetadata = message.metadata?.batch;
|
|
191
192
|
if (this.pendingMessages.isEmpty() || batchEndMetadata === false) {
|
|
192
193
|
// Get the batch begin metadata from the first message in the batch.
|
|
193
|
-
const batchBeginMetadata =
|
|
194
|
+
const batchBeginMetadata = this.pendingBatchBeginMessage.metadata?.batch;
|
|
194
195
|
// There could be just a single message in the batch. If so, it should not have any batch metadata. If there
|
|
195
196
|
// are multiple messages in the batch, verify that we got the correct batch begin and end metadata.
|
|
196
197
|
if (this.pendingBatchBeginMessage === message) {
|
|
@@ -222,7 +223,6 @@ class PendingStateManager {
|
|
|
222
223
|
* states in its queue. This includes triggering resubmission of unacked ops.
|
|
223
224
|
*/
|
|
224
225
|
replayPendingStates() {
|
|
225
|
-
var _a, _b, _c, _d, _e;
|
|
226
226
|
(0, common_utils_1.assert)(this.stateHandler.connected(), 0x172 /* "The connection state is not consistent with the runtime" */);
|
|
227
227
|
// This assert suggests we are about to send same ops twice, which will result in data loss.
|
|
228
228
|
(0, common_utils_1.assert)(this.clientId !== this.stateHandler.clientId(), 0x173 /* "replayPendingStates called twice for same clientId!" */);
|
|
@@ -237,13 +237,13 @@ class PendingStateManager {
|
|
|
237
237
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
238
238
|
let pendingMessage = this.pendingMessages.shift();
|
|
239
239
|
remainingPendingMessagesCount--;
|
|
240
|
-
(0, common_utils_1.assert)(
|
|
240
|
+
(0, common_utils_1.assert)(pendingMessage.opMetadata?.batch !== false, 0x41b /* We cannot process batches in chunks */);
|
|
241
241
|
/**
|
|
242
242
|
* We want to ensure grouped messages get processed in a batch.
|
|
243
243
|
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
244
244
|
* either receive the whole batch ack or nothing at all.
|
|
245
245
|
*/
|
|
246
|
-
if (
|
|
246
|
+
if (pendingMessage.opMetadata?.batch) {
|
|
247
247
|
(0, common_utils_1.assert)(remainingPendingMessagesCount > 0, 0x554 /* Last pending message cannot be a batch begin */);
|
|
248
248
|
const batch = [];
|
|
249
249
|
// check is >= because batch end may be last pending message
|
|
@@ -253,14 +253,14 @@ class PendingStateManager {
|
|
|
253
253
|
localOpMetadata: pendingMessage.localOpMetadata,
|
|
254
254
|
opMetadata: pendingMessage.opMetadata,
|
|
255
255
|
});
|
|
256
|
-
if (
|
|
256
|
+
if (pendingMessage.opMetadata?.batch === false) {
|
|
257
257
|
break;
|
|
258
258
|
}
|
|
259
259
|
(0, common_utils_1.assert)(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
|
|
260
260
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
261
261
|
pendingMessage = this.pendingMessages.shift();
|
|
262
262
|
remainingPendingMessagesCount--;
|
|
263
|
-
(0, common_utils_1.assert)(
|
|
263
|
+
(0, common_utils_1.assert)(pendingMessage.opMetadata?.batch !== true, 0x556 /* Batch start needs a corresponding batch end */);
|
|
264
264
|
}
|
|
265
265
|
this.stateHandler.reSubmitBatch(batch);
|
|
266
266
|
}
|
|
@@ -276,7 +276,7 @@ class PendingStateManager {
|
|
|
276
276
|
// on a write connection and replay again. This filters out the replay that happens on the read connection so
|
|
277
277
|
// we only see the replays on write connections (that have a chance to go through).
|
|
278
278
|
if (this.stateHandler.isActiveConnection()) {
|
|
279
|
-
|
|
279
|
+
this.logger?.sendTelemetryEvent({
|
|
280
280
|
eventName: "PendingStatesReplayed",
|
|
281
281
|
count: initialPendingMessagesCount,
|
|
282
282
|
clientId: this.stateHandler.clientId(),
|
|
@@ -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 { IDisposable } from \"@fluidframework/common-definitions\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { DataProcessingError } from \"@fluidframework/container-utils\";\nimport { Lazy } from \"@fluidframework/core-utils\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { ITelemetryLoggerExt } from \"@fluidframework/telemetry-utils\";\nimport Deque from \"double-ended-queue\";\nimport { ContainerMessageType } from \"./containerRuntime\";\nimport { pkgVersion } from \"./packageVersion\";\nimport { IBatchMetadata } from \"./metadata\";\n\n/**\n * ! TODO: Remove this interface in \"2.0.0-internal.7.0.0\" once we only read IPendingMessageNew\n */\nexport interface IPendingMessageOld {\n\ttype: \"message\";\n\tmessageType: ContainerMessageType;\n\tclientSequenceNumber: number;\n\treferenceSequenceNumber: number;\n\tcontent: any;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\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 */\nexport interface IPendingMessageNew {\n\ttype: \"message\";\n\tclientSequenceNumber: number;\n\treferenceSequenceNumber: number;\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\n/**\n * ! TODO: Remove this type in \"2.0.0-internal.7.0.0\"\n */\nexport type IPendingState = IPendingMessageOld | IPendingMessageNew;\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingState[];\n}\n\nexport interface IPendingBatchMessage {\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tclose(error?: ICriticalContainerError): void;\n\tapplyStashedOp(content: string): Promise<unknown>;\n\treSubmit(message: IPendingBatchMessage): void;\n\treSubmitBatch(batch: IPendingBatchMessage[]): void;\n\tisActiveConnection: () => boolean;\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\tprivate readonly pendingMessages = new Deque<IPendingMessageNew>();\n\tprivate readonly initialMessages = new Deque<IPendingMessageNew>();\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length;\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\tprivate clientId: string | undefined;\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.pendingMessages.isEmpty() || !this.initialMessages.isEmpty();\n\t}\n\n\tpublic getLocalState(): IPendingLocalState | undefined {\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\tif (!this.pendingMessages.isEmpty()) {\n\t\t\treturn {\n\t\t\t\tpendingStates: this.pendingMessages.toArray().map((message) => {\n\t\t\t\t\t// ! TODO: Remove conversion to IPendingMessageOld in \"2.0.0-internal.6.0.0\" AB#3826\n\t\t\t\t\tconst content = JSON.parse(message.content);\n\t\t\t\t\t// IdAllocations need their localOpMetadata stashed in the contents\n\t\t\t\t\t// of the op to correctly resume the session when processing stashed ops\n\t\t\t\t\tif (content.type === ContainerMessageType.IdAllocation) {\n\t\t\t\t\t\tcontent.contents.stashedState = message.localOpMetadata;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...message,\n\t\t\t\t\t\tmessageType: content.type,\n\t\t\t\t\t\tcontent: content.contents,\n\t\t\t\t\t\t// delete localOpMetadata since it may not be serializable\n\t\t\t\t\t\t// and will be regenerated by applyStashedOp()\n\t\t\t\t\t\tlocalOpMetadata: undefined,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t};\n\t\t}\n\t}\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tinitialLocalState: IPendingLocalState | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt | undefined,\n\t) {\n\t\t/**\n\t\t * Convert old local state format to the new format (IPendingMessageOld to IPendingMessageNew)\n\t\t * ! TODO: Remove this conversion in \"2.0.0-internal.7.0.0\"\n\t\t */\n\t\tif (initialLocalState?.pendingStates) {\n\t\t\tfor (const initialState of initialLocalState.pendingStates) {\n\t\t\t\tlet messageContent = initialState.content;\n\t\t\t\tif (\n\t\t\t\t\t(initialState as IPendingMessageOld).messageType !== undefined &&\n\t\t\t\t\ttypeof initialState.content !== \"string\"\n\t\t\t\t) {\n\t\t\t\t\t// Convert IPendingMessageOld to IPendingMessageNew\n\t\t\t\t\tmessageContent = JSON.stringify({\n\t\t\t\t\t\ttype: (initialState as IPendingMessageOld).messageType,\n\t\t\t\t\t\tcontents: initialState.content,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// Note: this object may contain \"messageType\" prop, but it should not be easily accesible due to interface being used\n\t\t\t\tthis.initialMessages.push({\n\t\t\t\t\t...initialState,\n\t\t\t\t\tcontent: messageContent,\n\t\t\t\t});\n\t\t\t}\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 * Called when a message is submitted locally. Adds the message and the associated details to the pending state\n\t * queue.\n\t * @param type - The container message type.\n\t * @param content - The message content.\n\t * @param localOpMetadata - The local metadata associated with the message.\n\t */\n\tpublic onSubmitMessage(\n\t\tcontent: string,\n\t\treferenceSequenceNumber: number,\n\t\tlocalOpMetadata: unknown,\n\t\topMetadata: Record<string, unknown> | undefined,\n\t) {\n\t\tconst pendingMessage: IPendingMessageNew = {\n\t\t\ttype: \"message\",\n\t\t\tclientSequenceNumber: -1, // dummy value (not to be used anywhere)\n\t\t\treferenceSequenceNumber,\n\t\t\tcontent,\n\t\t\tlocalOpMetadata,\n\t\t\topMetadata,\n\t\t};\n\n\t\tthis.pendingMessages.push(pendingMessage);\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\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.peekFront()!;\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\tif (nextMessage.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 (nextMessage.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\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\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\n\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tthis.pendingMessages.push(this.initialMessages.shift()!);\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 */\n\tpublic processPendingLocalMessage(message: ISequencedDocumentMessage): unknown {\n\t\t// Pre-processing part - This may be the start of a batch.\n\t\tthis.maybeProcessBatchBegin(message);\n\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\t\tthis.pendingMessages.shift();\n\n\t\tconst messageContent = JSON.stringify({ type: message.type, contents: message.contents });\n\t\t// Stringified content does not 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 */\n\tprivate maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\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\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 */\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.clientId !== this.stateHandler.clientId(),\n\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t);\n\t\tthis.clientId = 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\t\t\tassert(\n\t\t\t\tpendingMessage.opMetadata?.batch !== false,\n\t\t\t\t0x41b /* We cannot process batches in chunks */,\n\t\t\t);\n\n\t\t\t/**\n\t\t\t * We want to ensure grouped messages get processed in a batch.\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.\n\t\t\t */\n\t\t\tif (pendingMessage.opMetadata?.batch) {\n\t\t\t\tassert(\n\t\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t\t);\n\n\t\t\t\tconst batch: IPendingBatchMessage[] = [];\n\n\t\t\t\t// check is >= because batch end may be last pending message\n\t\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\t\tbatch.push({\n\t\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\t\tassert(\n\t\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(batch);\n\t\t\t} else {\n\t\t\t\tthis.stateHandler.reSubmit({\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\t\t\t}\n\t\t}\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"]}
|
|
1
|
+
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAGH,+DAAsD;AAEtD,qEAAsE;AACtE,2DAAkD;AAGlD,4EAAuC;AACvC,yDAA0D;AAC1D,qDAA8C;AAyD9C;;;;;;;;GAQG;AACH,MAAa,mBAAmB;IAsD/B,YACkB,YAAkC,EACnD,iBAAiD,EAChC,MAAuC;QAFvC,iBAAY,GAAZ,YAAY,CAAsB;QAElC,WAAM,GAAN,MAAM,CAAiC;QAxDxC,oBAAe,GAAG,IAAI,4BAAK,EAAsB,CAAC;QAClD,oBAAe,GAAG,IAAI,4BAAK,EAAsB,CAAC;QAClD,gBAAW,GAAG,IAAI,iBAAI,CAAO,GAAG,EAAE;YAClD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC9B,CAAC,CAAC,CAAC;QAMH,+CAA+C;QACvC,sBAAiB,GAAY,KAAK,CAAC;QA2E3B,YAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;QA7BtD;;;WAGG;QACH,IAAI,iBAAiB,EAAE,aAAa,EAAE;YACrC,KAAK,MAAM,YAAY,IAAI,iBAAiB,CAAC,aAAa,EAAE;gBAC3D,IAAI,cAAc,GAAG,YAAY,CAAC,OAAO,CAAC;gBAC1C,IACE,YAAmC,CAAC,WAAW,KAAK,SAAS;oBAC9D,OAAO,YAAY,CAAC,OAAO,KAAK,QAAQ,EACvC;oBACD,mDAAmD;oBACnD,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC;wBAC/B,IAAI,EAAG,YAAmC,CAAC,WAAW;wBACtD,QAAQ,EAAE,YAAY,CAAC,OAAO;qBAC9B,CAAC,CAAC;iBACH;gBACD,sHAAsH;gBACtH,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;oBACzB,GAAG,YAAY;oBACf,OAAO,EAAE,cAAc;iBACvB,CAAC,CAAC;aACH;SACD;IACF,CAAC;IA3ED,IAAW,oBAAoB;QAC9B,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;IACpC,CAAC;IAWD;;;OAGG;IACI,kBAAkB;QACxB,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;IAC3E,CAAC;IAEM,aAAa;QACnB,IAAA,qBAAM,EACL,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAC9B,KAAK,CAAC,+DAA+D,CACrE,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE;YACpC,OAAO;gBACN,aAAa,EAAE,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;oBAC7D,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;oBAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBAC1C,mEAAmE;oBACnE,wEAAwE;oBACxE,IAAI,aAAa,CAAC,IAAI,KAAK,uCAAoB,CAAC,YAAY,EAAE;wBAC7D,aAAa,CAAC,QAAQ,CAAC,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC;wBAC9D,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;qBACxC;oBAED,0DAA0D;oBAC1D,8CAA8C;oBAC9C,OAAO,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC;gBAC5D,CAAC,CAAC;aACF,CAAC;SACF;IACF,CAAC;IAiCD,IAAW,QAAQ;QAClB,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC;IACnC,CAAC;IAGD;;;;;;OAMG;IACI,eAAe,CACrB,OAAe,EACf,uBAA+B,EAC/B,eAAwB,EACxB,UAA+C;QAE/C,MAAM,cAAc,GAAuB;YAC1C,IAAI,EAAE,SAAS;YACf,oBAAoB,EAAE,CAAC,CAAC;YACxB,uBAAuB;YACvB,OAAO;YACP,eAAe;YACf,UAAU;SACV,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3C,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,iBAAiB,CAAC,MAAe;QAC7C,uCAAuC;QACvC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE;YACvC,oEAAoE;YACpE,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAG,CAAC;YACtD,IAAI,MAAM,KAAK,SAAS,EAAE;gBACzB,IAAI,WAAW,CAAC,uBAAuB,GAAG,MAAM,EAAE;oBACjD,MAAM,CAAC,6CAA6C;iBACpD;gBACD,IAAI,WAAW,CAAC,uBAAuB,GAAG,MAAM,EAAE;oBACjD,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;iBACxE;aACD;YAED,IAAI;gBACH,gGAAgG;gBAChG,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;gBACpF,WAAW,CAAC,eAAe,GAAG,eAAe,CAAC;aAC9C;YAAC,OAAO,KAAK,EAAE;gBACf,MAAM,qCAAmB,CAAC,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,EAAE,WAAW,CAAC,CAAC;aACnF;YAED,qGAAqG;YACrG,oEAAoE;YACpE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC,CAAC;SACzD;IACF,CAAC;IAED;;;;OAIG;IACI,0BAA0B,CAAC,OAAkC;QACnE,0DAA0D;QAC1D,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAErC,wEAAwE;QACxE,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,CAAC;QACxD,IAAA,qBAAM,EACL,cAAc,KAAK,SAAS,EAC5B,KAAK,CAAC,wDAAwD,CAC9D,CAAC;QACF,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAE7B,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC1F,qCAAqC;QACrC,IAAI,cAAc,CAAC,OAAO,KAAK,cAAc,EAAE;YAC9C,IAAI,CAAC,YAAY,CAAC,KAAK,CACtB,qCAAmB,CAAC,MAAM,CACzB,wCAAwC,EACxC,uBAAuB,EACvB,OAAO,EACP;gBACC,mBAAmB,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,IAAI;aAC5D,CACD,CACD,CAAC;YACF,OAAO;SACP;QAED,wGAAwG;QACxG,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAEnC,OAAO,cAAc,CAAC,eAAe,CAAC;IACvC,CAAC;IAED;;;OAGG;IACK,sBAAsB,CAAC,OAAkC;QAChE,8FAA8F;QAC9F,IAAK,OAAO,CAAC,QAAuC,EAAE,KAAK,EAAE;YAC5D,kGAAkG;YAClG,IAAA,qBAAM,EACL,CAAC,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,wBAAwB,KAAK,SAAS,EACtE,KAAK,CAAC,2EAA2E,CACjF,CAAC;YAEF,6EAA6E;YAC7E,IAAI,CAAC,wBAAwB,GAAG,OAAO,CAAC;YACxC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;SAC9B;IACF,CAAC;IAED;;;OAGG;IACK,oBAAoB,CAAC,OAAkC;QAC9D,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE;YAC5B,OAAO;SACP;QAED,iDAAiD;QACjD,IAAA,qBAAM,EACL,IAAI,CAAC,wBAAwB,KAAK,SAAS,EAC3C,KAAK,CAAC,+CAA+C,CACrD,CAAC;QAEF,MAAM,gBAAgB,GAAI,OAAO,CAAC,QAAuC,EAAE,KAAK,CAAC;QACjF,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,gBAAgB,KAAK,KAAK,EAAE;YACjE,oEAAoE;YACpE,MAAM,kBAAkB,GACvB,IAAI,CAAC,wBAAwB,CAAC,QAC9B,EAAE,KAAK,CAAC;YAET,4GAA4G;YAC5G,mGAAmG;YACnG,IAAI,IAAI,CAAC,wBAAwB,KAAK,OAAO,EAAE;gBAC9C,IAAA,qBAAM,EACL,kBAAkB,KAAK,SAAS,EAChC,KAAK,CAAC,gEAAgE,CACtE,CAAC;aACF;iBAAM;gBACN,IAAI,kBAAkB,KAAK,IAAI,IAAI,gBAAgB,KAAK,KAAK,EAAE;oBAC9D,IAAI,CAAC,YAAY,CAAC,KAAK,CACtB,qCAAmB,CAAC,MAAM,CACzB,6BAA6B,EAAE,4CAA4C;oBAC3E,4BAA4B,EAC5B,OAAO,EACP;wBACC,cAAc,EAAE,2BAAU;wBAC1B,aAAa,EACZ,IAAI,CAAC,wBAAwB,CAAC,QAAQ,KAAK,IAAI;4BAC9C,CAAC,CAAC,MAAM;4BACR,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,QAAQ;wBAC1C,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE;wBACtC,aAAa,EAAE,kBAAkB,KAAK,IAAI;wBAC1C,WAAW,EAAE,gBAAgB,KAAK,KAAK;wBACvC,WAAW,EAAE,OAAO,CAAC,IAAI;wBACzB,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;qBAC/C,CACD,CACD,CAAC;iBACF;aACD;YAED,6EAA6E;YAC7E,IAAI,CAAC,wBAAwB,GAAG,SAAS,CAAC;YAC1C,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;SAC/B;IACF,CAAC;IAED;;;OAGG;IACI,mBAAmB;QACzB,IAAA,qBAAM,EACL,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,EAC7B,KAAK,CAAC,+DAA+D,CACrE,CAAC;QAEF,4FAA4F;QAC5F,IAAA,qBAAM,EACL,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAC9C,KAAK,CAAC,2DAA2D,CACjE,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;QAE7C,IAAA,qBAAM,EACL,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAC9B,KAAK,CAAC,+DAA+D,CACrE,CAAC;QAEF,MAAM,2BAA2B,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;QAChE,IAAI,6BAA6B,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;QAEhE,8GAA8G;QAC9G,0GAA0G;QAC1G,8BAA8B;QAC9B,OAAO,6BAA6B,GAAG,CAAC,EAAE;YACzC,oEAAoE;YACpE,IAAI,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC;YACnD,6BAA6B,EAAE,CAAC;YAChC,IAAA,qBAAM,EACL,cAAc,CAAC,UAAU,EAAE,KAAK,KAAK,KAAK,EAC1C,KAAK,CAAC,yCAAyC,CAC/C,CAAC;YAEF;;;;eAIG;YACH,IAAI,cAAc,CAAC,UAAU,EAAE,KAAK,EAAE;gBACrC,IAAA,qBAAM,EACL,6BAA6B,GAAG,CAAC,EACjC,KAAK,CAAC,kDAAkD,CACxD,CAAC;gBAEF,MAAM,KAAK,GAA2B,EAAE,CAAC;gBAEzC,4DAA4D;gBAC5D,OAAO,6BAA6B,IAAI,CAAC,EAAE;oBAC1C,KAAK,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,cAAc,CAAC,OAAO;wBAC/B,eAAe,EAAE,cAAc,CAAC,eAAe;wBAC/C,UAAU,EAAE,cAAc,CAAC,UAAU;qBACrC,CAAC,CAAC;oBAEH,IAAI,cAAc,CAAC,UAAU,EAAE,KAAK,KAAK,KAAK,EAAE;wBAC/C,MAAM;qBACN;oBACD,IAAA,qBAAM,EAAC,6BAA6B,GAAG,CAAC,EAAE,KAAK,CAAC,wBAAwB,CAAC,CAAC;oBAE1E,oEAAoE;oBACpE,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC;oBAC/C,6BAA6B,EAAE,CAAC;oBAChC,IAAA,qBAAM,EACL,cAAc,CAAC,UAAU,EAAE,KAAK,KAAK,IAAI,EACzC,KAAK,CAAC,iDAAiD,CACvD,CAAC;iBACF;gBAED,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACvC;iBAAM;gBACN,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;oBAC1B,OAAO,EAAE,cAAc,CAAC,OAAO;oBAC/B,eAAe,EAAE,cAAc,CAAC,eAAe;oBAC/C,UAAU,EAAE,cAAc,CAAC,UAAU;iBACrC,CAAC,CAAC;aACH;SACD;QAED,+GAA+G;QAC/G,6GAA6G;QAC7G,mFAAmF;QACnF,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,EAAE;YAC3C,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAC/B,SAAS,EAAE,uBAAuB;gBAClC,KAAK,EAAE,2BAA2B;gBAClC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE;aACtC,CAAC,CAAC;SACH;IACF,CAAC;CACD;AArWD,kDAqWC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable } from \"@fluidframework/common-definitions\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { DataProcessingError } from \"@fluidframework/container-utils\";\nimport { Lazy } from \"@fluidframework/core-utils\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { ITelemetryLoggerExt } from \"@fluidframework/telemetry-utils\";\nimport Deque from \"double-ended-queue\";\nimport { ContainerMessageType } from \"./containerRuntime\";\nimport { pkgVersion } from \"./packageVersion\";\nimport { IBatchMetadata } from \"./metadata\";\n\n/**\n * ! TODO: Remove this interface in \"2.0.0-internal.7.0.0\" once we only read IPendingMessageNew (AB#4763)\n */\nexport interface IPendingMessageOld {\n\ttype: \"message\";\n\tmessageType: ContainerMessageType;\n\tclientSequenceNumber: number;\n\treferenceSequenceNumber: number;\n\tcontent: any;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\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 */\nexport interface IPendingMessageNew {\n\ttype: \"message\";\n\tclientSequenceNumber: number;\n\treferenceSequenceNumber: number;\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\n/**\n * ! TODO: Remove this type in \"2.0.0-internal.7.0.0\" (AB#4763)\n */\nexport type IPendingState = IPendingMessageOld | IPendingMessageNew;\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingState[];\n}\n\nexport interface IPendingBatchMessage {\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tclose(error?: ICriticalContainerError): void;\n\tapplyStashedOp(content: string): Promise<unknown>;\n\treSubmit(message: IPendingBatchMessage): void;\n\treSubmitBatch(batch: IPendingBatchMessage[]): void;\n\tisActiveConnection: () => boolean;\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\tprivate readonly pendingMessages = new Deque<IPendingMessageNew>();\n\tprivate readonly initialMessages = new Deque<IPendingMessageNew>();\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length;\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\tprivate clientId: string | undefined;\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.pendingMessages.isEmpty() || !this.initialMessages.isEmpty();\n\t}\n\n\tpublic getLocalState(): IPendingLocalState | undefined {\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\tif (!this.pendingMessages.isEmpty()) {\n\t\t\treturn {\n\t\t\t\tpendingStates: this.pendingMessages.toArray().map((message) => {\n\t\t\t\t\tlet content = message.content;\n\t\t\t\t\tconst parsedContent = JSON.parse(content);\n\t\t\t\t\t// IdAllocations need their localOpMetadata stashed in the contents\n\t\t\t\t\t// of the op to correctly resume the session when processing stashed ops\n\t\t\t\t\tif (parsedContent.type === ContainerMessageType.IdAllocation) {\n\t\t\t\t\t\tparsedContent.contents.stashedState = message.localOpMetadata;\n\t\t\t\t\t\tcontent = JSON.stringify(parsedContent);\n\t\t\t\t\t}\n\n\t\t\t\t\t// delete localOpMetadata since it may not be serializable\n\t\t\t\t\t// and will be regenerated by applyStashedOp()\n\t\t\t\t\treturn { ...message, content, localOpMetadata: undefined };\n\t\t\t\t}),\n\t\t\t};\n\t\t}\n\t}\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tinitialLocalState: IPendingLocalState | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt | undefined,\n\t) {\n\t\t/**\n\t\t * Convert old local state format to the new format (IPendingMessageOld to IPendingMessageNew)\n\t\t * ! TODO: Remove this conversion in \"2.0.0-internal.7.0.0\" (AB#4763)\n\t\t */\n\t\tif (initialLocalState?.pendingStates) {\n\t\t\tfor (const initialState of initialLocalState.pendingStates) {\n\t\t\t\tlet messageContent = initialState.content;\n\t\t\t\tif (\n\t\t\t\t\t(initialState as IPendingMessageOld).messageType !== undefined &&\n\t\t\t\t\ttypeof initialState.content !== \"string\"\n\t\t\t\t) {\n\t\t\t\t\t// Convert IPendingMessageOld to IPendingMessageNew\n\t\t\t\t\tmessageContent = JSON.stringify({\n\t\t\t\t\t\ttype: (initialState as IPendingMessageOld).messageType,\n\t\t\t\t\t\tcontents: initialState.content,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// Note: this object may contain \"messageType\" prop, but it should not be easily accesible due to interface being used\n\t\t\t\tthis.initialMessages.push({\n\t\t\t\t\t...initialState,\n\t\t\t\t\tcontent: messageContent,\n\t\t\t\t});\n\t\t\t}\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 * Called when a message is submitted locally. Adds the message and the associated details to the pending state\n\t * queue.\n\t * @param type - The container message type.\n\t * @param content - The message content.\n\t * @param localOpMetadata - The local metadata associated with the message.\n\t */\n\tpublic onSubmitMessage(\n\t\tcontent: string,\n\t\treferenceSequenceNumber: number,\n\t\tlocalOpMetadata: unknown,\n\t\topMetadata: Record<string, unknown> | undefined,\n\t) {\n\t\tconst pendingMessage: IPendingMessageNew = {\n\t\t\ttype: \"message\",\n\t\t\tclientSequenceNumber: -1, // dummy value (not to be used anywhere)\n\t\t\treferenceSequenceNumber,\n\t\t\tcontent,\n\t\t\tlocalOpMetadata,\n\t\t\topMetadata,\n\t\t};\n\n\t\tthis.pendingMessages.push(pendingMessage);\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\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.peekFront()!;\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\tif (nextMessage.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 (nextMessage.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\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\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\n\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tthis.pendingMessages.push(this.initialMessages.shift()!);\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 */\n\tpublic processPendingLocalMessage(message: ISequencedDocumentMessage): unknown {\n\t\t// Pre-processing part - This may be the start of a batch.\n\t\tthis.maybeProcessBatchBegin(message);\n\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\t\tthis.pendingMessages.shift();\n\n\t\tconst messageContent = JSON.stringify({ type: message.type, contents: message.contents });\n\t\t// Stringified content does not 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 */\n\tprivate maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\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\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 */\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.clientId !== this.stateHandler.clientId(),\n\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t);\n\t\tthis.clientId = 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\t\t\tassert(\n\t\t\t\tpendingMessage.opMetadata?.batch !== false,\n\t\t\t\t0x41b /* We cannot process batches in chunks */,\n\t\t\t);\n\n\t\t\t/**\n\t\t\t * We want to ensure grouped messages get processed in a batch.\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.\n\t\t\t */\n\t\t\tif (pendingMessage.opMetadata?.batch) {\n\t\t\t\tassert(\n\t\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t\t);\n\n\t\t\t\tconst batch: IPendingBatchMessage[] = [];\n\n\t\t\t\t// check is >= because batch end may be last pending message\n\t\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\t\tbatch.push({\n\t\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\t\tassert(\n\t\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(batch);\n\t\t\t} else {\n\t\t\t\tthis.stateHandler.reSubmit({\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\t\t\t}\n\t\t}\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"]}
|
package/dist/scheduleManager.js
CHANGED
|
@@ -27,20 +27,18 @@ class ScheduleManager {
|
|
|
27
27
|
void new ScheduleManagerCore(deltaManager, getClientId, logger);
|
|
28
28
|
}
|
|
29
29
|
beforeOpProcessing(message) {
|
|
30
|
-
var _a;
|
|
31
30
|
if (this.batchClientId !== message.clientId) {
|
|
32
31
|
(0, common_utils_1.assert)(this.batchClientId === undefined, 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
|
|
33
32
|
// This could be the beginning of a new batch or an individual message.
|
|
34
33
|
this.emitter.emit("batchBegin", message);
|
|
35
34
|
this.deltaScheduler.batchBegin(message);
|
|
36
|
-
const batch =
|
|
35
|
+
const batch = message?.metadata?.batch;
|
|
37
36
|
// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
|
|
38
37
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
39
38
|
this.batchClientId = batch ? message.clientId : undefined;
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
afterOpProcessing(error, message) {
|
|
43
|
-
var _a;
|
|
44
42
|
// If this is no longer true, we need to revisit what we do where we set this.hitError.
|
|
45
43
|
(0, common_utils_1.assert)(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
|
|
46
44
|
if (error) {
|
|
@@ -52,7 +50,7 @@ class ScheduleManager {
|
|
|
52
50
|
this.deltaScheduler.batchEnd(message);
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
|
-
const batch =
|
|
53
|
+
const batch = message?.metadata?.batch;
|
|
56
54
|
// If no batchClientId has been set then we're in an individual batch. Else, if we get
|
|
57
55
|
// batch end metadata, this is end of the current batch.
|
|
58
56
|
if (this.batchClientId === undefined || batch === false) {
|
|
@@ -83,7 +81,7 @@ class ScheduleManagerCore {
|
|
|
83
81
|
}
|
|
84
82
|
// First message will have the batch flag set to true if doing a batched send
|
|
85
83
|
const firstMessageMetadata = messages[0].metadata;
|
|
86
|
-
if (!
|
|
84
|
+
if (!firstMessageMetadata?.batch) {
|
|
87
85
|
return;
|
|
88
86
|
}
|
|
89
87
|
// If the batch contains only a single op, clear the batch flag.
|
|
@@ -95,7 +93,7 @@ class ScheduleManagerCore {
|
|
|
95
93
|
const lastMessage = messages[messages.length - 1];
|
|
96
94
|
// TODO: It's not clear if this shallow clone is required, as opposed to just setting "batch" to false.
|
|
97
95
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
98
|
-
lastMessage.metadata =
|
|
96
|
+
lastMessage.metadata = { ...lastMessage.metadata, batch: false };
|
|
99
97
|
});
|
|
100
98
|
// Listen for updates and peek at the inbound
|
|
101
99
|
this.deltaManager.inbound.on("push", (message) => {
|
|
@@ -117,7 +115,6 @@ class ScheduleManagerCore {
|
|
|
117
115
|
* to make decision if op processing should be paused or not after that.
|
|
118
116
|
*/
|
|
119
117
|
afterOpProcessing(message) {
|
|
120
|
-
var _a;
|
|
121
118
|
(0, common_utils_1.assert)(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
|
|
122
119
|
// If the inbound queue is ever empty, nothing to do!
|
|
123
120
|
if (this.deltaManager.inbound.length === 0) {
|
|
@@ -137,7 +134,7 @@ class ScheduleManagerCore {
|
|
|
137
134
|
"Incomplete batch", "ScheduleManager", message, {
|
|
138
135
|
type: message.type,
|
|
139
136
|
contentType: typeof message.contents,
|
|
140
|
-
batch:
|
|
137
|
+
batch: message.metadata?.batch,
|
|
141
138
|
compression: message.compression,
|
|
142
139
|
pauseSeqNum: this.pauseSequenceNumber,
|
|
143
140
|
});
|
|
@@ -186,7 +183,7 @@ class ScheduleManagerCore {
|
|
|
186
183
|
const metadata = message.metadata;
|
|
187
184
|
// batchMetadata will be true for the message that starts a batch, false for the one that ends it, and
|
|
188
185
|
// undefined for all other messages.
|
|
189
|
-
const batchMetadata = metadata
|
|
186
|
+
const batchMetadata = metadata?.batch;
|
|
190
187
|
// Protocol messages are never part of a runtime batch of messages
|
|
191
188
|
if (!(0, driver_utils_1.isRuntimeMessage)(message)) {
|
|
192
189
|
// Protocol messages should never show up in the middle of the batch!
|
|
@@ -213,7 +210,14 @@ class ScheduleManagerCore {
|
|
|
213
210
|
// started the current batch (it should not be possible for ops from other clients to get interleaved with a batch).
|
|
214
211
|
if (this.currentBatchClientId !== undefined &&
|
|
215
212
|
this.currentBatchClientId !== message.clientId) {
|
|
216
|
-
throw new container_utils_1.DataCorruptionError("OpBatchIncomplete",
|
|
213
|
+
throw new container_utils_1.DataCorruptionError("OpBatchIncomplete", {
|
|
214
|
+
runtimeVersion: packageVersion_1.pkgVersion,
|
|
215
|
+
batchClientId: this.currentBatchClientId === null ? "null" : this.currentBatchClientId,
|
|
216
|
+
pauseSequenceNumber: this.pauseSequenceNumber,
|
|
217
|
+
localBatch: this.currentBatchClientId === this.getClientId(),
|
|
218
|
+
localMessage: message.clientId === this.getClientId(),
|
|
219
|
+
...(0, container_utils_1.extractSafePropertiesFromMessage)(message),
|
|
220
|
+
});
|
|
217
221
|
}
|
|
218
222
|
// The queue is
|
|
219
223
|
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduleManager.js","sourceRoot":"","sources":["../src/scheduleManager.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 */\nimport { EventEmitter } from \"events\";\nimport { IDeltaManager } from \"@fluidframework/container-definitions\";\nimport { IDocumentMessage, ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { ITelemetryLoggerExt, createChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { assert, performance } from \"@fluidframework/common-utils\";\nimport { isRuntimeMessage } from \"@fluidframework/driver-utils\";\nimport {\n\tDataCorruptionError,\n\tDataProcessingError,\n\textractSafePropertiesFromMessage,\n} from \"@fluidframework/container-utils\";\nimport { DeltaScheduler } from \"./deltaScheduler\";\nimport { pkgVersion } from \"./packageVersion\";\nimport { IBatchMetadata } from \"./metadata\";\n\ntype IRuntimeMessageMetadata =\n\t| undefined\n\t| {\n\t\t\tbatch?: boolean;\n\t };\n\n/**\n * This class has the following responsibilities:\n *\n * 1. It tracks batches as we process ops and raises \"batchBegin\" and \"batchEnd\" events.\n * As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)\n *\n * 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch\n * unless all ops of the batch are in.\n */\nexport class ScheduleManager {\n\tprivate readonly deltaScheduler: DeltaScheduler;\n\tprivate batchClientId: string | undefined;\n\tprivate hitError = false;\n\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,\n\t\tprivate readonly emitter: EventEmitter,\n\t\treadonly getClientId: () => string | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t) {\n\t\tthis.deltaScheduler = new DeltaScheduler(\n\t\t\tthis.deltaManager,\n\t\t\tcreateChildLogger({ logger: this.logger, namespace: \"DeltaScheduler\" }),\n\t\t);\n\t\tvoid new ScheduleManagerCore(deltaManager, getClientId, logger);\n\t}\n\n\tpublic beforeOpProcessing(message: ISequencedDocumentMessage) {\n\t\tif (this.batchClientId !== message.clientId) {\n\t\t\tassert(\n\t\t\t\tthis.batchClientId === undefined,\n\t\t\t\t0x2a2 /* \"Batch is interrupted by other client op. Should be caught by trackPending()\" */,\n\t\t\t);\n\n\t\t\t// This could be the beginning of a new batch or an individual message.\n\t\t\tthis.emitter.emit(\"batchBegin\", message);\n\t\t\tthis.deltaScheduler.batchBegin(message);\n\n\t\t\tconst batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;\n\t\t\t// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n\t\t\tthis.batchClientId = batch ? (message.clientId as string) : undefined;\n\t\t}\n\t}\n\n\tpublic afterOpProcessing(error: any | undefined, message: ISequencedDocumentMessage) {\n\t\t// If this is no longer true, we need to revisit what we do where we set this.hitError.\n\t\tassert(!this.hitError, 0x2a3 /* \"container should be closed on any error\" */);\n\n\t\tif (error) {\n\t\t\t// We assume here that loader will close container and stop processing all future ops.\n\t\t\t// This is implicit dependency. If this flow changes, this code might no longer be correct.\n\t\t\tthis.hitError = true;\n\t\t\tthis.batchClientId = undefined;\n\t\t\tthis.emitter.emit(\"batchEnd\", error, message);\n\t\t\tthis.deltaScheduler.batchEnd(message);\n\t\t\treturn;\n\t\t}\n\n\t\tconst batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;\n\t\t// If no batchClientId has been set then we're in an individual batch. Else, if we get\n\t\t// batch end metadata, this is end of the current batch.\n\t\tif (this.batchClientId === undefined || batch === false) {\n\t\t\tthis.batchClientId = undefined;\n\t\t\tthis.emitter.emit(\"batchEnd\", undefined, message);\n\t\t\tthis.deltaScheduler.batchEnd(message);\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/**\n * This class controls pausing and resuming of inbound queue to ensure that we never\n * start processing ops in a batch IF we do not have all ops in the batch.\n */\nclass ScheduleManagerCore {\n\tprivate pauseSequenceNumber: number | undefined;\n\tprivate currentBatchClientId: string | undefined;\n\tprivate localPaused = false;\n\tprivate timePaused = 0;\n\tprivate batchCount = 0;\n\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,\n\t\tprivate readonly getClientId: () => string | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t) {\n\t\t// Listen for delta manager sends and add batch metadata to messages\n\t\tthis.deltaManager.on(\"prepareSend\", (messages: IDocumentMessage[]) => {\n\t\t\tif (messages.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// First message will have the batch flag set to true if doing a batched send\n\t\t\tconst firstMessageMetadata = messages[0].metadata as IRuntimeMessageMetadata;\n\t\t\tif (!firstMessageMetadata?.batch) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If the batch contains only a single op, clear the batch flag.\n\t\t\tif (messages.length === 1) {\n\t\t\t\tdelete firstMessageMetadata.batch;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the batch flag to false on the last message to indicate the end of the send batch\n\t\t\tconst lastMessage = messages[messages.length - 1];\n\t\t\t// TODO: It's not clear if this shallow clone is required, as opposed to just setting \"batch\" to false.\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n\t\t\tlastMessage.metadata = { ...(lastMessage.metadata as any), batch: false };\n\t\t});\n\n\t\t// Listen for updates and peek at the inbound\n\t\tthis.deltaManager.inbound.on(\"push\", (message: ISequencedDocumentMessage) => {\n\t\t\tthis.trackPending(message);\n\t\t});\n\n\t\t// Start with baseline - empty inbound queue.\n\t\tassert(!this.localPaused, 0x293 /* \"initial state\" */);\n\n\t\tconst allPending = this.deltaManager.inbound.toArray();\n\t\tfor (const pending of allPending) {\n\t\t\tthis.trackPending(pending);\n\t\t}\n\n\t\t// We are intentionally directly listening to the \"op\" to inspect system ops as well.\n\t\t// If we do not observe system ops, we are likely to hit 0x296 assert when system ops\n\t\t// precedes start of incomplete batch.\n\t\tthis.deltaManager.on(\"op\", (message) => this.afterOpProcessing(message));\n\t}\n\n\t/**\n\t * The only public function in this class - called when we processed an op,\n\t * to make decision if op processing should be paused or not after that.\n\t */\n\tpublic afterOpProcessing(message: ISequencedDocumentMessage) {\n\t\tassert(\n\t\t\t!this.localPaused,\n\t\t\t0x294 /* \"can't have op processing paused if we are processing an op\" */,\n\t\t);\n\n\t\t// If the inbound queue is ever empty, nothing to do!\n\t\tif (this.deltaManager.inbound.length === 0) {\n\t\t\tassert(\n\t\t\t\tthis.pauseSequenceNumber === undefined,\n\t\t\t\t0x295 /* \"there should be no pending batch if we have no ops\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// The queue is\n\t\t// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:\n\t\t// - here (processing ops until reaching start of incomplete batch)\n\t\t// - in trackPending(), when queue was empty and start of batch showed up.\n\t\t// 2. resumed when batch end comes in (in trackPending())\n\n\t\t// do we have incomplete batch to worry about?\n\t\tif (this.pauseSequenceNumber !== undefined) {\n\t\t\tif (message.sequenceNumber >= this.pauseSequenceNumber) {\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t// Former assert 0x296\n\t\t\t\t\t\"Incomplete batch\",\n\t\t\t\t\t\"ScheduleManager\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: message.type,\n\t\t\t\t\t\tcontentType: typeof message.contents,\n\t\t\t\t\t\tbatch: (message.metadata as IBatchMetadata | undefined)?.batch,\n\t\t\t\t\t\tcompression: message.compression,\n\t\t\t\t\t\tpauseSeqNum: this.pauseSequenceNumber,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!\n\t\t\tif (message.sequenceNumber + 1 === this.pauseSequenceNumber) {\n\t\t\t\tthis.pauseQueue();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate pauseQueue() {\n\t\tassert(!this.localPaused, 0x297 /* \"always called from resumed state\" */);\n\t\tthis.localPaused = true;\n\t\tthis.timePaused = performance.now();\n\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\tthis.deltaManager.inbound.pause();\n\t}\n\n\tprivate resumeQueue(startBatch: number, messageEndBatch: ISequencedDocumentMessage) {\n\t\tconst endBatch = messageEndBatch.sequenceNumber;\n\t\tconst duration = this.localPaused ? performance.now() - this.timePaused : undefined;\n\n\t\tthis.batchCount++;\n\t\tif (this.batchCount % 1000 === 1) {\n\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"BatchStats\",\n\t\t\t\tsequenceNumber: endBatch,\n\t\t\t\tlength: endBatch - startBatch + 1,\n\t\t\t\tmsnDistance: endBatch - messageEndBatch.minimumSequenceNumber,\n\t\t\t\tduration,\n\t\t\t\tbatchCount: this.batchCount,\n\t\t\t\tinterrupted: this.localPaused,\n\t\t\t});\n\t\t}\n\n\t\t// Return early if no change in value\n\t\tif (!this.localPaused) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.localPaused = false;\n\n\t\tthis.deltaManager.inbound.resume();\n\t}\n\n\t/**\n\t * Called for each incoming op (i.e. inbound \"push\" notification)\n\t */\n\tprivate trackPending(message: ISequencedDocumentMessage) {\n\t\tassert(\n\t\t\tthis.deltaManager.inbound.length !== 0,\n\t\t\t0x298 /* \"we have something in the queue that generates this event\" */,\n\t\t);\n\n\t\tassert(\n\t\t\t(this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined),\n\t\t\t0x299 /* \"non-synchronized state\" */,\n\t\t);\n\n\t\tconst metadata = message.metadata as IRuntimeMessageMetadata;\n\t\t// batchMetadata will be true for the message that starts a batch, false for the one that ends it, and\n\t\t// undefined for all other messages.\n\t\tconst batchMetadata = metadata?.batch;\n\n\t\t// Protocol messages are never part of a runtime batch of messages\n\t\tif (!isRuntimeMessage(message)) {\n\t\t\t// Protocol messages should never show up in the middle of the batch!\n\t\t\tif (this.currentBatchClientId !== undefined) {\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t\"Received a system message during batch processing\", // Formerly known as assert 0x29a\n\t\t\t\t\t\"trackPending\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\tthis.currentBatchClientId === null ? \"null\" : this.currentBatchClientId,\n\t\t\t\t\t\tpauseSequenceNumber: this.pauseSequenceNumber,\n\t\t\t\t\t\tlocalBatch: this.currentBatchClientId === this.getClientId(),\n\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tassert(batchMetadata === undefined, 0x29b /* \"system op in a batch?\" */);\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29c /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.currentBatchClientId === undefined && batchMetadata === undefined) {\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29d /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// If we got here, the message is part of a batch. Either starting, in progress, or ending.\n\n\t\t// If this is not the start of the batch, error out if the message was sent by a client other than the one that\n\t\t// started the current batch (it should not be possible for ops from other clients to get interleaved with a batch).\n\t\tif (\n\t\t\tthis.currentBatchClientId !== undefined &&\n\t\t\tthis.currentBatchClientId !== message.clientId\n\t\t) {\n\t\t\tthrow new DataCorruptionError(\"OpBatchIncomplete\", {\n\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\tbatchClientId:\n\t\t\t\t\tthis.currentBatchClientId === null ? \"null\" : this.currentBatchClientId,\n\t\t\t\tpauseSequenceNumber: this.pauseSequenceNumber,\n\t\t\t\tlocalBatch: this.currentBatchClientId === this.getClientId(),\n\t\t\t\tlocalMessage: message.clientId === this.getClientId(),\n\t\t\t\t...extractSafePropertiesFromMessage(message),\n\t\t\t});\n\t\t}\n\n\t\t// The queue is\n\t\t// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:\n\t\t// - in afterOpProcessing() - processing ops until reaching start of incomplete batch\n\t\t// - here, when queue was empty and start of batch showed up (batchMetadata === true below).\n\t\t// 2. resumed when batch end comes in (batchMetadata === false below)\n\n\t\tif (batchMetadata) {\n\t\t\tassert(\n\t\t\t\tthis.currentBatchClientId === undefined,\n\t\t\t\t0x29e /* \"there can't be active batch\" */,\n\t\t\t);\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29f /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\tthis.pauseSequenceNumber = message.sequenceNumber;\n\t\t\t// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n\t\t\tthis.currentBatchClientId = message.clientId as string;\n\t\t\t// Start of the batch\n\t\t\t// Only pause processing if queue has no other ops!\n\t\t\t// If there are any other ops in the queue, processing will be stopped when they are processed!\n\t\t\tif (this.deltaManager.inbound.length === 1) {\n\t\t\t\tthis.pauseQueue();\n\t\t\t}\n\t\t} else if (batchMetadata === false) {\n\t\t\tassert(\n\t\t\t\tthis.pauseSequenceNumber !== undefined,\n\t\t\t\t0x2a0 /* \"batch presence was validated above\" */,\n\t\t\t);\n\t\t\t// Batch is complete, we can process it!\n\t\t\tthis.resumeQueue(this.pauseSequenceNumber, message);\n\t\t\tthis.pauseSequenceNumber = undefined;\n\t\t\tthis.currentBatchClientId = undefined;\n\t\t} else {\n\t\t\t// Continuation of current batch. Do nothing\n\t\t\tassert(this.currentBatchClientId !== undefined, 0x2a1 /* \"logic error\" */);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scheduleManager.js","sourceRoot":"","sources":["../src/scheduleManager.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 */\nimport { EventEmitter } from \"events\";\nimport { IDeltaManager } from \"@fluidframework/container-definitions\";\nimport { IDocumentMessage, ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { ITelemetryLoggerExt, createChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { assert, performance } from \"@fluidframework/common-utils\";\nimport { isRuntimeMessage } from \"@fluidframework/driver-utils\";\nimport {\n\tDataCorruptionError,\n\tDataProcessingError,\n\textractSafePropertiesFromMessage,\n} from \"@fluidframework/container-utils\";\nimport { DeltaScheduler } from \"./deltaScheduler\";\nimport { pkgVersion } from \"./packageVersion\";\nimport { IBatchMetadata } from \"./metadata\";\n\ntype IRuntimeMessageMetadata =\n\t| undefined\n\t| {\n\t\t\tbatch?: boolean;\n\t };\n\n/**\n * This class has the following responsibilities:\n *\n * 1. It tracks batches as we process ops and raises \"batchBegin\" and \"batchEnd\" events.\n * As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)\n *\n * 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch\n * unless all ops of the batch are in.\n */\nexport class ScheduleManager {\n\tprivate readonly deltaScheduler: DeltaScheduler;\n\tprivate batchClientId: string | undefined;\n\tprivate hitError = false;\n\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,\n\t\tprivate readonly emitter: EventEmitter,\n\t\treadonly getClientId: () => string | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t) {\n\t\tthis.deltaScheduler = new DeltaScheduler(\n\t\t\tthis.deltaManager,\n\t\t\tcreateChildLogger({ logger: this.logger, namespace: \"DeltaScheduler\" }),\n\t\t);\n\t\tvoid new ScheduleManagerCore(deltaManager, getClientId, logger);\n\t}\n\n\tpublic beforeOpProcessing(message: ISequencedDocumentMessage) {\n\t\tif (this.batchClientId !== message.clientId) {\n\t\t\tassert(\n\t\t\t\tthis.batchClientId === undefined,\n\t\t\t\t0x2a2 /* \"Batch is interrupted by other client op. Should be caught by trackPending()\" */,\n\t\t\t);\n\n\t\t\t// This could be the beginning of a new batch or an individual message.\n\t\t\tthis.emitter.emit(\"batchBegin\", message);\n\t\t\tthis.deltaScheduler.batchBegin(message);\n\n\t\t\tconst batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;\n\t\t\t// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n\t\t\tthis.batchClientId = batch ? (message.clientId as string) : undefined;\n\t\t}\n\t}\n\n\tpublic afterOpProcessing(error: any | undefined, message: ISequencedDocumentMessage) {\n\t\t// If this is no longer true, we need to revisit what we do where we set this.hitError.\n\t\tassert(!this.hitError, 0x2a3 /* \"container should be closed on any error\" */);\n\n\t\tif (error) {\n\t\t\t// We assume here that loader will close container and stop processing all future ops.\n\t\t\t// This is implicit dependency. If this flow changes, this code might no longer be correct.\n\t\t\tthis.hitError = true;\n\t\t\tthis.batchClientId = undefined;\n\t\t\tthis.emitter.emit(\"batchEnd\", error, message);\n\t\t\tthis.deltaScheduler.batchEnd(message);\n\t\t\treturn;\n\t\t}\n\n\t\tconst batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;\n\t\t// If no batchClientId has been set then we're in an individual batch. Else, if we get\n\t\t// batch end metadata, this is end of the current batch.\n\t\tif (this.batchClientId === undefined || batch === false) {\n\t\t\tthis.batchClientId = undefined;\n\t\t\tthis.emitter.emit(\"batchEnd\", undefined, message);\n\t\t\tthis.deltaScheduler.batchEnd(message);\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/**\n * This class controls pausing and resuming of inbound queue to ensure that we never\n * start processing ops in a batch IF we do not have all ops in the batch.\n */\nclass ScheduleManagerCore {\n\tprivate pauseSequenceNumber: number | undefined;\n\tprivate currentBatchClientId: string | undefined;\n\tprivate localPaused = false;\n\tprivate timePaused = 0;\n\tprivate batchCount = 0;\n\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,\n\t\tprivate readonly getClientId: () => string | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t) {\n\t\t// Listen for delta manager sends and add batch metadata to messages\n\t\tthis.deltaManager.on(\"prepareSend\", (messages: IDocumentMessage[]) => {\n\t\t\tif (messages.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// First message will have the batch flag set to true if doing a batched send\n\t\t\tconst firstMessageMetadata = messages[0].metadata as IRuntimeMessageMetadata;\n\t\t\tif (!firstMessageMetadata?.batch) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If the batch contains only a single op, clear the batch flag.\n\t\t\tif (messages.length === 1) {\n\t\t\t\tdelete firstMessageMetadata.batch;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the batch flag to false on the last message to indicate the end of the send batch\n\t\t\tconst lastMessage = messages[messages.length - 1];\n\t\t\t// TODO: It's not clear if this shallow clone is required, as opposed to just setting \"batch\" to false.\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n\t\t\tlastMessage.metadata = { ...(lastMessage.metadata as any), batch: false };\n\t\t});\n\n\t\t// Listen for updates and peek at the inbound\n\t\tthis.deltaManager.inbound.on(\"push\", (message: ISequencedDocumentMessage) => {\n\t\t\tthis.trackPending(message);\n\t\t});\n\n\t\t// Start with baseline - empty inbound queue.\n\t\tassert(!this.localPaused, 0x293 /* \"initial state\" */);\n\n\t\tconst allPending = this.deltaManager.inbound.toArray();\n\t\tfor (const pending of allPending) {\n\t\t\tthis.trackPending(pending);\n\t\t}\n\n\t\t// We are intentionally directly listening to the \"op\" to inspect system ops as well.\n\t\t// If we do not observe system ops, we are likely to hit 0x296 assert when system ops\n\t\t// precedes start of incomplete batch.\n\t\tthis.deltaManager.on(\"op\", (message) => this.afterOpProcessing(message));\n\t}\n\n\t/**\n\t * The only public function in this class - called when we processed an op,\n\t * to make decision if op processing should be paused or not after that.\n\t */\n\tpublic afterOpProcessing(message: ISequencedDocumentMessage) {\n\t\tassert(\n\t\t\t!this.localPaused,\n\t\t\t0x294 /* \"can't have op processing paused if we are processing an op\" */,\n\t\t);\n\n\t\t// If the inbound queue is ever empty, nothing to do!\n\t\tif (this.deltaManager.inbound.length === 0) {\n\t\t\tassert(\n\t\t\t\tthis.pauseSequenceNumber === undefined,\n\t\t\t\t0x295 /* \"there should be no pending batch if we have no ops\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// The queue is\n\t\t// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:\n\t\t// - here (processing ops until reaching start of incomplete batch)\n\t\t// - in trackPending(), when queue was empty and start of batch showed up.\n\t\t// 2. resumed when batch end comes in (in trackPending())\n\n\t\t// do we have incomplete batch to worry about?\n\t\tif (this.pauseSequenceNumber !== undefined) {\n\t\t\tif (message.sequenceNumber >= this.pauseSequenceNumber) {\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t// Former assert 0x296\n\t\t\t\t\t\"Incomplete batch\",\n\t\t\t\t\t\"ScheduleManager\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: message.type,\n\t\t\t\t\t\tcontentType: typeof message.contents,\n\t\t\t\t\t\tbatch: (message.metadata as IBatchMetadata | undefined)?.batch,\n\t\t\t\t\t\tcompression: message.compression,\n\t\t\t\t\t\tpauseSeqNum: this.pauseSequenceNumber,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!\n\t\t\tif (message.sequenceNumber + 1 === this.pauseSequenceNumber) {\n\t\t\t\tthis.pauseQueue();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate pauseQueue() {\n\t\tassert(!this.localPaused, 0x297 /* \"always called from resumed state\" */);\n\t\tthis.localPaused = true;\n\t\tthis.timePaused = performance.now();\n\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\tthis.deltaManager.inbound.pause();\n\t}\n\n\tprivate resumeQueue(startBatch: number, messageEndBatch: ISequencedDocumentMessage) {\n\t\tconst endBatch = messageEndBatch.sequenceNumber;\n\t\tconst duration = this.localPaused ? performance.now() - this.timePaused : undefined;\n\n\t\tthis.batchCount++;\n\t\tif (this.batchCount % 1000 === 1) {\n\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"BatchStats\",\n\t\t\t\tsequenceNumber: endBatch,\n\t\t\t\tlength: endBatch - startBatch + 1,\n\t\t\t\tmsnDistance: endBatch - messageEndBatch.minimumSequenceNumber,\n\t\t\t\tduration,\n\t\t\t\tbatchCount: this.batchCount,\n\t\t\t\tinterrupted: this.localPaused,\n\t\t\t});\n\t\t}\n\n\t\t// Return early if no change in value\n\t\tif (!this.localPaused) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.localPaused = false;\n\n\t\tthis.deltaManager.inbound.resume();\n\t}\n\n\t/**\n\t * Called for each incoming op (i.e. inbound \"push\" notification)\n\t */\n\tprivate trackPending(message: ISequencedDocumentMessage) {\n\t\tassert(\n\t\t\tthis.deltaManager.inbound.length !== 0,\n\t\t\t0x298 /* \"we have something in the queue that generates this event\" */,\n\t\t);\n\n\t\tassert(\n\t\t\t(this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined),\n\t\t\t0x299 /* \"non-synchronized state\" */,\n\t\t);\n\n\t\tconst metadata = message.metadata as IRuntimeMessageMetadata;\n\t\t// batchMetadata will be true for the message that starts a batch, false for the one that ends it, and\n\t\t// undefined for all other messages.\n\t\tconst batchMetadata = metadata?.batch;\n\n\t\t// Protocol messages are never part of a runtime batch of messages\n\t\tif (!isRuntimeMessage(message)) {\n\t\t\t// Protocol messages should never show up in the middle of the batch!\n\t\t\tif (this.currentBatchClientId !== undefined) {\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t\"Received a system message during batch processing\", // Formerly known as assert 0x29a\n\t\t\t\t\t\"trackPending\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\tthis.currentBatchClientId === null ? \"null\" : this.currentBatchClientId,\n\t\t\t\t\t\tpauseSequenceNumber: this.pauseSequenceNumber,\n\t\t\t\t\t\tlocalBatch: this.currentBatchClientId === this.getClientId(),\n\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tassert(batchMetadata === undefined, 0x29b /* \"system op in a batch?\" */);\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29c /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.currentBatchClientId === undefined && batchMetadata === undefined) {\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29d /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// If we got here, the message is part of a batch. Either starting, in progress, or ending.\n\n\t\t// If this is not the start of the batch, error out if the message was sent by a client other than the one that\n\t\t// started the current batch (it should not be possible for ops from other clients to get interleaved with a batch).\n\t\tif (\n\t\t\tthis.currentBatchClientId !== undefined &&\n\t\t\tthis.currentBatchClientId !== message.clientId\n\t\t) {\n\t\t\tthrow new DataCorruptionError(\"OpBatchIncomplete\", {\n\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\tbatchClientId:\n\t\t\t\t\tthis.currentBatchClientId === null ? \"null\" : this.currentBatchClientId,\n\t\t\t\tpauseSequenceNumber: this.pauseSequenceNumber,\n\t\t\t\tlocalBatch: this.currentBatchClientId === this.getClientId(),\n\t\t\t\tlocalMessage: message.clientId === this.getClientId(),\n\t\t\t\t...extractSafePropertiesFromMessage(message),\n\t\t\t});\n\t\t}\n\n\t\t// The queue is\n\t\t// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:\n\t\t// - in afterOpProcessing() - processing ops until reaching start of incomplete batch\n\t\t// - here, when queue was empty and start of batch showed up (batchMetadata === true below).\n\t\t// 2. resumed when batch end comes in (batchMetadata === false below)\n\n\t\tif (batchMetadata) {\n\t\t\tassert(\n\t\t\t\tthis.currentBatchClientId === undefined,\n\t\t\t\t0x29e /* \"there can't be active batch\" */,\n\t\t\t);\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29f /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\tthis.pauseSequenceNumber = message.sequenceNumber;\n\t\t\t// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n\t\t\tthis.currentBatchClientId = message.clientId as string;\n\t\t\t// Start of the batch\n\t\t\t// Only pause processing if queue has no other ops!\n\t\t\t// If there are any other ops in the queue, processing will be stopped when they are processed!\n\t\t\tif (this.deltaManager.inbound.length === 1) {\n\t\t\t\tthis.pauseQueue();\n\t\t\t}\n\t\t} else if (batchMetadata === false) {\n\t\t\tassert(\n\t\t\t\tthis.pauseSequenceNumber !== undefined,\n\t\t\t\t0x2a0 /* \"batch presence was validated above\" */,\n\t\t\t);\n\t\t\t// Batch is complete, we can process it!\n\t\t\tthis.resumeQueue(this.pauseSequenceNumber, message);\n\t\t\tthis.pauseSequenceNumber = undefined;\n\t\t\tthis.currentBatchClientId = undefined;\n\t\t} else {\n\t\t\t// Continuation of current batch. Do nothing\n\t\t\tassert(this.currentBatchClientId !== undefined, 0x2a1 /* \"logic error\" */);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -7,6 +7,7 @@ import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
|
|
|
7
7
|
import { TypedEventEmitter } from "@fluidframework/common-utils";
|
|
8
8
|
import { IDeltaManager } from "@fluidframework/container-definitions";
|
|
9
9
|
import { IClient, IQuorumClients } from "@fluidframework/protocol-definitions";
|
|
10
|
+
import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
10
11
|
export declare type ImmutablePrimitives = undefined | null | boolean | string | number | Function;
|
|
11
12
|
export declare type Immutable<T> = T extends ImmutablePrimitives ? T : T extends (infer A)[] ? readonly Immutable<A>[] : T extends Map<infer K, infer V> ? ReadonlyMap<Immutable<K>, Immutable<V>> : T extends Set<infer V> ? ReadonlySet<Immutable<V>> : {
|
|
12
13
|
readonly [K in keyof T]: Immutable<T[K]>;
|
|
@@ -64,7 +65,7 @@ export declare class OrderedClientCollection extends TypedEventEmitter<IOrderedC
|
|
|
64
65
|
private readonly logger;
|
|
65
66
|
get count(): number;
|
|
66
67
|
get oldestClient(): ILinkedClient | undefined;
|
|
67
|
-
constructor(logger:
|
|
68
|
+
constructor(logger: ITelemetryBaseLogger, deltaManager: Pick<IDeltaManager<unknown, unknown>, "lastSequenceNumber">, quorum: Pick<IQuorumClients, "getMembers" | "on">);
|
|
68
69
|
private addClient;
|
|
69
70
|
private removeClient;
|
|
70
71
|
/** Returns an array of all clients being tracked in order from oldest to newest. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orderedClientElection.d.ts","sourceRoot":"","sources":["../../src/summary/orderedClientElection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAqB,MAAM,iCAAiC,CAAC;AACzF,OAAO,EAAU,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAoB,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"orderedClientElection.d.ts","sourceRoot":"","sources":["../../src/summary/orderedClientElection.ts"],"names":[],"mappings":"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"}
|
|
@@ -69,7 +69,7 @@ class OrderedClientCollection extends common_utils_1.TypedEventEmitter {
|
|
|
69
69
|
const newClient = {
|
|
70
70
|
clientId,
|
|
71
71
|
sequenceNumber: client.sequenceNumber,
|
|
72
|
-
client:
|
|
72
|
+
client: { ...client.client },
|
|
73
73
|
olderClient: currClient,
|
|
74
74
|
youngerClient: currClient.youngerClient,
|
|
75
75
|
};
|
|
@@ -156,13 +156,13 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
156
156
|
}
|
|
157
157
|
else {
|
|
158
158
|
// Override the initially elected client with the initial state.
|
|
159
|
-
if (
|
|
159
|
+
if (initialClient?.clientId !== initialState.electedClientId) {
|
|
160
160
|
// Cannot find initially elected client, so elect undefined.
|
|
161
161
|
this.logger.sendErrorEvent({
|
|
162
162
|
eventName: "InitialElectedClientNotFound",
|
|
163
163
|
electionSequenceNumber: initialState.electionSequenceNumber,
|
|
164
164
|
expectedClientId: initialState.electedClientId,
|
|
165
|
-
electedClientId: initialClient
|
|
165
|
+
electedClientId: initialClient?.clientId,
|
|
166
166
|
clientCount: orderedClientCollection.count,
|
|
167
167
|
});
|
|
168
168
|
}
|
|
@@ -173,7 +173,7 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
173
173
|
eventName: "InitialElectedClientIneligible",
|
|
174
174
|
electionSequenceNumber: initialState.electionSequenceNumber,
|
|
175
175
|
expectedClientId: initialState.electedClientId,
|
|
176
|
-
electedClientId: initialClient
|
|
176
|
+
electedClientId: initialClient?.clientId,
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
179
|
this._electedParent = initialParent;
|
|
@@ -236,7 +236,7 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
236
236
|
*/
|
|
237
237
|
tryElectingClient(client, sequenceNumber) {
|
|
238
238
|
let change = false;
|
|
239
|
-
const isSummarizerClient =
|
|
239
|
+
const isSummarizerClient = client?.client.details.type === summarizerClientElection_1.summarizerClientType;
|
|
240
240
|
const prevClient = this._electedClient;
|
|
241
241
|
if (this._electedClient !== client) {
|
|
242
242
|
// Changing the elected client. Record the sequence number and note that we have to fire an event.
|
|
@@ -281,11 +281,10 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
281
281
|
* @param sequenceNumber - sequence number when client was added
|
|
282
282
|
*/
|
|
283
283
|
addClient(client, sequenceNumber) {
|
|
284
|
-
var _a;
|
|
285
284
|
if (this.isEligibleFn(client)) {
|
|
286
285
|
this._eligibleCount++;
|
|
287
286
|
const newClientIsSummarizer = client.client.details.type === summarizerClientElection_1.summarizerClientType;
|
|
288
|
-
const electedClientIsSummarizer =
|
|
287
|
+
const electedClientIsSummarizer = this._electedClient?.client.details.type === summarizerClientElection_1.summarizerClientType;
|
|
289
288
|
// Note that we allow a summarizer client to supersede an interactive client as elected client.
|
|
290
289
|
if (this._electedClient === undefined ||
|
|
291
290
|
(!electedClientIsSummarizer && newClientIsSummarizer)) {
|
|
@@ -304,7 +303,6 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
304
303
|
* @param sequenceNumber - sequence number when client was removed
|
|
305
304
|
*/
|
|
306
305
|
removeClient(client, sequenceNumber) {
|
|
307
|
-
var _a, _b, _c, _d, _e;
|
|
308
306
|
if (this.isEligibleFn(client)) {
|
|
309
307
|
this._eligibleCount--;
|
|
310
308
|
if (this._electedClient === client) {
|
|
@@ -320,7 +318,8 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
320
318
|
else {
|
|
321
319
|
// 2. The _electedClient is an interactive client that has left the quorum.
|
|
322
320
|
// Automatically shift to next oldest client.
|
|
323
|
-
const nextClient =
|
|
321
|
+
const nextClient = this.findFirstEligibleParent(this._electedParent?.youngerClient) ??
|
|
322
|
+
this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
|
|
324
323
|
this.tryElectingClient(nextClient, sequenceNumber);
|
|
325
324
|
}
|
|
326
325
|
}
|
|
@@ -328,10 +327,11 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
328
327
|
// Removing the _electedParent (but not _electedClient).
|
|
329
328
|
// Shift to the next oldest parent, but do not replace the _electedClient,
|
|
330
329
|
// which is a summarizer that is still doing work.
|
|
331
|
-
if (
|
|
330
|
+
if (this._electedClient?.client.details.type !== summarizerClientElection_1.summarizerClientType) {
|
|
332
331
|
throw new container_utils_1.UsageError("Elected client should be a summarizer client 2");
|
|
333
332
|
}
|
|
334
|
-
const nextParent =
|
|
333
|
+
const nextParent = this.findFirstEligibleParent(this._electedParent?.youngerClient) ??
|
|
334
|
+
this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
|
|
335
335
|
this.tryElectingParent(nextParent, sequenceNumber);
|
|
336
336
|
}
|
|
337
337
|
}
|
|
@@ -344,8 +344,8 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
344
344
|
* or if the current summarizer is not responsive and we want to stop it and spawn a new one.
|
|
345
345
|
*/
|
|
346
346
|
incrementElectedClient(sequenceNumber) {
|
|
347
|
-
|
|
348
|
-
|
|
347
|
+
const nextClient = this.findFirstEligibleParent(this._electedParent?.youngerClient) ??
|
|
348
|
+
this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
|
|
349
349
|
if (this._electedClient === undefined || this._electedClient === this._electedParent) {
|
|
350
350
|
this.tryElectingClient(nextClient, sequenceNumber);
|
|
351
351
|
}
|
|
@@ -371,15 +371,14 @@ class OrderedClientElection extends common_utils_1.TypedEventEmitter {
|
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
373
|
peekNextElectedClient() {
|
|
374
|
-
|
|
375
|
-
|
|
374
|
+
return (this.findFirstEligibleParent(this._electedParent?.youngerClient) ??
|
|
375
|
+
this.findFirstEligibleParent(this.orderedClientCollection.oldestClient));
|
|
376
376
|
}
|
|
377
377
|
serialize() {
|
|
378
|
-
var _a, _b;
|
|
379
378
|
return {
|
|
380
379
|
electionSequenceNumber: this.electionSequenceNumber,
|
|
381
|
-
electedClientId:
|
|
382
|
-
electedParentId:
|
|
380
|
+
electedClientId: this.electedClient?.clientId,
|
|
381
|
+
electedParentId: this.electedParent?.clientId,
|
|
383
382
|
};
|
|
384
383
|
}
|
|
385
384
|
}
|