@fluidframework/container-runtime 2.0.0-internal.1.1.1 → 2.0.0-internal.1.2.0.93071
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/batchManager.d.ts +32 -0
- package/dist/batchManager.d.ts.map +1 -0
- package/dist/batchManager.js +71 -0
- package/dist/batchManager.js.map +1 -0
- package/dist/containerRuntime.d.ts +44 -17
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +197 -95
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +4 -4
- package/dist/dataStoreContext.js +5 -5
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +0 -11
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +6 -43
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.js +1 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.js +1 -1
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizerTypes.d.ts +3 -3
- package/dist/summarizerTypes.js +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryCollection.d.ts +1 -0
- package/dist/summaryCollection.d.ts.map +1 -1
- package/dist/summaryCollection.js +32 -13
- package/dist/summaryCollection.js.map +1 -1
- package/lib/batchManager.d.ts +32 -0
- package/lib/batchManager.d.ts.map +1 -0
- package/lib/batchManager.js +67 -0
- package/lib/batchManager.js.map +1 -0
- package/lib/containerRuntime.d.ts +44 -17
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +200 -98
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +4 -4
- package/lib/dataStoreContext.js +5 -5
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +0 -11
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +6 -43
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.js +1 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.js +2 -2
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizerTypes.d.ts +3 -3
- package/lib/summarizerTypes.js +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryCollection.d.ts +1 -0
- package/lib/summaryCollection.d.ts.map +1 -1
- package/lib/summaryCollection.js +32 -13
- package/lib/summaryCollection.js.map +1 -1
- package/package.json +17 -17
- package/src/batchManager.ts +88 -0
- package/src/containerRuntime.ts +273 -156
- package/src/dataStoreContext.ts +7 -7
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +6 -56
- package/src/runningSummarizer.ts +1 -1
- package/src/scheduleManager.ts +2 -2
- package/src/summarizerTypes.ts +3 -3
- package/src/summaryCollection.ts +33 -16
package/dist/containerRuntime.js
CHANGED
|
@@ -18,6 +18,7 @@ const summarizer_1 = require("./summarizer");
|
|
|
18
18
|
const summaryManager_1 = require("./summaryManager");
|
|
19
19
|
const connectionTelemetry_1 = require("./connectionTelemetry");
|
|
20
20
|
const pendingStateManager_1 = require("./pendingStateManager");
|
|
21
|
+
const batchManager_1 = require("./batchManager");
|
|
21
22
|
const packageVersion_1 = require("./packageVersion");
|
|
22
23
|
const blobManager_1 = require("./blobManager");
|
|
23
24
|
const dataStores_1 = require("./dataStores");
|
|
@@ -78,11 +79,10 @@ var RuntimeHeaders;
|
|
|
78
79
|
RuntimeHeaders["viaHandle"] = "viaHandle";
|
|
79
80
|
})(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
|
|
80
81
|
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
81
|
-
// By default, we should reject any op larger than 768KB,
|
|
82
|
-
// in order to account for some extra overhead from serialization
|
|
83
|
-
// to not reach the 1MB limits in socket.io and Kafka.
|
|
84
|
-
const defaultMaxOpSizeInBytes = 768000;
|
|
85
82
|
const defaultFlushMode = runtime_definitions_1.FlushMode.TurnBased;
|
|
83
|
+
/**
|
|
84
|
+
* @deprecated - use ContainerRuntimeMessage instead
|
|
85
|
+
*/
|
|
86
86
|
var RuntimeMessage;
|
|
87
87
|
(function (RuntimeMessage) {
|
|
88
88
|
RuntimeMessage["FluidDataStoreOp"] = "component";
|
|
@@ -93,6 +93,9 @@ var RuntimeMessage;
|
|
|
93
93
|
RuntimeMessage["Alias"] = "alias";
|
|
94
94
|
RuntimeMessage["Operation"] = "op";
|
|
95
95
|
})(RuntimeMessage = exports.RuntimeMessage || (exports.RuntimeMessage = {}));
|
|
96
|
+
/**
|
|
97
|
+
* @deprecated - please use version in driver-utils
|
|
98
|
+
*/
|
|
96
99
|
function isRuntimeMessage(message) {
|
|
97
100
|
if (Object.values(RuntimeMessage).includes(message.type)) {
|
|
98
101
|
return true;
|
|
@@ -100,6 +103,12 @@ function isRuntimeMessage(message) {
|
|
|
100
103
|
return false;
|
|
101
104
|
}
|
|
102
105
|
exports.isRuntimeMessage = isRuntimeMessage;
|
|
106
|
+
/**
|
|
107
|
+
* Unpacks runtime messages
|
|
108
|
+
* @internal - no promises RE back-compat - this is internal API.
|
|
109
|
+
* @param message - message (as it observed in storage / service)
|
|
110
|
+
* @returns unpacked runtime message
|
|
111
|
+
*/
|
|
103
112
|
function unpackRuntimeMessage(message) {
|
|
104
113
|
if (message.type === protocol_definitions_1.MessageType.Operation) {
|
|
105
114
|
// legacy op format?
|
|
@@ -113,14 +122,15 @@ function unpackRuntimeMessage(message) {
|
|
|
113
122
|
message.type = innerContents.type;
|
|
114
123
|
message.contents = innerContents.contents;
|
|
115
124
|
}
|
|
116
|
-
|
|
125
|
+
return true;
|
|
117
126
|
}
|
|
118
127
|
else {
|
|
119
128
|
// Legacy format, but it's already "unpacked",
|
|
120
129
|
// i.e. message.type is actually ContainerMessageType.
|
|
130
|
+
// Or it's non-runtime message.
|
|
121
131
|
// Nothing to do in such case.
|
|
132
|
+
return false;
|
|
122
133
|
}
|
|
123
|
-
return message;
|
|
124
134
|
}
|
|
125
135
|
exports.unpackRuntimeMessage = unpackRuntimeMessage;
|
|
126
136
|
/**
|
|
@@ -163,7 +173,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
163
173
|
this.summaryConfiguration = summaryConfiguration;
|
|
164
174
|
this.defaultMaxConsecutiveReconnects = 7;
|
|
165
175
|
this._orderSequentiallyCalls = 0;
|
|
166
|
-
this.needsFlush = false;
|
|
167
176
|
this.flushTrigger = false;
|
|
168
177
|
this.savedOps = [];
|
|
169
178
|
this.consecutiveReconnects = 0;
|
|
@@ -176,6 +185,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
176
185
|
signalTimestamp: 0,
|
|
177
186
|
trackingSignalSequenceNumber: undefined,
|
|
178
187
|
};
|
|
188
|
+
this.batchManager = new batchManager_1.BatchManager();
|
|
179
189
|
this.summarizeOnDemand = (...args) => {
|
|
180
190
|
if (this.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
|
|
181
191
|
return this.summarizer.summarizeOnDemand(...args);
|
|
@@ -270,7 +280,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
270
280
|
flush: this.flush.bind(this),
|
|
271
281
|
flushMode: () => this.flushMode,
|
|
272
282
|
reSubmit: this.reSubmit.bind(this),
|
|
273
|
-
rollback: this.rollback.bind(this),
|
|
274
283
|
setFlushMode: (mode) => this.setFlushMode(mode),
|
|
275
284
|
}, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
|
|
276
285
|
this.context.quorum.on("removeMember", (clientId) => {
|
|
@@ -742,7 +751,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
742
751
|
// Feature disabled, we never stop reconnecting
|
|
743
752
|
return true;
|
|
744
753
|
}
|
|
745
|
-
if (!this.
|
|
754
|
+
if (!this.hasPendingMessages()) {
|
|
746
755
|
// If there are no pending messages, we can always reconnect
|
|
747
756
|
this.resetReconnectCount();
|
|
748
757
|
return true;
|
|
@@ -847,13 +856,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
847
856
|
this._perfSignalData.signalTimestamp = 0;
|
|
848
857
|
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
849
858
|
}
|
|
859
|
+
else {
|
|
860
|
+
(0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, "Connection is possible only if container exists in storage");
|
|
861
|
+
}
|
|
850
862
|
// Fail while disconnected
|
|
851
863
|
if (reconnection) {
|
|
852
864
|
this.consecutiveReconnects++;
|
|
853
865
|
if (!this.shouldContinueReconnecting()) {
|
|
854
|
-
this.closeFn(
|
|
855
|
-
//
|
|
856
|
-
|
|
866
|
+
this.closeFn(container_utils_1.DataProcessingError.create(
|
|
867
|
+
// eslint-disable-next-line max-len
|
|
868
|
+
"Runtime detected too many reconnects with no progress syncing local ops. Batch of ops is likely too large (over 1Mb)", "setConnectionState", undefined, {
|
|
857
869
|
dataLoss: 1,
|
|
858
870
|
attempts: this.consecutiveReconnects,
|
|
859
871
|
pendingMessages: this.pendingStateManager.pendingMessagesCount,
|
|
@@ -870,41 +882,42 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
870
882
|
process(messageArg, local) {
|
|
871
883
|
var _a;
|
|
872
884
|
this.verifyNotClosed();
|
|
873
|
-
// If it's not message for runtime, bail out right away.
|
|
874
|
-
if (!(0, driver_utils_1.isUnpackedRuntimeMessage)(messageArg)) {
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
878
|
-
this.savedOps.push(messageArg);
|
|
879
|
-
}
|
|
880
885
|
// Do shallow copy of message, as methods below will modify it.
|
|
881
886
|
// There might be multiple container instances receiving same message
|
|
882
887
|
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
883
888
|
// but would not modify contents details
|
|
884
889
|
let message = Object.assign({}, messageArg);
|
|
890
|
+
// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
|
|
891
|
+
// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
|
|
892
|
+
// Old ops may contain empty string (I assume noops).
|
|
893
|
+
if (typeof message.contents === "string" && message.contents !== "") {
|
|
894
|
+
message.contents = JSON.parse(message.contents);
|
|
895
|
+
}
|
|
896
|
+
// Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
|
|
897
|
+
// This format was not shipped to production workflows.
|
|
898
|
+
const runtimeMessage = unpackRuntimeMessage(message);
|
|
899
|
+
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
900
|
+
this.savedOps.push(messageArg);
|
|
901
|
+
}
|
|
885
902
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
886
903
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
887
904
|
// messages once a batch has been fully processed.
|
|
888
905
|
this.scheduleManager.beforeOpProcessing(message);
|
|
889
906
|
try {
|
|
890
|
-
message = unpackRuntimeMessage(message);
|
|
891
907
|
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
892
908
|
// once all pieces are available
|
|
893
909
|
message = this.processRemoteChunkedMessage(message);
|
|
894
910
|
let localOpMetadata;
|
|
895
|
-
if (local) {
|
|
896
|
-
|
|
897
|
-
// Do not process local chunked ops until all pieces are available.
|
|
898
|
-
if (message.type !== ContainerMessageType.ChunkedOp) {
|
|
899
|
-
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
900
|
-
}
|
|
911
|
+
if (local && runtimeMessage) {
|
|
912
|
+
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
901
913
|
}
|
|
902
914
|
// If there are no more pending messages after processing a local message,
|
|
903
915
|
// the document is no longer dirty.
|
|
904
|
-
if (!this.
|
|
916
|
+
if (!this.hasPendingMessages()) {
|
|
905
917
|
this.updateDocumentDirtyState(false);
|
|
906
918
|
}
|
|
907
|
-
|
|
919
|
+
const type = message.type;
|
|
920
|
+
switch (type) {
|
|
908
921
|
case ContainerMessageType.Attach:
|
|
909
922
|
this.dataStores.processAttachMessage(message, local);
|
|
910
923
|
break;
|
|
@@ -917,9 +930,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
917
930
|
case ContainerMessageType.BlobAttach:
|
|
918
931
|
this.blobManager.processBlobAttachOp(message, local);
|
|
919
932
|
break;
|
|
933
|
+
case ContainerMessageType.ChunkedOp:
|
|
934
|
+
case ContainerMessageType.Rejoin:
|
|
935
|
+
break;
|
|
920
936
|
default:
|
|
937
|
+
(0, common_utils_1.assert)(!runtimeMessage, "Runtime message of unknown type");
|
|
938
|
+
}
|
|
939
|
+
// For back-compat, notify only about runtime messages for now.
|
|
940
|
+
if (runtimeMessage) {
|
|
941
|
+
this.emit("op", message, runtimeMessage);
|
|
921
942
|
}
|
|
922
|
-
this.emit("op", message);
|
|
923
943
|
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
924
944
|
if (local) {
|
|
925
945
|
// If we have processed a local op, this means that the container is
|
|
@@ -1013,25 +1033,57 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1013
1033
|
}
|
|
1014
1034
|
flush() {
|
|
1015
1035
|
(0, common_utils_1.assert)(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
|
|
1016
|
-
|
|
1017
|
-
|
|
1036
|
+
const batch = this.batchManager.popBatch();
|
|
1037
|
+
this.flushBatch(batch);
|
|
1038
|
+
(0, common_utils_1.assert)(this.batchManager.empty, "reentrancy");
|
|
1039
|
+
}
|
|
1040
|
+
flushBatch(batch) {
|
|
1041
|
+
const length = batch.length;
|
|
1042
|
+
if (length > 1) {
|
|
1043
|
+
batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
|
|
1044
|
+
batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
|
|
1045
|
+
// This assert fires for the following reason (there might be more cases like that):
|
|
1046
|
+
// AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
|
|
1047
|
+
// i.e. in the middle of op processing!
|
|
1048
|
+
// Sending ops while processing ops is not good idea - it's not defined when
|
|
1049
|
+
// referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
|
|
1050
|
+
// If we send ops in response to processing multiple ops, then we for sure hit this assert!
|
|
1051
|
+
// Tracked via ADO #1834
|
|
1052
|
+
// assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
|
|
1053
|
+
// "Batch should be generated synchronously, without processing ops in the middle!");
|
|
1018
1054
|
}
|
|
1019
|
-
|
|
1020
|
-
// Note that this should happen before the `this.needsFlush` check below because in the scenario where we are
|
|
1021
|
-
// not connected, `this.needsFlush` will be false but the PendingStateManager might have pending messages and
|
|
1022
|
-
// hence needs to track this.
|
|
1023
|
-
this.pendingStateManager.onFlush();
|
|
1024
|
-
// If flush has already been called then exit early
|
|
1025
|
-
if (!this.needsFlush) {
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
|
-
this.needsFlush = false;
|
|
1055
|
+
let clientSequenceNumber = -1;
|
|
1029
1056
|
// Did we disconnect in the middle of turn-based batch?
|
|
1030
1057
|
// If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
1058
|
+
if (this.canSendOps()) {
|
|
1059
|
+
if (this.context.submitBatchFn !== undefined) {
|
|
1060
|
+
const batchToSend = [];
|
|
1061
|
+
for (const message of batch) {
|
|
1062
|
+
batchToSend.push({ contents: message.contents, metadata: message.metadata });
|
|
1063
|
+
}
|
|
1064
|
+
// returns clientSequenceNumber of last message in a batch
|
|
1065
|
+
clientSequenceNumber = this.context.submitBatchFn(batchToSend);
|
|
1066
|
+
}
|
|
1067
|
+
else {
|
|
1068
|
+
// Legacy path - supporting old loader versions. Can be removed only when LTS moves above
|
|
1069
|
+
// version that has support for batches (submitBatchFn)
|
|
1070
|
+
for (const message of batch) {
|
|
1071
|
+
clientSequenceNumber = this.context.submitFn(protocol_definitions_1.MessageType.Operation, message.deserializedContent, true, // batch
|
|
1072
|
+
message.metadata);
|
|
1073
|
+
}
|
|
1074
|
+
this.deltaSender.flush();
|
|
1075
|
+
}
|
|
1076
|
+
// Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
|
|
1077
|
+
clientSequenceNumber -= batch.length - 1;
|
|
1078
|
+
(0, common_utils_1.assert)(clientSequenceNumber >= 0, "clientSequenceNumber can't be negative");
|
|
1079
|
+
}
|
|
1080
|
+
// Let the PendingStateManager know that a message was submitted.
|
|
1081
|
+
// In future, need to shift toward keeping batch as a whole!
|
|
1082
|
+
for (const message of batch) {
|
|
1083
|
+
this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
|
|
1084
|
+
clientSequenceNumber++;
|
|
1033
1085
|
}
|
|
1034
|
-
|
|
1086
|
+
this.pendingStateManager.onFlush();
|
|
1035
1087
|
}
|
|
1036
1088
|
orderSequentially(callback) {
|
|
1037
1089
|
// If flush mode is already TurnBased we are either
|
|
@@ -1056,7 +1108,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1056
1108
|
trackOrderSequentiallyCalls(callback) {
|
|
1057
1109
|
let checkpoint;
|
|
1058
1110
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1059
|
-
checkpoint = this.
|
|
1111
|
+
checkpoint = this.batchManager.checkpoint();
|
|
1060
1112
|
}
|
|
1061
1113
|
try {
|
|
1062
1114
|
this._orderSequentiallyCalls++;
|
|
@@ -1065,7 +1117,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1065
1117
|
catch (error) {
|
|
1066
1118
|
if (checkpoint) {
|
|
1067
1119
|
// This will throw and close the container if rollback fails
|
|
1068
|
-
|
|
1120
|
+
try {
|
|
1121
|
+
checkpoint.rollback((message) => this.rollback(message.deserializedContent.type, message.deserializedContent.contents, message.localOpMetadata));
|
|
1122
|
+
}
|
|
1123
|
+
catch (err) {
|
|
1124
|
+
const error2 = (0, telemetry_utils_1.wrapError)(err, (message) => {
|
|
1125
|
+
return container_utils_1.DataProcessingError.create(`RollbackError: ${message}`, "checkpointRollback", undefined);
|
|
1126
|
+
});
|
|
1127
|
+
this.closeFn(error2);
|
|
1128
|
+
throw error2;
|
|
1129
|
+
}
|
|
1069
1130
|
}
|
|
1070
1131
|
else {
|
|
1071
1132
|
// pre-0.58 error message: orderSequentiallyCallbackException
|
|
@@ -1170,7 +1231,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1170
1231
|
(0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x12e /* "Container Context should already be in attached state" */);
|
|
1171
1232
|
this.emit("attached");
|
|
1172
1233
|
}
|
|
1173
|
-
if (attachState === container_definitions_1.AttachState.Attached && !this.
|
|
1234
|
+
if (attachState === container_definitions_1.AttachState.Attached && !this.hasPendingMessages()) {
|
|
1174
1235
|
this.updateDocumentDirtyState(false);
|
|
1175
1236
|
}
|
|
1176
1237
|
this.dataStores.setAttachState(attachState);
|
|
@@ -1365,6 +1426,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1365
1426
|
const summaryNumberLogger = telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, {
|
|
1366
1427
|
all: { summaryNumber },
|
|
1367
1428
|
});
|
|
1429
|
+
(0, common_utils_1.assert)(this.batchManager.empty, "Can't trigger summary in the middle of a batch");
|
|
1368
1430
|
let latestSnapshotVersionId;
|
|
1369
1431
|
if (refreshLatestAck) {
|
|
1370
1432
|
const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
|
|
@@ -1528,7 +1590,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1528
1590
|
}
|
|
1529
1591
|
let clientSequenceNumber;
|
|
1530
1592
|
try {
|
|
1531
|
-
clientSequenceNumber = this.
|
|
1593
|
+
clientSequenceNumber = this.submitSummaryMessage(summaryMessage);
|
|
1532
1594
|
}
|
|
1533
1595
|
catch (error) {
|
|
1534
1596
|
return Object.assign(Object.assign({ stage: "upload" }, uploadData), { error });
|
|
@@ -1576,7 +1638,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1576
1638
|
this.chunkMap.delete(clientId);
|
|
1577
1639
|
}
|
|
1578
1640
|
}
|
|
1641
|
+
hasPendingMessages() {
|
|
1642
|
+
return this.pendingStateManager.hasPendingMessages() || !this.batchManager.empty;
|
|
1643
|
+
}
|
|
1579
1644
|
updateDocumentDirtyState(dirty) {
|
|
1645
|
+
if (this.attachState !== container_definitions_1.AttachState.Attached) {
|
|
1646
|
+
(0, common_utils_1.assert)(dirty, "Non-attached container is dirty");
|
|
1647
|
+
}
|
|
1648
|
+
else {
|
|
1649
|
+
// Other way is not true = see this.isContainerMessageDirtyable()
|
|
1650
|
+
(0, common_utils_1.assert)(!dirty || this.hasPendingMessages(), "if doc is dirty, there has to be pending ops");
|
|
1651
|
+
}
|
|
1580
1652
|
if (this.dirtyContainer === dirty) {
|
|
1581
1653
|
return;
|
|
1582
1654
|
}
|
|
@@ -1604,20 +1676,52 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1604
1676
|
this.verifyNotClosed();
|
|
1605
1677
|
return this.blobManager.createBlob(blob);
|
|
1606
1678
|
}
|
|
1607
|
-
submit(type,
|
|
1679
|
+
submit(type, contents, localOpMetadata = undefined, metadata = undefined) {
|
|
1608
1680
|
this.verifyNotClosed();
|
|
1609
1681
|
// There should be no ops in detached container state!
|
|
1610
1682
|
(0, common_utils_1.assert)(this.attachState !== container_definitions_1.AttachState.Detached, 0x132 /* "sending ops in detached container" */);
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
if (this.
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1683
|
+
const deserializedContent = { type, contents };
|
|
1684
|
+
const serializedContent = JSON.stringify(deserializedContent);
|
|
1685
|
+
if (this.deltaManager.readOnlyInfo.readonly) {
|
|
1686
|
+
this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
|
|
1687
|
+
}
|
|
1688
|
+
const message = {
|
|
1689
|
+
contents: serializedContent,
|
|
1690
|
+
deserializedContent,
|
|
1691
|
+
metadata,
|
|
1692
|
+
localOpMetadata,
|
|
1693
|
+
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
1694
|
+
};
|
|
1695
|
+
try {
|
|
1696
|
+
// If this is attach message for new data store, and we are in a batch, send this op out of order
|
|
1697
|
+
// Is it safe:
|
|
1698
|
+
// Yes, this should be safe reordering. Newly created data stores are not visible through API surface.
|
|
1699
|
+
// They become visible only when aliased, or handle to some sub-element of newly created datastore
|
|
1700
|
+
// is stored in some DDS, i.e. only after some other op.
|
|
1701
|
+
// Why:
|
|
1702
|
+
// Attach ops are large, and expensive to process. Plus there are scenarios where a lot of new data
|
|
1703
|
+
// stores are created, causing issues like relay service throttling (too many ops) and catastrophic
|
|
1704
|
+
// failure (batch is too large). Pushing them earlier and outside of main batch should alleviate
|
|
1705
|
+
// these issues.
|
|
1706
|
+
// Cons:
|
|
1707
|
+
// With large batches, relay service may throttle clients. Clients may disconnect while throttled.
|
|
1708
|
+
// This change creates new possibility of a lot of newly created data stores never being referenced
|
|
1709
|
+
// because client died before it had a change to submit the rest of the ops. This will create more
|
|
1710
|
+
// garbage that needs to be collected leveraging GC (Garbage Collection) feature.
|
|
1711
|
+
// Please note that this does not change file format, so it can be disabled in the future if this
|
|
1712
|
+
// optimization no longer makes sense (for example, batch compression may make it less appealing).
|
|
1713
|
+
if (this._flushMode === runtime_definitions_1.FlushMode.TurnBased && type === ContainerMessageType.Attach &&
|
|
1714
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
|
|
1715
|
+
this.flushBatch([message]);
|
|
1716
|
+
}
|
|
1717
|
+
else {
|
|
1718
|
+
this.batchManager.push(message);
|
|
1719
|
+
if (this._flushMode !== runtime_definitions_1.FlushMode.TurnBased) {
|
|
1720
|
+
this.flush();
|
|
1721
|
+
}
|
|
1722
|
+
else if (!this.flushTrigger) {
|
|
1723
|
+
this.flushTrigger = true;
|
|
1724
|
+
// Queue a microtask to detect the end of the turn and force a flush.
|
|
1621
1725
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1622
1726
|
Promise.resolve().then(() => {
|
|
1623
1727
|
this.flushTrigger = false;
|
|
@@ -1625,43 +1729,27 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1625
1729
|
});
|
|
1626
1730
|
}
|
|
1627
1731
|
}
|
|
1628
|
-
if (!serializedContent || serializedContent.length <= defaultMaxOpSizeInBytes) {
|
|
1629
|
-
clientSequenceNumber = this.submitRuntimeMessage(type, content, this._flushMode === runtime_definitions_1.FlushMode.TurnBased /* batch */, opMetadataInternal);
|
|
1630
|
-
}
|
|
1631
|
-
else {
|
|
1632
|
-
// If the content length is larger than the client configured message size
|
|
1633
|
-
// instead of splitting the content, we will fail by explicitly closing the container
|
|
1634
|
-
this.closeFn(new container_utils_1.GenericError("OpTooLarge",
|
|
1635
|
-
/* error */ undefined, {
|
|
1636
|
-
length: serializedContent.length,
|
|
1637
|
-
limit: defaultMaxOpSizeInBytes,
|
|
1638
|
-
}));
|
|
1639
|
-
clientSequenceNumber = -1;
|
|
1640
|
-
}
|
|
1641
1732
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1733
|
+
catch (error) {
|
|
1734
|
+
this.closeFn(error);
|
|
1735
|
+
throw error;
|
|
1736
|
+
}
|
|
1737
|
+
if (this.isContainerMessageDirtyable(type, contents)) {
|
|
1645
1738
|
this.updateDocumentDirtyState(true);
|
|
1646
1739
|
}
|
|
1647
1740
|
}
|
|
1648
|
-
|
|
1741
|
+
submitSummaryMessage(contents) {
|
|
1649
1742
|
this.verifyNotClosed();
|
|
1650
1743
|
(0, common_utils_1.assert)(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
1651
1744
|
// System message should not be sent in the middle of the batch.
|
|
1652
|
-
|
|
1653
|
-
//
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1745
|
+
(0, common_utils_1.assert)(this.batchManager.empty, "System op in the middle of a batch");
|
|
1746
|
+
// back-compat: ADO #1385: Make this call unconditional in the future
|
|
1747
|
+
if (this.context.submitSummaryFn !== undefined) {
|
|
1748
|
+
return this.context.submitSummaryFn(contents);
|
|
1749
|
+
}
|
|
1750
|
+
else {
|
|
1751
|
+
return this.context.submitFn(protocol_definitions_1.MessageType.Summarize, contents, false); // batch
|
|
1657
1752
|
}
|
|
1658
|
-
return this.context.submitFn(type, contents, middleOfBatch);
|
|
1659
|
-
}
|
|
1660
|
-
submitRuntimeMessage(type, contents, batch, appData) {
|
|
1661
|
-
this.verifyNotClosed();
|
|
1662
|
-
(0, common_utils_1.assert)(this.connected, 0x259 /* "Container disconnected when trying to submit system message" */);
|
|
1663
|
-
const payload = { type, contents };
|
|
1664
|
-
return this.context.submitFn(protocol_definitions_1.MessageType.Operation, payload, batch, appData);
|
|
1665
1753
|
}
|
|
1666
1754
|
/**
|
|
1667
1755
|
* Throw an error if the runtime is closed. Methods that are expected to potentially
|
|
@@ -1715,13 +1803,18 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1715
1803
|
/** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
|
|
1716
1804
|
async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
|
|
1717
1805
|
const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1806
|
+
// The call to fetch the snapshot is very expensive and not always needed.
|
|
1807
|
+
// It should only be done by the summarizerNode, if required.
|
|
1808
|
+
const snapshotTreeFetcher = async () => {
|
|
1809
|
+
const fetchResult = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
|
|
1810
|
+
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
1811
|
+
ackHandle,
|
|
1812
|
+
summaryRefSeq,
|
|
1813
|
+
fetchLatest: false,
|
|
1814
|
+
});
|
|
1815
|
+
return fetchResult.snapshotTree;
|
|
1816
|
+
};
|
|
1817
|
+
const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, snapshotTreeFetcher, readAndParseBlob, summaryLogger);
|
|
1725
1818
|
// Notify the garbage collector so it can update its latest summary state.
|
|
1726
1819
|
await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
|
|
1727
1820
|
}
|
|
@@ -1777,6 +1870,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1777
1870
|
if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
|
|
1778
1871
|
throw new container_utils_1.UsageError("can't get state when offline load disabled");
|
|
1779
1872
|
}
|
|
1873
|
+
// Flush pending batch.
|
|
1874
|
+
// getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
|
|
1875
|
+
// to close current batch.
|
|
1876
|
+
this.flush();
|
|
1780
1877
|
const previousPendingState = this.context.pendingLocalState;
|
|
1781
1878
|
if (previousPendingState) {
|
|
1782
1879
|
return {
|
|
@@ -1831,6 +1928,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1831
1928
|
// we may not have seen every sequence number (because of system ops) so apply everything once we
|
|
1832
1929
|
// don't have any more saved ops
|
|
1833
1930
|
await this.pendingStateManager.applyStashedOpsAt();
|
|
1931
|
+
// If it's not the case, we should take it into account when calculating dirty state.
|
|
1932
|
+
(0, common_utils_1.assert)(this.context.attachState === container_definitions_1.AttachState.Attached, "this function is called for attached containers only");
|
|
1933
|
+
if (!this.hasPendingMessages()) {
|
|
1934
|
+
this.updateDocumentDirtyState(false);
|
|
1935
|
+
}
|
|
1834
1936
|
}
|
|
1835
1937
|
validateSummaryHeuristicConfiguration(configuration) {
|
|
1836
1938
|
// eslint-disable-next-line no-restricted-syntax
|