Skip to content

Commit

Permalink
revert: "Enable Synchronous Child Datastore Creation (#22962)" (#23133)
Browse files Browse the repository at this point in the history
and collateral "Tag asserts for release (#23128)"

This reverts commits 67b5e4d and
eefaaf3.
  • Loading branch information
jason-ha authored Nov 19, 2024
1 parent 3cd1f01 commit b92a003
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 577 deletions.
78 changes: 0 additions & 78 deletions RELEASE_NOTES/2.10.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
- [Key Features and Use Cases:](#key-features-and-use-cases)
- [Configuration and Compatibility Requirements:](#configuration-and-compatibility-requirements)
- [Usage Example:](#usage-example)
- [Enable Synchronous Child Datastore Creation (#22962)](#enable-synchronous-child-datastore-creation-22962)
- [Overview](#overview)
- [Key Benefits](#key-benefits)
- [Use Cases](#use-cases)
Expand Down Expand Up @@ -83,83 +82,6 @@ Affected packages:

[⬆️ Table of contents](#contents)

### Enable Synchronous Child Datastore Creation ([#22962](https://github.com/microsoft/FluidFramework/issues/22962))

## Overview

This feature introduces a new pattern for creating datastores synchronously within the Fluid Framework. It allows for the synchronous creation of a child datastore from an existing datastore, provided that the child datastore is available synchronously via the existing datastore's registry and that the child's factory supports synchronous creation. This method also ensures strong typing for the consumer.

In this context, "child" refers specifically to the organization of factories and registries, not to any hierarchical or hosting relationship between datastores. The parent datastore does not control the runtime behaviors of the child datastore beyond its creation.

The synchronous creation of child datastores enhances the flexibility of datastore management within the Fluid Framework. It ensures type safety and provides a different way to manage datastores within a container. However, it is important to consider the overhead associated with datastores, as they are stored, summarized, garbage collected, loaded, and referenced independently. This overhead should be justified by the scenario's requirements.

Datastores offer increased capabilities, such as the ability to reference them via handles, allowing multiple references to exist and enabling those references to be moved, swapped, or changed. Additionally, datastores are garbage collected after becoming unreferenced, which can simplify final cleanup across clients. This is in contrast to subdirectories in a shared directory, which do not have native capabilities for referencing or garbage collection but are very low overhead to create.

Synchronous creation relies on both the factory and the datastore to support it. This means that asynchronous operations, such as resolving handles, some browser API calls, consensus-based operations, or other asynchronous tasks, cannot be performed during the creation flow. Therefore, synchronous child datastore creation is best limited to scenarios where the existing asynchronous process cannot be used, such as when a new datastore must be created in direct response to synchronous user input.

## Key Benefits

- **Synchronous Creation**: Allows for the immediate creation of child datastores without waiting for asynchronous operations.
- **Strong Typing**: Ensures type safety and better developer experience by leveraging TypeScript's type system.

## Use Cases

### Example 1: Creating a Child Datastore

In this example, we demonstrate how to support creating a child datastore synchronously from a parent datastore.

```typescript
/**
* This is the parent DataObject, which is also a datastore. It has a
* synchronous method to create child datastores, which could be called
* in response to synchronous user input, like a key press.
*/
class ParentDataObject extends DataObject {
get ParentDataObject() {
return this;
}
protected override async initializingFirstTime(): Promise<void> {
// create synchronously during initialization
this.createChild("parentCreation");
}

createChild(name: string): ChildDataStore {
assert(
this.context.createChildDataStore !== undefined,
"this.context.createChildDataStore",
);
// creates a detached context with a factory who's package path is the same
// as the current datastore, but with another copy of its own type.
const { entrypoint } = this.context.createChildDataStore(
ChildDataStoreFactory.instance,
);
const dir = this.root.createSubDirectory("children");
dir.set(name, entrypoint.handle);
entrypoint.setProperty("childValue", name);

return entrypoint;
}

getChild(name: string): IFluidHandle<ChildDataStore> | undefined {
const dir = this.root.getSubDirectory("children");
return dir?.get<IFluidHandle<ChildDataStore>>(name);
}
}
```

For a complete example see the follow test: <https://github.com/microsoft/FluidFramework/blob/main/packages/test/local-server-tests/src/test/synchronousDataStoreCreation.spec.ts>

#### Change details

Commit: [`67b5e4d`](https://github.com/microsoft/FluidFramework/commit/67b5e4dca8ae7ae2b2878ecb289e08624f467129)

Affected packages:

- @fluidframework/container-runtime
- @fluidframework/runtime-definitions

[⬆️ Table of contents](#contents)

## 🌳 SharedTree DDS Changes

### Provide more comprehensive replacement to the `commitApplied` event ([#22977](https://github.com/microsoft/FluidFramework/issues/22977))
Expand Down
64 changes: 1 addition & 63 deletions packages/runtime/container-runtime/src/dataStoreContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@ import {
type IEvent,
} from "@fluidframework/core-interfaces";
import { type IFluidHandleInternal } from "@fluidframework/core-interfaces/internal";
import {
assert,
isPromiseLike,
LazyPromise,
unreachableCase,
} from "@fluidframework/core-utils/internal";
import { assert, LazyPromise, unreachableCase } from "@fluidframework/core-utils/internal";
import { IClientDetails, IQuorumClients } from "@fluidframework/driver-definitions";
import {
IDocumentStorageService,
Expand Down Expand Up @@ -60,7 +55,6 @@ import {
IInboundSignalMessage,
type IPendingMessagesState,
type IRuntimeMessageCollection,
type IFluidDataStoreFactory,
} from "@fluidframework/runtime-definitions/internal";
import {
addBlobToSummary,
Expand All @@ -71,7 +65,6 @@ import {
LoggingError,
MonitoringContext,
ThresholdCounter,
UsageError,
createChildMonitoringContext,
extractSafePropertiesFromMessage,
generateStack,
Expand Down Expand Up @@ -524,43 +517,6 @@ export abstract class FluidDataStoreContext
return factory;
}

createChildDataStore<T extends IFluidDataStoreFactory>(
childFactory: T,
): ReturnType<Exclude<T["createDataStore"], undefined>> {
const maybe = this.registry?.get(childFactory.type);

const isUndefined = maybe === undefined;
const isPromise = isPromiseLike(maybe);
const diffInstance = isPromise || maybe?.IFluidDataStoreFactory !== childFactory;

if (isUndefined || isPromise || diffInstance) {
throw new UsageError(
"The provided factory instance must be synchronously available as a child of this datastore",
{ isUndefined, isPromise, diffInstance },
);
}
if (childFactory?.createDataStore === undefined) {
throw new UsageError("createDataStore must exist on the provided factory", {
noCreateDataStore: true,
});
}

const context = this._containerRuntime.createDetachedDataStore([
...this.packagePath,
childFactory.type,
]);
assert(
context instanceof LocalDetachedFluidDataStoreContext,
0xa81 /* must be a LocalDetachedFluidDataStoreContext */,
);

const created = childFactory.createDataStore(context) as ReturnType<
Exclude<T["createDataStore"], undefined>
>;
context.unsafe_AttachRuntimeSync(created.runtime);
return created;
}

private async realizeCore(existing: boolean) {
const details = await this.getInitialSnapshotDetails();
// Base snapshot is the baseline where pending ops are applied to.
Expand Down Expand Up @@ -1472,24 +1428,6 @@ export class LocalDetachedFluidDataStoreContext
return this.channelToDataStoreFn(await this.channelP);
}

/**
* This method provides a synchronous path for binding a runtime to the context.
*
* Due to its synchronous nature, it is unable to validate that the runtime
* represents a datastore which is instantiable by remote clients. This could
* happen if the runtime's package path does not return a factory when looked up
* in the container runtime's registry, or if the runtime's entrypoint is not
* properly initialized. As both of these validation's are asynchronous to preform.
*
* If used incorrectly, this function can result in permanent data corruption.
*/
public unsafe_AttachRuntimeSync(channel: IFluidDataStoreChannel) {
this.channelP = Promise.resolve(channel);
this.processPendingOps(channel);
this.completeBindingRuntime(channel);
return this.channelToDataStoreFn(channel);
}

public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
if (this.detachedRuntimeCreation) {
throw new Error(
Expand Down
7 changes: 1 addition & 6 deletions packages/runtime/container-runtime/src/dataStoreRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@ export class FluidDataStoreRegistry implements IFluidDataStoreRegistry {
}
}

public get(
name: string,
):
| Promise<FluidDataStoreRegistryEntry | undefined>
| FluidDataStoreRegistryEntry
| undefined {
public async get(name: string): Promise<FluidDataStoreRegistryEntry | undefined> {
if (this.map.has(name)) {
return this.map.get(name);
}
Expand Down

This file was deleted.

Loading

0 comments on commit b92a003

Please sign in to comment.