@fluidframework/container-runtime 2.41.0 → 2.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/channelCollection.d.ts +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +4 -4
- package/dist/channelCollection.js.map +1 -1
- package/dist/compatUtils.d.ts +22 -1
- package/dist/compatUtils.d.ts.map +1 -1
- package/dist/compatUtils.js +109 -7
- package/dist/compatUtils.js.map +1 -1
- package/dist/containerRuntime.d.ts +34 -13
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +158 -59
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +5 -0
- package/dist/dataStore.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +2 -0
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +1 -1
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +5 -4
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/metadata.d.ts +1 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +6 -5
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +9 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +6 -4
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSerialization.d.ts +2 -1
- package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
- package/dist/opLifecycle/opSerialization.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 +18 -5
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +20 -13
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summary/documentSchema.d.ts +42 -18
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +62 -52
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/index.d.ts +1 -1
- package/dist/summary/index.d.ts.map +1 -1
- package/dist/summary/index.js.map +1 -1
- package/lib/channelCollection.d.ts +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +4 -4
- package/lib/channelCollection.js.map +1 -1
- package/lib/compatUtils.d.ts +22 -1
- package/lib/compatUtils.d.ts.map +1 -1
- package/lib/compatUtils.js +102 -3
- package/lib/compatUtils.js.map +1 -1
- package/lib/containerRuntime.d.ts +34 -13
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +160 -61
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +5 -0
- package/lib/dataStore.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +2 -0
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +1 -1
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +5 -4
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/metadata.d.ts +1 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +6 -5
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +9 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +6 -4
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSerialization.d.ts +2 -1
- package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
- package/lib/opLifecycle/opSerialization.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 +18 -5
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +20 -13
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summary/documentSchema.d.ts +42 -18
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +62 -52
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/index.d.ts +1 -1
- package/lib/summary/index.d.ts.map +1 -1
- package/lib/summary/index.js.map +1 -1
- package/package.json +18 -18
- package/src/channelCollection.ts +4 -4
- package/src/compatUtils.ts +145 -10
- package/src/containerRuntime.ts +209 -73
- package/src/dataStore.ts +7 -0
- package/src/gc/garbageCollection.ts +2 -0
- package/src/gc/gcDefinitions.ts +1 -1
- package/src/index.ts +2 -1
- package/src/messageTypes.ts +12 -5
- package/src/metadata.ts +1 -1
- package/src/opLifecycle/definitions.ts +7 -3
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/opGroupingManager.ts +17 -4
- package/src/opLifecycle/opSerialization.ts +6 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +49 -22
- package/src/summary/documentSchema.ts +111 -86
- package/src/summary/index.ts +2 -1
package/dist/containerRuntime.js
CHANGED
|
@@ -197,6 +197,20 @@ async function loadContainerRuntime(params) {
|
|
|
197
197
|
}
|
|
198
198
|
exports.loadContainerRuntime = loadContainerRuntime;
|
|
199
199
|
const defaultMaxConsecutiveReconnects = 7;
|
|
200
|
+
/**
|
|
201
|
+
* These are the ONLY message types that are allowed to be submitted while in staging mode
|
|
202
|
+
* (Does not apply to pre-StagingMode batches that are resubmitted, those are not considered to be staged)
|
|
203
|
+
*/
|
|
204
|
+
function canStageMessageOfType(type) {
|
|
205
|
+
return (
|
|
206
|
+
// These are user changes coming up from the runtime's DataStores
|
|
207
|
+
type === messageTypes_js_1.ContainerMessageType.FluidDataStoreOp ||
|
|
208
|
+
// GC ops are used to detect issues in the reference graph so all clients can repair their GC state.
|
|
209
|
+
// These can be submitted at any time, including while in Staging Mode.
|
|
210
|
+
type === messageTypes_js_1.ContainerMessageType.GC ||
|
|
211
|
+
// These are typically sent shortly after boot and will not be common in Staging Mode, but it's possible.
|
|
212
|
+
type === messageTypes_js_1.ContainerMessageType.DocumentSchemaChange);
|
|
213
|
+
}
|
|
200
214
|
/**
|
|
201
215
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
202
216
|
* It will define the store level mappings.
|
|
@@ -245,6 +259,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
245
259
|
if (!(0, compatUtils_js_1.isValidMinVersionForCollab)(minVersionForCollab)) {
|
|
246
260
|
throw new internal_8.UsageError(`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`);
|
|
247
261
|
}
|
|
262
|
+
// We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
|
|
263
|
+
// were manually set.
|
|
264
|
+
(0, compatUtils_js_1.validateRuntimeOptions)(minVersionForCollab, runtimeOptions);
|
|
248
265
|
const defaultsAffectingDocSchema = (0, compatUtils_js_1.getMinVersionForCollabDefaults)(minVersionForCollab);
|
|
249
266
|
// The following are the default values for the options that do not affect the DocumentSchema.
|
|
250
267
|
const defaultsNotAffectingDocSchema = {
|
|
@@ -510,8 +527,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
510
527
|
ensureNoDataModelChanges(callback) {
|
|
511
528
|
return this.dataModelChangeRunner.run(callback);
|
|
512
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Indicates whether the container is in a state where it is able to send
|
|
532
|
+
* ops (connected to op stream and not in readonly mode).
|
|
533
|
+
*/
|
|
513
534
|
get connected() {
|
|
514
|
-
return this.
|
|
535
|
+
return this.canSendOps;
|
|
515
536
|
}
|
|
516
537
|
/**
|
|
517
538
|
* clientId of parent (non-summarizing) container that owns summarizer container
|
|
@@ -599,7 +620,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
599
620
|
// eslint-disable-next-line import/no-deprecated
|
|
600
621
|
this.enterStagingMode = () => {
|
|
601
622
|
if (this.stageControls !== undefined) {
|
|
602
|
-
throw new
|
|
623
|
+
throw new internal_8.UsageError("already in staging mode");
|
|
624
|
+
}
|
|
625
|
+
if (this.attachState === container_definitions_1.AttachState.Detached) {
|
|
626
|
+
throw new internal_8.UsageError("cannot enter staging mode while detached");
|
|
603
627
|
}
|
|
604
628
|
// Make sure all BatchManagers are empty before entering staging mode,
|
|
605
629
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
@@ -620,7 +644,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
620
644
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
621
645
|
this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
622
646
|
(0, internal_2.assert)(runtimeOp !== undefined, 0xb82 /* Staged batches expected to have runtimeOp defined */);
|
|
623
|
-
this.
|
|
647
|
+
this.rollbackStagedChanges(runtimeOp, localOpMetadata);
|
|
624
648
|
});
|
|
625
649
|
this.updateDocumentDirtyState();
|
|
626
650
|
}),
|
|
@@ -657,6 +681,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
657
681
|
this.mc = (0, internal_8.createChildMonitoringContext)({
|
|
658
682
|
logger: this.baseLogger,
|
|
659
683
|
namespace: "ContainerRuntime",
|
|
684
|
+
properties: {
|
|
685
|
+
all: {
|
|
686
|
+
inStagingMode: this.inStagingMode,
|
|
687
|
+
},
|
|
688
|
+
},
|
|
660
689
|
});
|
|
661
690
|
// If we support multiple algorithms in the future, then we would need to manage it here carefully.
|
|
662
691
|
// We can use runtimeOptions.compressionOptions.compressionAlgorithm, but only if it's in the schema list!
|
|
@@ -697,7 +726,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
697
726
|
// Values are generally expected to be set from the runtime side.
|
|
698
727
|
this.options = options ?? {};
|
|
699
728
|
this.clientDetails = clientDetails;
|
|
700
|
-
|
|
729
|
+
this.isSummarizerClient = this.clientDetails.type === index_js_4.summarizerClientType;
|
|
701
730
|
this.loadedFromVersionId = context.getLoadedFromVersion()?.id;
|
|
702
731
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
703
732
|
this._getClientId = () => context.clientId;
|
|
@@ -734,7 +763,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
734
763
|
details: { attachState: this.attachState },
|
|
735
764
|
}));
|
|
736
765
|
// In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
|
|
737
|
-
this.closeFn = isSummarizerClient ? this.disposeFn : closeFn;
|
|
766
|
+
this.closeFn = this.isSummarizerClient ? this.disposeFn : closeFn;
|
|
738
767
|
let loadSummaryNumber;
|
|
739
768
|
// Get the container creation metadata. For new container, we initialize these. For existing containers,
|
|
740
769
|
// get the values from the metadata blob.
|
|
@@ -758,7 +787,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
758
787
|
this.messageAtLastSummary = lastMessageFromMetadata(metadata);
|
|
759
788
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
760
789
|
// Later updates come through calls to setConnectionState.
|
|
761
|
-
this.
|
|
790
|
+
this.canSendOps = connected;
|
|
762
791
|
this.mc.logger.sendTelemetryEvent({
|
|
763
792
|
eventName: "GCFeatureMatrix",
|
|
764
793
|
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
@@ -851,7 +880,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
851
880
|
existing,
|
|
852
881
|
metadata,
|
|
853
882
|
createContainerMetadata: this.createContainerMetadata,
|
|
854
|
-
isSummarizerClient,
|
|
883
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
855
884
|
getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
|
|
856
885
|
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
857
886
|
readAndParseBlob: async (id) => (0, internal_4.readAndParse)(this.storage, id),
|
|
@@ -935,7 +964,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
935
964
|
// Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
|
|
936
965
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
937
966
|
this.outbox = new index_js_3.Outbox({
|
|
938
|
-
shouldSend: () => this.
|
|
967
|
+
shouldSend: () => this.shouldSendOps(),
|
|
939
968
|
pendingStateManager: this.pendingStateManager,
|
|
940
969
|
submitBatchFn,
|
|
941
970
|
legacySendBatchFn,
|
|
@@ -1074,7 +1103,14 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1074
1103
|
await this.initializeSummarizer(loader);
|
|
1075
1104
|
if (this.sessionSchema.idCompressorMode === "on" ||
|
|
1076
1105
|
(this.sessionSchema.idCompressorMode === "delayed" && this.connected)) {
|
|
1077
|
-
this.
|
|
1106
|
+
internal_8.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnBoot" }, (event) => {
|
|
1107
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1108
|
+
event.end({
|
|
1109
|
+
details: {
|
|
1110
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
1111
|
+
},
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1078
1114
|
// This is called from loadRuntime(), long before we process any ops, so there should be no ops accumulated yet.
|
|
1079
1115
|
(0, internal_2.assert)(this.pendingIdCompressorOps.length === 0, 0x8ec /* no pending ops */);
|
|
1080
1116
|
}
|
|
@@ -1103,8 +1139,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1103
1139
|
const orderedClientCollection = new index_js_4.OrderedClientCollection(orderedClientLogger, this.innerDeltaManager, this._quorum);
|
|
1104
1140
|
const orderedClientElectionForSummarizer = new index_js_4.OrderedClientElection(orderedClientLogger, orderedClientCollection, this.electedSummarizerData ?? this.innerDeltaManager.lastSequenceNumber, index_js_4.SummarizerClientElection.isClientEligible, this.mc.config.getBoolean("Fluid.ContainerRuntime.OrderedClientElection.EnablePerformanceEvents"));
|
|
1105
1141
|
this.summarizerClientElection = new index_js_4.SummarizerClientElection(orderedClientLogger, summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary);
|
|
1106
|
-
|
|
1107
|
-
if (isSummarizerClient) {
|
|
1142
|
+
if (this.isSummarizerClient) {
|
|
1108
1143
|
// We want to dynamically import any thing inside summaryDelayLoadedModule module only when we are the summarizer client,
|
|
1109
1144
|
// so that all non summarizer clients don't have to load the code inside this module.
|
|
1110
1145
|
const module = await import(
|
|
@@ -1452,7 +1487,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1452
1487
|
}
|
|
1453
1488
|
replayPendingStates() {
|
|
1454
1489
|
// We need to be able to send ops to replay states
|
|
1455
|
-
if (!this.
|
|
1490
|
+
if (!this.shouldSendOps()) {
|
|
1456
1491
|
return;
|
|
1457
1492
|
}
|
|
1458
1493
|
// Replaying is an internal operation and we don't want to generate noise while doing it.
|
|
@@ -1534,25 +1569,35 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1534
1569
|
loadIdCompressor() {
|
|
1535
1570
|
if (this._idCompressor === undefined &&
|
|
1536
1571
|
this.sessionSchema.idCompressorMode !== undefined) {
|
|
1537
|
-
this.
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1572
|
+
internal_8.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnDelayedLoad" }, (event) => {
|
|
1573
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1574
|
+
// Finalize any ranges we received while the compressor was turned off.
|
|
1575
|
+
const ops = this.pendingIdCompressorOps;
|
|
1576
|
+
this.pendingIdCompressorOps = [];
|
|
1577
|
+
const trace = client_utils_1.Trace.start();
|
|
1578
|
+
for (const range of ops) {
|
|
1579
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
1580
|
+
}
|
|
1581
|
+
event.end({
|
|
1582
|
+
details: {
|
|
1583
|
+
finalizeCreationRangeDuration: trace.trace().duration,
|
|
1584
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
1585
|
+
pendingIdCompressorOps: ops.length,
|
|
1586
|
+
},
|
|
1587
|
+
});
|
|
1588
|
+
});
|
|
1544
1589
|
(0, internal_2.assert)(this.pendingIdCompressorOps.length === 0, 0x976 /* No new ops added */);
|
|
1545
1590
|
}
|
|
1546
1591
|
}
|
|
1547
|
-
setConnectionState(
|
|
1592
|
+
setConnectionState(canSendOps, clientId) {
|
|
1548
1593
|
// Validate we have consistent state
|
|
1549
1594
|
const currentClientId = this._audience.getSelf()?.clientId;
|
|
1550
1595
|
(0, internal_2.assert)(clientId === currentClientId, 0x977 /* input clientId does not match Audience */);
|
|
1551
1596
|
(0, internal_2.assert)(this.clientId === currentClientId, 0x978 /* this.clientId does not match Audience */);
|
|
1552
|
-
if (
|
|
1597
|
+
if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
|
|
1553
1598
|
this.loadIdCompressor();
|
|
1554
1599
|
}
|
|
1555
|
-
if (
|
|
1600
|
+
if (canSendOps === false && this.delayConnectClientId !== undefined) {
|
|
1556
1601
|
this.delayConnectClientId = undefined;
|
|
1557
1602
|
this.mc.logger.sendTelemetryEvent({
|
|
1558
1603
|
eventName: "UnsuccessfulConnectedTransition",
|
|
@@ -1560,37 +1605,39 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1560
1605
|
// Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
|
|
1561
1606
|
return;
|
|
1562
1607
|
}
|
|
1563
|
-
if (!connected) {
|
|
1564
|
-
this.documentsSchemaController.onDisconnect();
|
|
1565
|
-
}
|
|
1566
1608
|
// If there are stashed blobs in the pending state, we need to delay
|
|
1567
1609
|
// propagation of the "connected" event until we have uploaded them to
|
|
1568
1610
|
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
1569
|
-
const connecting =
|
|
1611
|
+
const connecting = canSendOps && !this.canSendOps;
|
|
1570
1612
|
if (connecting && this.blobManager.hasPendingStashedUploads()) {
|
|
1571
1613
|
(0, internal_2.assert)(!this.delayConnectClientId, 0x791 /* Connect event delay must be canceled before subsequent connect event */);
|
|
1572
1614
|
(0, internal_2.assert)(!!clientId, 0x792 /* Must have clientId when connecting */);
|
|
1573
1615
|
this.delayConnectClientId = clientId;
|
|
1574
1616
|
return;
|
|
1575
1617
|
}
|
|
1576
|
-
this.setConnectionStateCore(
|
|
1618
|
+
this.setConnectionStateCore(canSendOps, clientId);
|
|
1577
1619
|
}
|
|
1578
|
-
|
|
1620
|
+
/**
|
|
1621
|
+
* Raises and propagates connected events.
|
|
1622
|
+
* @param canSendOps - Indicates whether the container can send ops or not (connected and not readonly).
|
|
1623
|
+
* @remarks The connection state from container context used here when raising connected events.
|
|
1624
|
+
*/
|
|
1625
|
+
setConnectionStateCore(canSendOps, clientId) {
|
|
1579
1626
|
(0, internal_2.assert)(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
|
|
1580
1627
|
this.verifyNotClosed();
|
|
1581
1628
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1582
|
-
const
|
|
1583
|
-
const reconnection =
|
|
1629
|
+
const canSendOpsChanged = this.canSendOps !== canSendOps;
|
|
1630
|
+
const reconnection = canSendOpsChanged && !canSendOps;
|
|
1584
1631
|
// We need to flush the ops currently collected by Outbox to preserve original order.
|
|
1585
1632
|
// This flush NEEDS to happen before we set the ContainerRuntime to "connected".
|
|
1586
1633
|
// We want these ops to get to the PendingStateManager without sending to service and have them return to the Outbox upon calling "replayPendingStates".
|
|
1587
|
-
if (
|
|
1634
|
+
if (canSendOpsChanged && canSendOps) {
|
|
1588
1635
|
this.flush();
|
|
1589
1636
|
}
|
|
1590
|
-
this.
|
|
1591
|
-
if (
|
|
1637
|
+
this.canSendOps = canSendOps;
|
|
1638
|
+
if (canSendOps) {
|
|
1592
1639
|
(0, internal_2.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x3cd /* Connection is possible only if container exists in storage */);
|
|
1593
|
-
if (
|
|
1640
|
+
if (canSendOpsChanged) {
|
|
1594
1641
|
this.signalTelemetryManager.resetTracking();
|
|
1595
1642
|
}
|
|
1596
1643
|
}
|
|
@@ -1606,12 +1653,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1606
1653
|
return;
|
|
1607
1654
|
}
|
|
1608
1655
|
}
|
|
1609
|
-
if (
|
|
1656
|
+
if (canSendOpsChanged) {
|
|
1610
1657
|
this.replayPendingStates();
|
|
1611
1658
|
}
|
|
1612
|
-
this.channelCollection.setConnectionState(
|
|
1613
|
-
this.garbageCollector.setConnectionState(
|
|
1614
|
-
(0, internal_8.raiseConnectedEvent)(this.mc.logger, this, connected
|
|
1659
|
+
this.channelCollection.setConnectionState(canSendOps, clientId);
|
|
1660
|
+
this.garbageCollector.setConnectionState(canSendOps, clientId);
|
|
1661
|
+
(0, internal_8.raiseConnectedEvent)(this.mc.logger, this, this.connected /* canSendOps */, clientId);
|
|
1615
1662
|
}
|
|
1616
1663
|
async notifyOpReplay(message) {
|
|
1617
1664
|
await this.pendingStateManager.applyStashedOpsAt(message.sequenceNumber);
|
|
@@ -2018,7 +2065,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2018
2065
|
if (checkpoint) {
|
|
2019
2066
|
// This will throw and close the container if rollback fails
|
|
2020
2067
|
try {
|
|
2021
|
-
checkpoint.rollback((message) =>
|
|
2068
|
+
checkpoint.rollback((message) =>
|
|
2069
|
+
// These changes are staged since we entered staging mode above
|
|
2070
|
+
this.rollbackStagedChanges(message.runtimeOp, message.localOpMetadata));
|
|
2022
2071
|
this.updateDocumentDirtyState();
|
|
2023
2072
|
stageControls?.discardChanges();
|
|
2024
2073
|
stageControls = undefined;
|
|
@@ -2095,7 +2144,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2095
2144
|
const context = this.channelCollection.createDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], loadingGroupId);
|
|
2096
2145
|
return (0, dataStore_js_1.channelToDataStore)(await context.realize(), context.id, this.channelCollection, this.mc.logger);
|
|
2097
2146
|
}
|
|
2098
|
-
|
|
2147
|
+
shouldSendOps() {
|
|
2099
2148
|
// Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
|
|
2100
2149
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
2101
2150
|
return (this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure);
|
|
@@ -2784,6 +2833,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2784
2833
|
try {
|
|
2785
2834
|
// If we're resubmitting a batch, keep the same "staged" value as before. Otherwise, use the current "global" state.
|
|
2786
2835
|
const staged = this.batchRunner.resubmitInfo?.staged ?? this.inStagingMode;
|
|
2836
|
+
(0, internal_2.assert)(!staged || canStageMessageOfType(type), 0xbba /* Unexpected message type submitted in Staging Mode */);
|
|
2787
2837
|
// Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
|
|
2788
2838
|
if (!staged) {
|
|
2789
2839
|
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
@@ -2791,7 +2841,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2791
2841
|
// Allow document schema controller to send a message if it needs to propose change in document schema.
|
|
2792
2842
|
// If it needs to send a message, it will call provided callback with payload of such message and rely
|
|
2793
2843
|
// on this callback to do actual sending.
|
|
2794
|
-
const schemaChangeMessage = this.documentsSchemaController.
|
|
2844
|
+
const schemaChangeMessage = this.documentsSchemaController.maybeGenerateSchemaMessage();
|
|
2795
2845
|
if (schemaChangeMessage) {
|
|
2796
2846
|
this.mc.logger.sendTelemetryEvent({
|
|
2797
2847
|
eventName: "SchemaChangeProposal",
|
|
@@ -2889,43 +2939,72 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2889
2939
|
}
|
|
2890
2940
|
/**
|
|
2891
2941
|
* Resubmits each message in the batch, and then flushes the outbox.
|
|
2942
|
+
* This typically happens when we reconnect and there are pending messages.
|
|
2892
2943
|
*
|
|
2893
|
-
* @remarks
|
|
2944
|
+
* @remarks
|
|
2945
|
+
* Attempting to resubmit a batch that has been successfully sequenced will not happen due to
|
|
2946
|
+
* checks in the ConnectionStateHandler (Loader layer)
|
|
2947
|
+
*
|
|
2948
|
+
* The only exception to this would be if the Container "forks" due to misuse of the "Offline Load" feature.
|
|
2949
|
+
* If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
|
|
2894
2950
|
* for correlation to detect container forking.
|
|
2895
2951
|
*/
|
|
2896
2952
|
reSubmitBatch(batch, { batchId, staged, squash }) {
|
|
2953
|
+
(0, internal_2.assert)(this._summarizer === undefined, 0x8f2 /* Summarizer never reconnects so should never resubmit */);
|
|
2897
2954
|
const resubmitInfo = {
|
|
2898
2955
|
// Only include Batch ID if "Offline Load" feature is enabled
|
|
2899
2956
|
// It's only needed to identify batches across container forks arising from misuse of offline load.
|
|
2900
2957
|
batchId: this.offlineEnabled ? batchId : undefined,
|
|
2901
2958
|
staged,
|
|
2902
2959
|
};
|
|
2960
|
+
const resubmitFn = squash
|
|
2961
|
+
? this.reSubmitWithSquashing.bind(this)
|
|
2962
|
+
: this.reSubmit.bind(this);
|
|
2903
2963
|
this.batchRunner.run(() => {
|
|
2904
2964
|
for (const message of batch) {
|
|
2905
|
-
|
|
2965
|
+
resubmitFn(message);
|
|
2906
2966
|
}
|
|
2907
2967
|
}, resubmitInfo);
|
|
2908
2968
|
this.flush(resubmitInfo);
|
|
2909
2969
|
}
|
|
2910
|
-
|
|
2911
|
-
|
|
2970
|
+
/**
|
|
2971
|
+
* Resubmit the given message as part of a squash rebase upon exiting Staging Mode.
|
|
2972
|
+
* How exactly to resubmit the message is up to the subsystem that submitted the op to begin with.
|
|
2973
|
+
*/
|
|
2974
|
+
reSubmitWithSquashing(resubmitData) {
|
|
2975
|
+
const message = resubmitData.runtimeOp;
|
|
2976
|
+
(0, internal_2.assert)(canStageMessageOfType(message.type), 0xbbb /* Expected message type to be compatible with staging */);
|
|
2977
|
+
switch (message.type) {
|
|
2978
|
+
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
2979
|
+
this.channelCollection.reSubmit(message.type, message.contents, resubmitData.localOpMetadata,
|
|
2980
|
+
/* squash: */ true);
|
|
2981
|
+
break;
|
|
2982
|
+
}
|
|
2983
|
+
// NOTE: Squash doesn't apply to GC or DocumentSchemaChange ops, fallback to typical resubmit logic.
|
|
2984
|
+
case messageTypes_js_1.ContainerMessageType.GC:
|
|
2985
|
+
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
2986
|
+
this.reSubmit(resubmitData);
|
|
2987
|
+
break;
|
|
2988
|
+
}
|
|
2989
|
+
default: {
|
|
2990
|
+
(0, internal_2.unreachableCase)(message.type);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2912
2993
|
}
|
|
2913
2994
|
/**
|
|
2914
|
-
*
|
|
2915
|
-
*
|
|
2916
|
-
*
|
|
2917
|
-
* @param message - The original LocalContainerRuntimeMessage.
|
|
2918
|
-
* @param localOpMetadata - The local metadata associated with the original message.
|
|
2995
|
+
* Resubmit the given message which was previously submitted to the ContainerRuntime but not successfully
|
|
2996
|
+
* transmitted to the ordering service (e.g. due to a disconnect, or being in Staging Mode)
|
|
2997
|
+
* How to resubmit is up to the subsystem that submitted the op to begin with
|
|
2919
2998
|
*/
|
|
2920
|
-
|
|
2921
|
-
(0, internal_2.assert)(this._summarizer === undefined, 0x8f2 /* Summarizer never reconnects so should never resubmit */);
|
|
2999
|
+
reSubmit({ runtimeOp: message, localOpMetadata, opMetadata, }) {
|
|
2922
3000
|
switch (message.type) {
|
|
2923
3001
|
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp:
|
|
2924
3002
|
case messageTypes_js_1.ContainerMessageType.Attach:
|
|
2925
3003
|
case messageTypes_js_1.ContainerMessageType.Alias: {
|
|
2926
3004
|
// For Operations, call resubmitDataStoreOp which will find the right store
|
|
2927
3005
|
// and trigger resubmission on it.
|
|
2928
|
-
this.channelCollection.reSubmit(message.type, message.contents, localOpMetadata,
|
|
3006
|
+
this.channelCollection.reSubmit(message.type, message.contents, localOpMetadata,
|
|
3007
|
+
/* squash: */ false);
|
|
2929
3008
|
break;
|
|
2930
3009
|
}
|
|
2931
3010
|
case messageTypes_js_1.ContainerMessageType.IdAllocation: {
|
|
@@ -2951,9 +3030,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2951
3030
|
break;
|
|
2952
3031
|
}
|
|
2953
3032
|
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
2954
|
-
//
|
|
2955
|
-
//
|
|
2956
|
-
|
|
3033
|
+
// We shouldn't directly resubmit due to Compare-And-Swap semantics.
|
|
3034
|
+
// If needed it will be generated from scratch before other ops are submitted.
|
|
3035
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
2957
3036
|
break;
|
|
2958
3037
|
}
|
|
2959
3038
|
default: {
|
|
@@ -2963,8 +3042,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2963
3042
|
}
|
|
2964
3043
|
}
|
|
2965
3044
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
3045
|
+
/**
|
|
3046
|
+
* Rollback the given op which was only staged but not yet submitted.
|
|
3047
|
+
*/
|
|
3048
|
+
rollbackStagedChanges({ type, contents }, localOpMetadata) {
|
|
3049
|
+
(0, internal_2.assert)(canStageMessageOfType(type), 0xbbc /* Unexpected message type to be rolled back */);
|
|
2968
3050
|
switch (type) {
|
|
2969
3051
|
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
2970
3052
|
// For operations, call rollbackDataStoreOp which will find the right store
|
|
@@ -2972,8 +3054,24 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2972
3054
|
this.channelCollection.rollback(type, contents, localOpMetadata);
|
|
2973
3055
|
break;
|
|
2974
3056
|
}
|
|
3057
|
+
case messageTypes_js_1.ContainerMessageType.GC: {
|
|
3058
|
+
// Just drop it, but log an error, this is not expected and not ideal, but not critical failure either.
|
|
3059
|
+
// Currently the only expected type here is TombstoneLoaded, which will have been preceded by one of these events as well:
|
|
3060
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_SubDataStore_Requested, GC_Tombstone_Blob_Requested
|
|
3061
|
+
this.mc.logger.sendErrorEvent({
|
|
3062
|
+
eventName: "GC_OpDiscarded",
|
|
3063
|
+
details: { subType: contents.type },
|
|
3064
|
+
});
|
|
3065
|
+
break;
|
|
3066
|
+
}
|
|
3067
|
+
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
3068
|
+
// Notify the document schema controller that the pending op was not acked.
|
|
3069
|
+
// This will allow it to propose the schema change again if needed.
|
|
3070
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
3071
|
+
break;
|
|
3072
|
+
}
|
|
2975
3073
|
default: {
|
|
2976
|
-
|
|
3074
|
+
(0, internal_2.unreachableCase)(type);
|
|
2977
3075
|
}
|
|
2978
3076
|
}
|
|
2979
3077
|
}
|
|
@@ -3151,6 +3249,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3151
3249
|
},
|
|
3152
3250
|
getQuorum: this.getQuorum.bind(this),
|
|
3153
3251
|
getAudience: this.getAudience.bind(this),
|
|
3252
|
+
supportedFeatures: this.ILayerCompatDetails.supportedFeatures,
|
|
3154
3253
|
};
|
|
3155
3254
|
entry = new factory(runtime, ...useContext);
|
|
3156
3255
|
this.extensions.set(id, entry);
|