Skip to content

Blob placeholders prototype #24158

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

Closed
wants to merge 22 commits into from
Closed
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8aef572
Initial impl and plumbing
ChumpChief Mar 24, 2025
aed597c
Handle placeholder in resolving handles
ChumpChief Mar 24, 2025
1ac5b37
Random is fun
ChumpChief Mar 24, 2025
1b22fe6
Enable experimental behavior by default
ChumpChief Mar 25, 2025
2d6242a
Remove explicit detached failing
ChumpChief Mar 26, 2025
c4ee0ad
Skip failing tests
ChumpChief Mar 26, 2025
e987a23
Blob Placeholder: Enable no wait (#24157)
anthony-murphy Mar 26, 2025
235a287
Skip 2 more seeds
ChumpChief Mar 26, 2025
37700db
Merge remote-tracking branch 'upstream/main' into test/blob-placeholders
ChumpChief Mar 28, 2025
ebade6c
Drop separate experimental API in favor of feature flag, and support …
ChumpChief Mar 28, 2025
e9b8fc9
Enable gc dedupe tests and delay returning the handle if in Attaching…
ChumpChief Mar 28, 2025
3f2ddc9
Merge branch 'main' into test/blob-placeholders
ChumpChief Mar 31, 2025
c838f8e
Test both with/without blob placeholders enabled (#24212)
ChumpChief Apr 1, 2025
e8ae597
Enable client build pipeline for test/blob-placeholders branch (#24214)
ChumpChief Apr 1, 2025
81b17ef
Add metadata to handles and use in BlobManager for placeholder blobs …
ChumpChief Apr 3, 2025
68e75ae
Switch to using DocumentSchema to enable blob placeholders (#24262)
ChumpChief Apr 7, 2025
6edfcf4
Revert "Switch to using DocumentSchema to enable blob placeholders (#…
scottn12 Apr 7, 2025
0c96d60
Switch to using DocumentSchema to enable blob placeholders (#24268)
ChumpChief Apr 7, 2025
630983d
Merge remote-tracking branch 'upstream/main' into test/blob-placeholders
ChumpChief Apr 7, 2025
079bcee
Replace metadata with placeholder tagging (#24308)
ChumpChief Apr 11, 2025
1427970
Merge remote-tracking branch 'upstream/main' into test/blob-placeholders
ChumpChief Apr 11, 2025
fa946d0
Add observability API surface for locally created BlobHandles (#24397)
ChumpChief Apr 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/apps/blobs/src/container/runtimeFactory.ts
Original file line number Diff line number Diff line change
@@ -44,6 +44,11 @@ export class BlobCollectionContainerRuntimeFactory implements IRuntimeFactory {
[blobCollectionRegistryKey, Promise.resolve(blobCollectionFactory)],
]),
provideEntryPoint,
runtimeOptions: {
// To use the new experimental blob placeholders features, we need to set these flags.
explicitSchemaControl: true,
createBlobPlaceholders: true,
},
existing,
});

Original file line number Diff line number Diff line change
@@ -282,6 +282,18 @@ export interface IFluidLoadable extends IProvideFluidLoadable {
readonly handle: IFluidHandle;
}

// @alpha
export interface IFluidPlaceholderHandle<T> extends IFluidHandle<T> {
readonly events: IEventProvider<IFluidPlaceholderHandleEvents>;
readonly payloadState: PayloadState;
}

// @alpha
export interface IFluidPlaceholderHandleEvents extends IEvent {
(event: "shared", listener: () => void): any;
(event: "failed", listener: (error: unknown) => void): any;
}

// @alpha
export interface ILoggingError extends Error {
getTelemetryProperties(): ITelemetryBaseProperties;
@@ -388,6 +400,9 @@ export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
// @public
export type Off = () => void;

// @alpha
export type PayloadState = "local" | "shared" | "placeholder" | "failed";

// @public
export type ReplaceIEventThisPlaceHolder<L extends any[], TThis> = L extends any[] ? {
[K in keyof L]: L[K] extends IEventThisPlaceHolder ? TThis : L[K];
64 changes: 64 additions & 0 deletions packages/common/core-interfaces/src/handles.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
*/

import type { ErasedType } from "./erasedType.js";
import type { IEvent, IEventProvider } from "./events.js";
import type { IRequest, IResponse } from "./fluidRouter.js";

/**
@@ -101,6 +102,69 @@ export interface IFluidHandleInternal<
bind(handle: IFluidHandleInternal): void;
}

/**
* Information on whether the handle is a placeholder or not.
* @privateRemarks
* Contents to be merged with IFluidHandleInternal, and then this separate interface should be removed.
* @internal
*/
export interface IFluidPlaceholderHandleInternal<
// REVIEW: Constrain `T` to something? How do we support dds and datastores safely?
out T = unknown, // FluidObject & IFluidLoadable,
> extends IFluidHandleInternal<T> {
/**
* Whether the handle is a placeholder, meaning that it may exist before its payload is retrievable.
* For instance, the BlobManager can generate handles before completing the blob upload/attach.
*/
readonly placeholder: boolean;
}

/**
* The state of the handle's payload.
* - "local" - The payload is only available to the local client, and not to remote collaborators
* - "shared" - The payload is availabe to both the local client and remote collaborators
* - "placeholder" - The payload is not yet available to the local client
* - "failed" - The payload is available to the local client but has failed in sharing to remote collaborators
* @legacy
* @alpha
*/
export type PayloadState = "local" | "shared" | "placeholder" | "failed";

/**
* Events which fire as the handle's payload state transitions.
* @legacy
* @alpha
*/
export interface IFluidPlaceholderHandleEvents extends IEvent {
/**
* Emitted when the payload becomes available to all clients.
*/
(event: "shared", listener: () => void);
/**
* Emitted for locally created handles when the payload fails sharing to remote collaborators.
*/
(event: "failed", listener: (error: unknown) => void);
}

/**
* Observable state on the handle regarding its payload sharing state.
*
* @privateRemarks
* Contents to be merged to IFluidHandle, and then this separate interface should be removed.
* @legacy
* @alpha
*/
export interface IFluidPlaceholderHandle<T> extends IFluidHandle<T> {
/**
* The current state of the handle's payload.
*/
readonly payloadState: PayloadState;
/**
* Event provider, with events that emit as the payload state transitions.
*/
readonly events: IEventProvider<IFluidPlaceholderHandleEvents>;
}

/**
* Symbol which must only be used on an {@link (IFluidHandle:interface)}, and is used to identify such objects.
*
4 changes: 4 additions & 0 deletions packages/common/core-interfaces/src/index.ts
Original file line number Diff line number Diff line change
@@ -31,7 +31,11 @@ export type {
IProvideFluidHandleContext,
IProvideFluidHandle,
IFluidHandleInternal,
IFluidPlaceholderHandle,
IFluidPlaceholderHandleEvents,
IFluidPlaceholderHandleInternal,
IFluidHandleErased,
PayloadState,
} from "./handles.js";
export { IFluidHandleContext, IFluidHandle, fluidHandleSymbol } from "./handles.js";

2 changes: 1 addition & 1 deletion packages/dds/shared-object-base/src/serializer.ts
Original file line number Diff line number Diff line change
@@ -153,7 +153,7 @@ export class FluidSerializer implements IFluidSerializer {
? value.url
: generateHandleContextPath(value.url, this.context);

return new RemoteFluidObjectHandle(absolutePath, this.root);
return new RemoteFluidObjectHandle(absolutePath, this.root, value.placeholder === true);
} else {
return value;
}
24 changes: 20 additions & 4 deletions packages/dds/shared-object-base/src/test/serializer.spec.ts
Original file line number Diff line number Diff line change
@@ -41,7 +41,11 @@ describe("FluidSerializer", () => {
describe("vanilla JSON", () => {
const context = new MockHandleContext();
const serializer = new FluidSerializer(context);
const handle = new RemoteFluidObjectHandle("/root", context);
const handle = new RemoteFluidObjectHandle(
"/root",
context,
false, // placeholder
);

// Start with the various JSON-serializable types. A mix of "truthy" and "falsy" values
// are of particular interest.
@@ -167,7 +171,11 @@ describe("FluidSerializer", () => {
describe("JSON w/embedded handles", () => {
const context = new MockHandleContext();
const serializer = new FluidSerializer(context);
const handle = new RemoteFluidObjectHandle("/root", context);
const handle = new RemoteFluidObjectHandle(
"/root",
context,
false, // placeholder
);
const serializedHandle = {
type: "__fluid_handle__",
url: "/root",
@@ -329,8 +337,16 @@ describe("FluidSerializer", () => {
describe("Utils", () => {
const serializer = new FluidSerializer(new MockHandleContext());
it("makeSerializable is idempotent", () => {
const bind = new RemoteFluidObjectHandle("/", new MockHandleContext());
const handle = new RemoteFluidObjectHandle("/okay", new MockHandleContext());
const bind = new RemoteFluidObjectHandle(
"/",
new MockHandleContext(),
false, // placeholder
);
const handle = new RemoteFluidObjectHandle(
"/okay",
new MockHandleContext(),
false, // placeholder
);
const input = { x: handle, y: 123 };
const serializedOnce = makeHandlesSerializable(input, serializer, bind) as {
x: { type: "__fluid_handle__" };
Original file line number Diff line number Diff line change
@@ -101,6 +101,7 @@ export interface ICompressionRuntimeOptions {
export interface IContainerRuntimeOptions {
readonly chunkSizeInBytes?: number;
readonly compressionOptions?: ICompressionRuntimeOptions;
readonly createBlobPlaceholders?: boolean;
// @deprecated
readonly enableGroupedBatching?: boolean;
readonly enableRuntimeIdCompressor?: IdCompressorMode;
Loading
Oops, something went wrong.