@fluidframework/container-runtime 2.1.0-276326 → 2.1.0-281041
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -21
- package/api-extractor/api-extractor.current.json +5 -0
- package/api-extractor/api-extractor.legacy.json +1 -1
- package/api-extractor.json +1 -1
- package/api-report/container-runtime.legacy.public.api.md +9 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
- package/dist/blobManager/blobManager.d.ts.map +1 -0
- package/dist/{blobManager.js → blobManager/blobManager.js} +42 -83
- package/dist/blobManager/blobManager.js.map +1 -0
- package/dist/blobManager/blobManagerSnapSum.d.ts +30 -0
- package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -0
- package/dist/blobManager/blobManagerSnapSum.js +82 -0
- package/dist/blobManager/blobManagerSnapSum.js.map +1 -0
- package/dist/blobManager/index.d.ts +7 -0
- package/dist/blobManager/index.d.ts.map +1 -0
- package/dist/blobManager/index.js +16 -0
- package/dist/blobManager/index.js.map +1 -0
- package/dist/channelCollection.d.ts +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +40 -8
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +15 -10
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +199 -162
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +5 -0
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +16 -5
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +16 -10
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +4 -2
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +12 -0
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +3 -2
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +6 -6
- package/dist/gc/gcTelemetry.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/legacy.d.ts +1 -1
- package/dist/metadata.d.ts +7 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +6 -0
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +8 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +37 -16
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +1 -1
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +12 -8
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +14 -11
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +11 -6
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +22 -6
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +43 -21
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +22 -6
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +59 -9
- package/dist/opLifecycle/remoteMessageProcessor.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 +39 -13
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +98 -33
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/scheduleManager.js +4 -0
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/index.d.ts +1 -1
- package/dist/summary/index.d.ts.map +1 -1
- package/dist/summary/index.js +1 -2
- package/dist/summary/index.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -0
- package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts +0 -1
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js +7 -4
- package/dist/summary/summaryFormat.js.map +1 -1
- package/internal.d.ts +1 -1
- package/legacy.d.ts +1 -1
- package/lib/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
- package/lib/blobManager/blobManager.d.ts.map +1 -0
- package/lib/{blobManager.js → blobManager/blobManager.js} +40 -83
- package/lib/blobManager/blobManager.js.map +1 -0
- package/lib/blobManager/blobManagerSnapSum.d.ts +30 -0
- package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -0
- package/lib/blobManager/blobManagerSnapSum.js +75 -0
- package/lib/blobManager/blobManagerSnapSum.js.map +1 -0
- package/lib/blobManager/index.d.ts +7 -0
- package/lib/blobManager/index.d.ts.map +1 -0
- package/lib/blobManager/index.js +7 -0
- package/lib/blobManager/index.js.map +1 -0
- package/lib/channelCollection.d.ts +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +40 -8
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +15 -10
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +149 -112
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +5 -0
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +17 -6
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +16 -10
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +4 -2
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +12 -0
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +3 -2
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +6 -6
- package/lib/gc/gcTelemetry.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/legacy.d.ts +1 -1
- package/lib/metadata.d.ts +7 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js +4 -1
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +8 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +35 -15
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +1 -1
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +12 -8
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +14 -11
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +11 -6
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +22 -6
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +44 -22
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +22 -6
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +57 -7
- package/lib/opLifecycle/remoteMessageProcessor.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 +39 -13
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +99 -34
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/scheduleManager.js +4 -0
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/index.d.ts +1 -1
- package/lib/summary/index.d.ts.map +1 -1
- package/lib/summary/index.js +1 -1
- package/lib/summary/index.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -0
- package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts +0 -1
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryFormat.js +5 -2
- package/lib/summary/summaryFormat.js.map +1 -1
- package/package.json +49 -34
- package/src/{blobManager.ts → blobManager/blobManager.ts} +57 -123
- package/src/blobManager/blobManagerSnapSum.ts +133 -0
- package/src/blobManager/index.ts +19 -0
- package/src/channelCollection.ts +48 -11
- package/src/containerRuntime.ts +213 -158
- package/src/dataStoreContext.ts +30 -6
- package/src/gc/garbageCollection.ts +17 -12
- package/src/gc/gcDefinitions.ts +7 -2
- package/src/gc/gcHelpers.ts +18 -6
- package/src/gc/gcTelemetry.ts +20 -8
- package/src/index.ts +1 -1
- package/src/metadata.ts +11 -1
- package/src/opLifecycle/README.md +0 -8
- package/src/opLifecycle/batchManager.ts +46 -16
- package/src/opLifecycle/definitions.ts +1 -1
- package/src/opLifecycle/index.ts +8 -1
- package/src/opLifecycle/opCompressor.ts +12 -8
- package/src/opLifecycle/opGroupingManager.ts +14 -11
- package/src/opLifecycle/opSplitter.ts +10 -6
- package/src/opLifecycle/outbox.ts +64 -26
- package/src/opLifecycle/remoteMessageProcessor.ts +84 -11
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +177 -60
- package/src/scheduleManager.ts +6 -2
- package/src/summary/README.md +81 -0
- package/src/summary/index.ts +0 -1
- package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
- package/src/summary/summaryFormat.ts +4 -2
- package/src/summary/summaryFormats.md +69 -8
- package/tsconfig.json +0 -1
- package/dist/blobManager.d.ts.map +0 -1
- package/dist/blobManager.js.map +0 -1
- package/lib/blobManager.d.ts.map +0 -1
- package/lib/blobManager.js.map +0 -1
- package/src/summary/images/appTree.png +0 -0
- package/src/summary/images/protocolAndAppTree.png +0 -0
- package/src/summary/images/summaryTree.png +0 -0
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
+
import { assert } from "@fluidframework/core-utils/internal";
|
|
5
6
|
import { MessageType, } from "@fluidframework/driver-definitions/internal";
|
|
6
7
|
import { ContainerMessageType, } from "../messageTypes.js";
|
|
8
|
+
import { asBatchMetadata } from "../metadata.js";
|
|
7
9
|
import { isGroupedBatch } from "./opGroupingManager.js";
|
|
8
10
|
import { isChunkedMessage } from "./opSplitter.js";
|
|
9
11
|
/**
|
|
@@ -17,6 +19,7 @@ export class RemoteMessageProcessor {
|
|
|
17
19
|
this.opSplitter = opSplitter;
|
|
18
20
|
this.opDecompressor = opDecompressor;
|
|
19
21
|
this.opGroupingManager = opGroupingManager;
|
|
22
|
+
this.processorBatch = [];
|
|
20
23
|
}
|
|
21
24
|
get partialMessages() {
|
|
22
25
|
return this.opSplitter.chunks;
|
|
@@ -25,7 +28,7 @@ export class RemoteMessageProcessor {
|
|
|
25
28
|
this.opSplitter.clearPartialChunks(clientId);
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
|
-
* Ungroups and Unchunks the runtime ops
|
|
31
|
+
* Ungroups and Unchunks the runtime ops of a batch received over the wire
|
|
29
32
|
* @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized
|
|
30
33
|
* (grouped, compressed, and/or chunked).
|
|
31
34
|
* Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure
|
|
@@ -40,9 +43,8 @@ export class RemoteMessageProcessor {
|
|
|
40
43
|
* 3. If grouped, ungroup the message
|
|
41
44
|
* For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound
|
|
42
45
|
*
|
|
43
|
-
* @returns the unchunked, decompressed, ungrouped, unpacked
|
|
44
|
-
*
|
|
45
|
-
* a singleton array [remoteMessageCopy] is returned
|
|
46
|
+
* @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch
|
|
47
|
+
* or undefined if the batch is not yet complete.
|
|
46
48
|
*/
|
|
47
49
|
process(remoteMessageCopy) {
|
|
48
50
|
let message = remoteMessageCopy;
|
|
@@ -51,7 +53,7 @@ export class RemoteMessageProcessor {
|
|
|
51
53
|
const chunkProcessingResult = this.opSplitter.processChunk(message);
|
|
52
54
|
// Only continue further if current chunk is the final chunk
|
|
53
55
|
if (!chunkProcessingResult.isFinalChunk) {
|
|
54
|
-
return
|
|
56
|
+
return;
|
|
55
57
|
}
|
|
56
58
|
// This message will always be compressed
|
|
57
59
|
message = chunkProcessingResult.message;
|
|
@@ -67,11 +69,59 @@ export class RemoteMessageProcessor {
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
if (isGroupedBatch(message)) {
|
|
70
|
-
|
|
72
|
+
// We should be awaiting a new batch (batchStartCsn undefined)
|
|
73
|
+
assert(this.batchStartCsn === undefined, "Grouped batch interrupting another batch");
|
|
74
|
+
assert(this.processorBatch.length === 0, "Processor batch should be empty on grouped batch");
|
|
75
|
+
return {
|
|
76
|
+
messages: this.opGroupingManager.ungroupOp(message).map(unpack),
|
|
77
|
+
batchStartCsn: message.clientSequenceNumber,
|
|
78
|
+
};
|
|
71
79
|
}
|
|
80
|
+
const batchStartCsn = this.getAndUpdateBatchStartCsn(message);
|
|
72
81
|
// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked
|
|
73
82
|
unpackRuntimeMessage(message);
|
|
74
|
-
|
|
83
|
+
this.processorBatch.push(message);
|
|
84
|
+
// this.batchStartCsn is undefined only if we have processed all messages in the batch.
|
|
85
|
+
// If it's still defined, we're still in the middle of a batch, so we return nothing, letting
|
|
86
|
+
// containerRuntime know that we're waiting for more messages to complete the batch.
|
|
87
|
+
if (this.batchStartCsn !== undefined) {
|
|
88
|
+
// batch not yet complete
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
const messages = [...this.processorBatch];
|
|
92
|
+
this.processorBatch.length = 0;
|
|
93
|
+
return {
|
|
94
|
+
messages,
|
|
95
|
+
batchStartCsn,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Based on pre-existing batch tracking info and the current message's batch metadata,
|
|
100
|
+
* this will return the starting CSN for this message's batch, and will also update
|
|
101
|
+
* the batch tracking info (this.batchStartCsn) based on whether we're still mid-batch.
|
|
102
|
+
*/
|
|
103
|
+
getAndUpdateBatchStartCsn(message) {
|
|
104
|
+
const batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;
|
|
105
|
+
if (this.batchStartCsn === undefined) {
|
|
106
|
+
// We are waiting for a new batch
|
|
107
|
+
assert(batchMetadataFlag !== false, "Unexpected batch end marker");
|
|
108
|
+
// Start of a new multi-message batch
|
|
109
|
+
if (batchMetadataFlag === true) {
|
|
110
|
+
this.batchStartCsn = message.clientSequenceNumber;
|
|
111
|
+
return this.batchStartCsn;
|
|
112
|
+
}
|
|
113
|
+
// Single-message batch (Since metadata flag is undefined)
|
|
114
|
+
// IMPORTANT: Leave this.batchStartCsn undefined, we're ready for the next batch now.
|
|
115
|
+
return message.clientSequenceNumber;
|
|
116
|
+
}
|
|
117
|
+
// We are in the middle or end of an existing multi-message batch. Return the current batchStartCsn
|
|
118
|
+
const batchStartCsn = this.batchStartCsn;
|
|
119
|
+
assert(batchMetadataFlag !== true, "Unexpected batch start marker");
|
|
120
|
+
if (batchMetadataFlag === false) {
|
|
121
|
+
// Batch end? Then get ready for the next batch to start
|
|
122
|
+
this.batchStartCsn = undefined;
|
|
123
|
+
}
|
|
124
|
+
return batchStartCsn;
|
|
75
125
|
}
|
|
76
126
|
}
|
|
77
127
|
/** Takes an incoming message and if the contents is a string, JSON.parse's it in place */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.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 {\n\tMessageType,\n\tISequencedDocumentMessage,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport {\n\tContainerMessageType,\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessageOrSystemMessage,\n\ttype InboundSequencedRecentlyAddedContainerRuntimeMessage,\n} from \"../messageTypes.js\";\n\nimport { OpDecompressor } from \"./opDecompressor.js\";\nimport { OpGroupingManager, isGroupedBatch } from \"./opGroupingManager.js\";\nimport { OpSplitter, isChunkedMessage } from \"./opSplitter.js\";\n\n/**\n * Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,\n * potentially across numerous inbound ops.\n *\n * @internal\n */\nexport class RemoteMessageProcessor {\n\tconstructor(\n\t\tprivate readonly opSplitter: OpSplitter,\n\t\tprivate readonly opDecompressor: OpDecompressor,\n\t\tprivate readonly opGroupingManager: OpGroupingManager,\n\t) {}\n\n\tpublic get partialMessages(): ReadonlyMap<string, string[]> {\n\t\treturn this.opSplitter.chunks;\n\t}\n\n\tpublic clearPartialMessagesFor(clientId: string) {\n\t\tthis.opSplitter.clearPartialChunks(clientId);\n\t}\n\n\t/**\n\t * Ungroups and Unchunks the runtime ops encapsulated by the single remoteMessage received over the wire\n\t * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized\n\t * (grouped, compressed, and/or chunked).\n\t * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure\n\t * depends on this object instance.\n\t * Note remoteMessageCopy.contents (and other object props) MUST not be modified,\n\t * but may be overwritten (as is the case with contents).\n\t *\n\t * Incoming messages will always have compression, chunking, and grouped batching happen in a defined order and that order cannot be changed.\n\t * When processing these messages, the order is:\n\t * 1. If chunked, process the chunk and only continue if this is a final chunk\n\t * 2. If compressed, decompress the message and store for further unrolling of the decompressed content\n\t * 3. If grouped, ungroup the message\n\t * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound\n\t *\n\t * @returns the unchunked, decompressed, ungrouped, unpacked SequencedContainerRuntimeMessages encapsulated in the remote message.\n\t * For ops that weren't virtualized (e.g. System ops that the ContainerRuntime will ultimately ignore),\n\t * a singleton array [remoteMessageCopy] is returned\n\t */\n\tpublic process(\n\t\tremoteMessageCopy: ISequencedDocumentMessage,\n\t): InboundSequencedContainerRuntimeMessageOrSystemMessage[] {\n\t\tlet message = remoteMessageCopy;\n\t\tensureContentsDeserialized(message);\n\n\t\tif (isChunkedMessage(message)) {\n\t\t\tconst chunkProcessingResult = this.opSplitter.processChunk(message);\n\t\t\t// Only continue further if current chunk is the final chunk\n\t\t\tif (!chunkProcessingResult.isFinalChunk) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\t// This message will always be compressed\n\t\t\tmessage = chunkProcessingResult.message;\n\t\t}\n\n\t\tif (this.opDecompressor.isCompressedMessage(message)) {\n\t\t\tthis.opDecompressor.decompressAndStore(message);\n\t\t}\n\n\t\tif (this.opDecompressor.currentlyUnrolling) {\n\t\t\tmessage = this.opDecompressor.unroll(message);\n\t\t\t// Need to unpack after unrolling if not a groupedBatch\n\t\t\tif (!isGroupedBatch(message)) {\n\t\t\t\tunpack(message);\n\t\t\t}\n\t\t}\n\n\t\tif (isGroupedBatch(message)) {\n\t\t\treturn this.opGroupingManager.ungroupOp(message).map(unpack);\n\t\t}\n\n\t\t// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked\n\t\tunpackRuntimeMessage(message);\n\t\treturn [message as InboundSequencedContainerRuntimeMessageOrSystemMessage];\n\t}\n}\n\n/** Takes an incoming message and if the contents is a string, JSON.parse's it in place */\nfunction ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void {\n\t// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!\n\t// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.\n\t// Old ops may contain empty string (I assume noops).\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t}\n}\n\n/**\n * For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.\n *\n * The return type illustrates the assumption that the message param\n * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns\n * (but there is no runtime validation of the 'type' or 'compatDetails' values).\n */\nfunction unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {\n\t// We assume the contents is an InboundContainerRuntimeMessage (the message is \"packed\")\n\tconst contents = message.contents as InboundContainerRuntimeMessage;\n\n\t// We're going to unpack message in-place (promoting those properties of contents up to message itself)\n\tconst messageUnpacked = message as InboundSequencedContainerRuntimeMessage;\n\n\tmessageUnpacked.type = contents.type;\n\tmessageUnpacked.contents = contents.contents;\n\tif (\"compatDetails\" in contents) {\n\t\t(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =\n\t\t\tcontents.compatDetails;\n\t}\n\treturn messageUnpacked;\n}\n\n/**\n * Unpacks runtime messages.\n *\n * @remarks This API makes no promises regarding backward-compatibility. This is internal API.\n * @param message - message (as it observed in storage / service)\n * @returns whether the given message was unpacked\n *\n * @internal\n */\nexport function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolean {\n\tif (message.type !== MessageType.Operation) {\n\t\t// Legacy format, but it's already \"unpacked\",\n\t\t// i.e. message.type is actually ContainerMessageType.\n\t\t// Or it's non-runtime message.\n\t\t// Nothing to do in such case.\n\t\treturn false;\n\t}\n\n\t// legacy op format?\n\t// TODO: Unsure if this is a real format we should be concerned with. There doesn't appear to be anything prepared to handle the address member.\n\tif (\n\t\t(message.contents as { address?: unknown }).address !== undefined &&\n\t\t(message.contents as { type?: unknown }).type === undefined\n\t) {\n\t\tmessage.type = ContainerMessageType.FluidDataStoreOp;\n\t} else {\n\t\t// new format\n\t\tunpack(message);\n\t}\n\n\treturn true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.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 { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tMessageType,\n\tISequencedDocumentMessage,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport {\n\tContainerMessageType,\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype InboundSequencedRecentlyAddedContainerRuntimeMessage,\n} from \"../messageTypes.js\";\nimport { asBatchMetadata } from \"../metadata.js\";\n\nimport { OpDecompressor } from \"./opDecompressor.js\";\nimport { OpGroupingManager, isGroupedBatch } from \"./opGroupingManager.js\";\nimport { OpSplitter, isChunkedMessage } from \"./opSplitter.js\";\n\n/**\n * Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,\n * potentially across numerous inbound ops.\n *\n * @internal\n */\nexport class RemoteMessageProcessor {\n\t/**\n\t * Client Sequence Number of the first message in the current batch being processed.\n\t * If undefined, we are expecting the next message to start a new batch.\n\t *\n\t * @remarks For chunked batches, this is the CSN of the \"representative\" chunk (the final chunk)\n\t */\n\tprivate batchStartCsn: number | undefined;\n\tprivate readonly processorBatch: InboundSequencedContainerRuntimeMessage[] = [];\n\n\tconstructor(\n\t\tprivate readonly opSplitter: OpSplitter,\n\t\tprivate readonly opDecompressor: OpDecompressor,\n\t\tprivate readonly opGroupingManager: OpGroupingManager,\n\t) {}\n\n\tpublic get partialMessages(): ReadonlyMap<string, string[]> {\n\t\treturn this.opSplitter.chunks;\n\t}\n\n\tpublic clearPartialMessagesFor(clientId: string) {\n\t\tthis.opSplitter.clearPartialChunks(clientId);\n\t}\n\n\t/**\n\t * Ungroups and Unchunks the runtime ops of a batch received over the wire\n\t * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized\n\t * (grouped, compressed, and/or chunked).\n\t * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure\n\t * depends on this object instance.\n\t * Note remoteMessageCopy.contents (and other object props) MUST not be modified,\n\t * but may be overwritten (as is the case with contents).\n\t *\n\t * Incoming messages will always have compression, chunking, and grouped batching happen in a defined order and that order cannot be changed.\n\t * When processing these messages, the order is:\n\t * 1. If chunked, process the chunk and only continue if this is a final chunk\n\t * 2. If compressed, decompress the message and store for further unrolling of the decompressed content\n\t * 3. If grouped, ungroup the message\n\t * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound\n\t *\n\t * @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch\n\t * or undefined if the batch is not yet complete.\n\t */\n\tpublic process(remoteMessageCopy: ISequencedDocumentMessage):\n\t\t| {\n\t\t\t\tmessages: InboundSequencedContainerRuntimeMessage[];\n\t\t\t\tbatchStartCsn: number;\n\t\t }\n\t\t| undefined {\n\t\tlet message = remoteMessageCopy;\n\n\t\tensureContentsDeserialized(message);\n\n\t\tif (isChunkedMessage(message)) {\n\t\t\tconst chunkProcessingResult = this.opSplitter.processChunk(message);\n\t\t\t// Only continue further if current chunk is the final chunk\n\t\t\tif (!chunkProcessingResult.isFinalChunk) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// This message will always be compressed\n\t\t\tmessage = chunkProcessingResult.message;\n\t\t}\n\n\t\tif (this.opDecompressor.isCompressedMessage(message)) {\n\t\t\tthis.opDecompressor.decompressAndStore(message);\n\t\t}\n\n\t\tif (this.opDecompressor.currentlyUnrolling) {\n\t\t\tmessage = this.opDecompressor.unroll(message);\n\t\t\t// Need to unpack after unrolling if not a groupedBatch\n\t\t\tif (!isGroupedBatch(message)) {\n\t\t\t\tunpack(message);\n\t\t\t}\n\t\t}\n\n\t\tif (isGroupedBatch(message)) {\n\t\t\t// We should be awaiting a new batch (batchStartCsn undefined)\n\t\t\tassert(this.batchStartCsn === undefined, \"Grouped batch interrupting another batch\");\n\t\t\tassert(\n\t\t\t\tthis.processorBatch.length === 0,\n\t\t\t\t\"Processor batch should be empty on grouped batch\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tmessages: this.opGroupingManager.ungroupOp(message).map(unpack),\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t};\n\t\t}\n\n\t\tconst batchStartCsn = this.getAndUpdateBatchStartCsn(message);\n\n\t\t// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked\n\t\tunpackRuntimeMessage(message);\n\t\tthis.processorBatch.push(message as InboundSequencedContainerRuntimeMessage);\n\n\t\t// this.batchStartCsn is undefined only if we have processed all messages in the batch.\n\t\t// If it's still defined, we're still in the middle of a batch, so we return nothing, letting\n\t\t// containerRuntime know that we're waiting for more messages to complete the batch.\n\t\tif (this.batchStartCsn !== undefined) {\n\t\t\t// batch not yet complete\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst messages = [...this.processorBatch];\n\t\tthis.processorBatch.length = 0;\n\t\treturn {\n\t\t\tmessages,\n\t\t\tbatchStartCsn,\n\t\t};\n\t}\n\n\t/**\n\t * Based on pre-existing batch tracking info and the current message's batch metadata,\n\t * this will return the starting CSN for this message's batch, and will also update\n\t * the batch tracking info (this.batchStartCsn) based on whether we're still mid-batch.\n\t */\n\tprivate getAndUpdateBatchStartCsn(message: ISequencedDocumentMessage): number {\n\t\tconst batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;\n\t\tif (this.batchStartCsn === undefined) {\n\t\t\t// We are waiting for a new batch\n\t\t\tassert(batchMetadataFlag !== false, \"Unexpected batch end marker\");\n\n\t\t\t// Start of a new multi-message batch\n\t\t\tif (batchMetadataFlag === true) {\n\t\t\t\tthis.batchStartCsn = message.clientSequenceNumber;\n\t\t\t\treturn this.batchStartCsn;\n\t\t\t}\n\n\t\t\t// Single-message batch (Since metadata flag is undefined)\n\t\t\t// IMPORTANT: Leave this.batchStartCsn undefined, we're ready for the next batch now.\n\t\t\treturn message.clientSequenceNumber;\n\t\t}\n\n\t\t// We are in the middle or end of an existing multi-message batch. Return the current batchStartCsn\n\t\tconst batchStartCsn = this.batchStartCsn;\n\n\t\tassert(batchMetadataFlag !== true, \"Unexpected batch start marker\");\n\t\tif (batchMetadataFlag === false) {\n\t\t\t// Batch end? Then get ready for the next batch to start\n\t\t\tthis.batchStartCsn = undefined;\n\t\t}\n\n\t\treturn batchStartCsn;\n\t}\n}\n\n/** Takes an incoming message and if the contents is a string, JSON.parse's it in place */\nfunction ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void {\n\t// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!\n\t// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.\n\t// Old ops may contain empty string (I assume noops).\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t}\n}\n\n/**\n * For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.\n *\n * The return type illustrates the assumption that the message param\n * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns\n * (but there is no runtime validation of the 'type' or 'compatDetails' values).\n */\nfunction unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {\n\t// We assume the contents is an InboundContainerRuntimeMessage (the message is \"packed\")\n\tconst contents = message.contents as InboundContainerRuntimeMessage;\n\n\t// We're going to unpack message in-place (promoting those properties of contents up to message itself)\n\tconst messageUnpacked = message as InboundSequencedContainerRuntimeMessage;\n\n\tmessageUnpacked.type = contents.type;\n\tmessageUnpacked.contents = contents.contents;\n\tif (\"compatDetails\" in contents) {\n\t\t(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =\n\t\t\tcontents.compatDetails;\n\t}\n\treturn messageUnpacked;\n}\n\n/**\n * Unpacks runtime messages.\n *\n * @remarks This API makes no promises regarding backward-compatibility. This is internal API.\n * @param message - message (as it observed in storage / service)\n * @returns whether the given message was unpacked\n *\n * @internal\n */\nexport function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolean {\n\tif (message.type !== MessageType.Operation) {\n\t\t// Legacy format, but it's already \"unpacked\",\n\t\t// i.e. message.type is actually ContainerMessageType.\n\t\t// Or it's non-runtime message.\n\t\t// Nothing to do in such case.\n\t\treturn false;\n\t}\n\n\t// legacy op format?\n\t// TODO: Unsure if this is a real format we should be concerned with. There doesn't appear to be anything prepared to handle the address member.\n\tif (\n\t\t(message.contents as { address?: unknown }).address !== undefined &&\n\t\t(message.contents as { type?: unknown }).type === undefined\n\t) {\n\t\tmessage.type = ContainerMessageType.FluidDataStoreOp;\n\t} else {\n\t\t// new format\n\t\tunpack(message);\n\t}\n\n\treturn true;\n}\n"]}
|
package/lib/packageVersion.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
|
|
6
6
|
*/
|
|
7
7
|
export declare const pkgName = "@fluidframework/container-runtime";
|
|
8
|
-
export declare const pkgVersion = "2.1.0-
|
|
8
|
+
export declare const pkgVersion = "2.1.0-281041";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
package/lib/packageVersion.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,cAAc,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.1.0-
|
|
1
|
+
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,cAAc,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.1.0-281041\";\n"]}
|
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
6
6
|
import { IDisposable } from "@fluidframework/core-interfaces";
|
|
7
7
|
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
8
|
-
import { InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
9
|
-
import
|
|
8
|
+
import { type InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
9
|
+
import { BatchId, BatchMessage } from "./opLifecycle/index.js";
|
|
10
10
|
/**
|
|
11
11
|
* This represents a message that has been submitted and is added to the pending queue when `submit` is called on the
|
|
12
12
|
* ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.
|
|
13
|
+
*
|
|
14
|
+
* @remarks This is the current serialization format for pending local state when a Container is serialized.
|
|
13
15
|
*/
|
|
14
16
|
export interface IPendingMessage {
|
|
15
17
|
type: "message";
|
|
@@ -18,7 +20,16 @@ export interface IPendingMessage {
|
|
|
18
20
|
localOpMetadata: unknown;
|
|
19
21
|
opMetadata: Record<string, unknown> | undefined;
|
|
20
22
|
sequenceNumber?: number;
|
|
21
|
-
|
|
23
|
+
/** Info needed to compute the batchId on reconnect */
|
|
24
|
+
batchIdContext: {
|
|
25
|
+
/** The Batch's original clientId, from when it was first flushed to be submitted */
|
|
26
|
+
clientId: string;
|
|
27
|
+
/**
|
|
28
|
+
* The Batch's original clientSequenceNumber, from when it was first flushed to be submitted
|
|
29
|
+
* @remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)
|
|
30
|
+
*/
|
|
31
|
+
batchStartCsn: number;
|
|
32
|
+
};
|
|
22
33
|
}
|
|
23
34
|
export interface IPendingLocalState {
|
|
24
35
|
/**
|
|
@@ -26,18 +37,14 @@ export interface IPendingLocalState {
|
|
|
26
37
|
*/
|
|
27
38
|
pendingStates: IPendingMessage[];
|
|
28
39
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
localOpMetadata: unknown;
|
|
32
|
-
opMetadata: Record<string, unknown> | undefined;
|
|
33
|
-
}
|
|
40
|
+
/** Info needed to replay/resubmit a pending message */
|
|
41
|
+
export type PendingMessageResubmitData = Pick<IPendingMessage, "content" | "localOpMetadata" | "opMetadata">;
|
|
34
42
|
export interface IRuntimeStateHandler {
|
|
35
43
|
connected(): boolean;
|
|
36
44
|
clientId(): string | undefined;
|
|
37
45
|
close(error?: ICriticalContainerError): void;
|
|
38
46
|
applyStashedOp(content: string): Promise<unknown>;
|
|
39
|
-
|
|
40
|
-
reSubmitBatch(batch: IPendingBatchMessage[]): void;
|
|
47
|
+
reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;
|
|
41
48
|
isActiveConnection: () => boolean;
|
|
42
49
|
isAttached: () => boolean;
|
|
43
50
|
}
|
|
@@ -53,16 +60,21 @@ export interface IRuntimeStateHandler {
|
|
|
53
60
|
export declare class PendingStateManager implements IDisposable {
|
|
54
61
|
private readonly stateHandler;
|
|
55
62
|
private readonly logger;
|
|
63
|
+
/** Messages that will need to be resubmitted if not ack'd before the next reconnection */
|
|
56
64
|
private readonly pendingMessages;
|
|
65
|
+
/** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */
|
|
57
66
|
private readonly initialMessages;
|
|
58
67
|
/**
|
|
59
68
|
* Sequenced local ops that are saved when stashing since pending ops may depend on them
|
|
60
69
|
*/
|
|
61
70
|
private savedOps;
|
|
71
|
+
/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
|
|
72
|
+
private negativeCounter;
|
|
62
73
|
private readonly disposeOnce;
|
|
63
74
|
private isProcessingBatch;
|
|
64
75
|
private pendingBatchBeginMessage;
|
|
65
|
-
|
|
76
|
+
/** Used to ensure we don't replay ops on the same connection twice */
|
|
77
|
+
private clientIdFromLastReplay;
|
|
66
78
|
/**
|
|
67
79
|
* The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with
|
|
68
80
|
* 'hasPendingMessages'.
|
|
@@ -80,7 +92,7 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
80
92
|
*/
|
|
81
93
|
hasPendingMessages(): boolean;
|
|
82
94
|
getLocalState(snapshotSequenceNumber?: number): IPendingLocalState;
|
|
83
|
-
constructor(stateHandler: IRuntimeStateHandler,
|
|
95
|
+
constructor(stateHandler: IRuntimeStateHandler, stashedLocalState: IPendingLocalState | undefined, logger: ITelemetryLoggerExt);
|
|
84
96
|
get disposed(): boolean;
|
|
85
97
|
readonly dispose: () => void;
|
|
86
98
|
/**
|
|
@@ -96,15 +108,29 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
96
108
|
* @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.
|
|
97
109
|
*/
|
|
98
110
|
applyStashedOpsAt(seqNum?: number): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Processes the incoming batch from the server. It verifies that messages are received in the right order and
|
|
113
|
+
* that the batch information is correct.
|
|
114
|
+
* @param batch - The batch that is being processed.
|
|
115
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch
|
|
116
|
+
*/
|
|
117
|
+
processPendingLocalBatch(batch: InboundSequencedContainerRuntimeMessage[], batchStartCsn: number): {
|
|
118
|
+
message: InboundSequencedContainerRuntimeMessage;
|
|
119
|
+
localOpMetadata: unknown;
|
|
120
|
+
}[];
|
|
99
121
|
/**
|
|
100
122
|
* Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
|
|
101
123
|
* the batch information was preserved for batch messages.
|
|
102
124
|
* @param message - The message that got ack'd and needs to be processed.
|
|
125
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
126
|
+
* (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)
|
|
103
127
|
*/
|
|
104
|
-
processPendingLocalMessage
|
|
128
|
+
private processPendingLocalMessage;
|
|
105
129
|
/**
|
|
106
130
|
* This message could be the first message in batch. If so, set batch state marking the beginning of a batch.
|
|
107
131
|
* @param message - The message that is being processed.
|
|
132
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
133
|
+
* @param pendingMessage - The corresponding pendingMessage.
|
|
108
134
|
*/
|
|
109
135
|
private maybeProcessBatchBegin;
|
|
110
136
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAG9D,OAAO,EACN,mBAAmB,
|
|
1
|
+
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import { assert, Lazy } from "@fluidframework/core-utils/internal";
|
|
6
|
-
import { DataProcessingError, LoggingError, } from "@fluidframework/telemetry-utils/internal";
|
|
6
|
+
import { DataProcessingError, LoggingError, extractSafePropertiesFromMessage, } from "@fluidframework/telemetry-utils/internal";
|
|
7
7
|
import Deque from "double-ended-queue";
|
|
8
|
+
import { v4 as uuid } from "uuid";
|
|
9
|
+
import { asBatchMetadata } from "./metadata.js";
|
|
10
|
+
import { generateBatchId } from "./opLifecycle/index.js";
|
|
8
11
|
import { pkgVersion } from "./packageVersion.js";
|
|
9
12
|
function buildPendingMessageContent(
|
|
10
13
|
// AnyComboFromUnion is needed need to gain access to compatDetails that
|
|
@@ -76,16 +79,19 @@ export class PendingStateManager {
|
|
|
76
79
|
],
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
|
-
constructor(stateHandler,
|
|
82
|
+
constructor(stateHandler, stashedLocalState, logger) {
|
|
80
83
|
this.stateHandler = stateHandler;
|
|
81
84
|
this.logger = logger;
|
|
85
|
+
/** Messages that will need to be resubmitted if not ack'd before the next reconnection */
|
|
82
86
|
this.pendingMessages = new Deque();
|
|
83
|
-
|
|
87
|
+
/** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */
|
|
84
88
|
this.initialMessages = new Deque();
|
|
85
89
|
/**
|
|
86
90
|
* Sequenced local ops that are saved when stashing since pending ops may depend on them
|
|
87
91
|
*/
|
|
88
92
|
this.savedOps = [];
|
|
93
|
+
/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
|
|
94
|
+
this.negativeCounter = -1;
|
|
89
95
|
this.disposeOnce = new Lazy(() => {
|
|
90
96
|
this.initialMessages.clear();
|
|
91
97
|
this.pendingMessages.clear();
|
|
@@ -93,8 +99,8 @@ export class PendingStateManager {
|
|
|
93
99
|
// Indicates whether we are processing a batch.
|
|
94
100
|
this.isProcessingBatch = false;
|
|
95
101
|
this.dispose = () => this.disposeOnce.value;
|
|
96
|
-
if (
|
|
97
|
-
this.initialMessages.push(...
|
|
102
|
+
if (stashedLocalState?.pendingStates) {
|
|
103
|
+
this.initialMessages.push(...stashedLocalState.pendingStates);
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
106
|
get disposed() {
|
|
@@ -108,6 +114,14 @@ export class PendingStateManager {
|
|
|
108
114
|
* or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)
|
|
109
115
|
*/
|
|
110
116
|
onFlushBatch(batch, clientSequenceNumber) {
|
|
117
|
+
// If we're connected this is the client of the current connection,
|
|
118
|
+
// otherwise it's the clientId that just disconnected
|
|
119
|
+
// It's only undefined if we've NEVER connected. This is a tight corner case and we can
|
|
120
|
+
// simply make up a unique ID in this case.
|
|
121
|
+
const clientId = this.stateHandler.clientId() ?? uuid();
|
|
122
|
+
// If the batch was not yet sent, we need to assign a unique batchStartCsn
|
|
123
|
+
// Use a negative number to distinguish these from real CSNs
|
|
124
|
+
const batchStartCsn = clientSequenceNumber ?? this.negativeCounter--;
|
|
111
125
|
for (const message of batch) {
|
|
112
126
|
const { contents: content = "", referenceSequenceNumber, localOpMetadata, metadata: opMetadata, } = message;
|
|
113
127
|
const pendingMessage = {
|
|
@@ -116,7 +130,8 @@ export class PendingStateManager {
|
|
|
116
130
|
content,
|
|
117
131
|
localOpMetadata,
|
|
118
132
|
opMetadata,
|
|
119
|
-
|
|
133
|
+
// Note: We only need this on the first message.
|
|
134
|
+
batchIdContext: { clientId, batchStartCsn },
|
|
120
135
|
};
|
|
121
136
|
this.pendingMessages.push(pendingMessage);
|
|
122
137
|
}
|
|
@@ -151,6 +166,7 @@ export class PendingStateManager {
|
|
|
151
166
|
else {
|
|
152
167
|
nextMessage.localOpMetadata = localOpMetadata;
|
|
153
168
|
// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
|
|
169
|
+
patchBatchIdContext(nextMessage); // Back compat
|
|
154
170
|
this.pendingMessages.push(nextMessage);
|
|
155
171
|
}
|
|
156
172
|
}
|
|
@@ -159,17 +175,31 @@ export class PendingStateManager {
|
|
|
159
175
|
}
|
|
160
176
|
}
|
|
161
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Processes the incoming batch from the server. It verifies that messages are received in the right order and
|
|
180
|
+
* that the batch information is correct.
|
|
181
|
+
* @param batch - The batch that is being processed.
|
|
182
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch
|
|
183
|
+
*/
|
|
184
|
+
processPendingLocalBatch(batch, batchStartCsn) {
|
|
185
|
+
return batch.map((message) => ({
|
|
186
|
+
message,
|
|
187
|
+
localOpMetadata: this.processPendingLocalMessage(message, batchStartCsn),
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
162
190
|
/**
|
|
163
191
|
* Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
|
|
164
192
|
* the batch information was preserved for batch messages.
|
|
165
193
|
* @param message - The message that got ack'd and needs to be processed.
|
|
194
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
195
|
+
* (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)
|
|
166
196
|
*/
|
|
167
|
-
processPendingLocalMessage(message) {
|
|
168
|
-
// Pre-processing part - This may be the start of a batch.
|
|
169
|
-
this.maybeProcessBatchBegin(message);
|
|
197
|
+
processPendingLocalMessage(message, batchStartCsn) {
|
|
170
198
|
// Get the next message from the pending queue. Verify a message exists.
|
|
171
199
|
const pendingMessage = this.pendingMessages.peekFront();
|
|
172
200
|
assert(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
|
|
201
|
+
// This may be the start of a batch.
|
|
202
|
+
this.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);
|
|
173
203
|
pendingMessage.sequenceNumber = message.sequenceNumber;
|
|
174
204
|
this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
|
|
175
205
|
this.pendingMessages.shift();
|
|
@@ -188,8 +218,26 @@ export class PendingStateManager {
|
|
|
188
218
|
/**
|
|
189
219
|
* This message could be the first message in batch. If so, set batch state marking the beginning of a batch.
|
|
190
220
|
* @param message - The message that is being processed.
|
|
221
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
222
|
+
* @param pendingMessage - The corresponding pendingMessage.
|
|
191
223
|
*/
|
|
192
|
-
maybeProcessBatchBegin(message) {
|
|
224
|
+
maybeProcessBatchBegin(message, batchStartCsn, pendingMessage) {
|
|
225
|
+
if (!this.isProcessingBatch) {
|
|
226
|
+
// Expecting the start of a batch (maybe single-message).
|
|
227
|
+
if (pendingMessage.batchIdContext.batchStartCsn !== batchStartCsn) {
|
|
228
|
+
this.logger?.sendErrorEvent({
|
|
229
|
+
eventName: "BatchClientSequenceNumberMismatch",
|
|
230
|
+
details: {
|
|
231
|
+
processingBatch: !!this.pendingBatchBeginMessage,
|
|
232
|
+
pendingBatchCsn: pendingMessage.batchIdContext.batchStartCsn,
|
|
233
|
+
batchStartCsn,
|
|
234
|
+
messageBatchMetadata: message.metadata?.batch,
|
|
235
|
+
pendingMessageBatchMetadata: pendingMessage.opMetadata?.batch,
|
|
236
|
+
},
|
|
237
|
+
messageDetails: extractSafePropertiesFromMessage(message),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
193
241
|
// This message is the first in a batch if the "batch" property on the metadata is set to true
|
|
194
242
|
if (message.metadata?.batch) {
|
|
195
243
|
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
@@ -249,8 +297,8 @@ export class PendingStateManager {
|
|
|
249
297
|
replayPendingStates() {
|
|
250
298
|
assert(this.stateHandler.connected(), 0x172 /* "The connection state is not consistent with the runtime" */);
|
|
251
299
|
// This assert suggests we are about to send same ops twice, which will result in data loss.
|
|
252
|
-
assert(this.
|
|
253
|
-
this.
|
|
300
|
+
assert(this.clientIdFromLastReplay !== this.stateHandler.clientId(), 0x173 /* "replayPendingStates called twice for same clientId!" */);
|
|
301
|
+
this.clientIdFromLastReplay = this.stateHandler.clientId();
|
|
254
302
|
assert(this.initialMessages.isEmpty(), 0x174 /* "initial states should be empty before replaying pending" */);
|
|
255
303
|
const initialPendingMessagesCount = this.pendingMessages.length;
|
|
256
304
|
let remainingPendingMessagesCount = this.pendingMessages.length;
|
|
@@ -261,40 +309,49 @@ export class PendingStateManager {
|
|
|
261
309
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
262
310
|
let pendingMessage = this.pendingMessages.shift();
|
|
263
311
|
remainingPendingMessagesCount--;
|
|
264
|
-
|
|
312
|
+
const batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;
|
|
313
|
+
assert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
|
|
314
|
+
// The next message starts a batch (possibly single-message), and we'll need its batchId.
|
|
315
|
+
// We'll find batchId on this message if it was previously generated.
|
|
316
|
+
// Otherwise, generate it now - this is the first time resubmitting this batch.
|
|
317
|
+
const batchId = asBatchMetadata(pendingMessage.opMetadata)?.batchId ??
|
|
318
|
+
generateBatchId(pendingMessage.batchIdContext.clientId, pendingMessage.batchIdContext.batchStartCsn);
|
|
265
319
|
/**
|
|
266
|
-
* We
|
|
320
|
+
* We must preserve the distinct batches on resubmit.
|
|
267
321
|
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
268
|
-
* either receive the whole batch ack or nothing at all.
|
|
322
|
+
* either receive the whole batch ack or nothing at all. @see ScheduleManager for how this works.
|
|
269
323
|
*/
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
while (remainingPendingMessagesCount >= 0) {
|
|
275
|
-
batch.push({
|
|
324
|
+
if (batchMetadataFlag === undefined) {
|
|
325
|
+
// Single-message batch
|
|
326
|
+
this.stateHandler.reSubmitBatch([
|
|
327
|
+
{
|
|
276
328
|
content: pendingMessage.content,
|
|
277
329
|
localOpMetadata: pendingMessage.localOpMetadata,
|
|
278
330
|
opMetadata: pendingMessage.opMetadata,
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
assert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
|
|
284
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
285
|
-
pendingMessage = this.pendingMessages.shift();
|
|
286
|
-
remainingPendingMessagesCount--;
|
|
287
|
-
assert(pendingMessage.opMetadata?.batch !== true, 0x556 /* Batch start needs a corresponding batch end */);
|
|
288
|
-
}
|
|
289
|
-
this.stateHandler.reSubmitBatch(batch);
|
|
331
|
+
},
|
|
332
|
+
], batchId);
|
|
333
|
+
continue;
|
|
290
334
|
}
|
|
291
|
-
else
|
|
292
|
-
|
|
335
|
+
// else: batchMetadataFlag === true (It's a typical multi-message batch)
|
|
336
|
+
assert(remainingPendingMessagesCount > 0, 0x554 /* Last pending message cannot be a batch begin */);
|
|
337
|
+
const batch = [];
|
|
338
|
+
// check is >= because batch end may be last pending message
|
|
339
|
+
while (remainingPendingMessagesCount >= 0) {
|
|
340
|
+
batch.push({
|
|
293
341
|
content: pendingMessage.content,
|
|
294
342
|
localOpMetadata: pendingMessage.localOpMetadata,
|
|
295
343
|
opMetadata: pendingMessage.opMetadata,
|
|
296
344
|
});
|
|
345
|
+
if (pendingMessage.opMetadata?.batch === false) {
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
assert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
|
|
349
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
350
|
+
pendingMessage = this.pendingMessages.shift();
|
|
351
|
+
remainingPendingMessagesCount--;
|
|
352
|
+
assert(pendingMessage.opMetadata?.batch !== true, 0x556 /* Batch start needs a corresponding batch end */);
|
|
297
353
|
}
|
|
354
|
+
this.stateHandler.reSubmitBatch(batch, batchId);
|
|
298
355
|
}
|
|
299
356
|
// pending ops should no longer depend on previous sequenced local ops after resubmit
|
|
300
357
|
this.savedOps = [];
|
|
@@ -310,4 +367,12 @@ export class PendingStateManager {
|
|
|
310
367
|
}
|
|
311
368
|
}
|
|
312
369
|
}
|
|
370
|
+
/** For back-compat if trying to apply stashed ops that pre-date batchIdContext */
|
|
371
|
+
function patchBatchIdContext(message) {
|
|
372
|
+
const batchIdContext = message.batchIdContext;
|
|
373
|
+
if (batchIdContext === undefined) {
|
|
374
|
+
// Using uuid guarantees uniqueness, retaining existing behavior
|
|
375
|
+
message.batchIdContext = { clientId: uuid(), batchStartCsn: -1 };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
313
378
|
//# sourceMappingURL=pendingStateManager.js.map
|