@fluidframework/container-runtime 2.0.0-internal.1.1.3 → 2.0.0-internal.1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/batchManager.d.ts +37 -0
- package/dist/batchManager.d.ts.map +1 -0
- package/dist/batchManager.js +73 -0
- package/dist/batchManager.js.map +1 -0
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js +1 -2
- package/dist/batchTracker.js.map +1 -1
- package/dist/containerRuntime.d.ts +52 -20
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +240 -119
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +12 -6
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +16 -13
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +6 -2
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +7 -9
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaScheduler.d.ts +6 -4
- package/dist/deltaScheduler.d.ts.map +1 -1
- package/dist/deltaScheduler.js +6 -4
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/garbageCollection.d.ts +41 -12
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +176 -98
- package/dist/garbageCollection.js.map +1 -1
- package/dist/gcSweepReadyUsageDetection.d.ts +53 -0
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -0
- package/dist/gcSweepReadyUsageDetection.js +135 -0
- package/dist/gcSweepReadyUsageDetection.js.map +1 -0
- package/dist/orderedClientElection.d.ts +28 -10
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +14 -4
- package/dist/orderedClientElection.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 +0 -11
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +9 -44
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.js +1 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.d.ts +6 -3
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +22 -14
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizerTypes.d.ts +16 -9
- package/dist/summarizerTypes.d.ts.map +1 -1
- 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 +29 -13
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryManager.d.ts +2 -2
- package/dist/summaryManager.js +2 -2
- package/dist/summaryManager.js.map +1 -1
- package/lib/batchManager.d.ts +37 -0
- package/lib/batchManager.d.ts.map +1 -0
- package/lib/batchManager.js +69 -0
- package/lib/batchManager.js.map +1 -0
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js +1 -2
- package/lib/batchTracker.js.map +1 -1
- package/lib/containerRuntime.d.ts +52 -20
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +243 -122
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +12 -6
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +16 -13
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +6 -2
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +7 -9
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaScheduler.d.ts +6 -4
- package/lib/deltaScheduler.d.ts.map +1 -1
- package/lib/deltaScheduler.js +6 -4
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/garbageCollection.d.ts +41 -12
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +175 -97
- package/lib/garbageCollection.js.map +1 -1
- package/lib/gcSweepReadyUsageDetection.d.ts +53 -0
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -0
- package/lib/gcSweepReadyUsageDetection.js +130 -0
- package/lib/gcSweepReadyUsageDetection.js.map +1 -0
- package/lib/orderedClientElection.d.ts +28 -10
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js +14 -4
- package/lib/orderedClientElection.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 +0 -11
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +9 -44
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.js +1 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.d.ts +6 -3
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +24 -16
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizerTypes.d.ts +16 -9
- package/lib/summarizerTypes.d.ts.map +1 -1
- 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 +29 -13
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryManager.d.ts +2 -2
- package/lib/summaryManager.js +2 -2
- package/lib/summaryManager.js.map +1 -1
- package/package.json +21 -18
- package/src/batchManager.ts +91 -0
- package/src/batchTracker.ts +1 -2
- package/src/containerRuntime.ts +336 -185
- package/src/dataStoreContext.ts +18 -14
- package/src/dataStores.ts +7 -8
- package/src/deltaScheduler.ts +6 -4
- package/src/garbageCollection.ts +224 -134
- package/src/gcSweepReadyUsageDetection.ts +147 -0
- package/src/orderedClientElection.ts +31 -10
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +9 -57
- package/src/runningSummarizer.ts +1 -1
- package/src/scheduleManager.ts +32 -12
- package/src/summarizerTypes.ts +17 -9
- package/src/summaryCollection.ts +31 -16
- package/src/summaryManager.ts +2 -2
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,15 @@ function isRuntimeMessage(message) {
|
|
|
100
103
|
return false;
|
|
101
104
|
}
|
|
102
105
|
exports.isRuntimeMessage = isRuntimeMessage;
|
|
106
|
+
/**
|
|
107
|
+
* Unpacks runtime messages
|
|
108
|
+
*
|
|
109
|
+
* @remarks This API makes no promises regarding backward-compatability. This is internal API.
|
|
110
|
+
* @param message - message (as it observed in storage / service)
|
|
111
|
+
* @returns unpacked runtime message
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
103
115
|
function unpackRuntimeMessage(message) {
|
|
104
116
|
if (message.type === protocol_definitions_1.MessageType.Operation) {
|
|
105
117
|
// legacy op format?
|
|
@@ -113,14 +125,15 @@ function unpackRuntimeMessage(message) {
|
|
|
113
125
|
message.type = innerContents.type;
|
|
114
126
|
message.contents = innerContents.contents;
|
|
115
127
|
}
|
|
116
|
-
|
|
128
|
+
return true;
|
|
117
129
|
}
|
|
118
130
|
else {
|
|
119
131
|
// Legacy format, but it's already "unpacked",
|
|
120
132
|
// i.e. message.type is actually ContainerMessageType.
|
|
133
|
+
// Or it's non-runtime message.
|
|
121
134
|
// Nothing to do in such case.
|
|
135
|
+
return false;
|
|
122
136
|
}
|
|
123
|
-
return message;
|
|
124
137
|
}
|
|
125
138
|
exports.unpackRuntimeMessage = unpackRuntimeMessage;
|
|
126
139
|
/**
|
|
@@ -163,7 +176,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
163
176
|
this.summaryConfiguration = summaryConfiguration;
|
|
164
177
|
this.defaultMaxConsecutiveReconnects = 7;
|
|
165
178
|
this._orderSequentiallyCalls = 0;
|
|
166
|
-
this.needsFlush = false;
|
|
167
179
|
this.flushTrigger = false;
|
|
168
180
|
this.savedOps = [];
|
|
169
181
|
this.consecutiveReconnects = 0;
|
|
@@ -176,6 +188,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
176
188
|
signalTimestamp: 0,
|
|
177
189
|
trackingSignalSequenceNumber: undefined,
|
|
178
190
|
};
|
|
191
|
+
// Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
|
|
192
|
+
// but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
|
|
193
|
+
// So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
|
|
194
|
+
// payloads. That number represents final (compressed) bits (once compression is implemented).
|
|
195
|
+
this.pendingAttachBatch = new batchManager_1.BatchManager(64 * 1024);
|
|
196
|
+
this.pendingBatch = new batchManager_1.BatchManager();
|
|
179
197
|
this.summarizeOnDemand = (...args) => {
|
|
180
198
|
if (this.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
|
|
181
199
|
return this.summarizer.summarizeOnDemand(...args);
|
|
@@ -233,6 +251,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
233
251
|
getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
|
|
234
252
|
getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
|
|
235
253
|
readAndParseBlob: async (id) => (0, driver_utils_1.readAndParse)(this.storage, id),
|
|
254
|
+
getContainerDiagnosticId: () => this.context.id,
|
|
255
|
+
activeConnection: () => this.deltaManager.active,
|
|
236
256
|
});
|
|
237
257
|
const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
|
|
238
258
|
this.summarizerNode = (0, runtime_utils_1.createRootSummarizerNodeWithGC)(telemetry_utils_1.ChildLogger.create(this.logger, "SummarizerNode"),
|
|
@@ -260,7 +280,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
260
280
|
this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
|
|
261
281
|
}
|
|
262
282
|
}, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
|
|
263
|
-
this.scheduleManager = new scheduleManager_1.ScheduleManager(context.deltaManager, this, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
|
|
283
|
+
this.scheduleManager = new scheduleManager_1.ScheduleManager(context.deltaManager, this, () => this.clientId, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
|
|
264
284
|
this.deltaSender = this.deltaManager;
|
|
265
285
|
this.pendingStateManager = new pendingStateManager_1.PendingStateManager({
|
|
266
286
|
applyStashedOp: this.applyStashedOp.bind(this),
|
|
@@ -270,7 +290,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
270
290
|
flush: this.flush.bind(this),
|
|
271
291
|
flushMode: () => this.flushMode,
|
|
272
292
|
reSubmit: this.reSubmit.bind(this),
|
|
273
|
-
rollback: this.rollback.bind(this),
|
|
274
293
|
setFlushMode: (mode) => this.setFlushMode(mode),
|
|
275
294
|
}, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
|
|
276
295
|
this.context.quorum.on("removeMember", (clientId) => {
|
|
@@ -497,6 +516,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
497
516
|
return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
|
|
498
517
|
}
|
|
499
518
|
get disposed() { return this._disposed; }
|
|
519
|
+
get emptyBatch() {
|
|
520
|
+
return this.pendingBatch.empty && this.pendingAttachBatch.empty;
|
|
521
|
+
}
|
|
500
522
|
get summarizer() {
|
|
501
523
|
(0, common_utils_1.assert)(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
|
|
502
524
|
return this._summarizer;
|
|
@@ -528,12 +550,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
528
550
|
if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
|
|
529
551
|
return true;
|
|
530
552
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
else {
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
553
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
554
|
+
? this.summaryConfiguration.summarizerClientElection === true
|
|
555
|
+
: false;
|
|
537
556
|
}
|
|
538
557
|
getMaxOpsSinceLastSummary() {
|
|
539
558
|
// back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
|
|
@@ -541,12 +560,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
541
560
|
if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
|
|
542
561
|
return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
|
|
543
562
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
else {
|
|
548
|
-
return 0;
|
|
549
|
-
}
|
|
563
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
564
|
+
? this.summaryConfiguration.maxOpsSinceLastSummary
|
|
565
|
+
: 0;
|
|
550
566
|
}
|
|
551
567
|
getInitialSummarizerDelayMs() {
|
|
552
568
|
// back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
|
|
@@ -554,12 +570,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
554
570
|
if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
|
|
555
571
|
return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
|
|
556
572
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
else {
|
|
561
|
-
return 0;
|
|
562
|
-
}
|
|
573
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
574
|
+
? this.summaryConfiguration.initialSummarizerDelayMs
|
|
575
|
+
: 0;
|
|
563
576
|
}
|
|
564
577
|
dispose(error) {
|
|
565
578
|
var _a;
|
|
@@ -633,16 +646,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
633
646
|
}
|
|
634
647
|
if (id === blobManager_1.BlobManager.basePath && requestParser.isLeaf(2)) {
|
|
635
648
|
const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
|
|
636
|
-
|
|
637
|
-
|
|
649
|
+
return blob
|
|
650
|
+
? {
|
|
638
651
|
status: 200,
|
|
639
652
|
mimeType: "fluid/object",
|
|
640
653
|
value: blob,
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
else {
|
|
644
|
-
return (0, runtime_utils_1.create404Response)(request);
|
|
645
|
-
}
|
|
654
|
+
} : (0, runtime_utils_1.create404Response)(request);
|
|
646
655
|
}
|
|
647
656
|
else if (requestParser.pathParts.length > 0) {
|
|
648
657
|
const dataStore = await this.getDataStoreFromRequest(id, request);
|
|
@@ -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, 0x3cd /* 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,
|
|
@@ -865,46 +877,48 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
865
877
|
this.replayPendingStates();
|
|
866
878
|
}
|
|
867
879
|
this.dataStores.setConnectionState(connected, clientId);
|
|
880
|
+
this.garbageCollector.setConnectionState(connected, clientId);
|
|
868
881
|
(0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, connected, clientId);
|
|
869
882
|
}
|
|
870
883
|
process(messageArg, local) {
|
|
871
884
|
var _a;
|
|
872
885
|
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
886
|
// Do shallow copy of message, as methods below will modify it.
|
|
881
887
|
// There might be multiple container instances receiving same message
|
|
882
888
|
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
883
889
|
// but would not modify contents details
|
|
884
890
|
let message = Object.assign({}, messageArg);
|
|
891
|
+
// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
|
|
892
|
+
// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
|
|
893
|
+
// Old ops may contain empty string (I assume noops).
|
|
894
|
+
if (typeof message.contents === "string" && message.contents !== "") {
|
|
895
|
+
message.contents = JSON.parse(message.contents);
|
|
896
|
+
}
|
|
897
|
+
// Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
|
|
898
|
+
// This format was not shipped to production workflows.
|
|
899
|
+
const runtimeMessage = unpackRuntimeMessage(message);
|
|
900
|
+
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
901
|
+
this.savedOps.push(messageArg);
|
|
902
|
+
}
|
|
885
903
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
886
904
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
887
905
|
// messages once a batch has been fully processed.
|
|
888
906
|
this.scheduleManager.beforeOpProcessing(message);
|
|
889
907
|
try {
|
|
890
|
-
message = unpackRuntimeMessage(message);
|
|
891
908
|
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
892
909
|
// once all pieces are available
|
|
893
910
|
message = this.processRemoteChunkedMessage(message);
|
|
894
911
|
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
|
-
}
|
|
912
|
+
if (local && runtimeMessage) {
|
|
913
|
+
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
901
914
|
}
|
|
902
915
|
// If there are no more pending messages after processing a local message,
|
|
903
916
|
// the document is no longer dirty.
|
|
904
|
-
if (!this.
|
|
917
|
+
if (!this.hasPendingMessages()) {
|
|
905
918
|
this.updateDocumentDirtyState(false);
|
|
906
919
|
}
|
|
907
|
-
|
|
920
|
+
const type = message.type;
|
|
921
|
+
switch (type) {
|
|
908
922
|
case ContainerMessageType.Attach:
|
|
909
923
|
this.dataStores.processAttachMessage(message, local);
|
|
910
924
|
break;
|
|
@@ -917,9 +931,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
917
931
|
case ContainerMessageType.BlobAttach:
|
|
918
932
|
this.blobManager.processBlobAttachOp(message, local);
|
|
919
933
|
break;
|
|
934
|
+
case ContainerMessageType.ChunkedOp:
|
|
935
|
+
case ContainerMessageType.Rejoin:
|
|
936
|
+
break;
|
|
920
937
|
default:
|
|
938
|
+
(0, common_utils_1.assert)(!runtimeMessage, 0x3ce /* Runtime message of unknown type */);
|
|
939
|
+
}
|
|
940
|
+
// For back-compat, notify only about runtime messages for now.
|
|
941
|
+
if (runtimeMessage) {
|
|
942
|
+
this.emit("op", message, runtimeMessage);
|
|
921
943
|
}
|
|
922
|
-
this.emit("op", message);
|
|
923
944
|
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
924
945
|
if (local) {
|
|
925
946
|
// If we have processed a local op, this means that the container is
|
|
@@ -1013,25 +1034,57 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1013
1034
|
}
|
|
1014
1035
|
flush() {
|
|
1015
1036
|
(0, common_utils_1.assert)(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1037
|
+
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
1038
|
+
this.flushBatch(this.pendingBatch.popBatch());
|
|
1039
|
+
(0, common_utils_1.assert)(this.emptyBatch, 0x3cf /* reentrancy */);
|
|
1040
|
+
}
|
|
1041
|
+
flushBatch(batch) {
|
|
1042
|
+
const length = batch.length;
|
|
1043
|
+
if (length > 1) {
|
|
1044
|
+
batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
|
|
1045
|
+
batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
|
|
1046
|
+
// This assert fires for the following reason (there might be more cases like that):
|
|
1047
|
+
// AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
|
|
1048
|
+
// i.e. in the middle of op processing!
|
|
1049
|
+
// Sending ops while processing ops is not good idea - it's not defined when
|
|
1050
|
+
// referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
|
|
1051
|
+
// If we send ops in response to processing multiple ops, then we for sure hit this assert!
|
|
1052
|
+
// Tracked via ADO #1834
|
|
1053
|
+
// assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
|
|
1054
|
+
// "Batch should be generated synchronously, without processing ops in the middle!");
|
|
1027
1055
|
}
|
|
1028
|
-
|
|
1056
|
+
let clientSequenceNumber = -1;
|
|
1029
1057
|
// Did we disconnect in the middle of turn-based batch?
|
|
1030
1058
|
// If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
1059
|
+
if (this.canSendOps()) {
|
|
1060
|
+
if (this.context.submitBatchFn !== undefined) {
|
|
1061
|
+
const batchToSend = [];
|
|
1062
|
+
for (const message of batch) {
|
|
1063
|
+
batchToSend.push({ contents: message.contents, metadata: message.metadata });
|
|
1064
|
+
}
|
|
1065
|
+
// returns clientSequenceNumber of last message in a batch
|
|
1066
|
+
clientSequenceNumber = this.context.submitBatchFn(batchToSend);
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
// Legacy path - supporting old loader versions. Can be removed only when LTS moves above
|
|
1070
|
+
// version that has support for batches (submitBatchFn)
|
|
1071
|
+
for (const message of batch) {
|
|
1072
|
+
clientSequenceNumber = this.context.submitFn(protocol_definitions_1.MessageType.Operation, message.deserializedContent, true, // batch
|
|
1073
|
+
message.metadata);
|
|
1074
|
+
}
|
|
1075
|
+
this.deltaSender.flush();
|
|
1076
|
+
}
|
|
1077
|
+
// Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
|
|
1078
|
+
clientSequenceNumber -= batch.length - 1;
|
|
1079
|
+
(0, common_utils_1.assert)(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
|
|
1080
|
+
}
|
|
1081
|
+
// Let the PendingStateManager know that a message was submitted.
|
|
1082
|
+
// In future, need to shift toward keeping batch as a whole!
|
|
1083
|
+
for (const message of batch) {
|
|
1084
|
+
this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
|
|
1085
|
+
clientSequenceNumber++;
|
|
1033
1086
|
}
|
|
1034
|
-
|
|
1087
|
+
this.pendingStateManager.onFlush();
|
|
1035
1088
|
}
|
|
1036
1089
|
orderSequentially(callback) {
|
|
1037
1090
|
// If flush mode is already TurnBased we are either
|
|
@@ -1056,7 +1109,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1056
1109
|
trackOrderSequentiallyCalls(callback) {
|
|
1057
1110
|
let checkpoint;
|
|
1058
1111
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1059
|
-
|
|
1112
|
+
// Note: we are not touching this.pendingAttachBatch here, for two reasons:
|
|
1113
|
+
// 1. It would not help, as we flush attach ops as they become available.
|
|
1114
|
+
// 2. There is no way to undo process of data store creation.
|
|
1115
|
+
checkpoint = this.pendingBatch.checkpoint();
|
|
1060
1116
|
}
|
|
1061
1117
|
try {
|
|
1062
1118
|
this._orderSequentiallyCalls++;
|
|
@@ -1065,7 +1121,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1065
1121
|
catch (error) {
|
|
1066
1122
|
if (checkpoint) {
|
|
1067
1123
|
// This will throw and close the container if rollback fails
|
|
1068
|
-
|
|
1124
|
+
try {
|
|
1125
|
+
checkpoint.rollback((message) => this.rollback(message.deserializedContent.type, message.deserializedContent.contents, message.localOpMetadata));
|
|
1126
|
+
}
|
|
1127
|
+
catch (err) {
|
|
1128
|
+
const error2 = (0, telemetry_utils_1.wrapError)(err, (message) => {
|
|
1129
|
+
return container_utils_1.DataProcessingError.create(`RollbackError: ${message}`, "checkpointRollback", undefined);
|
|
1130
|
+
});
|
|
1131
|
+
this.closeFn(error2);
|
|
1132
|
+
throw error2;
|
|
1133
|
+
}
|
|
1069
1134
|
}
|
|
1070
1135
|
else {
|
|
1071
1136
|
// pre-0.58 error message: orderSequentiallyCallbackException
|
|
@@ -1170,7 +1235,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1170
1235
|
(0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x12e /* "Container Context should already be in attached state" */);
|
|
1171
1236
|
this.emit("attached");
|
|
1172
1237
|
}
|
|
1173
|
-
if (attachState === container_definitions_1.AttachState.Attached && !this.
|
|
1238
|
+
if (attachState === container_definitions_1.AttachState.Attached && !this.hasPendingMessages()) {
|
|
1174
1239
|
this.updateDocumentDirtyState(false);
|
|
1175
1240
|
}
|
|
1176
1241
|
this.dataStores.setAttachState(attachState);
|
|
@@ -1334,7 +1399,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1334
1399
|
}
|
|
1335
1400
|
/**
|
|
1336
1401
|
* Runs garbage collection and updates the reference / used state of the nodes in the container.
|
|
1337
|
-
* @returns the statistics of the garbage collection run.
|
|
1402
|
+
* @returns the statistics of the garbage collection run; undefined if GC did not run.
|
|
1338
1403
|
*/
|
|
1339
1404
|
async collectGarbage(options) {
|
|
1340
1405
|
return this.garbageCollector.collectGarbage(options);
|
|
@@ -1365,6 +1430,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1365
1430
|
const summaryNumberLogger = telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, {
|
|
1366
1431
|
all: { summaryNumber },
|
|
1367
1432
|
});
|
|
1433
|
+
(0, common_utils_1.assert)(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
|
|
1368
1434
|
let latestSnapshotVersionId;
|
|
1369
1435
|
if (refreshLatestAck) {
|
|
1370
1436
|
const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
|
|
@@ -1439,7 +1505,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1439
1505
|
const forcedFullTree = this.garbageCollector.summaryStateNeedsReset;
|
|
1440
1506
|
try {
|
|
1441
1507
|
summarizeResult = await this.summarize({
|
|
1442
|
-
fullTree: fullTree
|
|
1508
|
+
fullTree: fullTree !== null && fullTree !== void 0 ? fullTree : forcedFullTree,
|
|
1443
1509
|
trackState: true,
|
|
1444
1510
|
summaryLogger: summaryNumberLogger,
|
|
1445
1511
|
runGC: this.garbageCollector.shouldRunGC,
|
|
@@ -1528,7 +1594,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1528
1594
|
}
|
|
1529
1595
|
let clientSequenceNumber;
|
|
1530
1596
|
try {
|
|
1531
|
-
clientSequenceNumber = this.
|
|
1597
|
+
clientSequenceNumber = this.submitSummaryMessage(summaryMessage);
|
|
1532
1598
|
}
|
|
1533
1599
|
catch (error) {
|
|
1534
1600
|
return Object.assign(Object.assign({ stage: "upload" }, uploadData), { error });
|
|
@@ -1576,7 +1642,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1576
1642
|
this.chunkMap.delete(clientId);
|
|
1577
1643
|
}
|
|
1578
1644
|
}
|
|
1645
|
+
hasPendingMessages() {
|
|
1646
|
+
return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
|
|
1647
|
+
}
|
|
1579
1648
|
updateDocumentDirtyState(dirty) {
|
|
1649
|
+
if (this.attachState !== container_definitions_1.AttachState.Attached) {
|
|
1650
|
+
(0, common_utils_1.assert)(dirty, 0x3d2 /* Non-attached container is dirty */);
|
|
1651
|
+
}
|
|
1652
|
+
else {
|
|
1653
|
+
// Other way is not true = see this.isContainerMessageDirtyable()
|
|
1654
|
+
(0, common_utils_1.assert)(!dirty || this.hasPendingMessages(), 0x3d3 /* if doc is dirty, there has to be pending ops */);
|
|
1655
|
+
}
|
|
1580
1656
|
if (this.dirtyContainer === dirty) {
|
|
1581
1657
|
return;
|
|
1582
1658
|
}
|
|
@@ -1604,64 +1680,100 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1604
1680
|
this.verifyNotClosed();
|
|
1605
1681
|
return this.blobManager.createBlob(blob);
|
|
1606
1682
|
}
|
|
1607
|
-
submit(type,
|
|
1683
|
+
submit(type, contents, localOpMetadata = undefined, metadata = undefined) {
|
|
1608
1684
|
this.verifyNotClosed();
|
|
1609
1685
|
// There should be no ops in detached container state!
|
|
1610
1686
|
(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
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1687
|
+
const deserializedContent = { type, contents };
|
|
1688
|
+
const serializedContent = JSON.stringify(deserializedContent);
|
|
1689
|
+
if (this.deltaManager.readOnlyInfo.readonly) {
|
|
1690
|
+
this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
|
|
1691
|
+
}
|
|
1692
|
+
const message = {
|
|
1693
|
+
contents: serializedContent,
|
|
1694
|
+
deserializedContent,
|
|
1695
|
+
metadata,
|
|
1696
|
+
localOpMetadata,
|
|
1697
|
+
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
1698
|
+
};
|
|
1699
|
+
try {
|
|
1700
|
+
// If this is attach message for new data store, and we are in a batch, send this op out of order
|
|
1701
|
+
// Is it safe:
|
|
1702
|
+
// Yes, this should be safe reordering. Newly created data stores are not visible through API surface.
|
|
1703
|
+
// They become visible only when aliased, or handle to some sub-element of newly created datastore
|
|
1704
|
+
// is stored in some DDS, i.e. only after some other op.
|
|
1705
|
+
// Why:
|
|
1706
|
+
// Attach ops are large, and expensive to process. Plus there are scenarios where a lot of new data
|
|
1707
|
+
// stores are created, causing issues like relay service throttling (too many ops) and catastrophic
|
|
1708
|
+
// failure (batch is too large). Pushing them earlier and outside of main batch should alleviate
|
|
1709
|
+
// these issues.
|
|
1710
|
+
// Cons:
|
|
1711
|
+
// 1. With large batches, relay service may throttle clients. Clients may disconnect while throttled.
|
|
1712
|
+
// This change creates new possibility of a lot of newly created data stores never being referenced
|
|
1713
|
+
// because client died before it had a change to submit the rest of the ops. This will create more
|
|
1714
|
+
// garbage that needs to be collected leveraging GC (Garbage Collection) feature.
|
|
1715
|
+
// 2. Sending ops out of order means they are excluded from rollback functionality. This is not an issue
|
|
1716
|
+
// today as rollback can't undo creation of data store. To some extent not sending them is a bigger
|
|
1717
|
+
// issue than sending.
|
|
1718
|
+
// Please note that this does not change file format, so it can be disabled in the future if this
|
|
1719
|
+
// optimization no longer makes sense (for example, batch compression may make it less appealing).
|
|
1720
|
+
if (type === ContainerMessageType.Attach &&
|
|
1721
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
|
|
1722
|
+
if (!this.pendingAttachBatch.push(message)) {
|
|
1723
|
+
// BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
|
|
1724
|
+
// when queue is not empty.
|
|
1725
|
+
// Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
|
|
1726
|
+
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
1727
|
+
if (!this.pendingAttachBatch.push(message)) {
|
|
1728
|
+
throw new container_utils_1.GenericError("BatchTooLarge",
|
|
1729
|
+
/* error */ undefined, {
|
|
1730
|
+
opSize: message.contents.length,
|
|
1731
|
+
count: this.pendingAttachBatch.length,
|
|
1732
|
+
limit: this.pendingAttachBatch.limit,
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
if (!this.pendingBatch.push(message)) {
|
|
1739
|
+
throw new container_utils_1.GenericError("BatchTooLarge",
|
|
1740
|
+
/* error */ undefined, {
|
|
1741
|
+
opSize: message.contents.length,
|
|
1742
|
+
count: this.pendingBatch.length,
|
|
1743
|
+
limit: this.pendingBatch.limit,
|
|
1625
1744
|
});
|
|
1626
1745
|
}
|
|
1627
1746
|
}
|
|
1628
|
-
if (
|
|
1629
|
-
|
|
1747
|
+
if (this._flushMode !== runtime_definitions_1.FlushMode.TurnBased) {
|
|
1748
|
+
this.flush();
|
|
1630
1749
|
}
|
|
1631
|
-
else {
|
|
1632
|
-
|
|
1633
|
-
//
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
})
|
|
1639
|
-
clientSequenceNumber = -1;
|
|
1750
|
+
else if (!this.flushTrigger) {
|
|
1751
|
+
this.flushTrigger = true;
|
|
1752
|
+
// Queue a microtask to detect the end of the turn and force a flush.
|
|
1753
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1754
|
+
Promise.resolve().then(() => {
|
|
1755
|
+
this.flushTrigger = false;
|
|
1756
|
+
this.flush();
|
|
1757
|
+
});
|
|
1640
1758
|
}
|
|
1641
1759
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1760
|
+
catch (error) {
|
|
1761
|
+
this.closeFn(error);
|
|
1762
|
+
throw error;
|
|
1763
|
+
}
|
|
1764
|
+
if (this.isContainerMessageDirtyable(type, contents)) {
|
|
1645
1765
|
this.updateDocumentDirtyState(true);
|
|
1646
1766
|
}
|
|
1647
1767
|
}
|
|
1648
|
-
|
|
1768
|
+
submitSummaryMessage(contents) {
|
|
1649
1769
|
this.verifyNotClosed();
|
|
1650
1770
|
(0, common_utils_1.assert)(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
1651
1771
|
// System message should not be sent in the middle of the batch.
|
|
1652
|
-
|
|
1653
|
-
//
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
this.
|
|
1657
|
-
}
|
|
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);
|
|
1772
|
+
(0, common_utils_1.assert)(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
|
|
1773
|
+
// back-compat: ADO #1385: Make this call unconditional in the future
|
|
1774
|
+
return this.context.submitSummaryFn !== undefined
|
|
1775
|
+
? this.context.submitSummaryFn(contents)
|
|
1776
|
+
: this.context.submitFn(protocol_definitions_1.MessageType.Summarize, contents, false);
|
|
1665
1777
|
}
|
|
1666
1778
|
/**
|
|
1667
1779
|
* Throw an error if the runtime is closed. Methods that are expected to potentially
|
|
@@ -1782,6 +1894,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1782
1894
|
if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
|
|
1783
1895
|
throw new container_utils_1.UsageError("can't get state when offline load disabled");
|
|
1784
1896
|
}
|
|
1897
|
+
// Flush pending batch.
|
|
1898
|
+
// getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
|
|
1899
|
+
// to close current batch.
|
|
1900
|
+
this.flush();
|
|
1785
1901
|
const previousPendingState = this.context.pendingLocalState;
|
|
1786
1902
|
if (previousPendingState) {
|
|
1787
1903
|
return {
|
|
@@ -1836,6 +1952,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1836
1952
|
// we may not have seen every sequence number (because of system ops) so apply everything once we
|
|
1837
1953
|
// don't have any more saved ops
|
|
1838
1954
|
await this.pendingStateManager.applyStashedOpsAt();
|
|
1955
|
+
// If it's not the case, we should take it into account when calculating dirty state.
|
|
1956
|
+
(0, common_utils_1.assert)(this.context.attachState === container_definitions_1.AttachState.Attached, 0x3d5 /* this function is called for attached containers only */);
|
|
1957
|
+
if (!this.hasPendingMessages()) {
|
|
1958
|
+
this.updateDocumentDirtyState(false);
|
|
1959
|
+
}
|
|
1839
1960
|
}
|
|
1840
1961
|
validateSummaryHeuristicConfiguration(configuration) {
|
|
1841
1962
|
// eslint-disable-next-line no-restricted-syntax
|