@fluidframework/container-runtime 2.90.0 → 2.92.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 +8 -0
- package/api-report/container-runtime.legacy.beta.api.md +2 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/containerCompatibility.d.ts +1 -1
- package/dist/containerCompatibility.d.ts.map +1 -1
- package/dist/containerCompatibility.js.map +1 -1
- package/dist/containerRuntime.d.ts +37 -10
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +105 -77
- package/dist/containerRuntime.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +1 -0
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +3 -8
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +4 -0
- 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 +2 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +2 -1
- package/dist/opLifecycle/batchManager.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 +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +6 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +11 -2
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSerialization.d.ts +3 -1
- package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
- package/dist/opLifecycle/opSerialization.js +11 -9
- package/dist/opLifecycle/opSerialization.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +0 -6
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +2 -9
- package/dist/opLifecycle/outbox.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 +7 -3
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +19 -7
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/runtimeLayerCompatState.d.ts +1 -1
- package/dist/summary/documentSchema.d.ts +9 -3
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +19 -3
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/orderedClientElection.js +2 -2
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/summaryManager.d.ts +9 -0
- package/dist/summary/summaryManager.d.ts.map +1 -1
- package/dist/summary/summaryManager.js +29 -0
- package/dist/summary/summaryManager.js.map +1 -1
- package/internal.d.ts +1 -1
- package/legacy.d.ts +1 -1
- package/lib/containerCompatibility.d.ts +1 -1
- package/lib/containerCompatibility.d.ts.map +1 -1
- package/lib/containerCompatibility.js.map +1 -1
- package/lib/containerRuntime.d.ts +37 -10
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +106 -79
- package/lib/containerRuntime.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +1 -0
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +3 -8
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +4 -0
- 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 +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +2 -1
- package/lib/opLifecycle/batchManager.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 +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +6 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +10 -1
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSerialization.d.ts +3 -1
- package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
- package/lib/opLifecycle/opSerialization.js +11 -9
- package/lib/opLifecycle/opSerialization.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +0 -6
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +2 -9
- package/lib/opLifecycle/outbox.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 +7 -3
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +19 -7
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/runtimeLayerCompatState.d.ts +1 -1
- package/lib/summary/documentSchema.d.ts +9 -3
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +19 -3
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/orderedClientElection.js +2 -2
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/summaryManager.d.ts +9 -0
- package/lib/summary/summaryManager.d.ts.map +1 -1
- package/lib/summary/summaryManager.js +29 -0
- package/lib/summary/summaryManager.js.map +1 -1
- package/package.json +28 -24
- package/src/containerCompatibility.ts +2 -0
- package/src/containerRuntime.ts +153 -93
- package/src/gc/garbageCollection.ts +4 -9
- package/src/gc/gcDefinitions.ts +4 -0
- package/src/index.ts +1 -0
- package/src/opLifecycle/batchManager.ts +2 -1
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/opGroupingManager.ts +11 -1
- package/src/opLifecycle/opSerialization.ts +14 -12
- package/src/opLifecycle/outbox.ts +2 -17
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +27 -11
- package/src/summary/documentSchema.ts +25 -2
- package/src/summary/orderedClientElection.ts +2 -2
- package/src/summary/summaryManager.ts +32 -0
package/src/containerRuntime.ts
CHANGED
|
@@ -95,6 +95,7 @@ import { FetchSource, MessageType } from "@fluidframework/driver-definitions/int
|
|
|
95
95
|
import { readAndParse } from "@fluidframework/driver-utils/internal";
|
|
96
96
|
import type { IIdCompressor } from "@fluidframework/id-compressor";
|
|
97
97
|
import type {
|
|
98
|
+
// eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
|
|
98
99
|
IIdCompressorCore,
|
|
99
100
|
IdCreationRange,
|
|
100
101
|
SerializedIdCompressorWithNoSession,
|
|
@@ -107,7 +108,6 @@ import {
|
|
|
107
108
|
} from "@fluidframework/id-compressor/internal";
|
|
108
109
|
import {
|
|
109
110
|
FlushMode,
|
|
110
|
-
FlushModeExperimental,
|
|
111
111
|
channelsTreeName,
|
|
112
112
|
gcTreeKey,
|
|
113
113
|
} from "@fluidframework/runtime-definitions/internal";
|
|
@@ -131,6 +131,7 @@ import type {
|
|
|
131
131
|
IContainerRuntimeBaseInternal,
|
|
132
132
|
MinimumVersionForCollab,
|
|
133
133
|
ContainerExtensionExpectations,
|
|
134
|
+
ContainerRuntimeBaseAlpha,
|
|
134
135
|
} from "@fluidframework/runtime-definitions/internal";
|
|
135
136
|
import {
|
|
136
137
|
addBlobToSummary,
|
|
@@ -220,7 +221,6 @@ import {
|
|
|
220
221
|
type IGCRuntimeOptions,
|
|
221
222
|
type IGCStats,
|
|
222
223
|
type IGarbageCollector,
|
|
223
|
-
gcGenerationOptionName,
|
|
224
224
|
type GarbageCollectionMessage,
|
|
225
225
|
type IGarbageCollectionRuntime,
|
|
226
226
|
} from "./gc/index.js";
|
|
@@ -244,6 +244,7 @@ import {
|
|
|
244
244
|
DuplicateBatchDetector,
|
|
245
245
|
ensureContentsDeserialized,
|
|
246
246
|
type IBatchCheckpoint,
|
|
247
|
+
largeBatchThreshold,
|
|
247
248
|
OpCompressor,
|
|
248
249
|
OpDecompressor,
|
|
249
250
|
OpGroupingManager,
|
|
@@ -259,6 +260,7 @@ import {
|
|
|
259
260
|
type IPendingLocalState,
|
|
260
261
|
PendingStateManager,
|
|
261
262
|
type PendingBatchResubmitMetadata,
|
|
263
|
+
type IPendingMessage,
|
|
262
264
|
} from "./pendingStateManager.js";
|
|
263
265
|
import { BatchRunCounter, RunCounter } from "./runCounter.js";
|
|
264
266
|
import {
|
|
@@ -476,6 +478,25 @@ export interface ContainerRuntimeOptions {
|
|
|
476
478
|
* When enabled (`true`), createBlob will return a handle before the blob upload completes.
|
|
477
479
|
*/
|
|
478
480
|
readonly createBlobPayloadPending: true | undefined;
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Controls automatic batch flushing during staging mode.
|
|
484
|
+
* Normal turn-based/async flush scheduling is suppressed while in staging mode
|
|
485
|
+
* until the accumulated batch reaches this many ops, at which point the batch
|
|
486
|
+
* is flushed. Incoming ops always break the current batch regardless of this setting.
|
|
487
|
+
*
|
|
488
|
+
* Set to Infinity to only break batches on system events (incoming ops).
|
|
489
|
+
*
|
|
490
|
+
* @defaultValue `largeBatchThreshold` (currently 1000)
|
|
491
|
+
*/
|
|
492
|
+
readonly stagingModeAutoFlushThreshold: number;
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* When this property is set to true, the runtime will never send DocumentSchemaChange ops
|
|
496
|
+
* and will throw an error if any incoming DocumentSchemaChange ops are received.
|
|
497
|
+
* This effectively freezes the document schema at whatever state it was in when the document was created.
|
|
498
|
+
*/
|
|
499
|
+
readonly disableSchemaUpgrade: boolean;
|
|
479
500
|
}
|
|
480
501
|
|
|
481
502
|
/**
|
|
@@ -608,6 +629,16 @@ const defaultMaxBatchSizeInBytes = 700 * 1024;
|
|
|
608
629
|
|
|
609
630
|
const defaultChunkSizeInBytes = 204800;
|
|
610
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Default maximum ops per staging-mode batch before automatic flush scheduling resumes.
|
|
634
|
+
*
|
|
635
|
+
* Chosen based on production telemetry: copy-paste operations routinely produce batches
|
|
636
|
+
* of 1000+ ops (435K instances over 30 days), and receivers on modern Fluid versions
|
|
637
|
+
* handle them without issues. Uses {@link largeBatchThreshold} to stay aligned with
|
|
638
|
+
* the existing "large batch" telemetry threshold ({@link OpGroupingManager}).
|
|
639
|
+
*/
|
|
640
|
+
const defaultStagingModeAutoFlushThreshold = largeBatchThreshold;
|
|
641
|
+
|
|
611
642
|
/**
|
|
612
643
|
* The default time to wait for pending ops to be processed during summarization
|
|
613
644
|
*/
|
|
@@ -805,6 +836,24 @@ export async function loadContainerRuntime(
|
|
|
805
836
|
return ContainerRuntime.loadRuntime(params);
|
|
806
837
|
}
|
|
807
838
|
|
|
839
|
+
/**
|
|
840
|
+
* Alpha variant of {@link loadContainerRuntime} that returns the runtime in an
|
|
841
|
+
* extendable object, allowing additional properties to be added in the future.
|
|
842
|
+
*
|
|
843
|
+
* @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
|
|
844
|
+
* @returns An object containing the runtime.
|
|
845
|
+
*
|
|
846
|
+
* @legacy @alpha
|
|
847
|
+
*/
|
|
848
|
+
export async function loadContainerRuntimeAlpha(params: LoadContainerRuntimeParams): Promise<{
|
|
849
|
+
runtime: IContainerRuntime & ContainerRuntimeBaseAlpha & IRuntime;
|
|
850
|
+
}> {
|
|
851
|
+
return ContainerRuntime.loadRuntime2({
|
|
852
|
+
...params,
|
|
853
|
+
registry: new FluidDataStoreRegistry(params.registryEntries),
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
808
857
|
const defaultMaxConsecutiveReconnects = 7;
|
|
809
858
|
|
|
810
859
|
/**
|
|
@@ -880,14 +929,15 @@ export class ContainerRuntime
|
|
|
880
929
|
return ContainerRuntime.loadRuntime2({
|
|
881
930
|
...params,
|
|
882
931
|
registry: new FluidDataStoreRegistry(params.registryEntries),
|
|
883
|
-
});
|
|
932
|
+
}).then((r) => r.runtime);
|
|
884
933
|
}
|
|
885
934
|
|
|
886
935
|
/**
|
|
887
|
-
* Load the stores from a snapshot and returns the runtime.
|
|
936
|
+
* Load the stores from a snapshot and returns an object containing the runtime.
|
|
888
937
|
* @remarks
|
|
889
938
|
* Same as {@link ContainerRuntime.loadRuntime},
|
|
890
939
|
* but with `registry` instead of `registryEntries` and more `runtimeOptions`.
|
|
940
|
+
* Returns `{ runtime }` to allow future extensions (e.g. staging mode controls).
|
|
891
941
|
*/
|
|
892
942
|
public static async loadRuntime2(
|
|
893
943
|
params: Omit<LoadContainerRuntimeParams, "registryEntries" | "runtimeOptions"> & {
|
|
@@ -906,7 +956,7 @@ export class ContainerRuntime
|
|
|
906
956
|
*/
|
|
907
957
|
runtimeOptions?: IContainerRuntimeOptionsInternal;
|
|
908
958
|
},
|
|
909
|
-
): Promise<ContainerRuntime> {
|
|
959
|
+
): Promise<{ runtime: ContainerRuntime }> {
|
|
910
960
|
const {
|
|
911
961
|
context,
|
|
912
962
|
registry,
|
|
@@ -962,6 +1012,8 @@ export class ContainerRuntime
|
|
|
962
1012
|
loadSequenceNumberVerification: "close",
|
|
963
1013
|
maxBatchSizeInBytes: defaultMaxBatchSizeInBytes,
|
|
964
1014
|
chunkSizeInBytes: defaultChunkSizeInBytes,
|
|
1015
|
+
stagingModeAutoFlushThreshold: defaultStagingModeAutoFlushThreshold,
|
|
1016
|
+
disableSchemaUpgrade: false,
|
|
965
1017
|
};
|
|
966
1018
|
|
|
967
1019
|
const defaultConfigs = {
|
|
@@ -987,6 +1039,8 @@ export class ContainerRuntime
|
|
|
987
1039
|
? disabledCompressionConfig
|
|
988
1040
|
: defaultConfigs.compressionOptions,
|
|
989
1041
|
createBlobPayloadPending = defaultConfigs.createBlobPayloadPending,
|
|
1042
|
+
stagingModeAutoFlushThreshold = defaultConfigs.stagingModeAutoFlushThreshold,
|
|
1043
|
+
disableSchemaUpgrade = defaultConfigs.disableSchemaUpgrade,
|
|
990
1044
|
}: IContainerRuntimeOptionsInternal = runtimeOptions;
|
|
991
1045
|
|
|
992
1046
|
// If explicitSchemaControl is off, ensure that options which require explicitSchemaControl are not enabled.
|
|
@@ -1131,6 +1185,7 @@ export class ContainerRuntime
|
|
|
1131
1185
|
idCompressorMode = desiredIdCompressorMode;
|
|
1132
1186
|
}
|
|
1133
1187
|
|
|
1188
|
+
// eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
|
|
1134
1189
|
const createIdCompressorFn = (): IIdCompressor & IIdCompressorCore => {
|
|
1135
1190
|
/**
|
|
1136
1191
|
* Because the IdCompressor emits so much telemetry, this function is used to sample
|
|
@@ -1185,6 +1240,7 @@ export class ContainerRuntime
|
|
|
1185
1240
|
},
|
|
1186
1241
|
{ minVersionForCollab },
|
|
1187
1242
|
logger,
|
|
1243
|
+
disableSchemaUpgrade,
|
|
1188
1244
|
);
|
|
1189
1245
|
|
|
1190
1246
|
// If the minVersionForCollab for this client is greater than the existing one, we should use that one going forward.
|
|
@@ -1215,6 +1271,8 @@ export class ContainerRuntime
|
|
|
1215
1271
|
enableGroupedBatching,
|
|
1216
1272
|
explicitSchemaControl,
|
|
1217
1273
|
createBlobPayloadPending,
|
|
1274
|
+
stagingModeAutoFlushThreshold,
|
|
1275
|
+
disableSchemaUpgrade,
|
|
1218
1276
|
};
|
|
1219
1277
|
|
|
1220
1278
|
validateMinimumVersionForCollab(updatedMinVersionForCollab);
|
|
@@ -1250,7 +1308,7 @@ export class ContainerRuntime
|
|
|
1250
1308
|
// or zero. This must be done before Container replays saved ops.
|
|
1251
1309
|
await runtime.pendingStateManager.applyStashedOpsAt(runtimeSequenceNumber ?? 0);
|
|
1252
1310
|
|
|
1253
|
-
return runtime;
|
|
1311
|
+
return { runtime };
|
|
1254
1312
|
}
|
|
1255
1313
|
|
|
1256
1314
|
public readonly options: Record<string | number, unknown>;
|
|
@@ -1320,6 +1378,7 @@ export class ContainerRuntime
|
|
|
1320
1378
|
return this.documentsSchemaController.sessionSchema.runtime;
|
|
1321
1379
|
}
|
|
1322
1380
|
|
|
1381
|
+
// eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
|
|
1323
1382
|
private _idCompressor: (IIdCompressor & IIdCompressorCore) | undefined;
|
|
1324
1383
|
|
|
1325
1384
|
// We accumulate Id compressor Ops while Id compressor is not loaded yet (only for "delayed" mode)
|
|
@@ -1335,6 +1394,7 @@ export class ContainerRuntime
|
|
|
1335
1394
|
/**
|
|
1336
1395
|
* {@inheritDoc @fluidframework/runtime-definitions#IContainerRuntimeBase.idCompressor}
|
|
1337
1396
|
*/
|
|
1397
|
+
// eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
|
|
1338
1398
|
public get idCompressor(): (IIdCompressor & IIdCompressorCore) | undefined {
|
|
1339
1399
|
// Expose ID Compressor only if it's On from the start.
|
|
1340
1400
|
// If container uses delayed mode, then we can only expose generateDocumentUniqueId() and nothing else.
|
|
@@ -1393,6 +1453,7 @@ export class ContainerRuntime
|
|
|
1393
1453
|
|
|
1394
1454
|
private readonly batchRunner = new BatchRunCounter();
|
|
1395
1455
|
private readonly _flushMode: FlushMode;
|
|
1456
|
+
private readonly stagingModeAutoFlushThreshold: number;
|
|
1396
1457
|
/**
|
|
1397
1458
|
* BatchId tracking is needed whenever there's a possibility of a "forked Container",
|
|
1398
1459
|
* where the same local state is pending in two different running Containers, each of
|
|
@@ -1534,13 +1595,6 @@ export class ContainerRuntime
|
|
|
1534
1595
|
return runtimeCompatDetailsForLoader;
|
|
1535
1596
|
}
|
|
1536
1597
|
|
|
1537
|
-
/**
|
|
1538
|
-
* If true, will skip Outbox flushing before processing an incoming message (and on DeltaManager "op" event for loader back-compat),
|
|
1539
|
-
* and instead the Outbox will check for a split batch on every submit.
|
|
1540
|
-
* This is a kill-bit switch for this simplification of logic, in case it causes unexpected issues.
|
|
1541
|
-
*/
|
|
1542
|
-
private readonly skipSafetyFlushDuringProcessStack: boolean;
|
|
1543
|
-
|
|
1544
1598
|
private readonly extensions = new Map<ContainerExtensionId, ExtensionEntry>();
|
|
1545
1599
|
|
|
1546
1600
|
/***/
|
|
@@ -1561,6 +1615,7 @@ export class ContainerRuntime
|
|
|
1561
1615
|
|
|
1562
1616
|
blobManagerLoadInfo: IBlobManagerLoadInfo,
|
|
1563
1617
|
private readonly _storage: IContainerStorageService,
|
|
1618
|
+
// eslint-disable-next-line import-x/no-deprecated -- Will be undeprecated in 2.100.0 when it becomes an internal API
|
|
1564
1619
|
private readonly createIdCompressorFn: () => IIdCompressor & IIdCompressorCore,
|
|
1565
1620
|
|
|
1566
1621
|
private readonly documentsSchemaController: DocumentsSchemaController,
|
|
@@ -1598,7 +1653,6 @@ export class ContainerRuntime
|
|
|
1598
1653
|
audience,
|
|
1599
1654
|
signalAudience,
|
|
1600
1655
|
pendingLocalState,
|
|
1601
|
-
supportedFeatures,
|
|
1602
1656
|
snapshotWithContents,
|
|
1603
1657
|
getConnectionState,
|
|
1604
1658
|
} = context;
|
|
@@ -1752,15 +1806,6 @@ export class ContainerRuntime
|
|
|
1752
1806
|
this.getConnectionState() === ConnectionState.CatchingUp
|
|
1753
1807
|
: undefined;
|
|
1754
1808
|
|
|
1755
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1756
|
-
eventName: "GCFeatureMatrix",
|
|
1757
|
-
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
1758
|
-
inputs: JSON.stringify({
|
|
1759
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
1760
|
-
gcOptions_gcGeneration: runtimeOptions.gcOptions[gcGenerationOptionName],
|
|
1761
|
-
}),
|
|
1762
|
-
});
|
|
1763
|
-
|
|
1764
1809
|
this.telemetryDocumentId = metadata?.telemetryDocumentId ?? uuid();
|
|
1765
1810
|
|
|
1766
1811
|
const opGroupingManager = new OpGroupingManager(
|
|
@@ -1833,22 +1878,19 @@ export class ContainerRuntime
|
|
|
1833
1878
|
this.maxConsecutiveReconnects =
|
|
1834
1879
|
this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? defaultMaxConsecutiveReconnects;
|
|
1835
1880
|
|
|
1836
|
-
|
|
1837
|
-
//
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
!referenceSequenceNumbersSupported
|
|
1845
|
-
) {
|
|
1846
|
-
// The loader does not support reference sequence numbers, falling back on FlushMode.TurnBased
|
|
1847
|
-
this.mc.logger.sendErrorEvent({ eventName: "FlushModeFallback" });
|
|
1848
|
-
this._flushMode = FlushMode.TurnBased;
|
|
1849
|
-
} else {
|
|
1850
|
-
this._flushMode = runtimeOptions.flushMode;
|
|
1881
|
+
this._flushMode = runtimeOptions.flushMode;
|
|
1882
|
+
// TODO: Added in 2.90.0 - Remove this validation once we've released and confirmed no consumer passes an invalid flushMode value.
|
|
1883
|
+
if (this._flushMode !== FlushMode.Immediate && this._flushMode !== FlushMode.TurnBased) {
|
|
1884
|
+
const error = new UsageError(
|
|
1885
|
+
"Invalid flushMode runtime option. Expected FlushMode.Immediate or FlushMode.TurnBased.",
|
|
1886
|
+
);
|
|
1887
|
+
this.closeFn(error);
|
|
1888
|
+
throw error;
|
|
1851
1889
|
}
|
|
1890
|
+
this.stagingModeAutoFlushThreshold =
|
|
1891
|
+
this.mc.config.getNumber("Fluid.ContainerRuntime.StagingModeAutoFlushThreshold") ??
|
|
1892
|
+
runtimeOptions.stagingModeAutoFlushThreshold ??
|
|
1893
|
+
defaultStagingModeAutoFlushThreshold;
|
|
1852
1894
|
this.batchIdTrackingEnabled =
|
|
1853
1895
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineFull") ??
|
|
1854
1896
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.enableBatchIdTracking") ??
|
|
@@ -2007,10 +2049,6 @@ export class ContainerRuntime
|
|
|
2007
2049
|
|
|
2008
2050
|
const legacySendBatchFn = makeLegacySendBatchFn(submitFn, this.innerDeltaManager);
|
|
2009
2051
|
|
|
2010
|
-
this.skipSafetyFlushDuringProcessStack =
|
|
2011
|
-
// Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
|
|
2012
|
-
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
2013
|
-
|
|
2014
2052
|
this.outbox = new Outbox({
|
|
2015
2053
|
shouldSend: () => this.shouldSendOps(),
|
|
2016
2054
|
pendingStateManager: this.pendingStateManager,
|
|
@@ -2021,8 +2059,6 @@ export class ContainerRuntime
|
|
|
2021
2059
|
config: {
|
|
2022
2060
|
compressionOptions,
|
|
2023
2061
|
maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
|
|
2024
|
-
// If we disable flush before process, we must be ready to flush partial batches
|
|
2025
|
-
flushPartialBatches: this.skipSafetyFlushDuringProcessStack,
|
|
2026
2062
|
},
|
|
2027
2063
|
logger: this.mc.logger,
|
|
2028
2064
|
groupingManager: opGroupingManager,
|
|
@@ -2079,14 +2115,12 @@ export class ContainerRuntime
|
|
|
2079
2115
|
this.lastEmittedDirty = this.computeCurrentDirtyState();
|
|
2080
2116
|
context.updateDirtyContainerState(this.lastEmittedDirty);
|
|
2081
2117
|
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
this.deltaManager.on("op", () => this.flush());
|
|
2089
|
-
}
|
|
2118
|
+
// Reference Sequence Number may have just changed, and it must be consistent across a batch,
|
|
2119
|
+
// so we should flush now to clear the way for the next ops.
|
|
2120
|
+
// NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
|
|
2121
|
+
// But we need this coverage for old loaders that don't call ContainerRuntime.process for non-runtime messages.
|
|
2122
|
+
// (We have to call flush _before_ processing a runtime op, but after is ok for non-runtime op)
|
|
2123
|
+
this.deltaManager.on("op", () => this.flush());
|
|
2090
2124
|
|
|
2091
2125
|
// logging hardware telemetry
|
|
2092
2126
|
this.baseLogger.send({
|
|
@@ -2102,7 +2136,9 @@ export class ContainerRuntime
|
|
|
2102
2136
|
summaryNumber: loadSummaryNumber,
|
|
2103
2137
|
summaryFormatVersion: metadata?.summaryFormatVersion,
|
|
2104
2138
|
disableIsolatedChannels: metadata?.disableIsolatedChannels,
|
|
2139
|
+
// This is useful even for interactive clients since they track unreferenced nodes and log errors.
|
|
2105
2140
|
gcVersion: metadata?.gcFeature,
|
|
2141
|
+
gcConfigs: this.garbageCollector.serializedConfigs,
|
|
2106
2142
|
options: JSON.stringify(runtimeOptions),
|
|
2107
2143
|
idCompressorModeMetadata: metadata?.documentSchema?.runtime?.idCompressorMode,
|
|
2108
2144
|
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
@@ -2110,7 +2146,6 @@ export class ContainerRuntime
|
|
|
2110
2146
|
featureGates: JSON.stringify({
|
|
2111
2147
|
...featureGatesForTelemetry,
|
|
2112
2148
|
closeSummarizerDelayOverride,
|
|
2113
|
-
disableFlushBeforeProcess: this.skipSafetyFlushDuringProcessStack,
|
|
2114
2149
|
}),
|
|
2115
2150
|
telemetryDocumentId: this.telemetryDocumentId,
|
|
2116
2151
|
groupedBatchingEnabled: this.groupedBatchingEnabled,
|
|
@@ -2732,12 +2767,10 @@ export class ContainerRuntime
|
|
|
2732
2767
|
this.emitDirtyDocumentEvent = false;
|
|
2733
2768
|
|
|
2734
2769
|
try {
|
|
2735
|
-
// Any ID Allocation ops that failed to submit
|
|
2736
|
-
// the
|
|
2737
|
-
//
|
|
2738
|
-
|
|
2739
|
-
this.submitIdAllocationOpIfNeeded({ resubmitOutstandingRanges: true, staged: false });
|
|
2740
|
-
this.scheduleFlush();
|
|
2770
|
+
// Any ID Allocation ops that failed to submit need to have their ranges included
|
|
2771
|
+
// in the next allocation op. Reset the compressor's unfinalized range cursor so that the next
|
|
2772
|
+
// call to takeNextCreationRange (during replay) will include those unfinalized ranges.
|
|
2773
|
+
this._idCompressor?.resetUnfinalizedCreationRange();
|
|
2741
2774
|
|
|
2742
2775
|
// replay the ops
|
|
2743
2776
|
this.pendingStateManager.replayPendingStates();
|
|
@@ -3032,10 +3065,8 @@ export class ContainerRuntime
|
|
|
3032
3065
|
|
|
3033
3066
|
this.verifyNotClosed();
|
|
3034
3067
|
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
this.flush();
|
|
3038
|
-
}
|
|
3068
|
+
// Reference Sequence Number may be about to change, and it must be consistent across a batch, so flush now
|
|
3069
|
+
this.flush();
|
|
3039
3070
|
|
|
3040
3071
|
this.ensureNoDataModelChanges(() => {
|
|
3041
3072
|
this.processInboundMessageOrBatch(messageCopy, local);
|
|
@@ -3639,20 +3670,39 @@ export class ContainerRuntime
|
|
|
3639
3670
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
3640
3671
|
this.flush();
|
|
3641
3672
|
|
|
3642
|
-
const exitStagingMode = (
|
|
3673
|
+
const exitStagingMode = (
|
|
3674
|
+
discardOrCommit: () => IPendingMessage["batchInfo"][],
|
|
3675
|
+
exitMethod: "commit" | "discard",
|
|
3676
|
+
): void => {
|
|
3643
3677
|
try {
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3678
|
+
PerformanceEvent.timedExec(
|
|
3679
|
+
this.mc.logger,
|
|
3680
|
+
{
|
|
3681
|
+
eventName: `ExitStagingMode_${exitMethod}`,
|
|
3682
|
+
},
|
|
3683
|
+
(event) => {
|
|
3684
|
+
// Final flush of any last staged changes
|
|
3685
|
+
// NOTE: We can't use this.flush() here, because orderSequentially uses StagingMode and in the rollback case we'll hit assert 0x24c
|
|
3686
|
+
this.outbox.flush();
|
|
3687
|
+
|
|
3688
|
+
this.stageControls = undefined;
|
|
3689
|
+
|
|
3690
|
+
// During Staging Mode, we avoid submitting any ID Allocation ops (apart from resubmitting pre-staging ops).
|
|
3691
|
+
// Now that we've exited, we need to submit an ID Allocation op for any IDs that were generated while in Staging Mode.
|
|
3692
|
+
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
3693
|
+
const batchInfos = discardOrCommit();
|
|
3694
|
+
event.reportProgress({
|
|
3695
|
+
details: {
|
|
3696
|
+
autoFlushThreshold: this.stagingModeAutoFlushThreshold,
|
|
3697
|
+
batches: batchInfos.length,
|
|
3698
|
+
batchesAtOrOverThreshold: batchInfos.filter(
|
|
3699
|
+
(b) => b.length >= this.stagingModeAutoFlushThreshold,
|
|
3700
|
+
).length,
|
|
3701
|
+
},
|
|
3702
|
+
});
|
|
3703
|
+
this.channelCollection.notifyStagingMode(false);
|
|
3704
|
+
},
|
|
3705
|
+
);
|
|
3656
3706
|
} catch (error) {
|
|
3657
3707
|
const normalizedError = normalizeError(error);
|
|
3658
3708
|
this.closeFn(normalizedError);
|
|
@@ -3664,21 +3714,24 @@ export class ContainerRuntime
|
|
|
3664
3714
|
discardChanges: () =>
|
|
3665
3715
|
exitStagingMode(() => {
|
|
3666
3716
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
3667
|
-
this.pendingStateManager.popStagedBatches(
|
|
3668
|
-
|
|
3669
|
-
|
|
3717
|
+
const batchInfos = this.pendingStateManager.popStagedBatches(
|
|
3718
|
+
({ runtimeOp, localOpMetadata }) => {
|
|
3719
|
+
this.rollbackStagedChange(runtimeOp, localOpMetadata);
|
|
3720
|
+
},
|
|
3721
|
+
);
|
|
3670
3722
|
this.updateDocumentDirtyState();
|
|
3671
|
-
|
|
3723
|
+
return batchInfos;
|
|
3724
|
+
}, "discard"),
|
|
3672
3725
|
commitChanges: (options) => {
|
|
3673
3726
|
const { squash } = { ...defaultStagingCommitOptions, ...options };
|
|
3674
3727
|
exitStagingMode(() => {
|
|
3675
3728
|
// Replay all staged batches in typical FIFO order.
|
|
3676
3729
|
// We'll be out of staging mode so they'll be sent to the service finally.
|
|
3677
|
-
this.pendingStateManager.replayPendingStates({
|
|
3730
|
+
return this.pendingStateManager.replayPendingStates({
|
|
3678
3731
|
committingStagedBatches: true,
|
|
3679
3732
|
squash,
|
|
3680
3733
|
});
|
|
3681
|
-
});
|
|
3734
|
+
}, "commit");
|
|
3682
3735
|
},
|
|
3683
3736
|
};
|
|
3684
3737
|
|
|
@@ -3786,7 +3839,7 @@ export class ContainerRuntime
|
|
|
3786
3839
|
}
|
|
3787
3840
|
|
|
3788
3841
|
/**
|
|
3789
|
-
* Returns true if the container is dirty: not attached, or
|
|
3842
|
+
* Returns true if the container is dirty: not attached, or has pending user messages (ignores "non-dirtyable" ones though)
|
|
3790
3843
|
*/
|
|
3791
3844
|
private computeCurrentDirtyState(): boolean {
|
|
3792
3845
|
return (
|
|
@@ -4809,6 +4862,20 @@ export class ContainerRuntime
|
|
|
4809
4862
|
}
|
|
4810
4863
|
|
|
4811
4864
|
private scheduleFlush(): void {
|
|
4865
|
+
// During staging mode, suppress automatic flush scheduling until the main batch
|
|
4866
|
+
// reaches or exceeds the threshold.
|
|
4867
|
+
// Incoming ops still break the batch via direct this.flush() calls elsewhere
|
|
4868
|
+
// (deltaManager "op" handler, process(), connection changes, getPendingLocalState,
|
|
4869
|
+
// exitStagingMode). Those all bypass scheduleFlush(), so they're unaffected by this check.
|
|
4870
|
+
// Additionally, outbox.maybeFlushPartialBatch() (called on every submit) detects
|
|
4871
|
+
// sequence number changes and throws if unexpected changes are detected.
|
|
4872
|
+
if (
|
|
4873
|
+
this.inStagingMode &&
|
|
4874
|
+
this.outbox.mainBatchMessageCount < this.stagingModeAutoFlushThreshold
|
|
4875
|
+
) {
|
|
4876
|
+
return;
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4812
4879
|
if (this.flushScheduled) {
|
|
4813
4880
|
return;
|
|
4814
4881
|
}
|
|
@@ -4830,15 +4897,6 @@ export class ContainerRuntime
|
|
|
4830
4897
|
break;
|
|
4831
4898
|
}
|
|
4832
4899
|
|
|
4833
|
-
// FlushModeExperimental is experimental and not exposed directly in the runtime APIs
|
|
4834
|
-
case FlushModeExperimental.Async as unknown as FlushMode: {
|
|
4835
|
-
// When in Async flush mode, the runtime will accumulate all operations across JS turns and send them as a single
|
|
4836
|
-
// batch when all micro-tasks are complete.
|
|
4837
|
-
// Compared to TurnBased, this flush mode will capture more ops into the same batch.
|
|
4838
|
-
setTimeout(() => this.flush(), 0);
|
|
4839
|
-
break;
|
|
4840
|
-
}
|
|
4841
|
-
|
|
4842
4900
|
default: {
|
|
4843
4901
|
fail(0x587 /* Unreachable unless manually accumulating a batch */);
|
|
4844
4902
|
}
|
|
@@ -5213,7 +5271,9 @@ export class ContainerRuntime
|
|
|
5213
5271
|
eventName: "getPendingLocalState",
|
|
5214
5272
|
},
|
|
5215
5273
|
(event) => {
|
|
5216
|
-
const pending = this.pendingStateManager.getLocalState(
|
|
5274
|
+
const { pending } = this.pendingStateManager.getLocalState(
|
|
5275
|
+
props?.snapshotSequenceNumber,
|
|
5276
|
+
);
|
|
5217
5277
|
const sessionExpiryTimerStarted =
|
|
5218
5278
|
props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
|
|
5219
5279
|
|
|
@@ -100,6 +100,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
100
100
|
|
|
101
101
|
private readonly configs: IGarbageCollectorConfigs;
|
|
102
102
|
|
|
103
|
+
public get serializedConfigs(): string {
|
|
104
|
+
return JSON.stringify(this.configs);
|
|
105
|
+
}
|
|
106
|
+
|
|
103
107
|
public get shouldRunGC(): boolean {
|
|
104
108
|
return this.configs.gcAllowed;
|
|
105
109
|
}
|
|
@@ -334,15 +338,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
334
338
|
|
|
335
339
|
return { gcData: { gcNodes }, usedRoutes };
|
|
336
340
|
});
|
|
337
|
-
|
|
338
|
-
// Log all the GC options and the state determined by the garbage collector.
|
|
339
|
-
// This is useful even for interactive clients since they track unreferenced nodes and log errors.
|
|
340
|
-
this.mc.logger.sendTelemetryEvent({
|
|
341
|
-
eventName: "GarbageCollectorLoaded",
|
|
342
|
-
gcConfigs: JSON.stringify(this.configs),
|
|
343
|
-
gcOptions: JSON.stringify(createParams.gcOptions),
|
|
344
|
-
...createParams.createContainerMetadata,
|
|
345
|
-
});
|
|
346
341
|
}
|
|
347
342
|
|
|
348
343
|
/**
|
package/src/gc/gcDefinitions.ts
CHANGED
|
@@ -373,6 +373,10 @@ export interface IGarbageCollectionRuntime {
|
|
|
373
373
|
* Defines the contract for the garbage collector.
|
|
374
374
|
*/
|
|
375
375
|
export interface IGarbageCollector {
|
|
376
|
+
/**
|
|
377
|
+
* The GC configurations serialized as a JSON string for telemetry.
|
|
378
|
+
*/
|
|
379
|
+
readonly serializedConfigs: string;
|
|
376
380
|
/**
|
|
377
381
|
* Tells the time at which session expiry timer started in a previous container.
|
|
378
382
|
* This is only set when loading from a stashed container and will be equal to the
|
package/src/index.ts
CHANGED
|
@@ -162,7 +162,8 @@ export class BatchManager {
|
|
|
162
162
|
throw new LoggingError("Ops generated during rollback", {
|
|
163
163
|
count,
|
|
164
164
|
...tagData(TelemetryDataTag.UserData, {
|
|
165
|
-
ops: serializeOp(this.pendingBatch.slice(startPoint).map((b) => b.runtimeOp))
|
|
165
|
+
ops: serializeOp(this.pendingBatch.slice(startPoint).map((b) => b.runtimeOp))
|
|
166
|
+
.content,
|
|
166
167
|
}),
|
|
167
168
|
});
|
|
168
169
|
}
|
package/src/opLifecycle/index.ts
CHANGED
|
@@ -17,6 +17,13 @@ import type {
|
|
|
17
17
|
OutboundSingletonBatch,
|
|
18
18
|
} from "./definitions.js";
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* The number of ops in a batch above which the batch is considered "large"
|
|
22
|
+
* for telemetry purposes. Used by both {@link OpGroupingManager} (GroupLargeBatch event)
|
|
23
|
+
* and as the default staging-mode auto-flush threshold.
|
|
24
|
+
*/
|
|
25
|
+
export const largeBatchThreshold = 1000;
|
|
26
|
+
|
|
20
27
|
/**
|
|
21
28
|
* Grouping makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.
|
|
22
29
|
*/
|
|
@@ -123,7 +130,10 @@ export class OpGroupingManager {
|
|
|
123
130
|
return batch as OutboundSingletonBatch;
|
|
124
131
|
}
|
|
125
132
|
|
|
126
|
-
|
|
133
|
+
// Use > (not >=) so that batches flushed exactly at the staging-mode
|
|
134
|
+
// auto-flush threshold (which defaults to largeBatchThreshold) don't
|
|
135
|
+
// trigger this event. Only genuinely oversized batches are logged.
|
|
136
|
+
if (batch.messages.length > largeBatchThreshold) {
|
|
127
137
|
this.logger.sendTelemetryEvent({
|
|
128
138
|
eventName: "GroupLargeBatch",
|
|
129
139
|
length: batch.messages.length,
|
|
@@ -40,16 +40,18 @@ export function serializeOp(
|
|
|
40
40
|
| EmptyGroupedBatch
|
|
41
41
|
| LocalContainerRuntimeMessage
|
|
42
42
|
| LocalContainerRuntimeMessage[],
|
|
43
|
-
): string {
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
): { content: string } {
|
|
44
|
+
return {
|
|
45
|
+
content: JSON.stringify(
|
|
46
|
+
toSerialize,
|
|
47
|
+
// replacer:
|
|
48
|
+
(key, value: unknown) => {
|
|
49
|
+
// If 'value' is an IFluidHandle return its encoded form.
|
|
50
|
+
if (isFluidHandle(value)) {
|
|
51
|
+
return encodeHandleForSerialization(toFluidHandleInternal(value));
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
};
|
|
55
57
|
}
|