Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert change to remove encoding of handles out of the DDS #20974

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ export interface IValueChanged {

// @internal
export class LocalValueMaker {
constructor();
constructor(serializer: IFluidSerializer);
fromInMemory(value: unknown): ILocalValue;
fromSerializable(serializable: ISerializableValue, serializer: IFluidSerializer, bind: IFluidHandle): ILocalValue;
fromSerializable(serializable: ISerializableValue): ILocalValue;
}

// @internal @sealed
Expand Down
21 changes: 8 additions & 13 deletions experimental/dds/attributable-map/src/localValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,34 +114,29 @@ export class PlainLocalValue implements ILocalValue {
export class LocalValueMaker {
/**
* Create a new LocalValueMaker.
* @param serializer - The serializer to serialize / parse handles.
*/
public constructor() {}
public constructor(private readonly serializer: IFluidSerializer) {}

/**
* Create a new local value from an incoming serialized value.
* @param serializable - The serializable value to make local
*/
public fromSerializable(
// eslint-disable-next-line import/no-deprecated
serializable: ISerializableValue,
serializer: IFluidSerializer,
bind: IFluidHandle,
): ILocalValue {
// eslint-disable-next-line import/no-deprecated
public fromSerializable(serializable: ISerializableValue): ILocalValue {
// Migrate from old shared value to handles
if (serializable.type === ValueType[ValueType.Shared]) {
serializable.type = ValueType[ValueType.Plain];
const handle: ISerializedHandle = {
type: "__fluid_handle__",
url: serializable.value as string,
};

// NOTE: here we require the use of `parseHandles` because the roundtrip
// through a string is necessary to resolve the absolute path of
// legacy handles (`ValueType.Shared`)
serializable.value = serializer.encode(parseHandles(handle, serializer), bind);
serializable.value = handle;
}

return new PlainLocalValue(serializable.value);
const translatedValue: unknown = parseHandles(serializable.value, this.serializer);

return new PlainLocalValue(translatedValue);
}

/**
Expand Down
31 changes: 11 additions & 20 deletions experimental/dds/attributable-map/src/mapKernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { IFluidHandle } from "@fluidframework/core-interfaces";
import { IFluidSerializer, ValueType, bindHandles } from "@fluidframework/shared-object-base";
import { IFluidSerializer, ValueType } from "@fluidframework/shared-object-base";
import { TypedEventEmitter } from "@fluid-internal/client-utils";
import { assert, unreachableCase } from "@fluidframework/core-utils";
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
Expand Down Expand Up @@ -193,7 +193,7 @@ export class AttributableMapKernel {
private readonly isAttached: () => boolean,
private readonly eventEmitter: TypedEventEmitter<ISharedMapEvents>,
) {
this.localValueMaker = new LocalValueMaker();
this.localValueMaker = new LocalValueMaker(serializer);
this.messageHandlers = this.getMessageHandlers();
this.attribution = new Map();
}
Expand Down Expand Up @@ -305,24 +305,21 @@ export class AttributableMapKernel {

// Create a local value and serialize it.
const localValue = this.localValueMaker.fromInMemory(value);
const serializableValue = makeSerializable(localValue, this.serializer, this.handle);

// Set the value and attribution locally.
const previousValue = this.setCore(key, localValue, true);
this.setAttribution(key);

// If we are not attached, don't submit the op.
if (!this.isAttached()) {
// this is necessary to bind the potential handles in the value
// to this DDS, as we do not walk the object normally unless we
// are attached
bindHandles(localValue.value, this.serializer, this.handle);
return;
}

const op: IMapSetOperation = {
key,
type: "set",
value: { type: localValue.type, value: localValue.value as unknown },
value: serializableValue,
};
this.submitMapKeyMessage(op, previousValue);
}
Expand Down Expand Up @@ -455,9 +452,7 @@ export class AttributableMapKernel {
* @param json - A JSON string containing serialized map data
*/
public populateFromSerializable(json: IMapDataObjectSerializable): void {
for (const [key, serializable] of Object.entries(
this.serializer.decode(json) as IMapDataObjectSerializable,
)) {
for (const [key, serializable] of Object.entries(json)) {
const localValue = {
key,
value: this.makeLocal(key, serializable),
Expand All @@ -479,6 +474,10 @@ export class AttributableMapKernel {
}
}

public populate(json: string): void {
this.populateFromSerializable(JSON.parse(json) as IMapDataObjectSerializable);
}

/**
* Submit the given op if a handler is registered.
* @param op - The operation to attempt to submit
Expand Down Expand Up @@ -507,11 +506,7 @@ export class AttributableMapKernel {
break;
}
case "set": {
this.set(
op.key,
this.localValueMaker.fromSerializable(op.value, this.serializer, this.handle)
.value,
);
this.set(op.key, this.localValueMaker.fromSerializable(op.value).value);
break;
}
default:
Expand Down Expand Up @@ -674,11 +669,7 @@ export class AttributableMapKernel {
serializable.type === ValueType[ValueType.Plain] ||
serializable.type === ValueType[ValueType.Shared]
) {
return this.localValueMaker.fromSerializable(
serializable,
this.serializer,
this.handle,
);
return this.localValueMaker.fromSerializable(serializable);
} else {
throw new Error("Unknown local value type");
}
Expand Down
2 changes: 1 addition & 1 deletion experimental/dds/tree/api-report/experimental-tree.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ export class SharedTreeFactory implements IChannelFactory {
static Attributes: IChannelAttributes;
get attributes(): IChannelAttributes;
create(runtime: IFluidDataStoreRuntime, id: string): SharedTree;
load(runtime: IFluidDataStoreRuntime, id: string, services: IChannelServices, _channelAttributes: Readonly<IChannelAttributes>): Promise<SharedTree>;
load(runtime: IFluidDataStoreRuntime, id: string, services: IChannelServices, _channelAttributes: Readonly<IChannelAttributes>): Promise<IChannel>;
static Type: string;
get type(): string;
}
Expand Down
31 changes: 25 additions & 6 deletions experimental/dds/tree/src/SharedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
IChannelFactory,
IChannelAttributes,
IChannelServices,
IChannel,
} from '@fluidframework/datastore-definitions';
import { AttachState } from '@fluidframework/container-definitions';
import {
Expand Down Expand Up @@ -245,7 +246,7 @@ export class SharedTreeFactory implements IChannelFactory {
id: string,
services: IChannelServices,
_channelAttributes: Readonly<IChannelAttributes>
): Promise<SharedTree> {
): Promise<IChannel> {
const sharedTree = this.createSharedTree(runtime, id);
await sharedTree.load(services);
return sharedTree;
Expand Down Expand Up @@ -1066,14 +1067,28 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
// TODO:Type Safety: Improve type safety around op sending/parsing (e.g. discriminated union over version field somehow)
switch (op.version) {
case WriteFormat.v0_0_2:
return this.encoder_0_0_2.decodeEditOp(op, (x) => x, this);
return this.encoder_0_0_2.decodeEditOp(op, this.encodeSemiSerializedEdit.bind(this), this);
case WriteFormat.v0_1_1:
return this.encoder_0_1_1.decodeEditOp(op, (x) => x, this.idNormalizer, this.interner);
return this.encoder_0_1_1.decodeEditOp(
op,
this.encodeSemiSerializedEdit.bind(this),
this.idNormalizer,
this.interner
);
default:
fail('Unknown op version');
}
}

private encodeSemiSerializedEdit<T>(semiSerializedEdit: Edit<T>): Edit<T> {
// semiSerializedEdit may have handles which have been replaced by `serializer.encode`.
// Since there is no API to un-replace them except via parse, re-stringify the edit, then parse it.
// Stringify using JSON, not IFluidSerializer since OPs use JSON directly.
// TODO:Performance:#48025: Avoid this serialization round trip.
const encodedEdit: Edit<T> = this.serializer.parse(JSON.stringify(semiSerializedEdit));
return encodedEdit;
}

private processSequencedEdit(edit: Edit<ChangeInternal>, message: ISequencedDocumentMessage): void {
const { id: editId } = edit;
const wasLocalEdit = this.editLog.isLocalEdit(editId);
Expand Down Expand Up @@ -1366,13 +1381,13 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
if (this.isAttached()) {
switch (this.writeFormat) {
case WriteFormat.v0_0_2:
this.submitOp(this.encoder_0_0_2.encodeEditOp(edit, (x) => x, this));
this.submitOp(this.encoder_0_0_2.encodeEditOp(edit, this.serializeEdit.bind(this), this));
break;
case WriteFormat.v0_1_1:
this.submitOp(
this.encoder_0_1_1.encodeEditOp(
edit,
(x) => x,
this.serializeEdit.bind(this),
this.idCompressor.takeNextCreationRange(),
this.idNormalizer,
this.interner
Expand All @@ -1385,6 +1400,10 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
}
}

private serializeEdit<TChange>(preparedEdit: Edit<TChange>): Edit<TChange> {
return this.serializer.encode(preparedEdit, this.handle) as Edit<TChange>;
}

/** A type-safe `submitLocalMessage` wrapper to enforce op format */
private submitOp(content: SharedTreeOp | SharedTreeOp_0_0_2, localOpMetadata: unknown = undefined): void {
assert(
Expand Down Expand Up @@ -1472,7 +1491,7 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI

stashedEdit = this.encoder_0_1_1.decodeEditOp(
sharedTreeOp,
(x) => x,
this.encodeSemiSerializedEdit.bind(this),
normalizer,
this.interner
);
Expand Down
4 changes: 2 additions & 2 deletions experimental/dds/tree/src/migration-shim/migrationShim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,12 @@ export class MigrationShim extends EventEmitterWithErrorHandling<IMigrationEvent
this.runtime.attachState === AttachState.Detached
? new NoDeltasChannelServices(services)
: this.generateShimServicesOnce(services);
this._legacyTree = await this.legacyTreeFactory.load(
this._legacyTree = (await this.legacyTreeFactory.load(
this.runtime,
this.id,
shimServices,
this.legacyTreeFactory.attributes
);
)) as LegacySharedTree;
}
public create(): void {
this._legacyTree = this.legacyTreeFactory.create(this.runtime, this.id);
Expand Down
21 changes: 13 additions & 8 deletions packages/dds/cell/src/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ export class SharedCell<T = any>
* {@inheritDoc ISharedCell.set}
*/
public set(value: Serializable<T>): void {
// Serialize the value if required.
const operationValue: ICellValue = {
value: this.serializer.encode(value, this.handle),
};

// Set the value locally.
const previousValue = this.setCore(value);
this.setAttribution();
Expand All @@ -145,10 +150,6 @@ export class SharedCell<T = any>
return;
}

const operationValue: ICellValue = {
value,
};

const op: ISetCellOperation = {
type: "setCell",
value: operationValue,
Expand Down Expand Up @@ -225,8 +226,7 @@ export class SharedCell<T = any>
protected async loadCore(storage: IChannelStorageService): Promise<void> {
const content = await readAndParse<ICellValue>(storage, snapshotFileName);

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.data = this.serializer.decode(content.value);
this.data = this.decode(content);
this.attribution = content.attribution;
}

Expand All @@ -250,7 +250,7 @@ export class SharedCell<T = any>
private applyInnerOp(content: ICellOperation): Serializable<T> | undefined {
switch (content.type) {
case "setCell": {
return this.setCore(content.value.value as Serializable<T>);
return this.setCore(this.decode(content.value));
}

case "deleteCell": {
Expand Down Expand Up @@ -322,6 +322,11 @@ export class SharedCell<T = any>
return previousLocalValue;
}

private decode(cellValue: ICellValue): Serializable<T> {
const value = cellValue.value;
return this.serializer.decode(value) as Serializable<T>;
}

private createLocalOpMetadata(
op: ICellOperation,
previousValue?: Serializable<T>,
Expand All @@ -346,7 +351,7 @@ export class SharedCell<T = any>
break;
}
case "setCell": {
this.set(cellContent.value.value as Serializable<T>);
this.set(this.decode(cellContent.value));
break;
}
default: {
Expand Down
6 changes: 3 additions & 3 deletions packages/dds/cell/src/cellFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ import { pkgVersion } from "./packageVersion.js";

/**
* {@link @fluidframework/datastore-definitions#IChannelFactory} for {@link ISharedCell}.
*
* @sealed
*
* @internal
* @sealed
*/
export class CellFactory implements IChannelFactory {
/**
Expand Down Expand Up @@ -51,6 +49,7 @@ export class CellFactory implements IChannelFactory {

/**
* {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.load}
* @internal
*/
public async load(
runtime: IFluidDataStoreRuntime,
Expand All @@ -65,6 +64,7 @@ export class CellFactory implements IChannelFactory {

/**
* {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.create}
* @internal
*/
public create(document: IFluidDataStoreRuntime, id: string): ISharedCell {
const cell = new SharedCell(id, document, this.attributes);
Expand Down
4 changes: 2 additions & 2 deletions packages/dds/map/api-report/map.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ export interface IValueChanged {

// @alpha
export class LocalValueMaker {
constructor();
constructor(serializer: IFluidSerializer);
fromInMemory(value: unknown): ILocalValue;
fromSerializable(serializable: ISerializableValue, serializer: IFluidSerializer, bind: IFluidHandle): ILocalValue;
fromSerializable(serializable: ISerializableValue): ILocalValue;
}

// @alpha @sealed
Expand Down