@fluidframework/container-runtime 2.40.0-336023 → 2.40.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 +14 -0
- package/api-report/container-runtime.legacy.alpha.api.md +4 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +31 -8
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +90 -17
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +8 -2
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +29 -6
- package/dist/channelCollection.js.map +1 -1
- package/dist/compatUtils.d.ts +19 -10
- package/dist/compatUtils.d.ts.map +1 -1
- package/dist/compatUtils.js +39 -32
- package/dist/compatUtils.js.map +1 -1
- package/dist/containerRuntime.d.ts +29 -13
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +139 -149
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +12 -4
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +37 -18
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -0
- 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/outbox.d.ts +20 -7
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +16 -20
- package/dist/opLifecycle/outbox.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 +22 -8
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +11 -16
- package/dist/pendingStateManager.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts +31 -8
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +91 -18
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +8 -2
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +29 -6
- package/lib/channelCollection.js.map +1 -1
- package/lib/compatUtils.d.ts +19 -10
- package/lib/compatUtils.d.ts.map +1 -1
- package/lib/compatUtils.js +36 -29
- package/lib/compatUtils.js.map +1 -1
- package/lib/containerRuntime.d.ts +29 -13
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +60 -70
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +12 -4
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +38 -19
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -0
- 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/outbox.d.ts +20 -7
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +16 -20
- package/lib/opLifecycle/outbox.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 +22 -8
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +11 -16
- package/lib/pendingStateManager.js.map +1 -1
- package/package.json +18 -18
- package/src/blobManager/blobManager.ts +141 -33
- package/src/channelCollection.ts +42 -6
- package/src/compatUtils.ts +53 -30
- package/src/containerRuntime.ts +102 -81
- package/src/dataStoreContext.ts +44 -25
- package/src/index.ts +1 -0
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/outbox.ts +42 -33
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +37 -20
package/src/containerRuntime.ts
CHANGED
|
@@ -74,6 +74,11 @@ import type {
|
|
|
74
74
|
SerializedIdCompressorWithNoSession,
|
|
75
75
|
SerializedIdCompressorWithOngoingSession,
|
|
76
76
|
} from "@fluidframework/id-compressor/internal";
|
|
77
|
+
import {
|
|
78
|
+
createIdCompressor,
|
|
79
|
+
createSessionId,
|
|
80
|
+
deserializeIdCompressor,
|
|
81
|
+
} from "@fluidframework/id-compressor/internal";
|
|
77
82
|
import type {
|
|
78
83
|
ISummaryTreeWithStats,
|
|
79
84
|
ITelemetryContext,
|
|
@@ -158,10 +163,11 @@ import {
|
|
|
158
163
|
wrapContext,
|
|
159
164
|
} from "./channelCollection.js";
|
|
160
165
|
import {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
defaultMinVersionForCollab,
|
|
167
|
+
getMinVersionForCollabDefaults,
|
|
168
|
+
isValidMinVersionForCollab,
|
|
164
169
|
type RuntimeOptionsAffectingDocSchema,
|
|
170
|
+
type MinimumVersionForCollab,
|
|
165
171
|
} from "./compatUtils.js";
|
|
166
172
|
import type { ICompressionRuntimeOptions } from "./compressionDefinitions.js";
|
|
167
173
|
import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
|
|
@@ -197,7 +203,6 @@ import {
|
|
|
197
203
|
} from "./messageTypes.js";
|
|
198
204
|
import { ISavedOpMetadata } from "./metadata.js";
|
|
199
205
|
import {
|
|
200
|
-
BatchId,
|
|
201
206
|
LocalBatchMessage,
|
|
202
207
|
BatchStartInfo,
|
|
203
208
|
DuplicateBatchDetector,
|
|
@@ -210,12 +215,14 @@ import {
|
|
|
210
215
|
Outbox,
|
|
211
216
|
RemoteMessageProcessor,
|
|
212
217
|
type OutboundBatch,
|
|
218
|
+
type BatchResubmitInfo,
|
|
213
219
|
} from "./opLifecycle/index.js";
|
|
214
220
|
import { pkgVersion } from "./packageVersion.js";
|
|
215
221
|
import {
|
|
216
222
|
PendingMessageResubmitData,
|
|
217
223
|
IPendingLocalState,
|
|
218
224
|
PendingStateManager,
|
|
225
|
+
type PendingBatchResubmitMetadata,
|
|
219
226
|
} from "./pendingStateManager.js";
|
|
220
227
|
import { RunCounter } from "./runCounter.js";
|
|
221
228
|
import {
|
|
@@ -504,6 +511,8 @@ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
|
|
|
504
511
|
allowTombstone: false,
|
|
505
512
|
};
|
|
506
513
|
|
|
514
|
+
const defaultStagingCommitOptions = { squash: false };
|
|
515
|
+
|
|
507
516
|
/**
|
|
508
517
|
* @deprecated
|
|
509
518
|
* Untagged logger is unsupported going forward. There are old loaders with old ContainerContexts that only
|
|
@@ -702,6 +711,27 @@ export interface LoadContainerRuntimeParams {
|
|
|
702
711
|
* @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
|
|
703
712
|
* */
|
|
704
713
|
requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Minimum version of the FF runtime that is required to collaborate on new documents.
|
|
717
|
+
* The input should be a string that represents the minimum version of the FF runtime that should be
|
|
718
|
+
* supported for collaboration. The format of the string must be in valid semver format.
|
|
719
|
+
*
|
|
720
|
+
* The inputted version will be used to determine the default configuration for
|
|
721
|
+
* {@link IContainerRuntimeOptionsInternal} to ensure compatibility with the specified version.
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* minVersionForCollab: "2.0.0"
|
|
725
|
+
*
|
|
726
|
+
* @privateRemarks
|
|
727
|
+
* Used to determine the default configuration for {@link IContainerRuntimeOptionsInternal} that affect the document schema.
|
|
728
|
+
* For example, let's say that feature `foo` was added in 2.0 which introduces a new op type. Additionally, option `bar`
|
|
729
|
+
* was added to `IContainerRuntimeOptionsInternal` in 2.0 to enable/disable `foo` since clients prior to 2.0 would not
|
|
730
|
+
* understand the new op type. If a customer were to set minVersionForCollab to 2.0.0, then `bar` would be set to
|
|
731
|
+
* enable `foo` by default. If a customer were to set minVersionForCollab to 1.0.0, then `bar` would be set to
|
|
732
|
+
* disable `foo` by default.
|
|
733
|
+
*/
|
|
734
|
+
minVersionForCollab?: MinimumVersionForCollab;
|
|
705
735
|
}
|
|
706
736
|
/**
|
|
707
737
|
* This is meant to be used by a {@link @fluidframework/container-definitions#IRuntimeFactory} to instantiate a container runtime.
|
|
@@ -751,6 +781,7 @@ export class ContainerRuntime
|
|
|
751
781
|
* - containerRuntimeCtor - Constructor to use to create the ContainerRuntime instance.
|
|
752
782
|
* This allows mixin classes to leverage this method to define their own async initializer.
|
|
753
783
|
* - provideEntryPoint - Promise that resolves to an object which will act as entryPoint for the Container.
|
|
784
|
+
* - minVersionForCollab - Minimum version of the FF runtime that this runtime supports collaboration with.
|
|
754
785
|
* This object should provide all the functionality that the Container is expected to provide to the loader layer.
|
|
755
786
|
*/
|
|
756
787
|
public static async loadRuntime(params: {
|
|
@@ -765,6 +796,7 @@ export class ContainerRuntime
|
|
|
765
796
|
*/
|
|
766
797
|
requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
|
|
767
798
|
provideEntryPoint: (containerRuntime: IContainerRuntime) => Promise<FluidObject>;
|
|
799
|
+
minVersionForCollab?: MinimumVersionForCollab;
|
|
768
800
|
}): Promise<ContainerRuntime> {
|
|
769
801
|
const {
|
|
770
802
|
context,
|
|
@@ -775,6 +807,7 @@ export class ContainerRuntime
|
|
|
775
807
|
runtimeOptions = {} satisfies IContainerRuntimeOptionsInternal,
|
|
776
808
|
containerScope = {},
|
|
777
809
|
containerRuntimeCtor = ContainerRuntime,
|
|
810
|
+
minVersionForCollab = defaultMinVersionForCollab,
|
|
778
811
|
} = params;
|
|
779
812
|
|
|
780
813
|
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
|
|
@@ -796,23 +829,21 @@ export class ContainerRuntime
|
|
|
796
829
|
const mc = loggerToMonitoringContext(logger);
|
|
797
830
|
|
|
798
831
|
// Some options require a minimum version of the FF runtime to operate, so the default configs will be generated
|
|
799
|
-
// based on the
|
|
800
|
-
// For example, if
|
|
801
|
-
// 1.0.0 or later. If the
|
|
832
|
+
// based on the minVersionForCollab.
|
|
833
|
+
// For example, if minVersionForCollab is set to "1.0.0", the default configs will ensure compatibility with FF runtime
|
|
834
|
+
// 1.0.0 or later. If the minVersionForCollab is set to "2.10.0", the default values will be generated to ensure compatibility
|
|
802
835
|
// with FF runtime 2.10.0 or later.
|
|
803
|
-
|
|
804
|
-
const compatibilityVersion = defaultCompatibilityVersion;
|
|
805
|
-
if (!isValidCompatVersion(compatibilityVersion)) {
|
|
836
|
+
if (!isValidMinVersionForCollab(minVersionForCollab)) {
|
|
806
837
|
throw new UsageError(
|
|
807
|
-
`Invalid
|
|
838
|
+
`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`,
|
|
808
839
|
);
|
|
809
840
|
}
|
|
810
|
-
const
|
|
811
|
-
getCompatibilityVersionDefaults(compatibilityVersion);
|
|
841
|
+
const defaultsAffectingDocSchema = getMinVersionForCollabDefaults(minVersionForCollab);
|
|
812
842
|
|
|
813
843
|
// The following are the default values for the options that do not affect the DocumentSchema.
|
|
814
|
-
const
|
|
815
|
-
|
|
844
|
+
const defaultsNotAffectingDocSchema: Omit<
|
|
845
|
+
ContainerRuntimeOptionsInternal,
|
|
846
|
+
keyof RuntimeOptionsAffectingDocSchema
|
|
816
847
|
> = {
|
|
817
848
|
summaryOptions: {},
|
|
818
849
|
loadSequenceNumberVerification: "close",
|
|
@@ -821,8 +852,8 @@ export class ContainerRuntime
|
|
|
821
852
|
};
|
|
822
853
|
|
|
823
854
|
const defaultConfigs = {
|
|
824
|
-
...
|
|
825
|
-
...
|
|
855
|
+
...defaultsAffectingDocSchema,
|
|
856
|
+
...defaultsNotAffectingDocSchema,
|
|
826
857
|
};
|
|
827
858
|
|
|
828
859
|
// Here we set each option to its corresponding default config value if it's not provided in runtimeOptions.
|
|
@@ -976,11 +1007,7 @@ export class ContainerRuntime
|
|
|
976
1007
|
idCompressorMode = desiredIdCompressorMode;
|
|
977
1008
|
}
|
|
978
1009
|
|
|
979
|
-
const createIdCompressorFn =
|
|
980
|
-
const { createIdCompressor, deserializeIdCompressor, createSessionId } = await import(
|
|
981
|
-
"@fluidframework/id-compressor/internal"
|
|
982
|
-
);
|
|
983
|
-
|
|
1010
|
+
const createIdCompressorFn = (): IIdCompressor & IIdCompressorCore => {
|
|
984
1011
|
/**
|
|
985
1012
|
* Because the IdCompressor emits so much telemetry, this function is used to sample
|
|
986
1013
|
* approximately 5% of all clients. Only the given percentage of sessions will emit telemetry.
|
|
@@ -1072,6 +1099,7 @@ export class ContainerRuntime
|
|
|
1072
1099
|
documentSchemaController,
|
|
1073
1100
|
featureGatesForTelemetry,
|
|
1074
1101
|
provideEntryPoint,
|
|
1102
|
+
minVersionForCollab,
|
|
1075
1103
|
requestHandler,
|
|
1076
1104
|
undefined, // summaryConfiguration
|
|
1077
1105
|
recentBatchInfo,
|
|
@@ -1190,12 +1218,6 @@ export class ContainerRuntime
|
|
|
1190
1218
|
}
|
|
1191
1219
|
}
|
|
1192
1220
|
|
|
1193
|
-
/**
|
|
1194
|
-
* True if we have ID compressor loading in-flight (async operation). Useful only for
|
|
1195
|
-
* this.sessionSchema.idCompressorMode === "delayed" mode
|
|
1196
|
-
*/
|
|
1197
|
-
protected _loadIdCompressor: Promise<void> | undefined;
|
|
1198
|
-
|
|
1199
1221
|
/**
|
|
1200
1222
|
* {@inheritDoc @fluidframework/runtime-definitions#IContainerRuntimeBase.generateDocumentUniqueId}
|
|
1201
1223
|
*/
|
|
@@ -1401,11 +1423,12 @@ export class ContainerRuntime
|
|
|
1401
1423
|
|
|
1402
1424
|
blobManagerLoadInfo: IBlobManagerLoadInfo,
|
|
1403
1425
|
private readonly _storage: IDocumentStorageService,
|
|
1404
|
-
private readonly
|
|
1426
|
+
private readonly createIdCompressorFn: () => IIdCompressor & IIdCompressorCore,
|
|
1405
1427
|
|
|
1406
1428
|
private readonly documentsSchemaController: DocumentsSchemaController,
|
|
1407
1429
|
featureGatesForTelemetry: Record<string, boolean | number | undefined>,
|
|
1408
1430
|
provideEntryPoint: (containerRuntime: IContainerRuntime) => Promise<FluidObject>,
|
|
1431
|
+
private readonly minVersionForCollab: MinimumVersionForCollab,
|
|
1409
1432
|
private readonly requestHandler?: (
|
|
1410
1433
|
request: IRequest,
|
|
1411
1434
|
runtime: IContainerRuntime,
|
|
@@ -1914,6 +1937,7 @@ export class ContainerRuntime
|
|
|
1914
1937
|
telemetryDocumentId: this.telemetryDocumentId,
|
|
1915
1938
|
groupedBatchingEnabled: this.groupedBatchingEnabled,
|
|
1916
1939
|
initialSequenceNumber: this.deltaManager.initialSequenceNumber,
|
|
1940
|
+
minVersionForCollab: this.minVersionForCollab,
|
|
1917
1941
|
});
|
|
1918
1942
|
|
|
1919
1943
|
ReportOpPerfTelemetry(this.clientId, this._deltaManager, this, this.baseLogger);
|
|
@@ -1946,7 +1970,6 @@ export class ContainerRuntime
|
|
|
1946
1970
|
// As it's implemented right now (with async initialization), this will only work for "off" -> "delayed" transitions.
|
|
1947
1971
|
// Anything else is too risky, and requires ability to initialize ID compressor synchronously!
|
|
1948
1972
|
if (schema.runtime.idCompressorMode !== undefined) {
|
|
1949
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1950
1973
|
this.loadIdCompressor();
|
|
1951
1974
|
}
|
|
1952
1975
|
}
|
|
@@ -1994,7 +2017,7 @@ export class ContainerRuntime
|
|
|
1994
2017
|
this.sessionSchema.idCompressorMode === "on" ||
|
|
1995
2018
|
(this.sessionSchema.idCompressorMode === "delayed" && this.connected)
|
|
1996
2019
|
) {
|
|
1997
|
-
this._idCompressor =
|
|
2020
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1998
2021
|
// This is called from loadRuntime(), long before we process any ops, so there should be no ops accumulated yet.
|
|
1999
2022
|
assert(this.pendingIdCompressorOps.length === 0, 0x8ec /* no pending ops */);
|
|
2000
2023
|
}
|
|
@@ -2592,29 +2615,20 @@ export class ContainerRuntime
|
|
|
2592
2615
|
}
|
|
2593
2616
|
}
|
|
2594
2617
|
|
|
2595
|
-
private
|
|
2618
|
+
private loadIdCompressor(): void {
|
|
2596
2619
|
if (
|
|
2597
2620
|
this._idCompressor === undefined &&
|
|
2598
|
-
this.sessionSchema.idCompressorMode !== undefined
|
|
2599
|
-
this._loadIdCompressor === undefined
|
|
2621
|
+
this.sessionSchema.idCompressorMode !== undefined
|
|
2600
2622
|
) {
|
|
2601
|
-
this.
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
assert(this.pendingIdCompressorOps.length === 0, 0x976 /* No new ops added */);
|
|
2610
|
-
this._idCompressor = compressor;
|
|
2611
|
-
})
|
|
2612
|
-
.catch((error) => {
|
|
2613
|
-
this.mc.logger.sendErrorEvent({ eventName: "IdCompressorDelayedLoad" }, error);
|
|
2614
|
-
throw error;
|
|
2615
|
-
});
|
|
2623
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
2624
|
+
// Finalize any ranges we received while the compressor was turned off.
|
|
2625
|
+
const ops = this.pendingIdCompressorOps;
|
|
2626
|
+
this.pendingIdCompressorOps = [];
|
|
2627
|
+
for (const range of ops) {
|
|
2628
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
2629
|
+
}
|
|
2630
|
+
assert(this.pendingIdCompressorOps.length === 0, 0x976 /* No new ops added */);
|
|
2616
2631
|
}
|
|
2617
|
-
return this._loadIdCompressor;
|
|
2618
2632
|
}
|
|
2619
2633
|
|
|
2620
2634
|
private readonly notifyReadOnlyState = (readonly: boolean): void =>
|
|
@@ -2630,7 +2644,6 @@ export class ContainerRuntime
|
|
|
2630
2644
|
);
|
|
2631
2645
|
|
|
2632
2646
|
if (connected && this.sessionSchema.idCompressorMode === "delayed") {
|
|
2633
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
2634
2647
|
this.loadIdCompressor();
|
|
2635
2648
|
}
|
|
2636
2649
|
if (connected === false && this.delayConnectClientId !== undefined) {
|
|
@@ -3190,23 +3203,20 @@ export class ContainerRuntime
|
|
|
3190
3203
|
}
|
|
3191
3204
|
|
|
3192
3205
|
/**
|
|
3193
|
-
* Flush the
|
|
3194
|
-
* This method is expected to be called
|
|
3206
|
+
* Flush the current batch of ops to the ordering service for sequencing
|
|
3207
|
+
* This method is not expected to be called in the middle of a batch.
|
|
3195
3208
|
* @remarks - If it throws (e.g. if the batch is too large to send), the container will be closed.
|
|
3196
3209
|
*
|
|
3197
|
-
* @param
|
|
3198
|
-
* with the given Batch ID, which must be preserved
|
|
3199
|
-
* @param resubmittingStagedBatch - If defined, indicates this is a resubmission of a batch that is staged,
|
|
3200
|
-
* meaning it should not be sent to the ordering service yet.
|
|
3210
|
+
* @param resubmitInfo - If defined, indicates this is a resubmission of a batch with the given Batch info needed for resubmit.
|
|
3201
3211
|
*/
|
|
3202
|
-
private flush(
|
|
3212
|
+
private flush(resubmitInfo?: BatchResubmitInfo): void {
|
|
3203
3213
|
try {
|
|
3204
3214
|
assert(
|
|
3205
3215
|
!this.batchRunner.running,
|
|
3206
3216
|
0x24c /* "Cannot call `flush()` while manually accumulating a batch (e.g. under orderSequentially) */,
|
|
3207
3217
|
);
|
|
3208
3218
|
|
|
3209
|
-
this.outbox.flush(
|
|
3219
|
+
this.outbox.flush(resubmitInfo);
|
|
3210
3220
|
assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
|
|
3211
3221
|
} catch (error) {
|
|
3212
3222
|
const error2 = normalizeError(error, {
|
|
@@ -3282,7 +3292,8 @@ export class ContainerRuntime
|
|
|
3282
3292
|
throw error; // throw the original error for the consumer of the runtime
|
|
3283
3293
|
}
|
|
3284
3294
|
});
|
|
3285
|
-
|
|
3295
|
+
|
|
3296
|
+
stageControls?.commitChanges({ squash: false });
|
|
3286
3297
|
|
|
3287
3298
|
// We don't flush on TurnBased since we expect all messages in the same JS turn to be part of the same batch
|
|
3288
3299
|
if (this.flushMode !== FlushMode.TurnBased && !this.batchRunner.running) {
|
|
@@ -3318,16 +3329,19 @@ export class ContainerRuntime
|
|
|
3318
3329
|
// Make sure all BatchManagers are empty before entering staging mode,
|
|
3319
3330
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
3320
3331
|
this.outbox.flush();
|
|
3332
|
+
|
|
3321
3333
|
const exitStagingMode = (discardOrCommit: () => void) => (): void => {
|
|
3322
3334
|
// Final flush of any last staged changes
|
|
3323
|
-
this.outbox.flush(
|
|
3335
|
+
this.outbox.flush();
|
|
3324
3336
|
|
|
3325
3337
|
this.stageControls = undefined;
|
|
3326
3338
|
|
|
3327
3339
|
discardOrCommit();
|
|
3340
|
+
this.channelCollection.notifyStagingMode(false);
|
|
3328
3341
|
};
|
|
3329
3342
|
|
|
3330
|
-
|
|
3343
|
+
// eslint-disable-next-line import/no-deprecated
|
|
3344
|
+
const stageControls: StageControlsExperimental = {
|
|
3331
3345
|
discardChanges: exitStagingMode(() => {
|
|
3332
3346
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
3333
3347
|
this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
@@ -3341,18 +3355,21 @@ export class ContainerRuntime
|
|
|
3341
3355
|
this.updateDocumentDirtyState(this.pendingMessagesCount !== 0);
|
|
3342
3356
|
}
|
|
3343
3357
|
}),
|
|
3344
|
-
commitChanges:
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
}
|
|
3352
|
-
}
|
|
3358
|
+
commitChanges: (optionsParam) => {
|
|
3359
|
+
const options = { ...defaultStagingCommitOptions, ...optionsParam };
|
|
3360
|
+
return exitStagingMode(() => {
|
|
3361
|
+
this.pendingStateManager.replayPendingStates({
|
|
3362
|
+
onlyStagedBatches: true,
|
|
3363
|
+
squash: options.squash ?? false,
|
|
3364
|
+
});
|
|
3365
|
+
})();
|
|
3366
|
+
},
|
|
3353
3367
|
};
|
|
3354
3368
|
|
|
3355
|
-
|
|
3369
|
+
this.stageControls = stageControls;
|
|
3370
|
+
this.channelCollection.notifyStagingMode(true);
|
|
3371
|
+
|
|
3372
|
+
return this.stageControls;
|
|
3356
3373
|
};
|
|
3357
3374
|
|
|
3358
3375
|
/**
|
|
@@ -3587,8 +3604,7 @@ export class ContainerRuntime
|
|
|
3587
3604
|
wrapSummaryInChannelsTree(summarizeResult);
|
|
3588
3605
|
const pathPartsForChildren = [channelsTreeName];
|
|
3589
3606
|
|
|
3590
|
-
|
|
3591
|
-
await this.loadIdCompressor();
|
|
3607
|
+
this.loadIdCompressor();
|
|
3592
3608
|
|
|
3593
3609
|
this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
|
|
3594
3610
|
return {
|
|
@@ -4547,22 +4563,21 @@ export class ContainerRuntime
|
|
|
4547
4563
|
*/
|
|
4548
4564
|
private reSubmitBatch(
|
|
4549
4565
|
batch: PendingMessageResubmitData[],
|
|
4550
|
-
batchId:
|
|
4551
|
-
staged: boolean,
|
|
4566
|
+
{ batchId, staged, squash }: PendingBatchResubmitMetadata,
|
|
4552
4567
|
): void {
|
|
4553
4568
|
this.batchRunner.run(() => {
|
|
4554
4569
|
for (const message of batch) {
|
|
4555
|
-
this.reSubmit(message);
|
|
4570
|
+
this.reSubmit(message, squash);
|
|
4556
4571
|
}
|
|
4557
4572
|
});
|
|
4558
4573
|
|
|
4559
4574
|
// Only include Batch ID if "Offline Load" feature is enabled
|
|
4560
4575
|
// It's only needed to identify batches across container forks arising from misuse of offline load.
|
|
4561
|
-
this.flush(this.offlineEnabled ? batchId : undefined, staged);
|
|
4576
|
+
this.flush({ batchId: this.offlineEnabled ? batchId : undefined, staged });
|
|
4562
4577
|
}
|
|
4563
4578
|
|
|
4564
|
-
private reSubmit(message: PendingMessageResubmitData): void {
|
|
4565
|
-
this.reSubmitCore(message.runtimeOp, message.localOpMetadata, message.opMetadata);
|
|
4579
|
+
private reSubmit(message: PendingMessageResubmitData, squash: boolean): void {
|
|
4580
|
+
this.reSubmitCore(message.runtimeOp, message.localOpMetadata, message.opMetadata, squash);
|
|
4566
4581
|
}
|
|
4567
4582
|
|
|
4568
4583
|
/**
|
|
@@ -4576,6 +4591,7 @@ export class ContainerRuntime
|
|
|
4576
4591
|
message: LocalContainerRuntimeMessage,
|
|
4577
4592
|
localOpMetadata: unknown,
|
|
4578
4593
|
opMetadata: Record<string, unknown> | undefined,
|
|
4594
|
+
squash: boolean,
|
|
4579
4595
|
): void {
|
|
4580
4596
|
assert(
|
|
4581
4597
|
this._summarizer === undefined,
|
|
@@ -4587,7 +4603,12 @@ export class ContainerRuntime
|
|
|
4587
4603
|
case ContainerMessageType.Alias: {
|
|
4588
4604
|
// For Operations, call resubmitDataStoreOp which will find the right store
|
|
4589
4605
|
// and trigger resubmission on it.
|
|
4590
|
-
this.channelCollection.reSubmit(
|
|
4606
|
+
this.channelCollection.reSubmit(
|
|
4607
|
+
message.type,
|
|
4608
|
+
message.contents,
|
|
4609
|
+
localOpMetadata,
|
|
4610
|
+
squash,
|
|
4611
|
+
);
|
|
4591
4612
|
break;
|
|
4592
4613
|
}
|
|
4593
4614
|
case ContainerMessageType.IdAllocation: {
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
FluidObject,
|
|
16
16
|
IDisposable,
|
|
17
17
|
ITelemetryBaseProperties,
|
|
18
|
-
type IErrorBase,
|
|
19
18
|
type IEvent,
|
|
20
19
|
} from "@fluidframework/core-interfaces";
|
|
21
20
|
import {
|
|
@@ -95,7 +94,6 @@ import {
|
|
|
95
94
|
getAttributesFormatVersion,
|
|
96
95
|
getFluidDataStoreAttributes,
|
|
97
96
|
hasIsolatedChannels,
|
|
98
|
-
summarizerClientType,
|
|
99
97
|
wrapSummaryInChannelsTree,
|
|
100
98
|
} from "./summary/index.js";
|
|
101
99
|
|
|
@@ -225,17 +223,14 @@ class ContextDeltaManagerProxy extends BaseDeltaManagerProxy {
|
|
|
225
223
|
}
|
|
226
224
|
|
|
227
225
|
/**
|
|
228
|
-
* Called by the owning datastore context to
|
|
229
|
-
*
|
|
226
|
+
* Called by the owning datastore context to emit the readonly
|
|
227
|
+
* event on the delta manger that is projected down to the datastore
|
|
230
228
|
* runtime. This state may not align with that of the true delta
|
|
231
229
|
* manager if the context wishes to control the read only state
|
|
232
230
|
* differently than the delta manager itself.
|
|
233
231
|
*/
|
|
234
|
-
public
|
|
235
|
-
readonly
|
|
236
|
-
readonlyConnectionReason?: { reason: string; error?: IErrorBase },
|
|
237
|
-
): void {
|
|
238
|
-
this.emit("readonly", readonly, readonlyConnectionReason);
|
|
232
|
+
public emitReadonly(): void {
|
|
233
|
+
this.emit("readonly", this.isReadOnly());
|
|
239
234
|
}
|
|
240
235
|
}
|
|
241
236
|
|
|
@@ -272,7 +267,10 @@ export abstract class FluidDataStoreContext
|
|
|
272
267
|
return this._contextDeltaManagerProxy;
|
|
273
268
|
}
|
|
274
269
|
|
|
275
|
-
|
|
270
|
+
private isStagingMode: boolean = false;
|
|
271
|
+
public isReadOnly = (): boolean =>
|
|
272
|
+
(this.isStagingMode && this.channel?.policies?.readonlyInStagingMode !== false) ||
|
|
273
|
+
this.parentContext.isReadOnly();
|
|
276
274
|
|
|
277
275
|
public get connected(): boolean {
|
|
278
276
|
return this.parentContext.connected;
|
|
@@ -679,11 +677,28 @@ export abstract class FluidDataStoreContext
|
|
|
679
677
|
this.channel!.setConnectionState(connected, clientId);
|
|
680
678
|
}
|
|
681
679
|
|
|
682
|
-
public notifyReadOnlyState(
|
|
680
|
+
public notifyReadOnlyState(): void {
|
|
683
681
|
this.verifyNotClosed("notifyReadOnlyState", false /* checkTombstone */);
|
|
684
682
|
|
|
685
|
-
|
|
686
|
-
this.
|
|
683
|
+
// These two calls achieve the same purpose, and are both needed for a time for back compat
|
|
684
|
+
this.channel?.notifyReadOnlyState?.(this.isReadOnly());
|
|
685
|
+
this._contextDeltaManagerProxy.emitReadonly();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Updates the readonly state of the data store based on the staging mode.
|
|
690
|
+
*
|
|
691
|
+
* @param staging - A boolean indicating whether the container is in staging mode.
|
|
692
|
+
* If true, the data store is set to readonly unless explicitly allowed by its policies.
|
|
693
|
+
*/
|
|
694
|
+
public notifyStagingMode(staging: boolean): void {
|
|
695
|
+
// If the `readonlyInStagingMode` policy is not explicitly set to `false`,
|
|
696
|
+
// the data store is treated as readonly in staging mode.
|
|
697
|
+
const oldReadOnlyState = this.isReadOnly();
|
|
698
|
+
this.isStagingMode = staging;
|
|
699
|
+
if (this.isReadOnly() !== oldReadOnlyState) {
|
|
700
|
+
this.notifyReadOnlyState();
|
|
701
|
+
}
|
|
687
702
|
}
|
|
688
703
|
|
|
689
704
|
/**
|
|
@@ -868,8 +883,8 @@ export abstract class FluidDataStoreContext
|
|
|
868
883
|
public submitMessage(type: string, content: unknown, localOpMetadata: unknown): void {
|
|
869
884
|
this.verifyNotClosed("submitMessage");
|
|
870
885
|
assert(!!this.channel, 0x146 /* "Channel must exist when submitting message" */);
|
|
871
|
-
//
|
|
872
|
-
this.
|
|
886
|
+
// Readonly clients should not submit messages.
|
|
887
|
+
this.identifyLocalChangeIfReadonly("DataStoreMessageWhileReadonly", type);
|
|
873
888
|
|
|
874
889
|
this.parentContext.submitMessage(type, content, localOpMetadata);
|
|
875
890
|
}
|
|
@@ -1027,9 +1042,14 @@ export abstract class FluidDataStoreContext
|
|
|
1027
1042
|
return {};
|
|
1028
1043
|
}
|
|
1029
1044
|
|
|
1030
|
-
public reSubmit(
|
|
1045
|
+
public reSubmit(
|
|
1046
|
+
type: string,
|
|
1047
|
+
contents: unknown,
|
|
1048
|
+
localOpMetadata: unknown,
|
|
1049
|
+
squash: boolean,
|
|
1050
|
+
): void {
|
|
1031
1051
|
assert(!!this.channel, 0x14b /* "Channel must exist when resubmitting ops" */);
|
|
1032
|
-
this.channel.reSubmit(type, contents, localOpMetadata);
|
|
1052
|
+
this.channel.reSubmit(type, contents, localOpMetadata, squash);
|
|
1033
1053
|
}
|
|
1034
1054
|
|
|
1035
1055
|
public rollback(type: string, contents: unknown, localOpMetadata: unknown): void {
|
|
@@ -1100,19 +1120,16 @@ export abstract class FluidDataStoreContext
|
|
|
1100
1120
|
}
|
|
1101
1121
|
|
|
1102
1122
|
/**
|
|
1103
|
-
*
|
|
1123
|
+
* Readonly client, including summarizer, should not have local changes. These changes can become part of the summary and can break
|
|
1104
1124
|
* eventual consistency. For example, the next summary (say at ref seq# 100) may contain these changes whereas
|
|
1105
1125
|
* other clients that are up-to-date till seq# 100 may not have them yet.
|
|
1106
1126
|
*/
|
|
1107
|
-
protected
|
|
1108
|
-
if (
|
|
1109
|
-
this.clientDetails.type !== summarizerClientType ||
|
|
1110
|
-
this.localChangesTelemetryCount <= 0
|
|
1111
|
-
) {
|
|
1127
|
+
protected identifyLocalChangeIfReadonly(eventName: string, type?: string): void {
|
|
1128
|
+
if (!this.isReadOnly() || this.localChangesTelemetryCount <= 0) {
|
|
1112
1129
|
return;
|
|
1113
1130
|
}
|
|
1114
1131
|
|
|
1115
|
-
// Log a telemetry if there are local changes in
|
|
1132
|
+
// Log a telemetry if there are local changes in readonly. This will give us data on how often
|
|
1116
1133
|
// this is happening and which data stores do this. The eventual goal is to disallow local changes
|
|
1117
1134
|
// in the summarizer and the data will help us plan this.
|
|
1118
1135
|
this.mc.logger.sendTelemetryEvent({
|
|
@@ -1120,6 +1137,8 @@ export abstract class FluidDataStoreContext
|
|
|
1120
1137
|
type,
|
|
1121
1138
|
isSummaryInProgress: this.summarizerNode.isSummaryInProgress?.(),
|
|
1122
1139
|
stack: generateStack(30),
|
|
1140
|
+
readonly: this.isReadOnly(),
|
|
1141
|
+
isStagingMode: this.isStagingMode,
|
|
1123
1142
|
});
|
|
1124
1143
|
this.localChangesTelemetryCount--;
|
|
1125
1144
|
}
|
|
@@ -1321,7 +1340,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
1321
1340
|
);
|
|
1322
1341
|
|
|
1323
1342
|
// Summarizer client should not create local data stores.
|
|
1324
|
-
this.
|
|
1343
|
+
this.identifyLocalChangeIfReadonly("DataStoreCreatedWhileReadonly");
|
|
1325
1344
|
|
|
1326
1345
|
this.snapshotTree = props.snapshotTree;
|
|
1327
1346
|
}
|
package/src/index.ts
CHANGED