Skip to content

Commit

Permalink
Add compat mode option to AzureClient and TinyliciousClient (microsof…
Browse files Browse the repository at this point in the history
  • Loading branch information
ChumpChief authored May 21, 2024
1 parent 0f7233c commit 2730787
Show file tree
Hide file tree
Showing 31 changed files with 989 additions and 736 deletions.
9 changes: 9 additions & 0 deletions .changeset/cute-radios-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@fluidframework/azure-client": minor
"@fluidframework/fluid-static": minor
"@fluidframework/tinylicious-client": minor
---

compatibilityMode parameter added to createContainer and getContainer on AzureClient and TinyliciousClient

To support migration from 1.x to 2.0, a compatibility mode parameter has been added to these methods on AzureClient and TinyliciousClient. When set to "1", this allows interop between the 2.0 clients and 1.x clients. When set to "2", interop with 1.x clients is disallowed but new 2.0 features may be used.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class DocCreatorRunner extends ScenarioRunner<
logger,
{ eventName: "create" },
async () => {
return ac.createContainer(schema);
return ac.createContainer(schema, "2");
},
{ start: true, end: true, cancel: "generic" },
));
Expand Down
2 changes: 1 addition & 1 deletion azure/packages/test/scenario-runner/src/DocLoaderRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class DocLoaderRunner extends ScenarioRunner<
logger,
{ eventName: "load" },
async () => {
return ac.getContainer(runConfig.docId, schema);
return ac.getContainer(runConfig.docId, schema, "2");
},
{ start: true, end: true, cancel: "generic" },
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class MapTrafficRunner extends ScenarioRunner<
logger,
{ eventName: "ContainerLoad", clientId: runConfig.childId },
async (_event) => {
return ac.getContainer(runConfig.docId, schema);
return ac.getContainer(runConfig.docId, schema, "2");
},
{ start: true, end: true, cancel: "generic" },
);
Expand Down
4 changes: 2 additions & 2 deletions azure/packages/test/scenario-runner/src/NestedMapRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export class NestedMapRunner extends ScenarioRunner<
logger,
{ eventName: "load" },
async () => {
return client.getContainer(docId, schema);
return client.getContainer(docId, schema, "2");
},
{ start: true, end: true, cancel: "generic" },
));
Expand Down Expand Up @@ -208,7 +208,7 @@ export class NestedMapRunner extends ScenarioRunner<
logger,
{ eventName: "create" },
async () => {
return client.createContainer(schema);
return client.createContainer(schema, "2");
},
{ start: true, end: true, cancel: "generic" },
));
Expand Down
8 changes: 6 additions & 2 deletions examples/benchmarks/tablebench/src/azure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ export async function initFluid() {
let container;

if (!location.hash) {
({ container } = await client.createContainer(containerSchema));
({ container } = await client.createContainer(containerSchema, "2"));

// TODO: Waiting for 'attach()' is a work around for https://dev.azure.com/fluidframework/internal/_workitems/edit/6805
await container.attach().then((containerId) => (location.hash = containerId));
} else {
({ container } = await client.getContainer(location.hash.substring(1), containerSchema));
({ container } = await client.getContainer(
location.hash.substring(1),
containerSchema,
"2",
));
}

return { tree: container.initialObjects.tree };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function createFluidContainer(
console.log("Creating new container...");
let createContainerResult: ContainerLoadResult;
try {
createContainerResult = await client.createContainer(containerSchema);
createContainerResult = await client.createContainer(containerSchema, "2");
} catch (error) {
console.error(`Encountered error creating Fluid container: "${error}".`);
throw error;
Expand Down Expand Up @@ -126,7 +126,7 @@ export async function loadExistingFluidContainer(
console.log("Loading existing container...");
let loadContainerResult: ContainerLoadResult;
try {
loadContainerResult = await client.getContainer(containerId, containerSchema);
loadContainerResult = await client.getContainer(containerId, containerSchema, "2");
} catch (error) {
console.error(`Encountered error loading Fluid container: "${error}".`);
throw error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ async function start(): Promise<void> {
if (createNew) {
// The client will create a new detached container using the schema
// A detached container will enable the app to modify the container before attaching it to the client
({ container, services } = await client.createContainer(containerSchema));
({ container, services } = await client.createContainer(containerSchema, "2"));
// const map1 = container.initialObjects.map1 as ISharedMap;
// map1.set("diceValue", 1);
// const map2 = container.initialObjects.map1 as ISharedMap;
Expand All @@ -160,7 +160,7 @@ async function start(): Promise<void> {
id = location.hash.slice(1);
// Use the unique container ID to fetch the container created earlier. It will already be connected to the
// collaboration session.
({ container, services } = await client.getContainer(id, containerSchema));
({ container, services } = await client.getContainer(id, containerSchema, "2"));
[diceRollerController1Props, diceRollerController2Props] =
createDiceRollerControllerPropsFromContainer(container);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ export async function createContainerAndRenderInElement(
// to store ops.
const { container, attach } = await getSessionStorageContainer(
documentId,
createDOProviderContainerRuntimeFactory({ schema: containerConfig }),
createDOProviderContainerRuntimeFactory({
schema: containerConfig,
compatibilityMode: "2",
}),
createNewFlag,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("reactSharedTreeView", () => {
// TODO: Ideally we would use a local-server service-client, but one does not appear to exist.
const tinyliciousClient = new TinyliciousClient();

const { container } = await tinyliciousClient.createContainer(containerSchema);
const { container } = await tinyliciousClient.createContainer(containerSchema, "2");
const tree = container.initialObjects.tree;
assert.equal(tree.tree.root.nuts, 5);
tree.tree.root.nuts += 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { IFluidLoadable } from '@fluidframework/core-interfaces';
import { IRuntimeFactory } from '@fluidframework/container-definitions/internal';
import { SharedObjectKind } from '@fluidframework/shared-object-base';

// @public
export type CompatibilityMode = "1" | "2";

// @public
export type ContainerAttachProps<T = unknown> = T;

Expand All @@ -27,6 +30,7 @@ export interface ContainerSchema {
// @internal
export function createDOProviderContainerRuntimeFactory(props: {
schema: ContainerSchema;
compatibilityMode: CompatibilityMode;
}): IRuntimeFactory;

// @internal
Expand Down
41 changes: 41 additions & 0 deletions packages/framework/fluid-static/src/compatibilityConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

import {
CompressionAlgorithms,
type IContainerRuntimeOptions,
} from "@fluidframework/container-runtime/internal";
import { FlushMode } from "@fluidframework/runtime-definitions/internal";
import type { CompatibilityMode } from "./types.js";

/**
* 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.
*/
export const compatibilityModeRuntimeOptions: Record<CompatibilityMode, IContainerRuntimeOptions> =
{
"1": {
// 1.x clients are compatible with TurnBased flushing, but here we elect to remain on Immediate flush mode
// as a work-around for inability to send batches larger than 1Mb. Immediate flushing keeps batches smaller as
// fewer messages will be included per flush.
flushMode: FlushMode.Immediate,
// Op compression is on by default but introduces a new type of op which is not compatible with 1.x clients.
compressionOptions: {
minimumBatchSizeInBytes: Number.POSITIVE_INFINITY, // disabled
compressionAlgorithm: CompressionAlgorithms.lz4,
},
// Grouped batching is on by default but introduces a new type of op which is not compatible with 1.x clients.
enableGroupedBatching: false,
// TODO: Include explicit disables for things that are currently off-by-default?
},
"2": {
// Explicit schema control explicitly makes the container incompatible with 1.x clients, to force their
// ejection from collaboration and prevent container corruption. It is off by default and must be explicitly enabled.
explicitSchemaControl: true,
// The runtime ID compressor is a prerequisite to use SharedTree but is off by default and must be explicitly enabled.
// It introduces a new type of op which is not compatible with 1.x clients.
enableRuntimeIdCompressor: "on",
},
};
25 changes: 13 additions & 12 deletions packages/framework/fluid-static/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ export {
} from "./fluidContainer.js";
export { createDOProviderContainerRuntimeFactory } from "./rootDataObject.js";
export { createServiceAudience } from "./serviceAudience.js";
export {
type ContainerSchema,
type ContainerAttachProps,
type IConnection,
type IMember,
type IRootDataObject,
type IServiceAudience,
type IServiceAudienceEvents,
type LoadableObjectRecord,
type MemberChangedListener,
type Myself,
type IProvideRootDataObject,
export type {
CompatibilityMode,
ContainerSchema,
ContainerAttachProps,
IConnection,
IMember,
IProvideRootDataObject,
IRootDataObject,
IServiceAudience,
IServiceAudienceEvents,
LoadableObjectRecord,
MemberChangedListener,
Myself,
} from "./types.js";
36 changes: 14 additions & 22 deletions packages/framework/fluid-static/src/rootDataObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
DataObjectFactory,
} from "@fluidframework/aqueduct/internal";
import { type IRuntimeFactory } from "@fluidframework/container-definitions/internal";
import { type ContainerRuntime } from "@fluidframework/container-runtime/internal";
import type { ContainerRuntime } from "@fluidframework/container-runtime/internal";
import { type IContainerRuntime } from "@fluidframework/container-runtime-definitions/internal";
import {
type FluidObject,
Expand All @@ -18,18 +18,19 @@ import {
type IResponse,
} from "@fluidframework/core-interfaces";
import { type IDirectory } from "@fluidframework/map/internal";
import { FlushMode } from "@fluidframework/runtime-definitions/internal";
import { RequestParser } from "@fluidframework/runtime-utils/internal";
import type { SharedObjectKind } from "@fluidframework/shared-object-base";
import type { ISharedObjectKind } from "@fluidframework/shared-object-base/internal";

import {
type DataObjectClass,
type ContainerSchema,
type IRootDataObject,
type LoadableObjectClass,
type LoadableObjectClassRecord,
type LoadableObjectRecord,
import { compatibilityModeRuntimeOptions } from "./compatibilityConfiguration.js";
import type {
CompatibilityMode,
ContainerSchema,
DataObjectClass,
IRootDataObject,
LoadableObjectClass,
LoadableObjectClassRecord,
LoadableObjectRecord,
} from "./types.js";
import {
isDataObjectClass,
Expand Down Expand Up @@ -169,8 +170,9 @@ const rootDataStoreId = "rootDOId";
*/
export function createDOProviderContainerRuntimeFactory(props: {
schema: ContainerSchema;
compatibilityMode: CompatibilityMode;
}): IRuntimeFactory {
return new DOProviderContainerRuntimeFactory(props.schema);
return new DOProviderContainerRuntimeFactory(props.schema, props.compatibilityMode);
}

/**
Expand All @@ -193,7 +195,7 @@ class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory {

private readonly initialObjects: LoadableObjectClassRecord;

public constructor(schema: ContainerSchema) {
public constructor(schema: ContainerSchema, compatibilityMode: CompatibilityMode) {
const [registryEntries, sharedObjects] = parseDataObjectsFromSharedObjects(schema);
const rootDataObjectFactory = new DataObjectFactory(
"rootDO",
Expand Down Expand Up @@ -231,17 +233,7 @@ class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory {
super({
registryEntries: [rootDataObjectFactory.registryEntry],
requestHandlers: [getDefaultObject],
// WARNING: These settigs are not compatible with FF 1.3 clients!
runtimeOptions: {
// temporary workaround to disable message batching until the message batch size issue is resolved
// resolution progress is tracked by the Feature 465 work item in AzDO
flushMode: FlushMode.Immediate,
// The runtime compressor is required to be on to use @fluidframework/tree.
enableRuntimeIdCompressor: "on",
// For now this was set to false to allow 1.x/2.x testing with AzureClient.
// Long term, this config will be set dynamically. See https://github.com/microsoft/FluidFramework/pull/20997.
explicitSchemaControl: false,
},
runtimeOptions: compatibilityModeRuntimeOptions[compatibilityMode],
provideEntryPoint,
});
this.rootDataObjectFactory = rootDataObjectFactory;
Expand Down
6 changes: 6 additions & 0 deletions packages/framework/fluid-static/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import type { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions
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
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime/container-runtime/src/containerRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2227,7 +2227,7 @@ export class ContainerRuntime
);

// Is document schema explicit control on?
const explitiSchemaControl = documentSchema?.runtime.explicitSchemaControl;
const explicitSchemaControl = documentSchema?.runtime.explicitSchemaControl;

const metadata: IContainerRuntimeMetadata = {
...this.createContainerMetadata,
Expand All @@ -2241,10 +2241,10 @@ export class ContainerRuntime
// runtimes (that preceed document schema control capabilities) to close container on load due to mismatch in
// last message's sequence number.
// See also lastMessageFromMetadata()
message: explitiSchemaControl
message: explicitSchemaControl
? ({ sequenceNumber: -1 } as any as ISummaryMetadataMessage)
: message,
lastMessage: explitiSchemaControl ? message : undefined,
lastMessage: explicitSchemaControl ? message : undefined,
documentSchema,
};

Expand Down
8 changes: 8 additions & 0 deletions packages/service-clients/azure-client/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,13 @@ module.exports = {
"unicorn/prevent-abbreviations": "off",
},
},
{
// Overrides for tests
files: ["src/test/*.spec.ts"],
rules: {
// Mocha tests should prefer regular functions, see https://mochajs.org/#arrow-functions
"prefer-arrow-callback": "off",
},
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
```ts

import { CompatibilityMode } from '@fluidframework/fluid-static/internal';
import { ContainerSchema } from '@fluidframework/fluid-static';
import { ICompressionStorageConfig } from '@fluidframework/driver-utils';
import { IConfigProviderBase } from '@fluidframework/core-interfaces';
Expand All @@ -21,16 +22,16 @@ import { ScopeType } from '@fluidframework/protocol-definitions';
// @public
export class AzureClient {
constructor(properties: AzureClientProps);
createContainer<const TContainerSchema extends ContainerSchema>(containerSchema: TContainerSchema): Promise<{
createContainer<const TContainerSchema extends ContainerSchema>(containerSchema: TContainerSchema, compatibilityMode: CompatibilityMode): Promise<{
container: IFluidContainer<TContainerSchema>;
services: AzureContainerServices;
}>;
getContainer<TContainerSchema extends ContainerSchema>(id: string, containerSchema: TContainerSchema): Promise<{
getContainer<TContainerSchema extends ContainerSchema>(id: string, containerSchema: TContainerSchema, compatibilityMode: CompatibilityMode): Promise<{
container: IFluidContainer<TContainerSchema>;
services: AzureContainerServices;
}>;
getContainerVersions(id: string, options?: AzureGetVersionsOptions): Promise<AzureContainerVersion[]>;
viewContainerVersion<TContainerSchema extends ContainerSchema>(id: string, containerSchema: TContainerSchema, version: AzureContainerVersion): Promise<{
viewContainerVersion<TContainerSchema extends ContainerSchema>(id: string, containerSchema: TContainerSchema, version: AzureContainerVersion, compatibilityMode: CompatibilityMode): Promise<{
container: IFluidContainer<TContainerSchema>;
}>;
}
Expand Down
Loading

0 comments on commit 2730787

Please sign in to comment.