@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/lib/containerRuntime.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { createEmitter, Trace, TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
6
6
|
import { AttachState } from "@fluidframework/container-definitions";
|
|
7
7
|
import { isIDeltaManagerFull } from "@fluidframework/container-definitions/internal";
|
|
8
|
-
import { assert, Deferred, Lazy, LazyPromise, PromiseCache, delay, fail, } from "@fluidframework/core-utils/internal";
|
|
8
|
+
import { assert, Deferred, Lazy, LazyPromise, PromiseCache, delay, fail, unreachableCase, } from "@fluidframework/core-utils/internal";
|
|
9
9
|
import { SummaryType } from "@fluidframework/driver-definitions";
|
|
10
10
|
import { FetchSource, MessageType } from "@fluidframework/driver-definitions/internal";
|
|
11
11
|
import { readAndParse } from "@fluidframework/driver-utils/internal";
|
|
@@ -19,7 +19,7 @@ import { v4 as uuid } from "uuid";
|
|
|
19
19
|
import { BindBatchTracker } from "./batchTracker.js";
|
|
20
20
|
import { BlobManager, blobManagerBasePath, blobsTreeName, isBlobPath, loadBlobManagerLoadInfo, } from "./blobManager/index.js";
|
|
21
21
|
import { ChannelCollection, getSummaryForDatastores, wrapContext, } from "./channelCollection.js";
|
|
22
|
-
import { defaultMinVersionForCollab, getMinVersionForCollabDefaults, isValidMinVersionForCollab, } from "./compatUtils.js";
|
|
22
|
+
import { defaultMinVersionForCollab, getMinVersionForCollabDefaults, isValidMinVersionForCollab, validateRuntimeOptions, } from "./compatUtils.js";
|
|
23
23
|
import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
|
|
24
24
|
import { ReportOpPerfTelemetry } from "./connectionTelemetry.js";
|
|
25
25
|
import { ContainerFluidHandleContext } from "./containerHandleContext.js";
|
|
@@ -191,6 +191,20 @@ export async function loadContainerRuntime(params) {
|
|
|
191
191
|
return ContainerRuntime.loadRuntime(params);
|
|
192
192
|
}
|
|
193
193
|
const defaultMaxConsecutiveReconnects = 7;
|
|
194
|
+
/**
|
|
195
|
+
* These are the ONLY message types that are allowed to be submitted while in staging mode
|
|
196
|
+
* (Does not apply to pre-StagingMode batches that are resubmitted, those are not considered to be staged)
|
|
197
|
+
*/
|
|
198
|
+
function canStageMessageOfType(type) {
|
|
199
|
+
return (
|
|
200
|
+
// These are user changes coming up from the runtime's DataStores
|
|
201
|
+
type === ContainerMessageType.FluidDataStoreOp ||
|
|
202
|
+
// GC ops are used to detect issues in the reference graph so all clients can repair their GC state.
|
|
203
|
+
// These can be submitted at any time, including while in Staging Mode.
|
|
204
|
+
type === ContainerMessageType.GC ||
|
|
205
|
+
// These are typically sent shortly after boot and will not be common in Staging Mode, but it's possible.
|
|
206
|
+
type === ContainerMessageType.DocumentSchemaChange);
|
|
207
|
+
}
|
|
194
208
|
/**
|
|
195
209
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
196
210
|
* It will define the store level mappings.
|
|
@@ -239,6 +253,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
239
253
|
if (!isValidMinVersionForCollab(minVersionForCollab)) {
|
|
240
254
|
throw new UsageError(`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`);
|
|
241
255
|
}
|
|
256
|
+
// We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
|
|
257
|
+
// were manually set.
|
|
258
|
+
validateRuntimeOptions(minVersionForCollab, runtimeOptions);
|
|
242
259
|
const defaultsAffectingDocSchema = getMinVersionForCollabDefaults(minVersionForCollab);
|
|
243
260
|
// The following are the default values for the options that do not affect the DocumentSchema.
|
|
244
261
|
const defaultsNotAffectingDocSchema = {
|
|
@@ -504,8 +521,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
504
521
|
ensureNoDataModelChanges(callback) {
|
|
505
522
|
return this.dataModelChangeRunner.run(callback);
|
|
506
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* Indicates whether the container is in a state where it is able to send
|
|
526
|
+
* ops (connected to op stream and not in readonly mode).
|
|
527
|
+
*/
|
|
507
528
|
get connected() {
|
|
508
|
-
return this.
|
|
529
|
+
return this.canSendOps;
|
|
509
530
|
}
|
|
510
531
|
/**
|
|
511
532
|
* clientId of parent (non-summarizing) container that owns summarizer container
|
|
@@ -593,7 +614,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
593
614
|
// eslint-disable-next-line import/no-deprecated
|
|
594
615
|
this.enterStagingMode = () => {
|
|
595
616
|
if (this.stageControls !== undefined) {
|
|
596
|
-
throw new
|
|
617
|
+
throw new UsageError("already in staging mode");
|
|
618
|
+
}
|
|
619
|
+
if (this.attachState === AttachState.Detached) {
|
|
620
|
+
throw new UsageError("cannot enter staging mode while detached");
|
|
597
621
|
}
|
|
598
622
|
// Make sure all BatchManagers are empty before entering staging mode,
|
|
599
623
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
@@ -614,7 +638,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
614
638
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
615
639
|
this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
616
640
|
assert(runtimeOp !== undefined, 0xb82 /* Staged batches expected to have runtimeOp defined */);
|
|
617
|
-
this.
|
|
641
|
+
this.rollbackStagedChanges(runtimeOp, localOpMetadata);
|
|
618
642
|
});
|
|
619
643
|
this.updateDocumentDirtyState();
|
|
620
644
|
}),
|
|
@@ -651,6 +675,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
651
675
|
this.mc = createChildMonitoringContext({
|
|
652
676
|
logger: this.baseLogger,
|
|
653
677
|
namespace: "ContainerRuntime",
|
|
678
|
+
properties: {
|
|
679
|
+
all: {
|
|
680
|
+
inStagingMode: this.inStagingMode,
|
|
681
|
+
},
|
|
682
|
+
},
|
|
654
683
|
});
|
|
655
684
|
// If we support multiple algorithms in the future, then we would need to manage it here carefully.
|
|
656
685
|
// We can use runtimeOptions.compressionOptions.compressionAlgorithm, but only if it's in the schema list!
|
|
@@ -691,7 +720,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
691
720
|
// Values are generally expected to be set from the runtime side.
|
|
692
721
|
this.options = options ?? {};
|
|
693
722
|
this.clientDetails = clientDetails;
|
|
694
|
-
|
|
723
|
+
this.isSummarizerClient = this.clientDetails.type === summarizerClientType;
|
|
695
724
|
this.loadedFromVersionId = context.getLoadedFromVersion()?.id;
|
|
696
725
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
697
726
|
this._getClientId = () => context.clientId;
|
|
@@ -728,7 +757,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
728
757
|
details: { attachState: this.attachState },
|
|
729
758
|
}));
|
|
730
759
|
// In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
|
|
731
|
-
this.closeFn = isSummarizerClient ? this.disposeFn : closeFn;
|
|
760
|
+
this.closeFn = this.isSummarizerClient ? this.disposeFn : closeFn;
|
|
732
761
|
let loadSummaryNumber;
|
|
733
762
|
// Get the container creation metadata. For new container, we initialize these. For existing containers,
|
|
734
763
|
// get the values from the metadata blob.
|
|
@@ -752,7 +781,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
752
781
|
this.messageAtLastSummary = lastMessageFromMetadata(metadata);
|
|
753
782
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
754
783
|
// Later updates come through calls to setConnectionState.
|
|
755
|
-
this.
|
|
784
|
+
this.canSendOps = connected;
|
|
756
785
|
this.mc.logger.sendTelemetryEvent({
|
|
757
786
|
eventName: "GCFeatureMatrix",
|
|
758
787
|
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
@@ -845,7 +874,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
845
874
|
existing,
|
|
846
875
|
metadata,
|
|
847
876
|
createContainerMetadata: this.createContainerMetadata,
|
|
848
|
-
isSummarizerClient,
|
|
877
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
849
878
|
getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
|
|
850
879
|
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
851
880
|
readAndParseBlob: async (id) => readAndParse(this.storage, id),
|
|
@@ -929,7 +958,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
929
958
|
// Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
|
|
930
959
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
931
960
|
this.outbox = new Outbox({
|
|
932
|
-
shouldSend: () => this.
|
|
961
|
+
shouldSend: () => this.shouldSendOps(),
|
|
933
962
|
pendingStateManager: this.pendingStateManager,
|
|
934
963
|
submitBatchFn,
|
|
935
964
|
legacySendBatchFn,
|
|
@@ -1068,7 +1097,14 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1068
1097
|
await this.initializeSummarizer(loader);
|
|
1069
1098
|
if (this.sessionSchema.idCompressorMode === "on" ||
|
|
1070
1099
|
(this.sessionSchema.idCompressorMode === "delayed" && this.connected)) {
|
|
1071
|
-
this.
|
|
1100
|
+
PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnBoot" }, (event) => {
|
|
1101
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1102
|
+
event.end({
|
|
1103
|
+
details: {
|
|
1104
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
1105
|
+
},
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1072
1108
|
// This is called from loadRuntime(), long before we process any ops, so there should be no ops accumulated yet.
|
|
1073
1109
|
assert(this.pendingIdCompressorOps.length === 0, 0x8ec /* no pending ops */);
|
|
1074
1110
|
}
|
|
@@ -1097,8 +1133,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1097
1133
|
const orderedClientCollection = new OrderedClientCollection(orderedClientLogger, this.innerDeltaManager, this._quorum);
|
|
1098
1134
|
const orderedClientElectionForSummarizer = new OrderedClientElection(orderedClientLogger, orderedClientCollection, this.electedSummarizerData ?? this.innerDeltaManager.lastSequenceNumber, SummarizerClientElection.isClientEligible, this.mc.config.getBoolean("Fluid.ContainerRuntime.OrderedClientElection.EnablePerformanceEvents"));
|
|
1099
1135
|
this.summarizerClientElection = new SummarizerClientElection(orderedClientLogger, summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary);
|
|
1100
|
-
|
|
1101
|
-
if (isSummarizerClient) {
|
|
1136
|
+
if (this.isSummarizerClient) {
|
|
1102
1137
|
// We want to dynamically import any thing inside summaryDelayLoadedModule module only when we are the summarizer client,
|
|
1103
1138
|
// so that all non summarizer clients don't have to load the code inside this module.
|
|
1104
1139
|
const module = await import(
|
|
@@ -1446,7 +1481,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1446
1481
|
}
|
|
1447
1482
|
replayPendingStates() {
|
|
1448
1483
|
// We need to be able to send ops to replay states
|
|
1449
|
-
if (!this.
|
|
1484
|
+
if (!this.shouldSendOps()) {
|
|
1450
1485
|
return;
|
|
1451
1486
|
}
|
|
1452
1487
|
// Replaying is an internal operation and we don't want to generate noise while doing it.
|
|
@@ -1528,25 +1563,35 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1528
1563
|
loadIdCompressor() {
|
|
1529
1564
|
if (this._idCompressor === undefined &&
|
|
1530
1565
|
this.sessionSchema.idCompressorMode !== undefined) {
|
|
1531
|
-
this.
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1566
|
+
PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnDelayedLoad" }, (event) => {
|
|
1567
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1568
|
+
// Finalize any ranges we received while the compressor was turned off.
|
|
1569
|
+
const ops = this.pendingIdCompressorOps;
|
|
1570
|
+
this.pendingIdCompressorOps = [];
|
|
1571
|
+
const trace = Trace.start();
|
|
1572
|
+
for (const range of ops) {
|
|
1573
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
1574
|
+
}
|
|
1575
|
+
event.end({
|
|
1576
|
+
details: {
|
|
1577
|
+
finalizeCreationRangeDuration: trace.trace().duration,
|
|
1578
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
1579
|
+
pendingIdCompressorOps: ops.length,
|
|
1580
|
+
},
|
|
1581
|
+
});
|
|
1582
|
+
});
|
|
1538
1583
|
assert(this.pendingIdCompressorOps.length === 0, 0x976 /* No new ops added */);
|
|
1539
1584
|
}
|
|
1540
1585
|
}
|
|
1541
|
-
setConnectionState(
|
|
1586
|
+
setConnectionState(canSendOps, clientId) {
|
|
1542
1587
|
// Validate we have consistent state
|
|
1543
1588
|
const currentClientId = this._audience.getSelf()?.clientId;
|
|
1544
1589
|
assert(clientId === currentClientId, 0x977 /* input clientId does not match Audience */);
|
|
1545
1590
|
assert(this.clientId === currentClientId, 0x978 /* this.clientId does not match Audience */);
|
|
1546
|
-
if (
|
|
1591
|
+
if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
|
|
1547
1592
|
this.loadIdCompressor();
|
|
1548
1593
|
}
|
|
1549
|
-
if (
|
|
1594
|
+
if (canSendOps === false && this.delayConnectClientId !== undefined) {
|
|
1550
1595
|
this.delayConnectClientId = undefined;
|
|
1551
1596
|
this.mc.logger.sendTelemetryEvent({
|
|
1552
1597
|
eventName: "UnsuccessfulConnectedTransition",
|
|
@@ -1554,37 +1599,39 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1554
1599
|
// Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
|
|
1555
1600
|
return;
|
|
1556
1601
|
}
|
|
1557
|
-
if (!connected) {
|
|
1558
|
-
this.documentsSchemaController.onDisconnect();
|
|
1559
|
-
}
|
|
1560
1602
|
// If there are stashed blobs in the pending state, we need to delay
|
|
1561
1603
|
// propagation of the "connected" event until we have uploaded them to
|
|
1562
1604
|
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
1563
|
-
const connecting =
|
|
1605
|
+
const connecting = canSendOps && !this.canSendOps;
|
|
1564
1606
|
if (connecting && this.blobManager.hasPendingStashedUploads()) {
|
|
1565
1607
|
assert(!this.delayConnectClientId, 0x791 /* Connect event delay must be canceled before subsequent connect event */);
|
|
1566
1608
|
assert(!!clientId, 0x792 /* Must have clientId when connecting */);
|
|
1567
1609
|
this.delayConnectClientId = clientId;
|
|
1568
1610
|
return;
|
|
1569
1611
|
}
|
|
1570
|
-
this.setConnectionStateCore(
|
|
1612
|
+
this.setConnectionStateCore(canSendOps, clientId);
|
|
1571
1613
|
}
|
|
1572
|
-
|
|
1614
|
+
/**
|
|
1615
|
+
* Raises and propagates connected events.
|
|
1616
|
+
* @param canSendOps - Indicates whether the container can send ops or not (connected and not readonly).
|
|
1617
|
+
* @remarks The connection state from container context used here when raising connected events.
|
|
1618
|
+
*/
|
|
1619
|
+
setConnectionStateCore(canSendOps, clientId) {
|
|
1573
1620
|
assert(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
|
|
1574
1621
|
this.verifyNotClosed();
|
|
1575
1622
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1576
|
-
const
|
|
1577
|
-
const reconnection =
|
|
1623
|
+
const canSendOpsChanged = this.canSendOps !== canSendOps;
|
|
1624
|
+
const reconnection = canSendOpsChanged && !canSendOps;
|
|
1578
1625
|
// We need to flush the ops currently collected by Outbox to preserve original order.
|
|
1579
1626
|
// This flush NEEDS to happen before we set the ContainerRuntime to "connected".
|
|
1580
1627
|
// We want these ops to get to the PendingStateManager without sending to service and have them return to the Outbox upon calling "replayPendingStates".
|
|
1581
|
-
if (
|
|
1628
|
+
if (canSendOpsChanged && canSendOps) {
|
|
1582
1629
|
this.flush();
|
|
1583
1630
|
}
|
|
1584
|
-
this.
|
|
1585
|
-
if (
|
|
1631
|
+
this.canSendOps = canSendOps;
|
|
1632
|
+
if (canSendOps) {
|
|
1586
1633
|
assert(this.attachState === AttachState.Attached, 0x3cd /* Connection is possible only if container exists in storage */);
|
|
1587
|
-
if (
|
|
1634
|
+
if (canSendOpsChanged) {
|
|
1588
1635
|
this.signalTelemetryManager.resetTracking();
|
|
1589
1636
|
}
|
|
1590
1637
|
}
|
|
@@ -1600,12 +1647,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1600
1647
|
return;
|
|
1601
1648
|
}
|
|
1602
1649
|
}
|
|
1603
|
-
if (
|
|
1650
|
+
if (canSendOpsChanged) {
|
|
1604
1651
|
this.replayPendingStates();
|
|
1605
1652
|
}
|
|
1606
|
-
this.channelCollection.setConnectionState(
|
|
1607
|
-
this.garbageCollector.setConnectionState(
|
|
1608
|
-
raiseConnectedEvent(this.mc.logger, this, connected
|
|
1653
|
+
this.channelCollection.setConnectionState(canSendOps, clientId);
|
|
1654
|
+
this.garbageCollector.setConnectionState(canSendOps, clientId);
|
|
1655
|
+
raiseConnectedEvent(this.mc.logger, this, this.connected /* canSendOps */, clientId);
|
|
1609
1656
|
}
|
|
1610
1657
|
async notifyOpReplay(message) {
|
|
1611
1658
|
await this.pendingStateManager.applyStashedOpsAt(message.sequenceNumber);
|
|
@@ -2012,7 +2059,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2012
2059
|
if (checkpoint) {
|
|
2013
2060
|
// This will throw and close the container if rollback fails
|
|
2014
2061
|
try {
|
|
2015
|
-
checkpoint.rollback((message) =>
|
|
2062
|
+
checkpoint.rollback((message) =>
|
|
2063
|
+
// These changes are staged since we entered staging mode above
|
|
2064
|
+
this.rollbackStagedChanges(message.runtimeOp, message.localOpMetadata));
|
|
2016
2065
|
this.updateDocumentDirtyState();
|
|
2017
2066
|
stageControls?.discardChanges();
|
|
2018
2067
|
stageControls = undefined;
|
|
@@ -2089,7 +2138,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2089
2138
|
const context = this.channelCollection.createDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], loadingGroupId);
|
|
2090
2139
|
return channelToDataStore(await context.realize(), context.id, this.channelCollection, this.mc.logger);
|
|
2091
2140
|
}
|
|
2092
|
-
|
|
2141
|
+
shouldSendOps() {
|
|
2093
2142
|
// Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
|
|
2094
2143
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
2095
2144
|
return (this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure);
|
|
@@ -2778,6 +2827,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2778
2827
|
try {
|
|
2779
2828
|
// If we're resubmitting a batch, keep the same "staged" value as before. Otherwise, use the current "global" state.
|
|
2780
2829
|
const staged = this.batchRunner.resubmitInfo?.staged ?? this.inStagingMode;
|
|
2830
|
+
assert(!staged || canStageMessageOfType(type), 0xbba /* Unexpected message type submitted in Staging Mode */);
|
|
2781
2831
|
// Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
|
|
2782
2832
|
if (!staged) {
|
|
2783
2833
|
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
@@ -2785,7 +2835,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2785
2835
|
// Allow document schema controller to send a message if it needs to propose change in document schema.
|
|
2786
2836
|
// If it needs to send a message, it will call provided callback with payload of such message and rely
|
|
2787
2837
|
// on this callback to do actual sending.
|
|
2788
|
-
const schemaChangeMessage = this.documentsSchemaController.
|
|
2838
|
+
const schemaChangeMessage = this.documentsSchemaController.maybeGenerateSchemaMessage();
|
|
2789
2839
|
if (schemaChangeMessage) {
|
|
2790
2840
|
this.mc.logger.sendTelemetryEvent({
|
|
2791
2841
|
eventName: "SchemaChangeProposal",
|
|
@@ -2883,43 +2933,72 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2883
2933
|
}
|
|
2884
2934
|
/**
|
|
2885
2935
|
* Resubmits each message in the batch, and then flushes the outbox.
|
|
2936
|
+
* This typically happens when we reconnect and there are pending messages.
|
|
2886
2937
|
*
|
|
2887
|
-
* @remarks
|
|
2938
|
+
* @remarks
|
|
2939
|
+
* Attempting to resubmit a batch that has been successfully sequenced will not happen due to
|
|
2940
|
+
* checks in the ConnectionStateHandler (Loader layer)
|
|
2941
|
+
*
|
|
2942
|
+
* The only exception to this would be if the Container "forks" due to misuse of the "Offline Load" feature.
|
|
2943
|
+
* If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
|
|
2888
2944
|
* for correlation to detect container forking.
|
|
2889
2945
|
*/
|
|
2890
2946
|
reSubmitBatch(batch, { batchId, staged, squash }) {
|
|
2947
|
+
assert(this._summarizer === undefined, 0x8f2 /* Summarizer never reconnects so should never resubmit */);
|
|
2891
2948
|
const resubmitInfo = {
|
|
2892
2949
|
// Only include Batch ID if "Offline Load" feature is enabled
|
|
2893
2950
|
// It's only needed to identify batches across container forks arising from misuse of offline load.
|
|
2894
2951
|
batchId: this.offlineEnabled ? batchId : undefined,
|
|
2895
2952
|
staged,
|
|
2896
2953
|
};
|
|
2954
|
+
const resubmitFn = squash
|
|
2955
|
+
? this.reSubmitWithSquashing.bind(this)
|
|
2956
|
+
: this.reSubmit.bind(this);
|
|
2897
2957
|
this.batchRunner.run(() => {
|
|
2898
2958
|
for (const message of batch) {
|
|
2899
|
-
|
|
2959
|
+
resubmitFn(message);
|
|
2900
2960
|
}
|
|
2901
2961
|
}, resubmitInfo);
|
|
2902
2962
|
this.flush(resubmitInfo);
|
|
2903
2963
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2964
|
+
/**
|
|
2965
|
+
* Resubmit the given message as part of a squash rebase upon exiting Staging Mode.
|
|
2966
|
+
* How exactly to resubmit the message is up to the subsystem that submitted the op to begin with.
|
|
2967
|
+
*/
|
|
2968
|
+
reSubmitWithSquashing(resubmitData) {
|
|
2969
|
+
const message = resubmitData.runtimeOp;
|
|
2970
|
+
assert(canStageMessageOfType(message.type), 0xbbb /* Expected message type to be compatible with staging */);
|
|
2971
|
+
switch (message.type) {
|
|
2972
|
+
case ContainerMessageType.FluidDataStoreOp: {
|
|
2973
|
+
this.channelCollection.reSubmit(message.type, message.contents, resubmitData.localOpMetadata,
|
|
2974
|
+
/* squash: */ true);
|
|
2975
|
+
break;
|
|
2976
|
+
}
|
|
2977
|
+
// NOTE: Squash doesn't apply to GC or DocumentSchemaChange ops, fallback to typical resubmit logic.
|
|
2978
|
+
case ContainerMessageType.GC:
|
|
2979
|
+
case ContainerMessageType.DocumentSchemaChange: {
|
|
2980
|
+
this.reSubmit(resubmitData);
|
|
2981
|
+
break;
|
|
2982
|
+
}
|
|
2983
|
+
default: {
|
|
2984
|
+
unreachableCase(message.type);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2906
2987
|
}
|
|
2907
2988
|
/**
|
|
2908
|
-
*
|
|
2909
|
-
*
|
|
2910
|
-
*
|
|
2911
|
-
* @param message - The original LocalContainerRuntimeMessage.
|
|
2912
|
-
* @param localOpMetadata - The local metadata associated with the original message.
|
|
2989
|
+
* Resubmit the given message which was previously submitted to the ContainerRuntime but not successfully
|
|
2990
|
+
* transmitted to the ordering service (e.g. due to a disconnect, or being in Staging Mode)
|
|
2991
|
+
* How to resubmit is up to the subsystem that submitted the op to begin with
|
|
2913
2992
|
*/
|
|
2914
|
-
|
|
2915
|
-
assert(this._summarizer === undefined, 0x8f2 /* Summarizer never reconnects so should never resubmit */);
|
|
2993
|
+
reSubmit({ runtimeOp: message, localOpMetadata, opMetadata, }) {
|
|
2916
2994
|
switch (message.type) {
|
|
2917
2995
|
case ContainerMessageType.FluidDataStoreOp:
|
|
2918
2996
|
case ContainerMessageType.Attach:
|
|
2919
2997
|
case ContainerMessageType.Alias: {
|
|
2920
2998
|
// For Operations, call resubmitDataStoreOp which will find the right store
|
|
2921
2999
|
// and trigger resubmission on it.
|
|
2922
|
-
this.channelCollection.reSubmit(message.type, message.contents, localOpMetadata,
|
|
3000
|
+
this.channelCollection.reSubmit(message.type, message.contents, localOpMetadata,
|
|
3001
|
+
/* squash: */ false);
|
|
2923
3002
|
break;
|
|
2924
3003
|
}
|
|
2925
3004
|
case ContainerMessageType.IdAllocation: {
|
|
@@ -2945,9 +3024,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2945
3024
|
break;
|
|
2946
3025
|
}
|
|
2947
3026
|
case ContainerMessageType.DocumentSchemaChange: {
|
|
2948
|
-
//
|
|
2949
|
-
//
|
|
2950
|
-
|
|
3027
|
+
// We shouldn't directly resubmit due to Compare-And-Swap semantics.
|
|
3028
|
+
// If needed it will be generated from scratch before other ops are submitted.
|
|
3029
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
2951
3030
|
break;
|
|
2952
3031
|
}
|
|
2953
3032
|
default: {
|
|
@@ -2957,8 +3036,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2957
3036
|
}
|
|
2958
3037
|
}
|
|
2959
3038
|
}
|
|
2960
|
-
|
|
2961
|
-
|
|
3039
|
+
/**
|
|
3040
|
+
* Rollback the given op which was only staged but not yet submitted.
|
|
3041
|
+
*/
|
|
3042
|
+
rollbackStagedChanges({ type, contents }, localOpMetadata) {
|
|
3043
|
+
assert(canStageMessageOfType(type), 0xbbc /* Unexpected message type to be rolled back */);
|
|
2962
3044
|
switch (type) {
|
|
2963
3045
|
case ContainerMessageType.FluidDataStoreOp: {
|
|
2964
3046
|
// For operations, call rollbackDataStoreOp which will find the right store
|
|
@@ -2966,8 +3048,24 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2966
3048
|
this.channelCollection.rollback(type, contents, localOpMetadata);
|
|
2967
3049
|
break;
|
|
2968
3050
|
}
|
|
3051
|
+
case ContainerMessageType.GC: {
|
|
3052
|
+
// Just drop it, but log an error, this is not expected and not ideal, but not critical failure either.
|
|
3053
|
+
// Currently the only expected type here is TombstoneLoaded, which will have been preceded by one of these events as well:
|
|
3054
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_SubDataStore_Requested, GC_Tombstone_Blob_Requested
|
|
3055
|
+
this.mc.logger.sendErrorEvent({
|
|
3056
|
+
eventName: "GC_OpDiscarded",
|
|
3057
|
+
details: { subType: contents.type },
|
|
3058
|
+
});
|
|
3059
|
+
break;
|
|
3060
|
+
}
|
|
3061
|
+
case ContainerMessageType.DocumentSchemaChange: {
|
|
3062
|
+
// Notify the document schema controller that the pending op was not acked.
|
|
3063
|
+
// This will allow it to propose the schema change again if needed.
|
|
3064
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
3065
|
+
break;
|
|
3066
|
+
}
|
|
2969
3067
|
default: {
|
|
2970
|
-
|
|
3068
|
+
unreachableCase(type);
|
|
2971
3069
|
}
|
|
2972
3070
|
}
|
|
2973
3071
|
}
|
|
@@ -3145,6 +3243,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
3145
3243
|
},
|
|
3146
3244
|
getQuorum: this.getQuorum.bind(this),
|
|
3147
3245
|
getAudience: this.getAudience.bind(this),
|
|
3246
|
+
supportedFeatures: this.ILayerCompatDetails.supportedFeatures,
|
|
3148
3247
|
};
|
|
3149
3248
|
entry = new factory(runtime, ...useContext);
|
|
3150
3249
|
this.extensions.set(id, entry);
|