diff --git a/packages/framework/fluid-static/api-report/fluid-static.alpha.api.md b/packages/framework/fluid-static/api-report/fluid-static.alpha.api.md index 3e583349f1a5..af6502702a23 100644 --- a/packages/framework/fluid-static/api-report/fluid-static.alpha.api.md +++ b/packages/framework/fluid-static/api-report/fluid-static.alpha.api.md @@ -4,8 +4,9 @@ ```ts -// @public -export type CompatibilityMode = "1" | "2"; +export { CompatibilityMode } + +export { compatibilityModeRuntimeOptions } // @public export type ContainerAttachProps = T; diff --git a/packages/framework/fluid-static/api-report/fluid-static.beta.api.md b/packages/framework/fluid-static/api-report/fluid-static.beta.api.md index d79be0f31535..4587004d0b68 100644 --- a/packages/framework/fluid-static/api-report/fluid-static.beta.api.md +++ b/packages/framework/fluid-static/api-report/fluid-static.beta.api.md @@ -4,8 +4,9 @@ ```ts -// @public -export type CompatibilityMode = "1" | "2"; +export { CompatibilityMode } + +export { compatibilityModeRuntimeOptions } // @public export type ContainerAttachProps = T; diff --git a/packages/framework/fluid-static/api-report/fluid-static.public.api.md b/packages/framework/fluid-static/api-report/fluid-static.public.api.md index 35d6cf0a0dc8..24d93af271b8 100644 --- a/packages/framework/fluid-static/api-report/fluid-static.public.api.md +++ b/packages/framework/fluid-static/api-report/fluid-static.public.api.md @@ -4,8 +4,9 @@ ```ts -// @public -export type CompatibilityMode = "1" | "2"; +export { CompatibilityMode } + +export { compatibilityModeRuntimeOptions } // @public export type ContainerAttachProps = T; diff --git a/packages/framework/fluid-static/src/index.ts b/packages/framework/fluid-static/src/index.ts index 4d9f853035a2..59d8c1fba49c 100644 --- a/packages/framework/fluid-static/src/index.ts +++ b/packages/framework/fluid-static/src/index.ts @@ -20,7 +20,6 @@ export { export { createDOProviderContainerRuntimeFactory } from "./rootDataObject.js"; export { createServiceAudience } from "./serviceAudience.js"; export type { - CompatibilityMode, ContainerSchema, ContainerAttachProps, IConnection, @@ -33,3 +32,8 @@ export type { MemberChangedListener, Myself, } from "./types.js"; + +// Re-export so other packages don't need to pull in container-runtime +// TODO: Should we re-export? +export type { CompatibilityMode } from "@fluidframework/container-runtime"; +export { compatibilityModeRuntimeOptions } from "@fluidframework/container-runtime/internal"; diff --git a/packages/framework/fluid-static/src/rootDataObject.ts b/packages/framework/fluid-static/src/rootDataObject.ts index 587d0a95352a..f1aeecb38366 100644 --- a/packages/framework/fluid-static/src/rootDataObject.ts +++ b/packages/framework/fluid-static/src/rootDataObject.ts @@ -10,15 +10,15 @@ import { DataObjectFactory, } from "@fluidframework/aqueduct/internal"; import type { IRuntimeFactory } from "@fluidframework/container-definitions/internal"; +import type { CompatibilityMode } from "@fluidframework/container-runtime"; +import { compatibilityModeRuntimeOptions } from "@fluidframework/container-runtime/internal"; import type { IContainerRuntime } from "@fluidframework/container-runtime-definitions/internal"; import type { FluidObject, IFluidLoadable } from "@fluidframework/core-interfaces"; import type { IDirectory } from "@fluidframework/map/internal"; import type { SharedObjectKind } from "@fluidframework/shared-object-base"; import type { ISharedObjectKind } from "@fluidframework/shared-object-base/internal"; -import { compatibilityModeRuntimeOptions } from "./compatibilityConfiguration.js"; import type { - CompatibilityMode, ContainerSchema, IRootDataObject, LoadableObjectKind, diff --git a/packages/framework/fluid-static/src/types.ts b/packages/framework/fluid-static/src/types.ts index 07257efd7410..340b2f892478 100644 --- a/packages/framework/fluid-static/src/types.ts +++ b/packages/framework/fluid-static/src/types.ts @@ -8,12 +8,6 @@ import type { IEvent, IEventProvider, IFluidLoadable } from "@fluidframework/cor import type { SharedObjectKind } from "@fluidframework/shared-object-base"; import type { ISharedObjectKind } from "@fluidframework/shared-object-base/internal"; -/** - * Valid compatibility modes that may be specified when creating a DOProviderContainerRuntimeFactory. - * @public - */ -export type CompatibilityMode = "1" | "2"; - /** * A mapping of string identifiers to instantiated `DataObject`s or `SharedObject`s. * @internal diff --git a/packages/runtime/container-runtime/api-report/container-runtime.beta.api.md b/packages/runtime/container-runtime/api-report/container-runtime.beta.api.md index 0c4aae12ceab..f80fc225b774 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.beta.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.beta.api.md @@ -4,6 +4,9 @@ ```ts +// @public +export type CompatibilityMode = "1" | "2"; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md b/packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md index 9f3fd2ca891b..89c42d2dc0d5 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md @@ -7,6 +7,9 @@ // @alpha export const AllowTombstoneRequestHeaderKey = "allowTombstone"; +// @public +export type CompatibilityMode = "1" | "2"; + // @alpha export enum CompressionAlgorithms { // (undocumented) @@ -192,6 +195,7 @@ export interface IContainerRuntimeMetadata extends ICreateContainerMetadata, IGC // @alpha export interface IContainerRuntimeOptions { readonly chunkSizeInBytes?: number; + readonly compatibilityMode?: CompatibilityMode; readonly compressionOptions?: ICompressionRuntimeOptions; // @deprecated readonly enableGroupedBatching?: boolean; @@ -201,6 +205,7 @@ export interface IContainerRuntimeOptions { readonly gcOptions?: IGCRuntimeOptions; readonly loadSequenceNumberVerification?: "close" | "log" | "bypass"; readonly maxBatchSizeInBytes?: number; + readonly protocolOptions?: IProtocolOptions; // (undocumented) readonly summaryOptions?: ISummaryRuntimeOptions; } @@ -354,6 +359,16 @@ export interface IOnDemandSummarizeOptions extends ISummarizeOptions { readonly retryOnFailure?: boolean; } +// @alpha +export interface IProtocolOptions { + readonly compressionOptions?: ICompressionRuntimeOptions; + readonly enableGCSweep?: true | undefined; + // @deprecated + readonly enableGroupedBatching?: boolean; + readonly enableRuntimeIdCompressor?: IdCompressorMode; + readonly explicitSchemaControl?: boolean; +} + // @alpha @deprecated export interface IRefreshSummaryAckOptions { readonly ackHandle: string; diff --git a/packages/runtime/container-runtime/api-report/container-runtime.legacy.public.api.md b/packages/runtime/container-runtime/api-report/container-runtime.legacy.public.api.md index 19bcd0a8e199..b92e0dc562ee 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.legacy.public.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.legacy.public.api.md @@ -4,6 +4,9 @@ ```ts +// @public +export type CompatibilityMode = "1" | "2"; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/runtime/container-runtime/api-report/container-runtime.public.api.md b/packages/runtime/container-runtime/api-report/container-runtime.public.api.md index 19bcd0a8e199..b92e0dc562ee 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.public.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.public.api.md @@ -4,6 +4,9 @@ ```ts +// @public +export type CompatibilityMode = "1" | "2"; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/framework/fluid-static/src/compatibilityConfiguration.ts b/packages/runtime/container-runtime/src/compatibilityConfiguration.ts similarity index 78% rename from packages/framework/fluid-static/src/compatibilityConfiguration.ts rename to packages/runtime/container-runtime/src/compatibilityConfiguration.ts index eca95091461e..2f3abcd6cd19 100644 --- a/packages/framework/fluid-static/src/compatibilityConfiguration.ts +++ b/packages/runtime/container-runtime/src/compatibilityConfiguration.ts @@ -3,17 +3,32 @@ * Licensed under the MIT License. */ +import { FlushMode } from "@fluidframework/runtime-definitions/internal"; + import { CompressionAlgorithms, type IContainerRuntimeOptionsInternal, -} from "@fluidframework/container-runtime/internal"; -import { FlushMode } from "@fluidframework/runtime-definitions/internal"; +} from "./containerRuntime.js"; + +/** + * Valid compatibility modes that may be specified when creating a DOProviderContainerRuntimeFactory. + * @public + */ +export type CompatibilityMode = "1" | "2"; -import type { CompatibilityMode } from "./types.js"; +/** + * The default compatibility mode is "1". + * This is based on our current cross-client compat policy, which states we must support the most recent + * adjacent public major version (currently 1.x). + * This value will need to be updated if our compat policy changes, or we release a major public version. + * @public + */ +export const defaultCompatibilityMode: CompatibilityMode = "1"; /** * The CompatibilityMode selected determines the set of runtime options to use. In "1" mode we support * full interop with true 1.x clients, while in "2" mode we only support interop with 2.x clients. + * @internal */ export const compatibilityModeRuntimeOptions: Record< CompatibilityMode, @@ -36,6 +51,8 @@ export const compatibilityModeRuntimeOptions: Record< // Explicitly disable running Sweep in compat mode "1". Sweep is supported only in 2.x. So, when 1.x and 2.x // clients are running in parallel, running sweep will fail 1.x clients. gcOptions: { enableGCSweep: undefined }, + + disallowedVersions: [], }, "2": { // Explicit schema control explicitly makes the container incompatible with 1.x clients, to force their @@ -47,5 +64,7 @@ export const compatibilityModeRuntimeOptions: Record< // Explicitly disable running Sweep in compat mode "2". Although sweep is supported in 2.x, it is disabled by default. // This setting explicitly disables it to be extra safe. gcOptions: { enableGCSweep: undefined }, + + disallowedVersions: ["<2.0"], }, }; diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 277f65f011b5..8488aab2a274 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -155,6 +155,11 @@ import { getSummaryForDatastores, wrapContext, } from "./channelCollection.js"; +import { + compatibilityModeRuntimeOptions, + defaultCompatibilityMode, + type CompatibilityMode, +} from "./compatibilityConfiguration.js"; import { IPerfSignalReport, ReportOpPerfTelemetry } from "./connectionTelemetry.js"; import { ContainerFluidHandleContext } from "./containerHandleContext.js"; import { channelToDataStore } from "./dataStore.js"; @@ -486,12 +491,71 @@ export interface ICompressionRuntimeOptions { readonly compressionAlgorithm: CompressionAlgorithms; } +/** + * TODO: TSDoc update + * Options for container runtime related to protocol/data format. + * All clients connected to the same container must be able to understand the same protocol. + * @legacy + * @alpha + */ +export interface IProtocolOptions { + /** + * Enable the IdCompressor in the runtime. + * @experimental Not ready for use. + */ + readonly enableRuntimeIdCompressor?: IdCompressorMode; + + /** + * If enabled, the runtime will group messages within a batch into a single + * message to be sent to the service. + * The grouping and ungrouping of such messages is handled by the "OpGroupingManager". + * + * By default, the feature is enabled. This feature can only be disabled when compression is also disabled. + * @deprecated The ability to disable Grouped Batching is deprecated and will be removed in a future release. This feature is required for the proper functioning of the Fluid Framework. + */ + readonly enableGroupedBatching?: boolean; + + /** + * When this property is set to true, it requires runtime to control is document schema properly through ops + * The benefit of this mode is that clients who do not understand schema will fail in predictable way, with predictable message, + * and will not attempt to limp along, which could cause data corruptions and crashes in random places. + * When this property is not set (or set to false), runtime operates in legacy mode, where new features (modifying document schema) + * are engaged as they become available, without giving legacy clients any chance to fail predictably. + */ + readonly explicitSchemaControl?: boolean; + + /** + * Flag that if true, will enable the full Sweep Phase of garbage collection for this session, + * where Tombstoned objects are permanently deleted from the container. + * + * IMPORTANT: This only applies if this document is allowed to run Sweep Phase. + * + * Current default behavior is for Sweep Phase not to delete Tombstoned objects, + * but merely to prevent them from being loaded. + */ + readonly enableGCSweep?: true | undefined; + + /** + * Enables the runtime to compress ops. See {@link ICompressionRuntimeOptions}. + */ + readonly compressionOptions?: ICompressionRuntimeOptions; +} + /** * Options for container runtime. * @legacy * @alpha */ export interface IContainerRuntimeOptions { + /** + * TODO: TSDoc + */ + readonly protocolOptions?: IProtocolOptions; + /** + * TODO: TSDoc + */ + readonly compatibilityMode?: CompatibilityMode; + readonly summaryOptions?: ISummaryRuntimeOptions; readonly gcOptions?: IGCRuntimeOptions; /** @@ -583,6 +647,11 @@ export interface IContainerRuntimeOptionsInternal extends IContainerRuntimeOptio * In that case, batched messages will be sent individually (but still all at the same time). */ readonly enableGroupedBatching?: boolean; + + /** + * TODO: TSDoc + */ + disallowedVersions?: string[]; } /** @@ -990,21 +1059,51 @@ export class ContainerRuntime const mc = loggerToMonitoringContext(logger); + // While we transition towards using IProtocolOptions/CompatibilityMode, we will have to handle the different sources of container runtime options. + // Please note that the following logic will be much simpler after the overlapping properties are removed from IContainerRuntimeOptions. + const protocolOptions: IProtocolOptions = runtimeOptions.protocolOptions ?? {}; + const compatibilityMode = runtimeOptions.compatibilityMode ?? defaultCompatibilityMode; + const defaultCompatibilityModeOptions: IContainerRuntimeOptionsInternal = + compatibilityModeRuntimeOptions[compatibilityMode]; + + // This loop does two things: + // 1. We check if any of the properties in protocolOptions are also defined in runtimeOptions. If they are not the same, we throw an error. + // 2. We set any undefined properties in runtimeOptions to the value in protocolOptions. + for (const key of Object.keys(protocolOptions)) { + if (protocolOptions[key] !== undefined) { + if (runtimeOptions[key] === undefined) { + runtimeOptions[key] = protocolOptions[key] as IContainerRuntimeOptions; + } else if (runtimeOptions[key] !== protocolOptions[key]) { + throw new Error( + `The ${key} property is defined differently in protocolOptions and runtimeOptions. Please remove one of them.`, + ); + } + } + } + const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", - flushMode = defaultFlushMode, + flushMode = defaultCompatibilityModeOptions.flushMode ?? defaultFlushMode, compressionOptions = runtimeOptions.enableGroupedBatching === false ? disabledCompressionConfig // Compression must be disabled if Grouping is disabled : defaultCompressionConfig, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, - enableRuntimeIdCompressor, + enableRuntimeIdCompressor = defaultCompatibilityModeOptions.enableRuntimeIdCompressor, chunkSizeInBytes = defaultChunkSizeInBytes, - enableGroupedBatching = true, - explicitSchemaControl = false, + enableGroupedBatching = defaultCompatibilityModeOptions.enableGroupedBatching ?? true, + explicitSchemaControl = defaultCompatibilityModeOptions.explicitSchemaControl ?? false, + // We currently dont' accept input for disallowedVersions, it is dictated by the compatibilityMode. + disallowedVersions = defaultCompatibilityModeOptions.disallowedVersions ?? [], }: IContainerRuntimeOptionsInternal = runtimeOptions; + if (protocolOptions.enableGCSweep !== undefined) { + // gcSweep is a special case, since it is can only be true or undefined. + // Therefore, if it's defined we can assign the value in runtimeOptions.gcOptions. + gcOptions.enableGCSweep = protocolOptions.enableGCSweep; + } + const registry = new FluidDataStoreRegistry(registryEntries); const tryFetchBlob = async (blobName: string): Promise => { @@ -1180,7 +1279,7 @@ export class ContainerRuntime compressionLz4, idCompressorMode, opGroupingEnabled: enableGroupedBatching, - disallowedVersions: [], + disallowedVersions, }, (schema) => { runtime.onSchemaChange(schema); @@ -1206,6 +1305,9 @@ export class ContainerRuntime enableRuntimeIdCompressor: enableRuntimeIdCompressor as "on" | "delayed", enableGroupedBatching, explicitSchemaControl, + protocolOptions, + compatibilityMode, + disallowedVersions, }; const runtime = new containerRuntimeCtor( diff --git a/packages/runtime/container-runtime/src/index.ts b/packages/runtime/container-runtime/src/index.ts index 6cb1c022215b..a271344967f7 100644 --- a/packages/runtime/container-runtime/src/index.ts +++ b/packages/runtime/container-runtime/src/index.ts @@ -24,6 +24,7 @@ export { CompressionAlgorithms, RuntimeHeaderData, disabledCompressionConfig, + IProtocolOptions, } from "./containerRuntime.js"; export { ContainerMessageType, @@ -121,3 +122,7 @@ export { IFluidDataStoreContextEvents, } from "./dataStoreContext.js"; export { DataStoreContexts } from "./dataStoreContexts.js"; +export { + CompatibilityMode, + compatibilityModeRuntimeOptions, +} from "./compatibilityConfiguration.js"; diff --git a/packages/test/test-service-load/src/optionsMatrix.ts b/packages/test/test-service-load/src/optionsMatrix.ts index 855473cc4e1f..dfa3e6c210c8 100644 --- a/packages/test/test-service-load/src/optionsMatrix.ts +++ b/packages/test/test-service-load/src/optionsMatrix.ts @@ -112,6 +112,9 @@ export function generateRuntimeOptions( enableRuntimeIdCompressor: ["on", undefined, "delayed"], enableGroupedBatching: [true, false], explicitSchemaControl: [true, false], + protocolOptions: [undefined, {}], // TODO: This will change in the future + compatibilityMode: ["1", "2"], + disallowedVersions: [], }; const pairwiseOptions = generatePairwiseOptions(