@fluidframework/container-runtime 2.41.0 → 2.43.0-343119
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 +24 -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 +36 -15
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +186 -71
- 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 +2 -2
- 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.d.ts.map +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 +79 -16
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +119 -53
- 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 +24 -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 +36 -15
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +188 -73
- 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 +2 -2
- 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.d.ts.map +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 +79 -16
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +119 -53
- 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 +147 -10
- package/src/containerRuntime.ts +242 -85
- package/src/dataStore.ts +7 -0
- package/src/gc/garbageCollection.ts +2 -0
- package/src/gc/gcDefinitions.ts +1 -1
- package/src/index.ts +4 -2
- 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 +228 -83
- package/src/summary/index.ts +3 -1
package/dist/containerRuntime.js
CHANGED
|
@@ -16,6 +16,7 @@ const internal_5 = require("@fluidframework/id-compressor/internal");
|
|
|
16
16
|
const internal_6 = require("@fluidframework/runtime-definitions/internal");
|
|
17
17
|
const internal_7 = require("@fluidframework/runtime-utils/internal");
|
|
18
18
|
const internal_8 = require("@fluidframework/telemetry-utils/internal");
|
|
19
|
+
const semver_ts_1 = require("semver-ts");
|
|
19
20
|
const uuid_1 = require("uuid");
|
|
20
21
|
const batchTracker_js_1 = require("./batchTracker.js");
|
|
21
22
|
const index_js_1 = require("./blobManager/index.js");
|
|
@@ -197,6 +198,20 @@ async function loadContainerRuntime(params) {
|
|
|
197
198
|
}
|
|
198
199
|
exports.loadContainerRuntime = loadContainerRuntime;
|
|
199
200
|
const defaultMaxConsecutiveReconnects = 7;
|
|
201
|
+
/**
|
|
202
|
+
* These are the ONLY message types that are allowed to be submitted while in staging mode
|
|
203
|
+
* (Does not apply to pre-StagingMode batches that are resubmitted, those are not considered to be staged)
|
|
204
|
+
*/
|
|
205
|
+
function canStageMessageOfType(type) {
|
|
206
|
+
return (
|
|
207
|
+
// These are user changes coming up from the runtime's DataStores
|
|
208
|
+
type === messageTypes_js_1.ContainerMessageType.FluidDataStoreOp ||
|
|
209
|
+
// GC ops are used to detect issues in the reference graph so all clients can repair their GC state.
|
|
210
|
+
// These can be submitted at any time, including while in Staging Mode.
|
|
211
|
+
type === messageTypes_js_1.ContainerMessageType.GC ||
|
|
212
|
+
// These are typically sent shortly after boot and will not be common in Staging Mode, but it's possible.
|
|
213
|
+
type === messageTypes_js_1.ContainerMessageType.DocumentSchemaChange);
|
|
214
|
+
}
|
|
200
215
|
/**
|
|
201
216
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
202
217
|
* It will define the store level mappings.
|
|
@@ -245,6 +260,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
245
260
|
if (!(0, compatUtils_js_1.isValidMinVersionForCollab)(minVersionForCollab)) {
|
|
246
261
|
throw new internal_8.UsageError(`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`);
|
|
247
262
|
}
|
|
263
|
+
// We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
|
|
264
|
+
// were manually set.
|
|
265
|
+
(0, compatUtils_js_1.validateRuntimeOptions)(minVersionForCollab, runtimeOptions);
|
|
248
266
|
const defaultsAffectingDocSchema = (0, compatUtils_js_1.getMinVersionForCollabDefaults)(minVersionForCollab);
|
|
249
267
|
// The following are the default values for the options that do not affect the DocumentSchema.
|
|
250
268
|
const defaultsNotAffectingDocSchema = {
|
|
@@ -402,7 +420,13 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
402
420
|
disallowedVersions: [],
|
|
403
421
|
}, (schema) => {
|
|
404
422
|
runtime.onSchemaChange(schema);
|
|
405
|
-
});
|
|
423
|
+
}, { minVersionForCollab }, logger);
|
|
424
|
+
// If the minVersionForCollab for this client is greater than the existing one, we should use that one going forward.
|
|
425
|
+
const existingMinVersionForCollab = documentSchemaController.sessionSchema.info.minVersionForCollab;
|
|
426
|
+
const updatedMinVersionForCollab = existingMinVersionForCollab === undefined ||
|
|
427
|
+
(0, semver_ts_1.gt)(minVersionForCollab, existingMinVersionForCollab)
|
|
428
|
+
? minVersionForCollab
|
|
429
|
+
: existingMinVersionForCollab;
|
|
406
430
|
if (compressionLz4 && !enableGroupedBatching) {
|
|
407
431
|
throw new internal_8.UsageError("If compression is enabled, op grouping must be enabled too");
|
|
408
432
|
}
|
|
@@ -421,7 +445,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
421
445
|
explicitSchemaControl,
|
|
422
446
|
createBlobPayloadPending,
|
|
423
447
|
};
|
|
424
|
-
const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], internalRuntimeOptions, containerScope, logger, existing, blobManagerLoadInfo, context.storage, createIdCompressorFn, documentSchemaController, featureGatesForTelemetry, provideEntryPoint,
|
|
448
|
+
const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], internalRuntimeOptions, containerScope, logger, existing, blobManagerLoadInfo, context.storage, createIdCompressorFn, documentSchemaController, featureGatesForTelemetry, provideEntryPoint, updatedMinVersionForCollab, requestHandler, undefined, // summaryConfiguration
|
|
425
449
|
recentBatchInfo);
|
|
426
450
|
runtime.blobManager.stashedBlobsUploadP.then(() => {
|
|
427
451
|
// make sure we didn't reconnect before the promise resolved
|
|
@@ -510,8 +534,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
510
534
|
ensureNoDataModelChanges(callback) {
|
|
511
535
|
return this.dataModelChangeRunner.run(callback);
|
|
512
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Indicates whether the container is in a state where it is able to send
|
|
539
|
+
* ops (connected to op stream and not in readonly mode).
|
|
540
|
+
*/
|
|
513
541
|
get connected() {
|
|
514
|
-
return this.
|
|
542
|
+
return this.canSendOps;
|
|
515
543
|
}
|
|
516
544
|
/**
|
|
517
545
|
* clientId of parent (non-summarizing) container that owns summarizer container
|
|
@@ -599,20 +627,31 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
599
627
|
// eslint-disable-next-line import/no-deprecated
|
|
600
628
|
this.enterStagingMode = () => {
|
|
601
629
|
if (this.stageControls !== undefined) {
|
|
602
|
-
throw new
|
|
630
|
+
throw new internal_8.UsageError("already in staging mode");
|
|
631
|
+
}
|
|
632
|
+
if (this.attachState === container_definitions_1.AttachState.Detached) {
|
|
633
|
+
throw new internal_8.UsageError("cannot enter staging mode while detached");
|
|
603
634
|
}
|
|
604
|
-
// Make sure
|
|
635
|
+
// Make sure Outbox is empty before entering staging mode,
|
|
605
636
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
606
|
-
this.
|
|
637
|
+
this.flush();
|
|
607
638
|
const exitStagingMode = (discardOrCommit) => () => {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
639
|
+
try {
|
|
640
|
+
// Final flush of any last staged changes
|
|
641
|
+
// NOTE: We can't use this.flush() here, because orderSequentially uses StagingMode and in the rollback case we'll hit assert 0x24c
|
|
642
|
+
this.outbox.flush();
|
|
643
|
+
this.stageControls = undefined;
|
|
644
|
+
// During Staging Mode, we avoid submitting any ID Allocation ops (apart from resubmitting pre-staging ops).
|
|
645
|
+
// Now that we've exited, we need to submit an ID Allocation op for any IDs that were generated while in Staging Mode.
|
|
646
|
+
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
647
|
+
discardOrCommit();
|
|
648
|
+
this.channelCollection.notifyStagingMode(false);
|
|
649
|
+
}
|
|
650
|
+
catch (error) {
|
|
651
|
+
const normalizedError = (0, internal_8.normalizeError)(error);
|
|
652
|
+
this.closeFn(normalizedError);
|
|
653
|
+
throw normalizedError;
|
|
654
|
+
}
|
|
616
655
|
};
|
|
617
656
|
// eslint-disable-next-line import/no-deprecated
|
|
618
657
|
const stageControls = {
|
|
@@ -620,7 +659,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
620
659
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
621
660
|
this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
622
661
|
(0, internal_2.assert)(runtimeOp !== undefined, 0xb82 /* Staged batches expected to have runtimeOp defined */);
|
|
623
|
-
this.
|
|
662
|
+
this.rollbackStagedChanges(runtimeOp, localOpMetadata);
|
|
624
663
|
});
|
|
625
664
|
this.updateDocumentDirtyState();
|
|
626
665
|
}),
|
|
@@ -657,6 +696,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
657
696
|
this.mc = (0, internal_8.createChildMonitoringContext)({
|
|
658
697
|
logger: this.baseLogger,
|
|
659
698
|
namespace: "ContainerRuntime",
|
|
699
|
+
properties: {
|
|
700
|
+
all: {
|
|
701
|
+
inStagingMode: this.inStagingMode,
|
|
702
|
+
},
|
|
703
|
+
},
|
|
660
704
|
});
|
|
661
705
|
// If we support multiple algorithms in the future, then we would need to manage it here carefully.
|
|
662
706
|
// We can use runtimeOptions.compressionOptions.compressionAlgorithm, but only if it's in the schema list!
|
|
@@ -697,7 +741,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
697
741
|
// Values are generally expected to be set from the runtime side.
|
|
698
742
|
this.options = options ?? {};
|
|
699
743
|
this.clientDetails = clientDetails;
|
|
700
|
-
|
|
744
|
+
this.isSummarizerClient = this.clientDetails.type === index_js_4.summarizerClientType;
|
|
701
745
|
this.loadedFromVersionId = context.getLoadedFromVersion()?.id;
|
|
702
746
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
703
747
|
this._getClientId = () => context.clientId;
|
|
@@ -734,7 +778,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
734
778
|
details: { attachState: this.attachState },
|
|
735
779
|
}));
|
|
736
780
|
// In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
|
|
737
|
-
this.closeFn = isSummarizerClient ? this.disposeFn : closeFn;
|
|
781
|
+
this.closeFn = this.isSummarizerClient ? this.disposeFn : closeFn;
|
|
738
782
|
let loadSummaryNumber;
|
|
739
783
|
// Get the container creation metadata. For new container, we initialize these. For existing containers,
|
|
740
784
|
// get the values from the metadata blob.
|
|
@@ -758,7 +802,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
758
802
|
this.messageAtLastSummary = lastMessageFromMetadata(metadata);
|
|
759
803
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
760
804
|
// Later updates come through calls to setConnectionState.
|
|
761
|
-
this.
|
|
805
|
+
this.canSendOps = connected;
|
|
762
806
|
this.mc.logger.sendTelemetryEvent({
|
|
763
807
|
eventName: "GCFeatureMatrix",
|
|
764
808
|
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
@@ -851,7 +895,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
851
895
|
existing,
|
|
852
896
|
metadata,
|
|
853
897
|
createContainerMetadata: this.createContainerMetadata,
|
|
854
|
-
isSummarizerClient,
|
|
898
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
855
899
|
getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
|
|
856
900
|
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
857
901
|
readAndParseBlob: async (id) => (0, internal_4.readAndParse)(this.storage, id),
|
|
@@ -935,7 +979,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
935
979
|
// Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
|
|
936
980
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
937
981
|
this.outbox = new index_js_3.Outbox({
|
|
938
|
-
shouldSend: () => this.
|
|
982
|
+
shouldSend: () => this.shouldSendOps(),
|
|
939
983
|
pendingStateManager: this.pendingStateManager,
|
|
940
984
|
submitBatchFn,
|
|
941
985
|
legacySendBatchFn,
|
|
@@ -1074,7 +1118,14 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1074
1118
|
await this.initializeSummarizer(loader);
|
|
1075
1119
|
if (this.sessionSchema.idCompressorMode === "on" ||
|
|
1076
1120
|
(this.sessionSchema.idCompressorMode === "delayed" && this.connected)) {
|
|
1077
|
-
this.
|
|
1121
|
+
internal_8.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnBoot" }, (event) => {
|
|
1122
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1123
|
+
event.end({
|
|
1124
|
+
details: {
|
|
1125
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
1126
|
+
},
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1078
1129
|
// This is called from loadRuntime(), long before we process any ops, so there should be no ops accumulated yet.
|
|
1079
1130
|
(0, internal_2.assert)(this.pendingIdCompressorOps.length === 0, 0x8ec /* no pending ops */);
|
|
1080
1131
|
}
|
|
@@ -1103,8 +1154,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1103
1154
|
const orderedClientCollection = new index_js_4.OrderedClientCollection(orderedClientLogger, this.innerDeltaManager, this._quorum);
|
|
1104
1155
|
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
1156
|
this.summarizerClientElection = new index_js_4.SummarizerClientElection(orderedClientLogger, summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary);
|
|
1106
|
-
|
|
1107
|
-
if (isSummarizerClient) {
|
|
1157
|
+
if (this.isSummarizerClient) {
|
|
1108
1158
|
// We want to dynamically import any thing inside summaryDelayLoadedModule module only when we are the summarizer client,
|
|
1109
1159
|
// so that all non summarizer clients don't have to load the code inside this module.
|
|
1110
1160
|
const module = await import(
|
|
@@ -1452,7 +1502,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1452
1502
|
}
|
|
1453
1503
|
replayPendingStates() {
|
|
1454
1504
|
// We need to be able to send ops to replay states
|
|
1455
|
-
if (!this.
|
|
1505
|
+
if (!this.shouldSendOps()) {
|
|
1456
1506
|
return;
|
|
1457
1507
|
}
|
|
1458
1508
|
// Replaying is an internal operation and we don't want to generate noise while doing it.
|
|
@@ -1534,25 +1584,35 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1534
1584
|
loadIdCompressor() {
|
|
1535
1585
|
if (this._idCompressor === undefined &&
|
|
1536
1586
|
this.sessionSchema.idCompressorMode !== undefined) {
|
|
1537
|
-
this.
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1587
|
+
internal_8.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnDelayedLoad" }, (event) => {
|
|
1588
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1589
|
+
// Finalize any ranges we received while the compressor was turned off.
|
|
1590
|
+
const ops = this.pendingIdCompressorOps;
|
|
1591
|
+
this.pendingIdCompressorOps = [];
|
|
1592
|
+
const trace = client_utils_1.Trace.start();
|
|
1593
|
+
for (const range of ops) {
|
|
1594
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
1595
|
+
}
|
|
1596
|
+
event.end({
|
|
1597
|
+
details: {
|
|
1598
|
+
finalizeCreationRangeDuration: trace.trace().duration,
|
|
1599
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
1600
|
+
pendingIdCompressorOps: ops.length,
|
|
1601
|
+
},
|
|
1602
|
+
});
|
|
1603
|
+
});
|
|
1544
1604
|
(0, internal_2.assert)(this.pendingIdCompressorOps.length === 0, 0x976 /* No new ops added */);
|
|
1545
1605
|
}
|
|
1546
1606
|
}
|
|
1547
|
-
setConnectionState(
|
|
1607
|
+
setConnectionState(canSendOps, clientId) {
|
|
1548
1608
|
// Validate we have consistent state
|
|
1549
1609
|
const currentClientId = this._audience.getSelf()?.clientId;
|
|
1550
1610
|
(0, internal_2.assert)(clientId === currentClientId, 0x977 /* input clientId does not match Audience */);
|
|
1551
1611
|
(0, internal_2.assert)(this.clientId === currentClientId, 0x978 /* this.clientId does not match Audience */);
|
|
1552
|
-
if (
|
|
1612
|
+
if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
|
|
1553
1613
|
this.loadIdCompressor();
|
|
1554
1614
|
}
|
|
1555
|
-
if (
|
|
1615
|
+
if (canSendOps === false && this.delayConnectClientId !== undefined) {
|
|
1556
1616
|
this.delayConnectClientId = undefined;
|
|
1557
1617
|
this.mc.logger.sendTelemetryEvent({
|
|
1558
1618
|
eventName: "UnsuccessfulConnectedTransition",
|
|
@@ -1560,37 +1620,39 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1560
1620
|
// Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
|
|
1561
1621
|
return;
|
|
1562
1622
|
}
|
|
1563
|
-
if (!connected) {
|
|
1564
|
-
this.documentsSchemaController.onDisconnect();
|
|
1565
|
-
}
|
|
1566
1623
|
// If there are stashed blobs in the pending state, we need to delay
|
|
1567
1624
|
// propagation of the "connected" event until we have uploaded them to
|
|
1568
1625
|
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
1569
|
-
const connecting =
|
|
1626
|
+
const connecting = canSendOps && !this.canSendOps;
|
|
1570
1627
|
if (connecting && this.blobManager.hasPendingStashedUploads()) {
|
|
1571
1628
|
(0, internal_2.assert)(!this.delayConnectClientId, 0x791 /* Connect event delay must be canceled before subsequent connect event */);
|
|
1572
1629
|
(0, internal_2.assert)(!!clientId, 0x792 /* Must have clientId when connecting */);
|
|
1573
1630
|
this.delayConnectClientId = clientId;
|
|
1574
1631
|
return;
|
|
1575
1632
|
}
|
|
1576
|
-
this.setConnectionStateCore(
|
|
1633
|
+
this.setConnectionStateCore(canSendOps, clientId);
|
|
1577
1634
|
}
|
|
1578
|
-
|
|
1635
|
+
/**
|
|
1636
|
+
* Raises and propagates connected events.
|
|
1637
|
+
* @param canSendOps - Indicates whether the container can send ops or not (connected and not readonly).
|
|
1638
|
+
* @remarks The connection state from container context used here when raising connected events.
|
|
1639
|
+
*/
|
|
1640
|
+
setConnectionStateCore(canSendOps, clientId) {
|
|
1579
1641
|
(0, internal_2.assert)(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
|
|
1580
1642
|
this.verifyNotClosed();
|
|
1581
1643
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1582
|
-
const
|
|
1583
|
-
const reconnection =
|
|
1644
|
+
const canSendOpsChanged = this.canSendOps !== canSendOps;
|
|
1645
|
+
const reconnection = canSendOpsChanged && !canSendOps;
|
|
1584
1646
|
// We need to flush the ops currently collected by Outbox to preserve original order.
|
|
1585
1647
|
// This flush NEEDS to happen before we set the ContainerRuntime to "connected".
|
|
1586
1648
|
// 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 (
|
|
1649
|
+
if (canSendOpsChanged && canSendOps) {
|
|
1588
1650
|
this.flush();
|
|
1589
1651
|
}
|
|
1590
|
-
this.
|
|
1591
|
-
if (
|
|
1652
|
+
this.canSendOps = canSendOps;
|
|
1653
|
+
if (canSendOps) {
|
|
1592
1654
|
(0, internal_2.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x3cd /* Connection is possible only if container exists in storage */);
|
|
1593
|
-
if (
|
|
1655
|
+
if (canSendOpsChanged) {
|
|
1594
1656
|
this.signalTelemetryManager.resetTracking();
|
|
1595
1657
|
}
|
|
1596
1658
|
}
|
|
@@ -1606,12 +1668,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1606
1668
|
return;
|
|
1607
1669
|
}
|
|
1608
1670
|
}
|
|
1609
|
-
if (
|
|
1671
|
+
if (canSendOpsChanged) {
|
|
1610
1672
|
this.replayPendingStates();
|
|
1611
1673
|
}
|
|
1612
|
-
this.channelCollection.setConnectionState(
|
|
1613
|
-
this.garbageCollector.setConnectionState(
|
|
1614
|
-
(0, internal_8.raiseConnectedEvent)(this.mc.logger, this, connected
|
|
1674
|
+
this.channelCollection.setConnectionState(canSendOps, clientId);
|
|
1675
|
+
this.garbageCollector.setConnectionState(canSendOps, clientId);
|
|
1676
|
+
(0, internal_8.raiseConnectedEvent)(this.mc.logger, this, this.connected /* canSendOps */, clientId);
|
|
1615
1677
|
}
|
|
1616
1678
|
async notifyOpReplay(message) {
|
|
1617
1679
|
await this.pendingStateManager.applyStashedOpsAt(message.sequenceNumber);
|
|
@@ -2018,7 +2080,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2018
2080
|
if (checkpoint) {
|
|
2019
2081
|
// This will throw and close the container if rollback fails
|
|
2020
2082
|
try {
|
|
2021
|
-
checkpoint.rollback((message) =>
|
|
2083
|
+
checkpoint.rollback((message) =>
|
|
2084
|
+
// These changes are staged since we entered staging mode above
|
|
2085
|
+
this.rollbackStagedChanges(message.runtimeOp, message.localOpMetadata));
|
|
2022
2086
|
this.updateDocumentDirtyState();
|
|
2023
2087
|
stageControls?.discardChanges();
|
|
2024
2088
|
stageControls = undefined;
|
|
@@ -2095,7 +2159,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2095
2159
|
const context = this.channelCollection.createDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], loadingGroupId);
|
|
2096
2160
|
return (0, dataStore_js_1.channelToDataStore)(await context.realize(), context.id, this.channelCollection, this.mc.logger);
|
|
2097
2161
|
}
|
|
2098
|
-
|
|
2162
|
+
shouldSendOps() {
|
|
2099
2163
|
// Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
|
|
2100
2164
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
2101
2165
|
return (this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure);
|
|
@@ -2784,6 +2848,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2784
2848
|
try {
|
|
2785
2849
|
// If we're resubmitting a batch, keep the same "staged" value as before. Otherwise, use the current "global" state.
|
|
2786
2850
|
const staged = this.batchRunner.resubmitInfo?.staged ?? this.inStagingMode;
|
|
2851
|
+
(0, internal_2.assert)(!staged || canStageMessageOfType(type), 0xbba /* Unexpected message type submitted in Staging Mode */);
|
|
2787
2852
|
// Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
|
|
2788
2853
|
if (!staged) {
|
|
2789
2854
|
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
@@ -2791,7 +2856,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2791
2856
|
// Allow document schema controller to send a message if it needs to propose change in document schema.
|
|
2792
2857
|
// If it needs to send a message, it will call provided callback with payload of such message and rely
|
|
2793
2858
|
// on this callback to do actual sending.
|
|
2794
|
-
const schemaChangeMessage = this.documentsSchemaController.
|
|
2859
|
+
const schemaChangeMessage = this.documentsSchemaController.maybeGenerateSchemaMessage();
|
|
2795
2860
|
if (schemaChangeMessage) {
|
|
2796
2861
|
this.mc.logger.sendTelemetryEvent({
|
|
2797
2862
|
eventName: "SchemaChangeProposal",
|
|
@@ -2800,6 +2865,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2800
2865
|
newRuntimeSchema: JSON.stringify(schemaChangeMessage.runtime),
|
|
2801
2866
|
sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
|
|
2802
2867
|
oldRuntimeSchema: JSON.stringify(this.metadata?.documentSchema?.runtime),
|
|
2868
|
+
minVersionForCollab: schemaChangeMessage.info?.minVersionForCollab,
|
|
2803
2869
|
});
|
|
2804
2870
|
const msg = {
|
|
2805
2871
|
type: messageTypes_js_1.ContainerMessageType.DocumentSchemaChange,
|
|
@@ -2889,43 +2955,72 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2889
2955
|
}
|
|
2890
2956
|
/**
|
|
2891
2957
|
* Resubmits each message in the batch, and then flushes the outbox.
|
|
2958
|
+
* This typically happens when we reconnect and there are pending messages.
|
|
2959
|
+
*
|
|
2960
|
+
* @remarks
|
|
2961
|
+
* Attempting to resubmit a batch that has been successfully sequenced will not happen due to
|
|
2962
|
+
* checks in the ConnectionStateHandler (Loader layer)
|
|
2892
2963
|
*
|
|
2893
|
-
*
|
|
2964
|
+
* The only exception to this would be if the Container "forks" due to misuse of the "Offline Load" feature.
|
|
2965
|
+
* If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
|
|
2894
2966
|
* for correlation to detect container forking.
|
|
2895
2967
|
*/
|
|
2896
2968
|
reSubmitBatch(batch, { batchId, staged, squash }) {
|
|
2969
|
+
(0, internal_2.assert)(this._summarizer === undefined, 0x8f2 /* Summarizer never reconnects so should never resubmit */);
|
|
2897
2970
|
const resubmitInfo = {
|
|
2898
2971
|
// Only include Batch ID if "Offline Load" feature is enabled
|
|
2899
2972
|
// It's only needed to identify batches across container forks arising from misuse of offline load.
|
|
2900
2973
|
batchId: this.offlineEnabled ? batchId : undefined,
|
|
2901
2974
|
staged,
|
|
2902
2975
|
};
|
|
2976
|
+
const resubmitFn = squash
|
|
2977
|
+
? this.reSubmitWithSquashing.bind(this)
|
|
2978
|
+
: this.reSubmit.bind(this);
|
|
2903
2979
|
this.batchRunner.run(() => {
|
|
2904
2980
|
for (const message of batch) {
|
|
2905
|
-
|
|
2981
|
+
resubmitFn(message);
|
|
2906
2982
|
}
|
|
2907
2983
|
}, resubmitInfo);
|
|
2908
2984
|
this.flush(resubmitInfo);
|
|
2909
2985
|
}
|
|
2910
|
-
|
|
2911
|
-
|
|
2986
|
+
/**
|
|
2987
|
+
* Resubmit the given message as part of a squash rebase upon exiting Staging Mode.
|
|
2988
|
+
* How exactly to resubmit the message is up to the subsystem that submitted the op to begin with.
|
|
2989
|
+
*/
|
|
2990
|
+
reSubmitWithSquashing(resubmitData) {
|
|
2991
|
+
const message = resubmitData.runtimeOp;
|
|
2992
|
+
(0, internal_2.assert)(canStageMessageOfType(message.type), 0xbbb /* Expected message type to be compatible with staging */);
|
|
2993
|
+
switch (message.type) {
|
|
2994
|
+
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
2995
|
+
this.channelCollection.reSubmit(message.type, message.contents, resubmitData.localOpMetadata,
|
|
2996
|
+
/* squash: */ true);
|
|
2997
|
+
break;
|
|
2998
|
+
}
|
|
2999
|
+
// NOTE: Squash doesn't apply to GC or DocumentSchemaChange ops, fallback to typical resubmit logic.
|
|
3000
|
+
case messageTypes_js_1.ContainerMessageType.GC:
|
|
3001
|
+
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
3002
|
+
this.reSubmit(resubmitData);
|
|
3003
|
+
break;
|
|
3004
|
+
}
|
|
3005
|
+
default: {
|
|
3006
|
+
(0, internal_2.unreachableCase)(message.type);
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
2912
3009
|
}
|
|
2913
3010
|
/**
|
|
2914
|
-
*
|
|
2915
|
-
*
|
|
2916
|
-
*
|
|
2917
|
-
* @param message - The original LocalContainerRuntimeMessage.
|
|
2918
|
-
* @param localOpMetadata - The local metadata associated with the original message.
|
|
3011
|
+
* Resubmit the given message which was previously submitted to the ContainerRuntime but not successfully
|
|
3012
|
+
* transmitted to the ordering service (e.g. due to a disconnect, or being in Staging Mode)
|
|
3013
|
+
* How to resubmit is up to the subsystem that submitted the op to begin with
|
|
2919
3014
|
*/
|
|
2920
|
-
|
|
2921
|
-
(0, internal_2.assert)(this._summarizer === undefined, 0x8f2 /* Summarizer never reconnects so should never resubmit */);
|
|
3015
|
+
reSubmit({ runtimeOp: message, localOpMetadata, opMetadata, }) {
|
|
2922
3016
|
switch (message.type) {
|
|
2923
3017
|
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp:
|
|
2924
3018
|
case messageTypes_js_1.ContainerMessageType.Attach:
|
|
2925
3019
|
case messageTypes_js_1.ContainerMessageType.Alias: {
|
|
2926
3020
|
// For Operations, call resubmitDataStoreOp which will find the right store
|
|
2927
3021
|
// and trigger resubmission on it.
|
|
2928
|
-
this.channelCollection.reSubmit(message.type, message.contents, localOpMetadata,
|
|
3022
|
+
this.channelCollection.reSubmit(message.type, message.contents, localOpMetadata,
|
|
3023
|
+
/* squash: */ false);
|
|
2929
3024
|
break;
|
|
2930
3025
|
}
|
|
2931
3026
|
case messageTypes_js_1.ContainerMessageType.IdAllocation: {
|
|
@@ -2951,9 +3046,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2951
3046
|
break;
|
|
2952
3047
|
}
|
|
2953
3048
|
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
2954
|
-
//
|
|
2955
|
-
//
|
|
2956
|
-
|
|
3049
|
+
// We shouldn't directly resubmit due to Compare-And-Swap semantics.
|
|
3050
|
+
// If needed it will be generated from scratch before other ops are submitted.
|
|
3051
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
2957
3052
|
break;
|
|
2958
3053
|
}
|
|
2959
3054
|
default: {
|
|
@@ -2963,8 +3058,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2963
3058
|
}
|
|
2964
3059
|
}
|
|
2965
3060
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
3061
|
+
/**
|
|
3062
|
+
* Rollback the given op which was only staged but not yet submitted.
|
|
3063
|
+
*/
|
|
3064
|
+
rollbackStagedChanges({ type, contents }, localOpMetadata) {
|
|
3065
|
+
(0, internal_2.assert)(canStageMessageOfType(type), 0xbbc /* Unexpected message type to be rolled back */);
|
|
2968
3066
|
switch (type) {
|
|
2969
3067
|
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
2970
3068
|
// For operations, call rollbackDataStoreOp which will find the right store
|
|
@@ -2972,8 +3070,24 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2972
3070
|
this.channelCollection.rollback(type, contents, localOpMetadata);
|
|
2973
3071
|
break;
|
|
2974
3072
|
}
|
|
3073
|
+
case messageTypes_js_1.ContainerMessageType.GC: {
|
|
3074
|
+
// Just drop it, but log an error, this is not expected and not ideal, but not critical failure either.
|
|
3075
|
+
// Currently the only expected type here is TombstoneLoaded, which will have been preceded by one of these events as well:
|
|
3076
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_SubDataStore_Requested, GC_Tombstone_Blob_Requested
|
|
3077
|
+
this.mc.logger.sendErrorEvent({
|
|
3078
|
+
eventName: "GC_OpDiscarded",
|
|
3079
|
+
details: { subType: contents.type },
|
|
3080
|
+
});
|
|
3081
|
+
break;
|
|
3082
|
+
}
|
|
3083
|
+
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
3084
|
+
// Notify the document schema controller that the pending op was not acked.
|
|
3085
|
+
// This will allow it to propose the schema change again if needed.
|
|
3086
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
3087
|
+
break;
|
|
3088
|
+
}
|
|
2975
3089
|
default: {
|
|
2976
|
-
|
|
3090
|
+
(0, internal_2.unreachableCase)(type);
|
|
2977
3091
|
}
|
|
2978
3092
|
}
|
|
2979
3093
|
}
|
|
@@ -3151,6 +3265,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3151
3265
|
},
|
|
3152
3266
|
getQuorum: this.getQuorum.bind(this),
|
|
3153
3267
|
getAudience: this.getAudience.bind(this),
|
|
3268
|
+
supportedFeatures: this.ILayerCompatDetails.supportedFeatures,
|
|
3154
3269
|
};
|
|
3155
3270
|
entry = new factory(runtime, ...useContext);
|
|
3156
3271
|
this.extensions.set(id, entry);
|