@fluidframework/container-runtime 2.0.0-internal.1.1.2 → 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 +252 -126
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +18 -9
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +24 -16
- 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 +255 -129
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +18 -9
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +25 -17
- 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 +331 -176
- package/src/dataStoreContext.ts +27 -17
- 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/lib/containerRuntime.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AttachState, LoaderHeader, } from "@fluidframework/container-definitions";
|
|
2
2
|
import { assert, Trace, TypedEventEmitter, unreachableCase, } from "@fluidframework/common-utils";
|
|
3
|
-
import { ChildLogger, raiseConnectedEvent, PerformanceEvent, TaggedLoggerAdapter, loggerToMonitoringContext, } from "@fluidframework/telemetry-utils";
|
|
3
|
+
import { ChildLogger, raiseConnectedEvent, PerformanceEvent, TaggedLoggerAdapter, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
4
4
|
import { DriverHeader, FetchSource, } from "@fluidframework/driver-definitions";
|
|
5
|
-
import { readAndParse
|
|
5
|
+
import { readAndParse } from "@fluidframework/driver-utils";
|
|
6
6
|
import { DataCorruptionError, DataProcessingError, GenericError, UsageError, } from "@fluidframework/container-utils";
|
|
7
7
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
8
8
|
import { FlushMode, channelsTreeName, } from "@fluidframework/runtime-definitions";
|
|
@@ -14,7 +14,8 @@ import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
|
14
14
|
import { Summarizer } from "./summarizer";
|
|
15
15
|
import { SummaryManager } from "./summaryManager";
|
|
16
16
|
import { ReportOpPerfTelemetry, } from "./connectionTelemetry";
|
|
17
|
-
import { PendingStateManager } from "./pendingStateManager";
|
|
17
|
+
import { PendingStateManager, } from "./pendingStateManager";
|
|
18
|
+
import { BatchManager } from "./batchManager";
|
|
18
19
|
import { pkgVersion } from "./packageVersion";
|
|
19
20
|
import { BlobManager } from "./blobManager";
|
|
20
21
|
import { DataStores, getSummaryForDatastores } from "./dataStores";
|
|
@@ -75,11 +76,10 @@ export var RuntimeHeaders;
|
|
|
75
76
|
RuntimeHeaders["viaHandle"] = "viaHandle";
|
|
76
77
|
})(RuntimeHeaders || (RuntimeHeaders = {}));
|
|
77
78
|
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
78
|
-
// By default, we should reject any op larger than 768KB,
|
|
79
|
-
// in order to account for some extra overhead from serialization
|
|
80
|
-
// to not reach the 1MB limits in socket.io and Kafka.
|
|
81
|
-
const defaultMaxOpSizeInBytes = 768000;
|
|
82
79
|
const defaultFlushMode = FlushMode.TurnBased;
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated - use ContainerRuntimeMessage instead
|
|
82
|
+
*/
|
|
83
83
|
export var RuntimeMessage;
|
|
84
84
|
(function (RuntimeMessage) {
|
|
85
85
|
RuntimeMessage["FluidDataStoreOp"] = "component";
|
|
@@ -90,12 +90,24 @@ export var RuntimeMessage;
|
|
|
90
90
|
RuntimeMessage["Alias"] = "alias";
|
|
91
91
|
RuntimeMessage["Operation"] = "op";
|
|
92
92
|
})(RuntimeMessage || (RuntimeMessage = {}));
|
|
93
|
+
/**
|
|
94
|
+
* @deprecated - please use version in driver-utils
|
|
95
|
+
*/
|
|
93
96
|
export function isRuntimeMessage(message) {
|
|
94
97
|
if (Object.values(RuntimeMessage).includes(message.type)) {
|
|
95
98
|
return true;
|
|
96
99
|
}
|
|
97
100
|
return false;
|
|
98
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Unpacks runtime messages
|
|
104
|
+
*
|
|
105
|
+
* @remarks This API makes no promises regarding backward-compatability. This is internal API.
|
|
106
|
+
* @param message - message (as it observed in storage / service)
|
|
107
|
+
* @returns unpacked runtime message
|
|
108
|
+
*
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
99
111
|
export function unpackRuntimeMessage(message) {
|
|
100
112
|
if (message.type === MessageType.Operation) {
|
|
101
113
|
// legacy op format?
|
|
@@ -109,14 +121,15 @@ export function unpackRuntimeMessage(message) {
|
|
|
109
121
|
message.type = innerContents.type;
|
|
110
122
|
message.contents = innerContents.contents;
|
|
111
123
|
}
|
|
112
|
-
|
|
124
|
+
return true;
|
|
113
125
|
}
|
|
114
126
|
else {
|
|
115
127
|
// Legacy format, but it's already "unpacked",
|
|
116
128
|
// i.e. message.type is actually ContainerMessageType.
|
|
129
|
+
// Or it's non-runtime message.
|
|
117
130
|
// Nothing to do in such case.
|
|
131
|
+
return false;
|
|
118
132
|
}
|
|
119
|
-
return message;
|
|
120
133
|
}
|
|
121
134
|
/**
|
|
122
135
|
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
|
|
@@ -157,7 +170,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
157
170
|
this.summaryConfiguration = summaryConfiguration;
|
|
158
171
|
this.defaultMaxConsecutiveReconnects = 7;
|
|
159
172
|
this._orderSequentiallyCalls = 0;
|
|
160
|
-
this.needsFlush = false;
|
|
161
173
|
this.flushTrigger = false;
|
|
162
174
|
this.savedOps = [];
|
|
163
175
|
this.consecutiveReconnects = 0;
|
|
@@ -170,6 +182,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
170
182
|
signalTimestamp: 0,
|
|
171
183
|
trackingSignalSequenceNumber: undefined,
|
|
172
184
|
};
|
|
185
|
+
// Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
|
|
186
|
+
// but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
|
|
187
|
+
// So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
|
|
188
|
+
// payloads. That number represents final (compressed) bits (once compression is implemented).
|
|
189
|
+
this.pendingAttachBatch = new BatchManager(64 * 1024);
|
|
190
|
+
this.pendingBatch = new BatchManager();
|
|
173
191
|
this.summarizeOnDemand = (...args) => {
|
|
174
192
|
if (this.clientDetails.type === summarizerClientType) {
|
|
175
193
|
return this.summarizer.summarizeOnDemand(...args);
|
|
@@ -227,6 +245,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
227
245
|
getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
|
|
228
246
|
getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
|
|
229
247
|
readAndParseBlob: async (id) => readAndParse(this.storage, id),
|
|
248
|
+
getContainerDiagnosticId: () => this.context.id,
|
|
249
|
+
activeConnection: () => this.deltaManager.active,
|
|
230
250
|
});
|
|
231
251
|
const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
|
|
232
252
|
this.summarizerNode = createRootSummarizerNodeWithGC(ChildLogger.create(this.logger, "SummarizerNode"),
|
|
@@ -254,7 +274,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
254
274
|
this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
|
|
255
275
|
}
|
|
256
276
|
}, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
|
|
257
|
-
this.scheduleManager = new ScheduleManager(context.deltaManager, this, ChildLogger.create(this.logger, "ScheduleManager"));
|
|
277
|
+
this.scheduleManager = new ScheduleManager(context.deltaManager, this, () => this.clientId, ChildLogger.create(this.logger, "ScheduleManager"));
|
|
258
278
|
this.deltaSender = this.deltaManager;
|
|
259
279
|
this.pendingStateManager = new PendingStateManager({
|
|
260
280
|
applyStashedOp: this.applyStashedOp.bind(this),
|
|
@@ -264,7 +284,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
264
284
|
flush: this.flush.bind(this),
|
|
265
285
|
flushMode: () => this.flushMode,
|
|
266
286
|
reSubmit: this.reSubmit.bind(this),
|
|
267
|
-
rollback: this.rollback.bind(this),
|
|
268
287
|
setFlushMode: (mode) => this.setFlushMode(mode),
|
|
269
288
|
}, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
|
|
270
289
|
this.context.quorum.on("removeMember", (clientId) => {
|
|
@@ -491,6 +510,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
491
510
|
return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
|
|
492
511
|
}
|
|
493
512
|
get disposed() { return this._disposed; }
|
|
513
|
+
get emptyBatch() {
|
|
514
|
+
return this.pendingBatch.empty && this.pendingAttachBatch.empty;
|
|
515
|
+
}
|
|
494
516
|
get summarizer() {
|
|
495
517
|
assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
|
|
496
518
|
return this._summarizer;
|
|
@@ -522,12 +544,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
522
544
|
if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
|
|
523
545
|
return true;
|
|
524
546
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
else {
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
547
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
548
|
+
? this.summaryConfiguration.summarizerClientElection === true
|
|
549
|
+
: false;
|
|
531
550
|
}
|
|
532
551
|
getMaxOpsSinceLastSummary() {
|
|
533
552
|
// back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
|
|
@@ -535,12 +554,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
535
554
|
if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
|
|
536
555
|
return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
|
|
537
556
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
else {
|
|
542
|
-
return 0;
|
|
543
|
-
}
|
|
557
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
558
|
+
? this.summaryConfiguration.maxOpsSinceLastSummary
|
|
559
|
+
: 0;
|
|
544
560
|
}
|
|
545
561
|
getInitialSummarizerDelayMs() {
|
|
546
562
|
// back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
|
|
@@ -548,12 +564,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
548
564
|
if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
|
|
549
565
|
return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
|
|
550
566
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
else {
|
|
555
|
-
return 0;
|
|
556
|
-
}
|
|
567
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
568
|
+
? this.summaryConfiguration.initialSummarizerDelayMs
|
|
569
|
+
: 0;
|
|
557
570
|
}
|
|
558
571
|
dispose(error) {
|
|
559
572
|
var _a;
|
|
@@ -627,16 +640,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
627
640
|
}
|
|
628
641
|
if (id === BlobManager.basePath && requestParser.isLeaf(2)) {
|
|
629
642
|
const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
|
|
630
|
-
|
|
631
|
-
|
|
643
|
+
return blob
|
|
644
|
+
? {
|
|
632
645
|
status: 200,
|
|
633
646
|
mimeType: "fluid/object",
|
|
634
647
|
value: blob,
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
return create404Response(request);
|
|
639
|
-
}
|
|
648
|
+
} : create404Response(request);
|
|
640
649
|
}
|
|
641
650
|
else if (requestParser.pathParts.length > 0) {
|
|
642
651
|
const dataStore = await this.getDataStoreFromRequest(id, request);
|
|
@@ -736,7 +745,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
736
745
|
// Feature disabled, we never stop reconnecting
|
|
737
746
|
return true;
|
|
738
747
|
}
|
|
739
|
-
if (!this.
|
|
748
|
+
if (!this.hasPendingMessages()) {
|
|
740
749
|
// If there are no pending messages, we can always reconnect
|
|
741
750
|
this.resetReconnectCount();
|
|
742
751
|
return true;
|
|
@@ -841,13 +850,16 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
841
850
|
this._perfSignalData.signalTimestamp = 0;
|
|
842
851
|
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
843
852
|
}
|
|
853
|
+
else {
|
|
854
|
+
assert(this.attachState === AttachState.Attached, 0x3cd /* Connection is possible only if container exists in storage */);
|
|
855
|
+
}
|
|
844
856
|
// Fail while disconnected
|
|
845
857
|
if (reconnection) {
|
|
846
858
|
this.consecutiveReconnects++;
|
|
847
859
|
if (!this.shouldContinueReconnecting()) {
|
|
848
|
-
this.closeFn(
|
|
849
|
-
//
|
|
850
|
-
|
|
860
|
+
this.closeFn(DataProcessingError.create(
|
|
861
|
+
// eslint-disable-next-line max-len
|
|
862
|
+
"Runtime detected too many reconnects with no progress syncing local ops. Batch of ops is likely too large (over 1Mb)", "setConnectionState", undefined, {
|
|
851
863
|
dataLoss: 1,
|
|
852
864
|
attempts: this.consecutiveReconnects,
|
|
853
865
|
pendingMessages: this.pendingStateManager.pendingMessagesCount,
|
|
@@ -859,46 +871,48 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
859
871
|
this.replayPendingStates();
|
|
860
872
|
}
|
|
861
873
|
this.dataStores.setConnectionState(connected, clientId);
|
|
874
|
+
this.garbageCollector.setConnectionState(connected, clientId);
|
|
862
875
|
raiseConnectedEvent(this.mc.logger, this, connected, clientId);
|
|
863
876
|
}
|
|
864
877
|
process(messageArg, local) {
|
|
865
878
|
var _a;
|
|
866
879
|
this.verifyNotClosed();
|
|
867
|
-
// If it's not message for runtime, bail out right away.
|
|
868
|
-
if (!isUnpackedRuntimeMessage(messageArg)) {
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
872
|
-
this.savedOps.push(messageArg);
|
|
873
|
-
}
|
|
874
880
|
// Do shallow copy of message, as methods below will modify it.
|
|
875
881
|
// There might be multiple container instances receiving same message
|
|
876
882
|
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
877
883
|
// but would not modify contents details
|
|
878
884
|
let message = Object.assign({}, messageArg);
|
|
885
|
+
// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
|
|
886
|
+
// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
|
|
887
|
+
// Old ops may contain empty string (I assume noops).
|
|
888
|
+
if (typeof message.contents === "string" && message.contents !== "") {
|
|
889
|
+
message.contents = JSON.parse(message.contents);
|
|
890
|
+
}
|
|
891
|
+
// Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
|
|
892
|
+
// This format was not shipped to production workflows.
|
|
893
|
+
const runtimeMessage = unpackRuntimeMessage(message);
|
|
894
|
+
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
895
|
+
this.savedOps.push(messageArg);
|
|
896
|
+
}
|
|
879
897
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
880
898
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
881
899
|
// messages once a batch has been fully processed.
|
|
882
900
|
this.scheduleManager.beforeOpProcessing(message);
|
|
883
901
|
try {
|
|
884
|
-
message = unpackRuntimeMessage(message);
|
|
885
902
|
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
886
903
|
// once all pieces are available
|
|
887
904
|
message = this.processRemoteChunkedMessage(message);
|
|
888
905
|
let localOpMetadata;
|
|
889
|
-
if (local) {
|
|
890
|
-
|
|
891
|
-
// Do not process local chunked ops until all pieces are available.
|
|
892
|
-
if (message.type !== ContainerMessageType.ChunkedOp) {
|
|
893
|
-
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
894
|
-
}
|
|
906
|
+
if (local && runtimeMessage) {
|
|
907
|
+
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
895
908
|
}
|
|
896
909
|
// If there are no more pending messages after processing a local message,
|
|
897
910
|
// the document is no longer dirty.
|
|
898
|
-
if (!this.
|
|
911
|
+
if (!this.hasPendingMessages()) {
|
|
899
912
|
this.updateDocumentDirtyState(false);
|
|
900
913
|
}
|
|
901
|
-
|
|
914
|
+
const type = message.type;
|
|
915
|
+
switch (type) {
|
|
902
916
|
case ContainerMessageType.Attach:
|
|
903
917
|
this.dataStores.processAttachMessage(message, local);
|
|
904
918
|
break;
|
|
@@ -911,9 +925,16 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
911
925
|
case ContainerMessageType.BlobAttach:
|
|
912
926
|
this.blobManager.processBlobAttachOp(message, local);
|
|
913
927
|
break;
|
|
928
|
+
case ContainerMessageType.ChunkedOp:
|
|
929
|
+
case ContainerMessageType.Rejoin:
|
|
930
|
+
break;
|
|
914
931
|
default:
|
|
932
|
+
assert(!runtimeMessage, 0x3ce /* Runtime message of unknown type */);
|
|
933
|
+
}
|
|
934
|
+
// For back-compat, notify only about runtime messages for now.
|
|
935
|
+
if (runtimeMessage) {
|
|
936
|
+
this.emit("op", message, runtimeMessage);
|
|
915
937
|
}
|
|
916
|
-
this.emit("op", message);
|
|
917
938
|
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
918
939
|
if (local) {
|
|
919
940
|
// If we have processed a local op, this means that the container is
|
|
@@ -1007,25 +1028,57 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1007
1028
|
}
|
|
1008
1029
|
flush() {
|
|
1009
1030
|
assert(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
|
|
1010
|
-
|
|
1011
|
-
|
|
1031
|
+
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
1032
|
+
this.flushBatch(this.pendingBatch.popBatch());
|
|
1033
|
+
assert(this.emptyBatch, 0x3cf /* reentrancy */);
|
|
1034
|
+
}
|
|
1035
|
+
flushBatch(batch) {
|
|
1036
|
+
const length = batch.length;
|
|
1037
|
+
if (length > 1) {
|
|
1038
|
+
batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
|
|
1039
|
+
batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
|
|
1040
|
+
// This assert fires for the following reason (there might be more cases like that):
|
|
1041
|
+
// AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
|
|
1042
|
+
// i.e. in the middle of op processing!
|
|
1043
|
+
// Sending ops while processing ops is not good idea - it's not defined when
|
|
1044
|
+
// referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
|
|
1045
|
+
// If we send ops in response to processing multiple ops, then we for sure hit this assert!
|
|
1046
|
+
// Tracked via ADO #1834
|
|
1047
|
+
// assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
|
|
1048
|
+
// "Batch should be generated synchronously, without processing ops in the middle!");
|
|
1012
1049
|
}
|
|
1013
|
-
|
|
1014
|
-
// Note that this should happen before the `this.needsFlush` check below because in the scenario where we are
|
|
1015
|
-
// not connected, `this.needsFlush` will be false but the PendingStateManager might have pending messages and
|
|
1016
|
-
// hence needs to track this.
|
|
1017
|
-
this.pendingStateManager.onFlush();
|
|
1018
|
-
// If flush has already been called then exit early
|
|
1019
|
-
if (!this.needsFlush) {
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
this.needsFlush = false;
|
|
1050
|
+
let clientSequenceNumber = -1;
|
|
1023
1051
|
// Did we disconnect in the middle of turn-based batch?
|
|
1024
1052
|
// If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
|
|
1025
|
-
if (
|
|
1026
|
-
|
|
1053
|
+
if (this.canSendOps()) {
|
|
1054
|
+
if (this.context.submitBatchFn !== undefined) {
|
|
1055
|
+
const batchToSend = [];
|
|
1056
|
+
for (const message of batch) {
|
|
1057
|
+
batchToSend.push({ contents: message.contents, metadata: message.metadata });
|
|
1058
|
+
}
|
|
1059
|
+
// returns clientSequenceNumber of last message in a batch
|
|
1060
|
+
clientSequenceNumber = this.context.submitBatchFn(batchToSend);
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
// Legacy path - supporting old loader versions. Can be removed only when LTS moves above
|
|
1064
|
+
// version that has support for batches (submitBatchFn)
|
|
1065
|
+
for (const message of batch) {
|
|
1066
|
+
clientSequenceNumber = this.context.submitFn(MessageType.Operation, message.deserializedContent, true, // batch
|
|
1067
|
+
message.metadata);
|
|
1068
|
+
}
|
|
1069
|
+
this.deltaSender.flush();
|
|
1070
|
+
}
|
|
1071
|
+
// Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
|
|
1072
|
+
clientSequenceNumber -= batch.length - 1;
|
|
1073
|
+
assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
|
|
1074
|
+
}
|
|
1075
|
+
// Let the PendingStateManager know that a message was submitted.
|
|
1076
|
+
// In future, need to shift toward keeping batch as a whole!
|
|
1077
|
+
for (const message of batch) {
|
|
1078
|
+
this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
|
|
1079
|
+
clientSequenceNumber++;
|
|
1027
1080
|
}
|
|
1028
|
-
|
|
1081
|
+
this.pendingStateManager.onFlush();
|
|
1029
1082
|
}
|
|
1030
1083
|
orderSequentially(callback) {
|
|
1031
1084
|
// If flush mode is already TurnBased we are either
|
|
@@ -1050,7 +1103,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1050
1103
|
trackOrderSequentiallyCalls(callback) {
|
|
1051
1104
|
let checkpoint;
|
|
1052
1105
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1053
|
-
|
|
1106
|
+
// Note: we are not touching this.pendingAttachBatch here, for two reasons:
|
|
1107
|
+
// 1. It would not help, as we flush attach ops as they become available.
|
|
1108
|
+
// 2. There is no way to undo process of data store creation.
|
|
1109
|
+
checkpoint = this.pendingBatch.checkpoint();
|
|
1054
1110
|
}
|
|
1055
1111
|
try {
|
|
1056
1112
|
this._orderSequentiallyCalls++;
|
|
@@ -1059,7 +1115,16 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1059
1115
|
catch (error) {
|
|
1060
1116
|
if (checkpoint) {
|
|
1061
1117
|
// This will throw and close the container if rollback fails
|
|
1062
|
-
|
|
1118
|
+
try {
|
|
1119
|
+
checkpoint.rollback((message) => this.rollback(message.deserializedContent.type, message.deserializedContent.contents, message.localOpMetadata));
|
|
1120
|
+
}
|
|
1121
|
+
catch (err) {
|
|
1122
|
+
const error2 = wrapError(err, (message) => {
|
|
1123
|
+
return DataProcessingError.create(`RollbackError: ${message}`, "checkpointRollback", undefined);
|
|
1124
|
+
});
|
|
1125
|
+
this.closeFn(error2);
|
|
1126
|
+
throw error2;
|
|
1127
|
+
}
|
|
1063
1128
|
}
|
|
1064
1129
|
else {
|
|
1065
1130
|
// pre-0.58 error message: orderSequentiallyCallbackException
|
|
@@ -1164,7 +1229,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1164
1229
|
assert(this.attachState === AttachState.Attached, 0x12e /* "Container Context should already be in attached state" */);
|
|
1165
1230
|
this.emit("attached");
|
|
1166
1231
|
}
|
|
1167
|
-
if (attachState === AttachState.Attached && !this.
|
|
1232
|
+
if (attachState === AttachState.Attached && !this.hasPendingMessages()) {
|
|
1168
1233
|
this.updateDocumentDirtyState(false);
|
|
1169
1234
|
}
|
|
1170
1235
|
this.dataStores.setAttachState(attachState);
|
|
@@ -1328,7 +1393,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1328
1393
|
}
|
|
1329
1394
|
/**
|
|
1330
1395
|
* Runs garbage collection and updates the reference / used state of the nodes in the container.
|
|
1331
|
-
* @returns the statistics of the garbage collection run.
|
|
1396
|
+
* @returns the statistics of the garbage collection run; undefined if GC did not run.
|
|
1332
1397
|
*/
|
|
1333
1398
|
async collectGarbage(options) {
|
|
1334
1399
|
return this.garbageCollector.collectGarbage(options);
|
|
@@ -1359,6 +1424,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1359
1424
|
const summaryNumberLogger = ChildLogger.create(summaryLogger, undefined, {
|
|
1360
1425
|
all: { summaryNumber },
|
|
1361
1426
|
});
|
|
1427
|
+
assert(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
|
|
1362
1428
|
let latestSnapshotVersionId;
|
|
1363
1429
|
if (refreshLatestAck) {
|
|
1364
1430
|
const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
|
|
@@ -1433,7 +1499,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1433
1499
|
const forcedFullTree = this.garbageCollector.summaryStateNeedsReset;
|
|
1434
1500
|
try {
|
|
1435
1501
|
summarizeResult = await this.summarize({
|
|
1436
|
-
fullTree: fullTree
|
|
1502
|
+
fullTree: fullTree !== null && fullTree !== void 0 ? fullTree : forcedFullTree,
|
|
1437
1503
|
trackState: true,
|
|
1438
1504
|
summaryLogger: summaryNumberLogger,
|
|
1439
1505
|
runGC: this.garbageCollector.shouldRunGC,
|
|
@@ -1522,7 +1588,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1522
1588
|
}
|
|
1523
1589
|
let clientSequenceNumber;
|
|
1524
1590
|
try {
|
|
1525
|
-
clientSequenceNumber = this.
|
|
1591
|
+
clientSequenceNumber = this.submitSummaryMessage(summaryMessage);
|
|
1526
1592
|
}
|
|
1527
1593
|
catch (error) {
|
|
1528
1594
|
return Object.assign(Object.assign({ stage: "upload" }, uploadData), { error });
|
|
@@ -1570,7 +1636,17 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1570
1636
|
this.chunkMap.delete(clientId);
|
|
1571
1637
|
}
|
|
1572
1638
|
}
|
|
1639
|
+
hasPendingMessages() {
|
|
1640
|
+
return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
|
|
1641
|
+
}
|
|
1573
1642
|
updateDocumentDirtyState(dirty) {
|
|
1643
|
+
if (this.attachState !== AttachState.Attached) {
|
|
1644
|
+
assert(dirty, 0x3d2 /* Non-attached container is dirty */);
|
|
1645
|
+
}
|
|
1646
|
+
else {
|
|
1647
|
+
// Other way is not true = see this.isContainerMessageDirtyable()
|
|
1648
|
+
assert(!dirty || this.hasPendingMessages(), 0x3d3 /* if doc is dirty, there has to be pending ops */);
|
|
1649
|
+
}
|
|
1574
1650
|
if (this.dirtyContainer === dirty) {
|
|
1575
1651
|
return;
|
|
1576
1652
|
}
|
|
@@ -1598,64 +1674,100 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1598
1674
|
this.verifyNotClosed();
|
|
1599
1675
|
return this.blobManager.createBlob(blob);
|
|
1600
1676
|
}
|
|
1601
|
-
submit(type,
|
|
1677
|
+
submit(type, contents, localOpMetadata = undefined, metadata = undefined) {
|
|
1602
1678
|
this.verifyNotClosed();
|
|
1603
1679
|
// There should be no ops in detached container state!
|
|
1604
1680
|
assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
if (this.
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1681
|
+
const deserializedContent = { type, contents };
|
|
1682
|
+
const serializedContent = JSON.stringify(deserializedContent);
|
|
1683
|
+
if (this.deltaManager.readOnlyInfo.readonly) {
|
|
1684
|
+
this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
|
|
1685
|
+
}
|
|
1686
|
+
const message = {
|
|
1687
|
+
contents: serializedContent,
|
|
1688
|
+
deserializedContent,
|
|
1689
|
+
metadata,
|
|
1690
|
+
localOpMetadata,
|
|
1691
|
+
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
1692
|
+
};
|
|
1693
|
+
try {
|
|
1694
|
+
// If this is attach message for new data store, and we are in a batch, send this op out of order
|
|
1695
|
+
// Is it safe:
|
|
1696
|
+
// Yes, this should be safe reordering. Newly created data stores are not visible through API surface.
|
|
1697
|
+
// They become visible only when aliased, or handle to some sub-element of newly created datastore
|
|
1698
|
+
// is stored in some DDS, i.e. only after some other op.
|
|
1699
|
+
// Why:
|
|
1700
|
+
// Attach ops are large, and expensive to process. Plus there are scenarios where a lot of new data
|
|
1701
|
+
// stores are created, causing issues like relay service throttling (too many ops) and catastrophic
|
|
1702
|
+
// failure (batch is too large). Pushing them earlier and outside of main batch should alleviate
|
|
1703
|
+
// these issues.
|
|
1704
|
+
// Cons:
|
|
1705
|
+
// 1. With large batches, relay service may throttle clients. Clients may disconnect while throttled.
|
|
1706
|
+
// This change creates new possibility of a lot of newly created data stores never being referenced
|
|
1707
|
+
// because client died before it had a change to submit the rest of the ops. This will create more
|
|
1708
|
+
// garbage that needs to be collected leveraging GC (Garbage Collection) feature.
|
|
1709
|
+
// 2. Sending ops out of order means they are excluded from rollback functionality. This is not an issue
|
|
1710
|
+
// today as rollback can't undo creation of data store. To some extent not sending them is a bigger
|
|
1711
|
+
// issue than sending.
|
|
1712
|
+
// Please note that this does not change file format, so it can be disabled in the future if this
|
|
1713
|
+
// optimization no longer makes sense (for example, batch compression may make it less appealing).
|
|
1714
|
+
if (type === ContainerMessageType.Attach &&
|
|
1715
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
|
|
1716
|
+
if (!this.pendingAttachBatch.push(message)) {
|
|
1717
|
+
// BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
|
|
1718
|
+
// when queue is not empty.
|
|
1719
|
+
// Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
|
|
1720
|
+
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
1721
|
+
if (!this.pendingAttachBatch.push(message)) {
|
|
1722
|
+
throw new GenericError("BatchTooLarge",
|
|
1723
|
+
/* error */ undefined, {
|
|
1724
|
+
opSize: message.contents.length,
|
|
1725
|
+
count: this.pendingAttachBatch.length,
|
|
1726
|
+
limit: this.pendingAttachBatch.limit,
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
else {
|
|
1732
|
+
if (!this.pendingBatch.push(message)) {
|
|
1733
|
+
throw new GenericError("BatchTooLarge",
|
|
1734
|
+
/* error */ undefined, {
|
|
1735
|
+
opSize: message.contents.length,
|
|
1736
|
+
count: this.pendingBatch.length,
|
|
1737
|
+
limit: this.pendingBatch.limit,
|
|
1619
1738
|
});
|
|
1620
1739
|
}
|
|
1621
1740
|
}
|
|
1622
|
-
if (
|
|
1623
|
-
|
|
1741
|
+
if (this._flushMode !== FlushMode.TurnBased) {
|
|
1742
|
+
this.flush();
|
|
1624
1743
|
}
|
|
1625
|
-
else {
|
|
1626
|
-
|
|
1627
|
-
//
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
})
|
|
1633
|
-
clientSequenceNumber = -1;
|
|
1744
|
+
else if (!this.flushTrigger) {
|
|
1745
|
+
this.flushTrigger = true;
|
|
1746
|
+
// Queue a microtask to detect the end of the turn and force a flush.
|
|
1747
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1748
|
+
Promise.resolve().then(() => {
|
|
1749
|
+
this.flushTrigger = false;
|
|
1750
|
+
this.flush();
|
|
1751
|
+
});
|
|
1634
1752
|
}
|
|
1635
1753
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
this.closeFn(error);
|
|
1756
|
+
throw error;
|
|
1757
|
+
}
|
|
1758
|
+
if (this.isContainerMessageDirtyable(type, contents)) {
|
|
1639
1759
|
this.updateDocumentDirtyState(true);
|
|
1640
1760
|
}
|
|
1641
1761
|
}
|
|
1642
|
-
|
|
1762
|
+
submitSummaryMessage(contents) {
|
|
1643
1763
|
this.verifyNotClosed();
|
|
1644
1764
|
assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
1645
1765
|
// System message should not be sent in the middle of the batch.
|
|
1646
|
-
|
|
1647
|
-
//
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
this.
|
|
1651
|
-
}
|
|
1652
|
-
return this.context.submitFn(type, contents, middleOfBatch);
|
|
1653
|
-
}
|
|
1654
|
-
submitRuntimeMessage(type, contents, batch, appData) {
|
|
1655
|
-
this.verifyNotClosed();
|
|
1656
|
-
assert(this.connected, 0x259 /* "Container disconnected when trying to submit system message" */);
|
|
1657
|
-
const payload = { type, contents };
|
|
1658
|
-
return this.context.submitFn(MessageType.Operation, payload, batch, appData);
|
|
1766
|
+
assert(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
|
|
1767
|
+
// back-compat: ADO #1385: Make this call unconditional in the future
|
|
1768
|
+
return this.context.submitSummaryFn !== undefined
|
|
1769
|
+
? this.context.submitSummaryFn(contents)
|
|
1770
|
+
: this.context.submitFn(MessageType.Summarize, contents, false);
|
|
1659
1771
|
}
|
|
1660
1772
|
/**
|
|
1661
1773
|
* Throw an error if the runtime is closed. Methods that are expected to potentially
|
|
@@ -1709,13 +1821,18 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1709
1821
|
/** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
|
|
1710
1822
|
async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
|
|
1711
1823
|
const readAndParseBlob = async (id) => readAndParse(this.storage, id);
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1824
|
+
// The call to fetch the snapshot is very expensive and not always needed.
|
|
1825
|
+
// It should only be done by the summarizerNode, if required.
|
|
1826
|
+
const snapshotTreeFetcher = async () => {
|
|
1827
|
+
const fetchResult = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
|
|
1828
|
+
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
1829
|
+
ackHandle,
|
|
1830
|
+
summaryRefSeq,
|
|
1831
|
+
fetchLatest: false,
|
|
1832
|
+
});
|
|
1833
|
+
return fetchResult.snapshotTree;
|
|
1834
|
+
};
|
|
1835
|
+
const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, snapshotTreeFetcher, readAndParseBlob, summaryLogger);
|
|
1719
1836
|
// Notify the garbage collector so it can update its latest summary state.
|
|
1720
1837
|
await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
|
|
1721
1838
|
}
|
|
@@ -1771,6 +1888,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1771
1888
|
if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
|
|
1772
1889
|
throw new UsageError("can't get state when offline load disabled");
|
|
1773
1890
|
}
|
|
1891
|
+
// Flush pending batch.
|
|
1892
|
+
// getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
|
|
1893
|
+
// to close current batch.
|
|
1894
|
+
this.flush();
|
|
1774
1895
|
const previousPendingState = this.context.pendingLocalState;
|
|
1775
1896
|
if (previousPendingState) {
|
|
1776
1897
|
return {
|
|
@@ -1825,6 +1946,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1825
1946
|
// we may not have seen every sequence number (because of system ops) so apply everything once we
|
|
1826
1947
|
// don't have any more saved ops
|
|
1827
1948
|
await this.pendingStateManager.applyStashedOpsAt();
|
|
1949
|
+
// If it's not the case, we should take it into account when calculating dirty state.
|
|
1950
|
+
assert(this.context.attachState === AttachState.Attached, 0x3d5 /* this function is called for attached containers only */);
|
|
1951
|
+
if (!this.hasPendingMessages()) {
|
|
1952
|
+
this.updateDocumentDirtyState(false);
|
|
1953
|
+
}
|
|
1828
1954
|
}
|
|
1829
1955
|
validateSummaryHeuristicConfiguration(configuration) {
|
|
1830
1956
|
// eslint-disable-next-line no-restricted-syntax
|