Skip to content

Commit

Permalink
Revert change to remove encoding of handles out of the DDS (#20974)
Browse files Browse the repository at this point in the history
Reverts #19242 due to a bug where duplicate attach ops are sent because
of the change to encoding handles.
  • Loading branch information
jzaffiro committed May 3, 2024
1 parent 9c0167b commit c8b2841
Show file tree
Hide file tree
Showing 36 changed files with 230 additions and 435 deletions.
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
Loading

0 comments on commit c8b2841

Please sign in to comment.