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

Make Container Attach Retriable for Non-Runtime Failures #19246

Merged
merged 49 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c046c2c
Refactor Attach and Attachment Data Handling to Support Retry
anthony-murphy Jan 11, 2024
8608062
move majority of attach function to free function
anthony-murphy Jan 12, 2024
d5983ee
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 12, 2024
bb26433
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 12, 2024
1eb8869
add unit tests
anthony-murphy Jan 12, 2024
1af8c8b
update end to end tests
anthony-murphy Jan 12, 2024
23be24d
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 12, 2024
ede8d0c
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 12, 2024
f6c5094
validate arguments
anthony-murphy Jan 12, 2024
bd2d377
some comments and clean up
anthony-murphy Jan 12, 2024
c5388ba
more clean up
anthony-murphy Jan 13, 2024
333a661
more clean up
anthony-murphy Jan 13, 2024
5978b60
add some comments
anthony-murphy Jan 16, 2024
e80a23d
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 16, 2024
a2ef6e2
more comments
anthony-murphy Jan 16, 2024
0a2dd90
fix runRetriableAttachProcess name
anthony-murphy Jan 16, 2024
ce3632e
more comments
anthony-murphy Jan 16, 2024
2cdaa7f
fix tests that expect container close on driver attach failure
anthony-murphy Jan 16, 2024
3c4d6d7
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 16, 2024
23a02bd
add a few more tests
anthony-murphy Jan 16, 2024
b86e97f
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 16, 2024
7f99942
fix imports
anthony-murphy Jan 16, 2024
947e047
fix test
anthony-murphy Jan 16, 2024
c282d27
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 16, 2024
59af26f
fix build nits
anthony-murphy Jan 16, 2024
66f9b50
Update packages/loader/container-loader/src/attachment.ts
anthony-murphy Jan 17, 2024
ee42bd4
make off by default and add changeset
anthony-murphy Jan 17, 2024
a3990fc
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 17, 2024
2ed0802
revert interface change while we ship dark
anthony-murphy Jan 18, 2024
e3bda8e
Update packages/loader/container-loader/src/container.ts
anthony-murphy Jan 19, 2024
e162f39
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 19, 2024
ddfcfbe
Update packages/loader/container-loader/src/container.ts
anthony-murphy Jan 19, 2024
f42a739
Update packages/loader/container-loader/src/attachment.ts
anthony-murphy Jan 19, 2024
c2a2f04
Merge branch 'make-attach-retyable' of https://github.com/anthony-mur…
anthony-murphy Jan 19, 2024
3b2975d
PR feedback
anthony-murphy Jan 19, 2024
5ec44d3
update test
anthony-murphy Jan 19, 2024
7f46fc9
fix typo in conversion code
anthony-murphy Jan 19, 2024
94c06f8
revert
anthony-murphy Jan 19, 2024
f013e77
Update packages/loader/container-loader/src/container.ts
anthony-murphy Jan 19, 2024
fe428f9
Merge branch 'make-attach-retyable' of https://github.com/anthony-mur…
anthony-murphy Jan 19, 2024
7fe57f9
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 22, 2024
5ca990f
Merge branch 'main' into make-attach-retyable
anthony-murphy Jan 25, 2024
66f4fe7
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 29, 2024
216d60e
Merge branch 'make-attach-retyable' of https://github.com/anthony-mur…
anthony-murphy Jan 29, 2024
eec68bb
move runSingle to utils
anthony-murphy Jan 29, 2024
885ea99
Update packages/loader/container-loader/src/container.ts
anthony-murphy Jan 30, 2024
40ac3a0
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 30, 2024
0b4ea15
pr feedback
anthony-murphy Jan 30, 2024
db41fb6
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Jan 30, 2024
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
6 changes: 2 additions & 4 deletions packages/common/container-definitions/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,8 @@ export interface IContainer extends IEventProvider<IContainerEvents> {
/**
* Attaches the Container to the Container specified by the given Request.
*
* @privateRemarks
*
* TODO - in the case of failure options should give a retry policy.
* Or some continuation function that allows attachment to a secondary document.
* Attachment can be retried if the container is not closed, and there is an error
* during attach.
*/
attach(
request: IRequest,
Expand Down
211 changes: 211 additions & 0 deletions packages/loader/container-loader/src/attachment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/
import { AttachState } from "@fluidframework/container-definitions";
import { CombinedAppAndProtocolSummary } from "@fluidframework/driver-utils";
import { ISnapshotTree } from "@fluidframework/protocol-definitions";
import { assert } from "@fluidframework/core-utils";
import { IDocumentStorageService } from "@fluidframework/driver-definitions";
import { getSnapshotTreeAndBlobsFromSerializedContainer } from "./utils";
import { ISerializableBlobContents } from "./containerStorageAdapter";
import { IDetachedBlobStorage } from ".";

/**
* The default state a newly created detached container will have.
* All but the state are optional and undefined, they just exist
* to make the union easy to deal with for both Detached types
*/
export interface DetachedDefaultData {
readonly state: AttachState.Detached;
readonly blobs?: undefined;
readonly summary?: undefined;
readonly redirectTable?: undefined;
}

/**
* This always follows DetachedDefaultData when there are
* outstanding blobs in the detached blob storage.
* The redirect table will get filled up to include data
* about the blobs as they are uploaded.
*/
export interface DetachedDataWithOutstandingBlobs {
readonly state: AttachState.Detached;
readonly blobs: "outstanding";
readonly summary?: undefined;
readonly redirectTable: Map<string, string>;
}

/**
* This state always follows DetachedDataWithOutstandingBlobs.
* It signals that all outstanding blobs are done being uploaded,
* so the container can move to the attaching state.
*/
export interface AttachingDataWithBlobs {
readonly state: AttachState.Attaching;
readonly blobs: "done";
readonly summary: CombinedAppAndProtocolSummary;
}

/**
* This always follows DefaultDetachedState when there are
* no blobs in the detached blob storage. Because there are
* no blobs we can immediately get the summary and transition
* to the attaching state.
*/
export interface AttachingDataWithoutBlobs {
readonly state: AttachState.Attaching;
readonly summary: CombinedAppAndProtocolSummary;
readonly blobs: "none";
}

/**
* The final attachment state which signals the container is fully attached.
* The baseSnapshotAndBlobs will only be enabled when offline load is enabled.
*/
export interface AttachedData {
readonly state: AttachState.Attached;
readonly baseSnapshotAndBlobs?: [ISnapshotTree, ISerializableBlobContents];
anthony-murphy marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* A union of all the attachment data types for
* tracking across all container attachment states
*/
export type AttachmentData =
anthony-murphy marked this conversation as resolved.
Show resolved Hide resolved
| DetachedDefaultData
| DetachedDataWithOutstandingBlobs
| AttachingDataWithoutBlobs
| AttachingDataWithBlobs
| AttachedData;

/**
* The data and services necessary for runRetriableAttachProcess.
*/
export interface AttachProcessProps {
/**
* The initial attachment data this call should start with
*/
readonly initialAttachmentData: Exclude<AttachmentData, AttachedData>;

/**
* The caller should us this callback to keep track of the current
anthony-murphy marked this conversation as resolved.
Show resolved Hide resolved
* attachment data, and perform any other operations necessary
* for dealing with attachment state changes, like emitting events
*
* @param attachmentData - the updated attachment data */
readonly setAttachmentData: (attachmentData: AttachmentData) => void;

/**
* The caller should create and or get services based on the data, and its own information.
* @param data - the data to create services from,
* the summary property being the most relevant part of the data.
* @returns A compatible storage service
*/
readonly createOrGetStorageService: (
data: DetachedDataWithOutstandingBlobs | AttachingDataWithBlobs | AttachingDataWithoutBlobs,
anthony-murphy marked this conversation as resolved.
Show resolved Hide resolved
) => Promise<Pick<IDocumentStorageService, "createBlob" | "uploadSummaryWithContext">>;
anthony-murphy marked this conversation as resolved.
Show resolved Hide resolved

/**
* The detached blob storage if it exists.
*/
readonly detachedBlobStorage?: Pick<IDetachedBlobStorage, "getBlobIds" | "readBlob" | "size">;

/**
* The caller should create the attachment summary for the container.
* @param redirectTable - Maps local blob ids to remote blobs ids.
* @returns The attachment summary for the container.
*/
readonly createAttachmentSummary: (
redirectTable?: Map<string, string>,
) => CombinedAppAndProtocolSummary;

/**
* Whether offline load is enabled or not.
*/
readonly offlineLoadEnabled: boolean;
}

/**
* Executes the attach process state machine based on the provided data and services.
* This method is retriable on failure. Based on the provided initialAttachmentData
* this method will resume the attachment process and attempt to complete it.
*
* @param props - The data and services necessary to run the attachment process
*/
export const runRetriableAttachProcess = async (props: AttachProcessProps): Promise<void> => {
let currentData: AttachmentData = props.initialAttachmentData;
anthony-murphy marked this conversation as resolved.
Show resolved Hide resolved

if (currentData.blobs === undefined) {
// If attachment blobs were uploaded in detached state we will go through a different attach flow
const outstandingAttachmentBlobs =
props.detachedBlobStorage !== undefined && props.detachedBlobStorage.size > 0;
// Determine the next phase of attaching which depends on if there are attachment blobs
// if there are, we will stay detached, so an empty file can be created, and the blobs
// uploaded, otherwise we will get the summary to create the file with and move to attaching
currentData = outstandingAttachmentBlobs
? {
state: AttachState.Detached,
blobs: "outstanding",
redirectTable: new Map<string, string>(),
}
: {
state: AttachState.Attaching,
summary: props.createAttachmentSummary(),
blobs: "none",
};
props.setAttachmentData(currentData);
}

// this has to run here, as it is what creates the file
// and we need to file for all possible cases after this point
const storage = await props.createOrGetStorageService(currentData);

if (currentData.blobs === "outstanding") {
const { redirectTable } = currentData;
// upload blobs to storage
assert(!!props.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);

// build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
// support blob handles that only know about the local IDs
while (redirectTable.size < props.detachedBlobStorage.size) {
anthony-murphy marked this conversation as resolved.
Show resolved Hide resolved
const newIds = props.detachedBlobStorage
.getBlobIds()
.filter((id) => !redirectTable.has(id));
for (const id of newIds) {
const blob = await props.detachedBlobStorage.readBlob(id);
const response = await storage.createBlob(blob);
anthony-murphy marked this conversation as resolved.
Show resolved Hide resolved
redirectTable.set(id, response.id);
}
}
props.setAttachmentData(
(currentData = {
state: AttachState.Attaching,
summary: props.createAttachmentSummary(redirectTable),
blobs: "done",
}),
);
}

assert(currentData.state === AttachState.Attaching, "must be attaching by this point");

if (currentData.blobs === "done") {
// done means outstanding blobs were uploaded.
// in that case an empty file was created, the blobs were uploaded
// and now this finally uploads the summary
await storage.uploadSummaryWithContext(currentData.summary, {
referenceSequenceNumber: 0,
ackHandle: undefined,
proposalHandle: undefined,
});
}

props.setAttachmentData(
(currentData = {
state: AttachState.Attached,
baseSnapshotAndBlobs: props.offlineLoadEnabled
? getSnapshotTreeAndBlobsFromSerializedContainer(currentData.summary)
: undefined,
}),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid assigning simultaneous with passing for readability

Suggested change
props.setAttachmentData(
(currentData = {
state: AttachState.Attached,
baseSnapshotAndBlobs: props.offlineLoadEnabled
? getSnapshotTreeAndBlobsFromSerializedContainer(currentData.summary)
: undefined,
}),
);
currentData = {
state: AttachState.Attached,
baseSnapshotAndBlobs: props.offlineLoadEnabled
? getSnapshotTreeAndBlobsFromSerializedContainer(currentData.summary)
: undefined,
};
props.setAttachmentData(currentData);

};
Loading