diff --git a/.changeset/loud-dolls-appear.md b/.changeset/loud-dolls-appear.md new file mode 100644 index 000000000000..b8cd77f1d4de --- /dev/null +++ b/.changeset/loud-dolls-appear.md @@ -0,0 +1,8 @@ +--- +"@fluidframework/shared-object-base": minor +"__section": legacy +--- +Added an optional boolean parameter "fullTree" to SharedObject's summarizeCore method + +This parameter tells the shared object that it should generate a full tree summary, i.e., it must not summarize incrementally. +Currently no known `SharedObject`'s do incremental summaries; however, any that do exist or are made in the future must take this "fullTree" parameter into consideration to function correctly. diff --git a/packages/dds/shared-object-base/api-report/shared-object-base.legacy.alpha.api.md b/packages/dds/shared-object-base/api-report/shared-object-base.legacy.alpha.api.md index 75a8ea9d309a..667067939f8c 100644 --- a/packages/dds/shared-object-base/api-report/shared-object-base.legacy.alpha.api.md +++ b/packages/dds/shared-object-base/api-report/shared-object-base.legacy.alpha.api.md @@ -47,7 +47,7 @@ export abstract class SharedObject; - protected abstract summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext, incrementalSummaryContext?: IExperimentalIncrementalSummaryContext): ISummaryTreeWithStats; + protected abstract summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext, incrementalSummaryContext?: IExperimentalIncrementalSummaryContext, fullTree?: boolean): ISummaryTreeWithStats; } // @alpha @legacy diff --git a/packages/dds/shared-object-base/src/sharedObject.ts b/packages/dds/shared-object-base/src/sharedObject.ts index 620a61ba6308..7d26ef4e6bdf 100644 --- a/packages/dds/shared-object-base/src/sharedObject.ts +++ b/packages/dds/shared-object-base/src/sharedObject.ts @@ -842,7 +842,12 @@ export abstract class SharedObject< trackState: boolean = false, telemetryContext?: ITelemetryContext, ): ISummaryTreeWithStats { - const result = this.summarizeCore(this.serializer, telemetryContext); + const result = this.summarizeCore( + this.serializer, + telemetryContext, + undefined /* incrementalSummaryContext */, + fullTree, + ); this.incrementTelemetryMetric( blobCountPropertyName, result.stats.blobNodeCount, @@ -869,6 +874,7 @@ export abstract class SharedObject< this.serializer, telemetryContext, incrementalSummaryContext, + fullTree, ); this.incrementTelemetryMetric( blobCountPropertyName, @@ -937,6 +943,7 @@ export abstract class SharedObject< serializer: IFluidSerializer, telemetryContext?: ITelemetryContext, incrementalSummaryContext?: IExperimentalIncrementalSummaryContext, + fullTree?: boolean, ): ISummaryTreeWithStats; private incrementTelemetryMetric( diff --git a/packages/dds/shared-object-base/src/sharedObjectKernel.ts b/packages/dds/shared-object-base/src/sharedObjectKernel.ts index 433ffee05dcd..cbfa8d78c257 100644 --- a/packages/dds/shared-object-base/src/sharedObjectKernel.ts +++ b/packages/dds/shared-object-base/src/sharedObjectKernel.ts @@ -62,6 +62,7 @@ export interface SharedKernel { serializer: IFluidSerializer, telemetryContext: ITelemetryContext | undefined, incrementalSummaryContext: IExperimentalIncrementalSummaryContext | undefined, + fullTree?: boolean, ): ISummaryTreeWithStats; /** @@ -146,8 +147,14 @@ class SharedObjectFromKernel< serializer: IFluidSerializer, telemetryContext?: ITelemetryContext, incrementalSummaryContext?: IExperimentalIncrementalSummaryContext, + fullTree?: boolean, ): ISummaryTreeWithStats { - return this.#kernel.summarizeCore(serializer, telemetryContext, incrementalSummaryContext); + return this.#kernel.summarizeCore( + serializer, + telemetryContext, + incrementalSummaryContext, + fullTree, + ); } protected override initializeLocalCore(): void { diff --git a/packages/dds/tree/src/feature-libraries/detachedFieldIndexSummarizer.ts b/packages/dds/tree/src/feature-libraries/detachedFieldIndexSummarizer.ts index dbb3bedd5085..118fce9be33d 100644 --- a/packages/dds/tree/src/feature-libraries/detachedFieldIndexSummarizer.ts +++ b/packages/dds/tree/src/feature-libraries/detachedFieldIndexSummarizer.ts @@ -6,6 +6,7 @@ import { bufferToString } from "@fluid-internal/client-utils"; import type { IChannelStorageService } from "@fluidframework/datastore-definitions/internal"; import type { + IExperimentalIncrementalSummaryContext, ISummaryTreeWithStats, ITelemetryContext, } from "@fluidframework/runtime-definitions/internal"; @@ -32,23 +33,15 @@ export class DetachedFieldIndexSummarizer implements Summarizable { public constructor(private readonly detachedFieldIndex: DetachedFieldIndex) {} - public getAttachSummary( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - ): ISummaryTreeWithStats { + public summarize(props: { + stringify: SummaryElementStringifier; + fullTree?: boolean; + trackState?: boolean; + telemetryContext?: ITelemetryContext; + incrementalSummaryContext?: IExperimentalIncrementalSummaryContext; + }): ISummaryTreeWithStats { const data = this.detachedFieldIndex.encode(); - return createSingleBlobSummary(detachedFieldIndexBlobKey, stringify(data)); - } - - public async summarize( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - ): Promise { - return this.getAttachSummary(stringify, fullTree, trackState, telemetryContext); + return createSingleBlobSummary(detachedFieldIndexBlobKey, props.stringify(data)); } public async load( diff --git a/packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts b/packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts index a23b4d3afe57..581a89cb46b1 100644 --- a/packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts +++ b/packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts @@ -8,6 +8,7 @@ import { assert } from "@fluidframework/core-utils/internal"; import type { IChannelStorageService } from "@fluidframework/datastore-definitions/internal"; import type { IIdCompressor } from "@fluidframework/id-compressor"; import type { + IExperimentalIncrementalSummaryContext, ISummaryTreeWithStats, ITelemetryContext, } from "@fluidframework/runtime-definitions/internal"; @@ -95,22 +96,14 @@ export class ForestSummarizer implements Summarizable { return stringify(encoded); } - public getAttachSummary( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - ): ISummaryTreeWithStats { - return createSingleBlobSummary(treeBlobKey, this.getTreeString(stringify)); - } - - public async summarize( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - ): Promise { - return createSingleBlobSummary(treeBlobKey, this.getTreeString(stringify)); + public summarize(props: { + stringify: SummaryElementStringifier; + fullTree?: boolean; + trackState?: boolean; + telemetryContext?: ITelemetryContext; + incrementalSummaryContext?: IExperimentalIncrementalSummaryContext; + }): ISummaryTreeWithStats { + return createSingleBlobSummary(treeBlobKey, this.getTreeString(props.stringify)); } public async load( diff --git a/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts b/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts index 143ef16b81e3..551cd12c52fa 100644 --- a/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts +++ b/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts @@ -51,15 +51,18 @@ export class SchemaSummarizer implements Summarizable { }); } - public getAttachSummary( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - incrementalSummaryContext?: IExperimentalIncrementalSummaryContext | undefined, - ): ISummaryTreeWithStats { + public summarize(props: { + stringify: SummaryElementStringifier; + fullTree?: boolean; + trackState?: boolean; + telemetryContext?: ITelemetryContext; + incrementalSummaryContext?: IExperimentalIncrementalSummaryContext; + }): ISummaryTreeWithStats { + const incrementalSummaryContext = props.incrementalSummaryContext; const builder = new SummaryTreeBuilder(); + const fullTree = props.fullTree ?? false; if ( + !fullTree && incrementalSummaryContext !== undefined && this.schemaIndexLastChangedSeq !== undefined && incrementalSummaryContext.latestSummarySequenceNumber >= this.schemaIndexLastChangedSeq @@ -67,7 +70,7 @@ export class SchemaSummarizer implements Summarizable { builder.addHandle( schemaStringKey, SummaryType.Blob, - `${incrementalSummaryContext.summaryPath}/indexes/${this.key}/${schemaStringKey}`, + `${incrementalSummaryContext.summaryPath}/${schemaStringKey}`, ); } else { const dataString = JSON.stringify(this.codec.encode(this.schema)); @@ -76,16 +79,6 @@ export class SchemaSummarizer implements Summarizable { return builder.getSummaryTree(); } - public async summarize( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - incrementalSummaryContext?: IExperimentalIncrementalSummaryContext | undefined, - ): Promise { - throw new Error("Method not implemented."); - } - public async load( services: IChannelStorageService, parse: SummaryElementParser, diff --git a/packages/dds/tree/src/shared-tree-core/editManagerSummarizer.ts b/packages/dds/tree/src/shared-tree-core/editManagerSummarizer.ts index c475f0604596..139bd1e28619 100644 --- a/packages/dds/tree/src/shared-tree-core/editManagerSummarizer.ts +++ b/packages/dds/tree/src/shared-tree-core/editManagerSummarizer.ts @@ -8,6 +8,7 @@ import { assert } from "@fluidframework/core-utils/internal"; import type { IChannelStorageService } from "@fluidframework/datastore-definitions/internal"; import type { IIdCompressor } from "@fluidframework/id-compressor"; import type { + IExperimentalIncrementalSummaryContext, ISummaryTreeWithStats, ITelemetryContext, } from "@fluidframework/runtime-definitions/internal"; @@ -49,22 +50,14 @@ export class EditManagerSummarizer implements Summarizable { private readonly schemaAndPolicy?: SchemaAndPolicy, ) {} - public getAttachSummary( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - ): ISummaryTreeWithStats { - return this.summarizeCore(stringify); - } - - public async summarize( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - ): Promise { - return this.summarizeCore(stringify); + public summarize(props: { + stringify: SummaryElementStringifier; + fullTree?: boolean; + trackState?: boolean; + telemetryContext?: ITelemetryContext; + incrementalSummaryContext?: IExperimentalIncrementalSummaryContext; + }): ISummaryTreeWithStats { + return this.summarizeCore(props.stringify); } private summarizeCore(stringify: SummaryElementStringifier): ISummaryTreeWithStats { diff --git a/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts b/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts index f647ee28d2f3..02df39d03fed 100644 --- a/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts +++ b/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts @@ -212,20 +212,30 @@ export class SharedTreeCore serializer: IFluidSerializer, telemetryContext?: ITelemetryContext, incrementalSummaryContext?: IExperimentalIncrementalSummaryContext, + fullTree?: boolean, ): ISummaryTreeWithStats { const builder = new SummaryTreeBuilder(); const summarizableBuilder = new SummaryTreeBuilder(); // Merge the summaries of all summarizables together under a single ISummaryTree for (const s of this.summarizables) { + // Add the summarizable's path in the summary tree to the incremental summary context's + // summary path, so that the summarizable can use it to generate incremental summaries. + const childIncrementalSummaryContext = + incrementalSummaryContext === undefined + ? undefined + : { + ...incrementalSummaryContext, + summaryPath: `${incrementalSummaryContext.summaryPath}/${summarizablesTreeKey}/${s.key}`, + }; summarizableBuilder.addWithStats( s.key, - s.getAttachSummary( - (contents) => serializer.stringify(contents, this.sharedObject.handle), - undefined, - undefined, + s.summarize({ + stringify: (contents: unknown) => + serializer.stringify(contents, this.sharedObject.handle), + fullTree, telemetryContext, - incrementalSummaryContext, - ), + incrementalSummaryContext: childIncrementalSummaryContext, + }), ); } @@ -434,28 +444,27 @@ export interface Summarizable { */ readonly key: string; - /** - * {@inheritDoc @fluidframework/datastore-definitions#(IChannel:interface).getAttachSummary} - * @param stringify - Serializes the contents of the component (including {@link (IFluidHandle:interface)}s) for storage. - */ - getAttachSummary( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - incrementalSummaryContext?: IExperimentalIncrementalSummaryContext, - ): ISummaryTreeWithStats; - /** * {@inheritDoc @fluidframework/datastore-definitions#(IChannel:interface).summarize} * @param stringify - Serializes the contents of the component (including {@link (IFluidHandle:interface)}s) for storage. + * @param fullTree - A flag indicating whether the attempt should generate a full + * summary tree without any handles for unchanged subtrees. It should only be set to true when generating + * a summary from the entire container. The default value is false. + * @param trackState - An optimization for tracking state of objects across summaries. If the state + * of an object did not change since last successful summary, an + * {@link @fluidframework/protocol-definitions#ISummaryHandle} can be used + * instead of re-summarizing it. If this is `false`, the expectation is that you should never + * send an `ISummaryHandle`, since you are not expected to track state. The default value is true. + * @param telemetryContext - See {@link @fluidframework/runtime-definitions#ITelemetryContext}. + * @param incrementalSummaryContext - See {@link @fluidframework/runtime-definitions#IExperimentalIncrementalSummaryContext}. */ - summarize( - stringify: SummaryElementStringifier, - fullTree?: boolean, - trackState?: boolean, - telemetryContext?: ITelemetryContext, - ): Promise; + summarize(props: { + stringify: SummaryElementStringifier; + fullTree?: boolean; + trackState?: boolean; + telemetryContext?: ITelemetryContext; + incrementalSummaryContext?: IExperimentalIncrementalSummaryContext; + }): ISummaryTreeWithStats; /** * Allows the component to perform custom loading. The storage service is scoped to this component and therefore diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts index ff566665b78d..ffe288d6f0a5 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts @@ -194,7 +194,7 @@ describe("End to end chunked encoding", () => { ); // This function is declared in the test to have access to the original uniform chunk for comparison. - function stringifier(content: unknown) { + function stringify(content: unknown) { const insertedChunk = decode((content as Format).fields as EncodedFieldBatch, { idCompressor, originatorId: idCompressor.localSessionId, @@ -203,7 +203,7 @@ describe("End to end chunked encoding", () => { assert(chunk.isShared()); return JSON.stringify(content); } - forestSummarizer.getAttachSummary(stringifier); + forestSummarizer.summarize({ stringify }); }); // See note on above test. @@ -227,7 +227,7 @@ describe("End to end chunked encoding", () => { ); // This function is declared in the test to have access to the original uniform chunk for comparison. - function stringifier(content: unknown) { + function stringify(content: unknown) { const insertedChunk = decode((content as Format).fields as EncodedFieldBatch, { idCompressor, originatorId: idCompressor.localSessionId, @@ -236,7 +236,7 @@ describe("End to end chunked encoding", () => { assert(chunk.isShared()); return JSON.stringify(content); } - forestSummarizer.getAttachSummary(stringifier); + forestSummarizer.summarize({ stringify }); }); describe("identifier field encoding", () => { @@ -254,10 +254,7 @@ describe("End to end chunked encoding", () => { testIdCompressor, ); - function stringifier(content: unknown) { - return JSON.stringify(content); - } - const { summary } = forestSummarizer.getAttachSummary(stringifier); + const { summary } = forestSummarizer.summarize({ stringify: JSON.stringify }); const tree = summary.tree.ForestTree; assert(tree.type === SummaryType.Blob); const treeContent = JSON.parse(tree.content as string); @@ -284,10 +281,7 @@ describe("End to end chunked encoding", () => { testIdCompressor, ); - function stringifier(content: unknown) { - return JSON.stringify(content); - } - const { summary } = forestSummarizer.getAttachSummary(stringifier); + const { summary } = forestSummarizer.summarize({ stringify: JSON.stringify }); const tree = summary.tree.ForestTree; assert(tree.type === SummaryType.Blob); const treeContent = JSON.parse(tree.content as string); @@ -309,10 +303,7 @@ describe("End to end chunked encoding", () => { testIdCompressor, ); - function stringifier(content: unknown) { - return JSON.stringify(content); - } - const { summary } = forestSummarizer.getAttachSummary(stringifier); + const { summary } = forestSummarizer.summarize({ stringify: JSON.stringify }); const tree = summary.tree.ForestTree; assert(tree.type === SummaryType.Blob); const treeContent = JSON.parse(tree.content as string); diff --git a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts index 060c524c1084..c8f422227a6b 100644 --- a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts @@ -586,7 +586,7 @@ describe("SharedTreeCore", () => { interface MockSummarizableEvents extends IEvent { (event: "loaded", listener: (blobContents?: string) => void): void; - (event: "summarize" | "summarizeAttached" | "summarizeAsync" | "gcRequested"): void; + (event: "summarize" | "summarizeAttached" | "gcRequested"): void; } class MockSummarizable @@ -613,24 +613,14 @@ describe("SharedTreeCore", () => { } } - public getAttachSummary( - stringify: SummaryElementStringifier, - fullTree?: boolean | undefined, - trackState?: boolean | undefined, - telemetryContext?: ITelemetryContext | undefined, - ): ISummaryTreeWithStats { + public summarize(props: { + stringify: SummaryElementStringifier; + fullTree?: boolean | undefined; + trackState?: boolean | undefined; + telemetryContext?: ITelemetryContext | undefined; + }): ISummaryTreeWithStats { this.emit("summarizeAttached"); - return this.summarizeCore(stringify); - } - - public async summarize( - stringify: SummaryElementStringifier, - fullTree?: boolean | undefined, - trackState?: boolean | undefined, - telemetryContext?: ITelemetryContext | undefined, - ): Promise { - this.emit("summarizeAsync"); - return this.summarizeCore(stringify); + return this.summarizeCore(props.stringify); } private summarizeCore(stringify: SummaryElementStringifier): ISummaryTreeWithStats {