@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/src/containerRuntime.ts
CHANGED
|
@@ -33,6 +33,8 @@ import type {
|
|
|
33
33
|
IContainerRuntime,
|
|
34
34
|
IContainerRuntimeEvents,
|
|
35
35
|
IContainerRuntimeInternal,
|
|
36
|
+
// eslint-disable-next-line import/no-deprecated
|
|
37
|
+
IContainerRuntimeWithResolveHandle_Deprecated,
|
|
36
38
|
OutboundExtensionMessage,
|
|
37
39
|
} from "@fluidframework/container-runtime-definitions/internal";
|
|
38
40
|
import type {
|
|
@@ -60,6 +62,7 @@ import {
|
|
|
60
62
|
PromiseCache,
|
|
61
63
|
delay,
|
|
62
64
|
fail,
|
|
65
|
+
unreachableCase,
|
|
63
66
|
} from "@fluidframework/core-utils/internal";
|
|
64
67
|
import type {
|
|
65
68
|
IClientDetails,
|
|
@@ -157,6 +160,7 @@ import {
|
|
|
157
160
|
tagCodeArtifacts,
|
|
158
161
|
normalizeError,
|
|
159
162
|
} from "@fluidframework/telemetry-utils/internal";
|
|
163
|
+
import { gt } from "semver-ts";
|
|
160
164
|
import { v4 as uuid } from "uuid";
|
|
161
165
|
|
|
162
166
|
import { BindBatchTracker } from "./batchTracker.js";
|
|
@@ -180,6 +184,8 @@ import {
|
|
|
180
184
|
isValidMinVersionForCollab,
|
|
181
185
|
type RuntimeOptionsAffectingDocSchema,
|
|
182
186
|
type MinimumVersionForCollab,
|
|
187
|
+
type SemanticVersion,
|
|
188
|
+
validateRuntimeOptions,
|
|
183
189
|
} from "./compatUtils.js";
|
|
184
190
|
import type { ICompressionRuntimeOptions } from "./compressionDefinitions.js";
|
|
185
191
|
import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
|
|
@@ -206,7 +212,7 @@ import {
|
|
|
206
212
|
import { InboundBatchAggregator } from "./inboundBatchAggregator.js";
|
|
207
213
|
import {
|
|
208
214
|
ContainerMessageType,
|
|
209
|
-
type
|
|
215
|
+
type OutboundContainerRuntimeDocumentSchemaMessage,
|
|
210
216
|
ContainerRuntimeGCMessage,
|
|
211
217
|
type ContainerRuntimeIdAllocationMessage,
|
|
212
218
|
type InboundSequencedContainerRuntimeMessage,
|
|
@@ -244,7 +250,7 @@ import {
|
|
|
244
250
|
import { SignalTelemetryManager } from "./signalTelemetryProcessing.js";
|
|
245
251
|
// These types are imported as types here because they are present in summaryDelayLoadedModule, which is loaded dynamically when required.
|
|
246
252
|
import type {
|
|
247
|
-
|
|
253
|
+
IDocumentSchemaChangeMessageIncoming,
|
|
248
254
|
IDocumentSchemaCurrent,
|
|
249
255
|
Summarizer,
|
|
250
256
|
IDocumentSchemaFeatures,
|
|
@@ -772,6 +778,27 @@ export async function loadContainerRuntime(
|
|
|
772
778
|
|
|
773
779
|
const defaultMaxConsecutiveReconnects = 7;
|
|
774
780
|
|
|
781
|
+
/**
|
|
782
|
+
* These are the ONLY message types that are allowed to be submitted while in staging mode
|
|
783
|
+
* (Does not apply to pre-StagingMode batches that are resubmitted, those are not considered to be staged)
|
|
784
|
+
*/
|
|
785
|
+
function canStageMessageOfType(
|
|
786
|
+
type: LocalContainerRuntimeMessage["type"],
|
|
787
|
+
): type is
|
|
788
|
+
| ContainerMessageType.FluidDataStoreOp
|
|
789
|
+
| ContainerMessageType.GC
|
|
790
|
+
| ContainerMessageType.DocumentSchemaChange {
|
|
791
|
+
return (
|
|
792
|
+
// These are user changes coming up from the runtime's DataStores
|
|
793
|
+
type === ContainerMessageType.FluidDataStoreOp ||
|
|
794
|
+
// GC ops are used to detect issues in the reference graph so all clients can repair their GC state.
|
|
795
|
+
// These can be submitted at any time, including while in Staging Mode.
|
|
796
|
+
type === ContainerMessageType.GC ||
|
|
797
|
+
// These are typically sent shortly after boot and will not be common in Staging Mode, but it's possible.
|
|
798
|
+
type === ContainerMessageType.DocumentSchemaChange
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
775
802
|
/**
|
|
776
803
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
777
804
|
* It will define the store level mappings.
|
|
@@ -784,6 +811,8 @@ export class ContainerRuntime
|
|
|
784
811
|
IContainerRuntimeInternal,
|
|
785
812
|
// eslint-disable-next-line import/no-deprecated
|
|
786
813
|
IContainerRuntimeBaseExperimental,
|
|
814
|
+
// eslint-disable-next-line import/no-deprecated
|
|
815
|
+
IContainerRuntimeWithResolveHandle_Deprecated,
|
|
787
816
|
IRuntime,
|
|
788
817
|
IGarbageCollectionRuntime,
|
|
789
818
|
ISummarizerRuntime,
|
|
@@ -862,6 +891,10 @@ export class ContainerRuntime
|
|
|
862
891
|
`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`,
|
|
863
892
|
);
|
|
864
893
|
}
|
|
894
|
+
// We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
|
|
895
|
+
// were manually set.
|
|
896
|
+
validateRuntimeOptions(minVersionForCollab, runtimeOptions);
|
|
897
|
+
|
|
865
898
|
const defaultsAffectingDocSchema = getMinVersionForCollabDefaults(minVersionForCollab);
|
|
866
899
|
|
|
867
900
|
// The following are the default values for the options that do not affect the DocumentSchema.
|
|
@@ -1083,8 +1116,19 @@ export class ContainerRuntime
|
|
|
1083
1116
|
(schema) => {
|
|
1084
1117
|
runtime.onSchemaChange(schema);
|
|
1085
1118
|
},
|
|
1119
|
+
{ minVersionForCollab },
|
|
1120
|
+
logger,
|
|
1086
1121
|
);
|
|
1087
1122
|
|
|
1123
|
+
// If the minVersionForCollab for this client is greater than the existing one, we should use that one going forward.
|
|
1124
|
+
const existingMinVersionForCollab =
|
|
1125
|
+
documentSchemaController.sessionSchema.info.minVersionForCollab;
|
|
1126
|
+
const updatedMinVersionForCollab =
|
|
1127
|
+
existingMinVersionForCollab === undefined ||
|
|
1128
|
+
gt(minVersionForCollab, existingMinVersionForCollab)
|
|
1129
|
+
? minVersionForCollab
|
|
1130
|
+
: existingMinVersionForCollab;
|
|
1131
|
+
|
|
1088
1132
|
if (compressionLz4 && !enableGroupedBatching) {
|
|
1089
1133
|
throw new UsageError("If compression is enabled, op grouping must be enabled too");
|
|
1090
1134
|
}
|
|
@@ -1123,7 +1167,7 @@ export class ContainerRuntime
|
|
|
1123
1167
|
documentSchemaController,
|
|
1124
1168
|
featureGatesForTelemetry,
|
|
1125
1169
|
provideEntryPoint,
|
|
1126
|
-
|
|
1170
|
+
updatedMinVersionForCollab,
|
|
1127
1171
|
requestHandler,
|
|
1128
1172
|
undefined, // summaryConfiguration
|
|
1129
1173
|
recentBatchInfo,
|
|
@@ -1160,6 +1204,8 @@ export class ContainerRuntime
|
|
|
1160
1204
|
|
|
1161
1205
|
public readonly clientDetails: IClientDetails;
|
|
1162
1206
|
|
|
1207
|
+
private readonly isSummarizerClient: boolean;
|
|
1208
|
+
|
|
1163
1209
|
public get storage(): IDocumentStorageService {
|
|
1164
1210
|
return this._storage;
|
|
1165
1211
|
}
|
|
@@ -1292,7 +1338,7 @@ export class ContainerRuntime
|
|
|
1292
1338
|
private readonly offlineEnabled: boolean;
|
|
1293
1339
|
private flushScheduled = false;
|
|
1294
1340
|
|
|
1295
|
-
private
|
|
1341
|
+
private canSendOps: boolean;
|
|
1296
1342
|
|
|
1297
1343
|
private consecutiveReconnects = 0;
|
|
1298
1344
|
|
|
@@ -1314,8 +1360,12 @@ export class ContainerRuntime
|
|
|
1314
1360
|
return this.dataModelChangeRunner.run(callback);
|
|
1315
1361
|
}
|
|
1316
1362
|
|
|
1363
|
+
/**
|
|
1364
|
+
* Indicates whether the container is in a state where it is able to send
|
|
1365
|
+
* ops (connected to op stream and not in readonly mode).
|
|
1366
|
+
*/
|
|
1317
1367
|
public get connected(): boolean {
|
|
1318
|
-
return this.
|
|
1368
|
+
return this.canSendOps;
|
|
1319
1369
|
}
|
|
1320
1370
|
|
|
1321
1371
|
/**
|
|
@@ -1454,7 +1504,7 @@ export class ContainerRuntime
|
|
|
1454
1504
|
private readonly documentsSchemaController: DocumentsSchemaController,
|
|
1455
1505
|
featureGatesForTelemetry: Record<string, boolean | number | undefined>,
|
|
1456
1506
|
provideEntryPoint: (containerRuntime: IContainerRuntime) => Promise<FluidObject>,
|
|
1457
|
-
private readonly minVersionForCollab:
|
|
1507
|
+
private readonly minVersionForCollab: SemanticVersion,
|
|
1458
1508
|
private readonly requestHandler?: (
|
|
1459
1509
|
request: IRequest,
|
|
1460
1510
|
runtime: IContainerRuntime,
|
|
@@ -1502,6 +1552,11 @@ export class ContainerRuntime
|
|
|
1502
1552
|
this.mc = createChildMonitoringContext({
|
|
1503
1553
|
logger: this.baseLogger,
|
|
1504
1554
|
namespace: "ContainerRuntime",
|
|
1555
|
+
properties: {
|
|
1556
|
+
all: {
|
|
1557
|
+
inStagingMode: this.inStagingMode,
|
|
1558
|
+
},
|
|
1559
|
+
},
|
|
1505
1560
|
});
|
|
1506
1561
|
|
|
1507
1562
|
// If we support multiple algorithms in the future, then we would need to manage it here carefully.
|
|
@@ -1558,7 +1613,7 @@ export class ContainerRuntime
|
|
|
1558
1613
|
// Values are generally expected to be set from the runtime side.
|
|
1559
1614
|
this.options = options ?? {};
|
|
1560
1615
|
this.clientDetails = clientDetails;
|
|
1561
|
-
|
|
1616
|
+
this.isSummarizerClient = this.clientDetails.type === summarizerClientType;
|
|
1562
1617
|
this.loadedFromVersionId = context.getLoadedFromVersion()?.id;
|
|
1563
1618
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
1564
1619
|
this._getClientId = () => context.clientId;
|
|
@@ -1599,7 +1654,7 @@ export class ContainerRuntime
|
|
|
1599
1654
|
);
|
|
1600
1655
|
|
|
1601
1656
|
// In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
|
|
1602
|
-
this.closeFn = isSummarizerClient ? this.disposeFn : closeFn;
|
|
1657
|
+
this.closeFn = this.isSummarizerClient ? this.disposeFn : closeFn;
|
|
1603
1658
|
|
|
1604
1659
|
let loadSummaryNumber: number;
|
|
1605
1660
|
// Get the container creation metadata. For new container, we initialize these. For existing containers,
|
|
@@ -1625,7 +1680,7 @@ export class ContainerRuntime
|
|
|
1625
1680
|
|
|
1626
1681
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
1627
1682
|
// Later updates come through calls to setConnectionState.
|
|
1628
|
-
this.
|
|
1683
|
+
this.canSendOps = connected;
|
|
1629
1684
|
|
|
1630
1685
|
this.mc.logger.sendTelemetryEvent({
|
|
1631
1686
|
eventName: "GCFeatureMatrix",
|
|
@@ -1760,7 +1815,7 @@ export class ContainerRuntime
|
|
|
1760
1815
|
existing,
|
|
1761
1816
|
metadata,
|
|
1762
1817
|
createContainerMetadata: this.createContainerMetadata,
|
|
1763
|
-
isSummarizerClient,
|
|
1818
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
1764
1819
|
getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
|
|
1765
1820
|
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
1766
1821
|
readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
|
|
@@ -1887,7 +1942,7 @@ export class ContainerRuntime
|
|
|
1887
1942
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
1888
1943
|
|
|
1889
1944
|
this.outbox = new Outbox({
|
|
1890
|
-
shouldSend: () => this.
|
|
1945
|
+
shouldSend: () => this.shouldSendOps(),
|
|
1891
1946
|
pendingStateManager: this.pendingStateManager,
|
|
1892
1947
|
submitBatchFn,
|
|
1893
1948
|
legacySendBatchFn,
|
|
@@ -2068,7 +2123,18 @@ export class ContainerRuntime
|
|
|
2068
2123
|
this.sessionSchema.idCompressorMode === "on" ||
|
|
2069
2124
|
(this.sessionSchema.idCompressorMode === "delayed" && this.connected)
|
|
2070
2125
|
) {
|
|
2071
|
-
|
|
2126
|
+
PerformanceEvent.timedExec(
|
|
2127
|
+
this.mc.logger,
|
|
2128
|
+
{ eventName: "CreateIdCompressorOnBoot" },
|
|
2129
|
+
(event) => {
|
|
2130
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
2131
|
+
event.end({
|
|
2132
|
+
details: {
|
|
2133
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
2134
|
+
},
|
|
2135
|
+
});
|
|
2136
|
+
},
|
|
2137
|
+
);
|
|
2072
2138
|
// This is called from loadRuntime(), long before we process any ops, so there should be no ops accumulated yet.
|
|
2073
2139
|
assert(this.pendingIdCompressorOps.length === 0, 0x8ec /* no pending ops */);
|
|
2074
2140
|
}
|
|
@@ -2125,8 +2191,7 @@ export class ContainerRuntime
|
|
|
2125
2191
|
maxOpsSinceLastSummary,
|
|
2126
2192
|
);
|
|
2127
2193
|
|
|
2128
|
-
|
|
2129
|
-
if (isSummarizerClient) {
|
|
2194
|
+
if (this.isSummarizerClient) {
|
|
2130
2195
|
// We want to dynamically import any thing inside summaryDelayLoadedModule module only when we are the summarizer client,
|
|
2131
2196
|
// so that all non summarizer clients don't have to load the code inside this module.
|
|
2132
2197
|
const module = await import(
|
|
@@ -2575,7 +2640,7 @@ export class ContainerRuntime
|
|
|
2575
2640
|
|
|
2576
2641
|
private replayPendingStates(): void {
|
|
2577
2642
|
// We need to be able to send ops to replay states
|
|
2578
|
-
if (!this.
|
|
2643
|
+
if (!this.shouldSendOps()) {
|
|
2579
2644
|
return;
|
|
2580
2645
|
}
|
|
2581
2646
|
|
|
@@ -2671,13 +2736,27 @@ export class ContainerRuntime
|
|
|
2671
2736
|
this._idCompressor === undefined &&
|
|
2672
2737
|
this.sessionSchema.idCompressorMode !== undefined
|
|
2673
2738
|
) {
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2739
|
+
PerformanceEvent.timedExec(
|
|
2740
|
+
this.mc.logger,
|
|
2741
|
+
{ eventName: "CreateIdCompressorOnDelayedLoad" },
|
|
2742
|
+
(event) => {
|
|
2743
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
2744
|
+
// Finalize any ranges we received while the compressor was turned off.
|
|
2745
|
+
const ops = this.pendingIdCompressorOps;
|
|
2746
|
+
this.pendingIdCompressorOps = [];
|
|
2747
|
+
const trace = Trace.start();
|
|
2748
|
+
for (const range of ops) {
|
|
2749
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
2750
|
+
}
|
|
2751
|
+
event.end({
|
|
2752
|
+
details: {
|
|
2753
|
+
finalizeCreationRangeDuration: trace.trace().duration,
|
|
2754
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
2755
|
+
pendingIdCompressorOps: ops.length,
|
|
2756
|
+
},
|
|
2757
|
+
});
|
|
2758
|
+
},
|
|
2759
|
+
);
|
|
2681
2760
|
assert(this.pendingIdCompressorOps.length === 0, 0x976 /* No new ops added */);
|
|
2682
2761
|
}
|
|
2683
2762
|
}
|
|
@@ -2685,7 +2764,7 @@ export class ContainerRuntime
|
|
|
2685
2764
|
private readonly notifyReadOnlyState = (readonly: boolean): void =>
|
|
2686
2765
|
this.channelCollection.notifyReadOnlyState(readonly);
|
|
2687
2766
|
|
|
2688
|
-
public setConnectionState(
|
|
2767
|
+
public setConnectionState(canSendOps: boolean, clientId?: string): void {
|
|
2689
2768
|
// Validate we have consistent state
|
|
2690
2769
|
const currentClientId = this._audience.getSelf()?.clientId;
|
|
2691
2770
|
assert(clientId === currentClientId, 0x977 /* input clientId does not match Audience */);
|
|
@@ -2694,10 +2773,10 @@ export class ContainerRuntime
|
|
|
2694
2773
|
0x978 /* this.clientId does not match Audience */,
|
|
2695
2774
|
);
|
|
2696
2775
|
|
|
2697
|
-
if (
|
|
2776
|
+
if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
|
|
2698
2777
|
this.loadIdCompressor();
|
|
2699
2778
|
}
|
|
2700
|
-
if (
|
|
2779
|
+
if (canSendOps === false && this.delayConnectClientId !== undefined) {
|
|
2701
2780
|
this.delayConnectClientId = undefined;
|
|
2702
2781
|
this.mc.logger.sendTelemetryEvent({
|
|
2703
2782
|
eventName: "UnsuccessfulConnectedTransition",
|
|
@@ -2706,14 +2785,10 @@ export class ContainerRuntime
|
|
|
2706
2785
|
return;
|
|
2707
2786
|
}
|
|
2708
2787
|
|
|
2709
|
-
if (!connected) {
|
|
2710
|
-
this.documentsSchemaController.onDisconnect();
|
|
2711
|
-
}
|
|
2712
|
-
|
|
2713
2788
|
// If there are stashed blobs in the pending state, we need to delay
|
|
2714
2789
|
// propagation of the "connected" event until we have uploaded them to
|
|
2715
2790
|
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
2716
|
-
const connecting =
|
|
2791
|
+
const connecting = canSendOps && !this.canSendOps;
|
|
2717
2792
|
if (connecting && this.blobManager.hasPendingStashedUploads()) {
|
|
2718
2793
|
assert(
|
|
2719
2794
|
!this.delayConnectClientId,
|
|
@@ -2724,10 +2799,15 @@ export class ContainerRuntime
|
|
|
2724
2799
|
return;
|
|
2725
2800
|
}
|
|
2726
2801
|
|
|
2727
|
-
this.setConnectionStateCore(
|
|
2802
|
+
this.setConnectionStateCore(canSendOps, clientId);
|
|
2728
2803
|
}
|
|
2729
2804
|
|
|
2730
|
-
|
|
2805
|
+
/**
|
|
2806
|
+
* Raises and propagates connected events.
|
|
2807
|
+
* @param canSendOps - Indicates whether the container can send ops or not (connected and not readonly).
|
|
2808
|
+
* @remarks The connection state from container context used here when raising connected events.
|
|
2809
|
+
*/
|
|
2810
|
+
private setConnectionStateCore(canSendOps: boolean, clientId?: string): void {
|
|
2731
2811
|
assert(
|
|
2732
2812
|
!this.delayConnectClientId,
|
|
2733
2813
|
0x394 /* connect event delay must be cleared before propagating connect event */,
|
|
@@ -2735,24 +2815,24 @@ export class ContainerRuntime
|
|
|
2735
2815
|
this.verifyNotClosed();
|
|
2736
2816
|
|
|
2737
2817
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
2738
|
-
const
|
|
2739
|
-
const reconnection =
|
|
2818
|
+
const canSendOpsChanged = this.canSendOps !== canSendOps;
|
|
2819
|
+
const reconnection = canSendOpsChanged && !canSendOps;
|
|
2740
2820
|
|
|
2741
2821
|
// We need to flush the ops currently collected by Outbox to preserve original order.
|
|
2742
2822
|
// This flush NEEDS to happen before we set the ContainerRuntime to "connected".
|
|
2743
2823
|
// We want these ops to get to the PendingStateManager without sending to service and have them return to the Outbox upon calling "replayPendingStates".
|
|
2744
|
-
if (
|
|
2824
|
+
if (canSendOpsChanged && canSendOps) {
|
|
2745
2825
|
this.flush();
|
|
2746
2826
|
}
|
|
2747
2827
|
|
|
2748
|
-
this.
|
|
2828
|
+
this.canSendOps = canSendOps;
|
|
2749
2829
|
|
|
2750
|
-
if (
|
|
2830
|
+
if (canSendOps) {
|
|
2751
2831
|
assert(
|
|
2752
2832
|
this.attachState === AttachState.Attached,
|
|
2753
2833
|
0x3cd /* Connection is possible only if container exists in storage */,
|
|
2754
2834
|
);
|
|
2755
|
-
if (
|
|
2835
|
+
if (canSendOpsChanged) {
|
|
2756
2836
|
this.signalTelemetryManager.resetTracking();
|
|
2757
2837
|
}
|
|
2758
2838
|
}
|
|
@@ -2778,14 +2858,14 @@ export class ContainerRuntime
|
|
|
2778
2858
|
}
|
|
2779
2859
|
}
|
|
2780
2860
|
|
|
2781
|
-
if (
|
|
2861
|
+
if (canSendOpsChanged) {
|
|
2782
2862
|
this.replayPendingStates();
|
|
2783
2863
|
}
|
|
2784
2864
|
|
|
2785
|
-
this.channelCollection.setConnectionState(
|
|
2786
|
-
this.garbageCollector.setConnectionState(
|
|
2865
|
+
this.channelCollection.setConnectionState(canSendOps, clientId);
|
|
2866
|
+
this.garbageCollector.setConnectionState(canSendOps, clientId);
|
|
2787
2867
|
|
|
2788
|
-
raiseConnectedEvent(this.mc.logger, this, connected
|
|
2868
|
+
raiseConnectedEvent(this.mc.logger, this, this.connected /* canSendOps */, clientId);
|
|
2789
2869
|
}
|
|
2790
2870
|
|
|
2791
2871
|
public async notifyOpReplay(message: ISequencedDocumentMessage): Promise<void> {
|
|
@@ -3161,7 +3241,7 @@ export class ContainerRuntime
|
|
|
3161
3241
|
}
|
|
3162
3242
|
case ContainerMessageType.DocumentSchemaChange: {
|
|
3163
3243
|
this.documentsSchemaController.processDocumentSchemaMessages(
|
|
3164
|
-
contents as
|
|
3244
|
+
contents as IDocumentSchemaChangeMessageIncoming[],
|
|
3165
3245
|
local,
|
|
3166
3246
|
message.sequenceNumber,
|
|
3167
3247
|
);
|
|
@@ -3334,7 +3414,8 @@ export class ContainerRuntime
|
|
|
3334
3414
|
// This will throw and close the container if rollback fails
|
|
3335
3415
|
try {
|
|
3336
3416
|
checkpoint.rollback((message: LocalBatchMessage) =>
|
|
3337
|
-
|
|
3417
|
+
// These changes are staged since we entered staging mode above
|
|
3418
|
+
this.rollbackStagedChanges(message.runtimeOp, message.localOpMetadata),
|
|
3338
3419
|
);
|
|
3339
3420
|
this.updateDocumentDirtyState();
|
|
3340
3421
|
stageControls?.discardChanges();
|
|
@@ -3400,25 +3481,35 @@ export class ContainerRuntime
|
|
|
3400
3481
|
// eslint-disable-next-line import/no-deprecated
|
|
3401
3482
|
public enterStagingMode = (): StageControlsExperimental => {
|
|
3402
3483
|
if (this.stageControls !== undefined) {
|
|
3403
|
-
throw new
|
|
3484
|
+
throw new UsageError("already in staging mode");
|
|
3485
|
+
}
|
|
3486
|
+
if (this.attachState === AttachState.Detached) {
|
|
3487
|
+
throw new UsageError("cannot enter staging mode while detached");
|
|
3404
3488
|
}
|
|
3405
3489
|
|
|
3406
|
-
// Make sure
|
|
3490
|
+
// Make sure Outbox is empty before entering staging mode,
|
|
3407
3491
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
3408
|
-
this.
|
|
3492
|
+
this.flush();
|
|
3409
3493
|
|
|
3410
3494
|
const exitStagingMode = (discardOrCommit: () => void) => (): void => {
|
|
3411
|
-
|
|
3412
|
-
|
|
3495
|
+
try {
|
|
3496
|
+
// Final flush of any last staged changes
|
|
3497
|
+
// NOTE: We can't use this.flush() here, because orderSequentially uses StagingMode and in the rollback case we'll hit assert 0x24c
|
|
3498
|
+
this.outbox.flush();
|
|
3413
3499
|
|
|
3414
|
-
|
|
3500
|
+
this.stageControls = undefined;
|
|
3415
3501
|
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3502
|
+
// During Staging Mode, we avoid submitting any ID Allocation ops (apart from resubmitting pre-staging ops).
|
|
3503
|
+
// Now that we've exited, we need to submit an ID Allocation op for any IDs that were generated while in Staging Mode.
|
|
3504
|
+
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
3505
|
+
discardOrCommit();
|
|
3420
3506
|
|
|
3421
|
-
|
|
3507
|
+
this.channelCollection.notifyStagingMode(false);
|
|
3508
|
+
} catch (error) {
|
|
3509
|
+
const normalizedError = normalizeError(error);
|
|
3510
|
+
this.closeFn(normalizedError);
|
|
3511
|
+
throw normalizedError;
|
|
3512
|
+
}
|
|
3422
3513
|
};
|
|
3423
3514
|
|
|
3424
3515
|
// eslint-disable-next-line import/no-deprecated
|
|
@@ -3430,7 +3521,7 @@ export class ContainerRuntime
|
|
|
3430
3521
|
runtimeOp !== undefined,
|
|
3431
3522
|
0xb82 /* Staged batches expected to have runtimeOp defined */,
|
|
3432
3523
|
);
|
|
3433
|
-
this.
|
|
3524
|
+
this.rollbackStagedChanges(runtimeOp, localOpMetadata);
|
|
3434
3525
|
});
|
|
3435
3526
|
this.updateDocumentDirtyState();
|
|
3436
3527
|
}),
|
|
@@ -3514,7 +3605,7 @@ export class ContainerRuntime
|
|
|
3514
3605
|
);
|
|
3515
3606
|
}
|
|
3516
3607
|
|
|
3517
|
-
private
|
|
3608
|
+
private shouldSendOps(): boolean {
|
|
3518
3609
|
// Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
|
|
3519
3610
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
3520
3611
|
return (
|
|
@@ -4479,6 +4570,11 @@ export class ContainerRuntime
|
|
|
4479
4570
|
// If we're resubmitting a batch, keep the same "staged" value as before. Otherwise, use the current "global" state.
|
|
4480
4571
|
const staged = this.batchRunner.resubmitInfo?.staged ?? this.inStagingMode;
|
|
4481
4572
|
|
|
4573
|
+
assert(
|
|
4574
|
+
!staged || canStageMessageOfType(type),
|
|
4575
|
+
0xbba /* Unexpected message type submitted in Staging Mode */,
|
|
4576
|
+
);
|
|
4577
|
+
|
|
4482
4578
|
// Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
|
|
4483
4579
|
if (!staged) {
|
|
4484
4580
|
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
@@ -4487,7 +4583,7 @@ export class ContainerRuntime
|
|
|
4487
4583
|
// Allow document schema controller to send a message if it needs to propose change in document schema.
|
|
4488
4584
|
// If it needs to send a message, it will call provided callback with payload of such message and rely
|
|
4489
4585
|
// on this callback to do actual sending.
|
|
4490
|
-
const schemaChangeMessage = this.documentsSchemaController.
|
|
4586
|
+
const schemaChangeMessage = this.documentsSchemaController.maybeGenerateSchemaMessage();
|
|
4491
4587
|
if (schemaChangeMessage) {
|
|
4492
4588
|
this.mc.logger.sendTelemetryEvent({
|
|
4493
4589
|
eventName: "SchemaChangeProposal",
|
|
@@ -4496,8 +4592,9 @@ export class ContainerRuntime
|
|
|
4496
4592
|
newRuntimeSchema: JSON.stringify(schemaChangeMessage.runtime),
|
|
4497
4593
|
sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
|
|
4498
4594
|
oldRuntimeSchema: JSON.stringify(this.metadata?.documentSchema?.runtime),
|
|
4595
|
+
minVersionForCollab: schemaChangeMessage.info?.minVersionForCollab,
|
|
4499
4596
|
});
|
|
4500
|
-
const msg:
|
|
4597
|
+
const msg: OutboundContainerRuntimeDocumentSchemaMessage = {
|
|
4501
4598
|
type: ContainerMessageType.DocumentSchemaChange,
|
|
4502
4599
|
contents: schemaChangeMessage,
|
|
4503
4600
|
};
|
|
@@ -4601,14 +4698,25 @@ export class ContainerRuntime
|
|
|
4601
4698
|
|
|
4602
4699
|
/**
|
|
4603
4700
|
* Resubmits each message in the batch, and then flushes the outbox.
|
|
4701
|
+
* This typically happens when we reconnect and there are pending messages.
|
|
4702
|
+
*
|
|
4703
|
+
* @remarks
|
|
4704
|
+
* Attempting to resubmit a batch that has been successfully sequenced will not happen due to
|
|
4705
|
+
* checks in the ConnectionStateHandler (Loader layer)
|
|
4604
4706
|
*
|
|
4605
|
-
*
|
|
4707
|
+
* The only exception to this would be if the Container "forks" due to misuse of the "Offline Load" feature.
|
|
4708
|
+
* If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
|
|
4606
4709
|
* for correlation to detect container forking.
|
|
4607
4710
|
*/
|
|
4608
4711
|
private reSubmitBatch(
|
|
4609
4712
|
batch: PendingMessageResubmitData[],
|
|
4610
4713
|
{ batchId, staged, squash }: PendingBatchResubmitMetadata,
|
|
4611
4714
|
): void {
|
|
4715
|
+
assert(
|
|
4716
|
+
this._summarizer === undefined,
|
|
4717
|
+
0x8f2 /* Summarizer never reconnects so should never resubmit */,
|
|
4718
|
+
);
|
|
4719
|
+
|
|
4612
4720
|
const resubmitInfo = {
|
|
4613
4721
|
// Only include Batch ID if "Offline Load" feature is enabled
|
|
4614
4722
|
// It's only needed to identify batches across container forks arising from misuse of offline load.
|
|
@@ -4616,36 +4724,61 @@ export class ContainerRuntime
|
|
|
4616
4724
|
staged,
|
|
4617
4725
|
};
|
|
4618
4726
|
|
|
4727
|
+
const resubmitFn = squash
|
|
4728
|
+
? this.reSubmitWithSquashing.bind(this)
|
|
4729
|
+
: this.reSubmit.bind(this);
|
|
4730
|
+
|
|
4619
4731
|
this.batchRunner.run(() => {
|
|
4620
4732
|
for (const message of batch) {
|
|
4621
|
-
|
|
4733
|
+
resubmitFn(message);
|
|
4622
4734
|
}
|
|
4623
4735
|
}, resubmitInfo);
|
|
4624
4736
|
|
|
4625
4737
|
this.flush(resubmitInfo);
|
|
4626
4738
|
}
|
|
4627
4739
|
|
|
4628
|
-
private reSubmit(message: PendingMessageResubmitData, squash: boolean): void {
|
|
4629
|
-
this.reSubmitCore(message.runtimeOp, message.localOpMetadata, message.opMetadata, squash);
|
|
4630
|
-
}
|
|
4631
|
-
|
|
4632
4740
|
/**
|
|
4633
|
-
*
|
|
4634
|
-
*
|
|
4635
|
-
* ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)
|
|
4636
|
-
* @param message - The original LocalContainerRuntimeMessage.
|
|
4637
|
-
* @param localOpMetadata - The local metadata associated with the original message.
|
|
4741
|
+
* Resubmit the given message as part of a squash rebase upon exiting Staging Mode.
|
|
4742
|
+
* How exactly to resubmit the message is up to the subsystem that submitted the op to begin with.
|
|
4638
4743
|
*/
|
|
4639
|
-
private
|
|
4640
|
-
message
|
|
4641
|
-
localOpMetadata: unknown,
|
|
4642
|
-
opMetadata: Record<string, unknown> | undefined,
|
|
4643
|
-
squash: boolean,
|
|
4644
|
-
): void {
|
|
4744
|
+
private reSubmitWithSquashing(resubmitData: PendingMessageResubmitData): void {
|
|
4745
|
+
const message = resubmitData.runtimeOp;
|
|
4645
4746
|
assert(
|
|
4646
|
-
|
|
4647
|
-
|
|
4747
|
+
canStageMessageOfType(message.type),
|
|
4748
|
+
0xbbb /* Expected message type to be compatible with staging */,
|
|
4648
4749
|
);
|
|
4750
|
+
switch (message.type) {
|
|
4751
|
+
case ContainerMessageType.FluidDataStoreOp: {
|
|
4752
|
+
this.channelCollection.reSubmit(
|
|
4753
|
+
message.type,
|
|
4754
|
+
message.contents,
|
|
4755
|
+
resubmitData.localOpMetadata,
|
|
4756
|
+
/* squash: */ true,
|
|
4757
|
+
);
|
|
4758
|
+
break;
|
|
4759
|
+
}
|
|
4760
|
+
// NOTE: Squash doesn't apply to GC or DocumentSchemaChange ops, fallback to typical resubmit logic.
|
|
4761
|
+
case ContainerMessageType.GC:
|
|
4762
|
+
case ContainerMessageType.DocumentSchemaChange: {
|
|
4763
|
+
this.reSubmit(resubmitData);
|
|
4764
|
+
break;
|
|
4765
|
+
}
|
|
4766
|
+
default: {
|
|
4767
|
+
unreachableCase(message.type);
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
|
|
4772
|
+
/**
|
|
4773
|
+
* Resubmit the given message which was previously submitted to the ContainerRuntime but not successfully
|
|
4774
|
+
* transmitted to the ordering service (e.g. due to a disconnect, or being in Staging Mode)
|
|
4775
|
+
* How to resubmit is up to the subsystem that submitted the op to begin with
|
|
4776
|
+
*/
|
|
4777
|
+
private reSubmit({
|
|
4778
|
+
runtimeOp: message,
|
|
4779
|
+
localOpMetadata,
|
|
4780
|
+
opMetadata,
|
|
4781
|
+
}: PendingMessageResubmitData): void {
|
|
4649
4782
|
switch (message.type) {
|
|
4650
4783
|
case ContainerMessageType.FluidDataStoreOp:
|
|
4651
4784
|
case ContainerMessageType.Attach:
|
|
@@ -4656,7 +4789,7 @@ export class ContainerRuntime
|
|
|
4656
4789
|
message.type,
|
|
4657
4790
|
message.contents,
|
|
4658
4791
|
localOpMetadata,
|
|
4659
|
-
squash,
|
|
4792
|
+
/* squash: */ false,
|
|
4660
4793
|
);
|
|
4661
4794
|
break;
|
|
4662
4795
|
}
|
|
@@ -4683,9 +4816,9 @@ export class ContainerRuntime
|
|
|
4683
4816
|
break;
|
|
4684
4817
|
}
|
|
4685
4818
|
case ContainerMessageType.DocumentSchemaChange: {
|
|
4686
|
-
//
|
|
4687
|
-
//
|
|
4688
|
-
|
|
4819
|
+
// We shouldn't directly resubmit due to Compare-And-Swap semantics.
|
|
4820
|
+
// If needed it will be generated from scratch before other ops are submitted.
|
|
4821
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
4689
4822
|
break;
|
|
4690
4823
|
}
|
|
4691
4824
|
default: {
|
|
@@ -4696,8 +4829,15 @@ export class ContainerRuntime
|
|
|
4696
4829
|
}
|
|
4697
4830
|
}
|
|
4698
4831
|
|
|
4699
|
-
|
|
4700
|
-
|
|
4832
|
+
/**
|
|
4833
|
+
* Rollback the given op which was only staged but not yet submitted.
|
|
4834
|
+
*/
|
|
4835
|
+
private rollbackStagedChanges(
|
|
4836
|
+
{ type, contents }: LocalContainerRuntimeMessage,
|
|
4837
|
+
localOpMetadata: unknown,
|
|
4838
|
+
): void {
|
|
4839
|
+
assert(canStageMessageOfType(type), 0xbbc /* Unexpected message type to be rolled back */);
|
|
4840
|
+
|
|
4701
4841
|
switch (type) {
|
|
4702
4842
|
case ContainerMessageType.FluidDataStoreOp: {
|
|
4703
4843
|
// For operations, call rollbackDataStoreOp which will find the right store
|
|
@@ -4705,8 +4845,24 @@ export class ContainerRuntime
|
|
|
4705
4845
|
this.channelCollection.rollback(type, contents, localOpMetadata);
|
|
4706
4846
|
break;
|
|
4707
4847
|
}
|
|
4848
|
+
case ContainerMessageType.GC: {
|
|
4849
|
+
// Just drop it, but log an error, this is not expected and not ideal, but not critical failure either.
|
|
4850
|
+
// Currently the only expected type here is TombstoneLoaded, which will have been preceded by one of these events as well:
|
|
4851
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_SubDataStore_Requested, GC_Tombstone_Blob_Requested
|
|
4852
|
+
this.mc.logger.sendErrorEvent({
|
|
4853
|
+
eventName: "GC_OpDiscarded",
|
|
4854
|
+
details: { subType: contents.type },
|
|
4855
|
+
});
|
|
4856
|
+
break;
|
|
4857
|
+
}
|
|
4858
|
+
case ContainerMessageType.DocumentSchemaChange: {
|
|
4859
|
+
// Notify the document schema controller that the pending op was not acked.
|
|
4860
|
+
// This will allow it to propose the schema change again if needed.
|
|
4861
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
4862
|
+
break;
|
|
4863
|
+
}
|
|
4708
4864
|
default: {
|
|
4709
|
-
|
|
4865
|
+
unreachableCase(type);
|
|
4710
4866
|
}
|
|
4711
4867
|
}
|
|
4712
4868
|
}
|
|
@@ -4986,6 +5142,7 @@ export class ContainerRuntime
|
|
|
4986
5142
|
},
|
|
4987
5143
|
getQuorum: this.getQuorum.bind(this),
|
|
4988
5144
|
getAudience: this.getAudience.bind(this),
|
|
5145
|
+
supportedFeatures: this.ILayerCompatDetails.supportedFeatures,
|
|
4989
5146
|
} satisfies ExtensionHost<TRuntimeProperties>;
|
|
4990
5147
|
entry = new factory(runtime, ...useContext);
|
|
4991
5148
|
this.extensions.set(id, entry);
|