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

Support Skipping Context Reload when New Code is Compatible with Old #4122

Merged
merged 24 commits into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1f34482
add check for compatiblitiy
anthony-murphy Oct 21, 2020
cc0d3f1
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 21, 2020
cc7c3ad
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 22, 2020
e80b6bc
update for code details move
anthony-murphy Oct 22, 2020
587b78d
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 22, 2020
e6fd19f
fix formatting
anthony-murphy Oct 23, 2020
a8182d0
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 23, 2020
60597bc
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 23, 2020
94d384d
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 23, 2020
98ade4a
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 26, 2020
eeb03dd
improve IFluidCodeDetailsComparer Api
anthony-murphy Oct 26, 2020
a2a4c05
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 26, 2020
4061a7d
fix up some comments
anthony-murphy Oct 27, 2020
fc358ce
add comparer to tests
anthony-murphy Oct 27, 2020
1cf890a
fix tests
anthony-murphy Oct 27, 2020
b3bff90
fix merge
anthony-murphy Oct 27, 2020
2b41f0b
fix docs
anthony-murphy Oct 27, 2020
2409279
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 27, 2020
acde692
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 28, 2020
6700b7e
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 28, 2020
c49e8c9
Update packages/loader/container-loader/src/containerContext.ts
anthony-murphy Oct 29, 2020
a9bc4c0
Merge branch 'main' of https://github.com/microsoft/FluidFramework in…
anthony-murphy Oct 29, 2020
4325f63
Merge branch 'compatible-code-details' of https://github.com/anthony-…
anthony-murphy Oct 29, 2020
6b36471
fix bad merge
anthony-murphy Oct 29, 2020
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
612 changes: 306 additions & 306 deletions lerna-package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/loader/container-definitions/src/fluidModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Licensed under the MIT License.
*/

import { IFluidObject } from "@fluidframework/core-interfaces";
import { IFluidObject, IProvideFluidCodeDetailsComparer } from "@fluidframework/core-interfaces";

export interface IFluidModule {
fluidExport: IFluidObject;
fluidExport: IFluidObject & Partial<Readonly<IProvideFluidCodeDetailsComparer>>;
}
11 changes: 9 additions & 2 deletions packages/loader/container-definitions/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
* Licensed under the MIT License.
*/

import { IRequest, IResponse, IFluidRouter, IFluidCodeDetails, IFluidPackage } from "@fluidframework/core-interfaces";
import {
IRequest,
IResponse,
IFluidRouter,
IFluidCodeDetails,
IFluidPackage,
IProvideFluidCodeDetailsComparer,
} from "@fluidframework/core-interfaces";
import {
IClientDetails,
IDocumentMessage,
Expand All @@ -21,7 +28,7 @@ import { AttachState } from "./runtime";
/**
* Code loading interface
*/
export interface ICodeLoader {
export interface ICodeLoader extends Partial<IProvideFluidCodeDetailsComparer> {
/**
* Loads the package specified by code details and returns a promise to its entry point exports.
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/loader/container-definitions/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
IFluidConfiguration,
IRequest,
IResponse,
IFluidCodeDetails,
} from "@fluidframework/core-interfaces";
import { IDocumentStorageService } from "@fluidframework/driver-definitions";
import {
Expand Down Expand Up @@ -117,6 +118,7 @@ export interface IContainerContext extends IDisposable {
readonly configuration: IFluidConfiguration;
readonly clientId: string | undefined;
readonly clientDetails: IClientDetails;
readonly codeDetails: IFluidCodeDetails;
readonly parentBranch: string | null;
readonly storage: IDocumentStorageService | undefined | null;
readonly connected: boolean;
Expand Down
21 changes: 18 additions & 3 deletions packages/loader/container-loader/src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ import {
} from "@fluidframework/driver-definitions";
import {
BlobCacheStorageService,
buildSnapshotTree,
readAndParse,
OnlineStatus,
isOnline,
ensureFluidResolvedUrl,
combineAppAndProtocolSummary,
readAndParseFromBlobs,
buildSnapshotTree,
} from "@fluidframework/driver-utils";
import {
isSystemMessage,
Expand Down Expand Up @@ -803,21 +803,36 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
throw new Error("Provided codeDetails are not IFluidCodeDetails");
}

if (this.codeLoader.IFluidCodeDetailsComparer) {
const comparision = await this.codeLoader.IFluidCodeDetailsComparer.compare(
codeDetails,
this.getCodeDetailsFromQuorum());
if (comparision !== undefined && comparision <= 0) {
throw new Error("Proposed code details should be greater than the current");
}
}

return this.getQuorum().propose("code", codeDetails)
.then(()=>true)
.catch(()=>false);
}

private async reloadContextCore(): Promise<void> {
const codeDetails = this.getCodeDetailsFromQuorum();

await Promise.all([
this.deltaManager.inbound.systemPause(),
this.deltaManager.inboundSignal.systemPause()]);

if (await this.context.satisfies(codeDetails) === true) {
this.deltaManager.inbound.systemResume();
this.deltaManager.inboundSignal.systemResume();
return;
}

const previousContextState = await this.context.snapshotRuntimeState();
this.context.dispose(new Error("ContextDisposedForReload"));

const codeDetails = this.getCodeDetailsFromQuorum();

// don't fire this event if we are transitioning from a null runtime to a real runtime
// with detached container we no longer need the null runtime, but for legacy
// reasons need to keep it around (old documents without summary before code proposal).
Expand Down
61 changes: 52 additions & 9 deletions packages/loader/container-loader/src/containerContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
IRequest,
IResponse,
IFluidCodeDetails,
IFluidCodeDetailsComparer,
} from "@fluidframework/core-interfaces";
import {
IAudience,
Expand All @@ -23,6 +24,7 @@ import {
ICriticalContainerError,
ContainerWarning,
AttachState,
IFluidModule,
} from "@fluidframework/container-definitions";
import { IDocumentStorageService } from "@fluidframework/driver-definitions";
import {
Expand All @@ -40,6 +42,7 @@ import {
IVersion,
} from "@fluidframework/protocol-definitions";
import { PerformanceEvent } from "@fluidframework/telemetry-utils";
import { LazyPromise } from "@fluidframework/common-utils";
import { Container } from "./container";
import { NullChaincode, NullRuntime } from "./nullRuntime";

Expand Down Expand Up @@ -165,6 +168,21 @@ export class ContainerContext implements IContainerContext {
return this._disposed;
}

private readonly fluidModuleP = new LazyPromise<IFluidModule>(async () => {
if (this.codeDetails === undefined) {
const fluidExport = new NullChaincode();
return {
fluidExport,
};
}

const fluidModule = await PerformanceEvent.timedExecAsync(this.logger, { eventName: "CodeLoad" },
async () => this.codeLoader.load(this.codeDetails),
);

return fluidModule;
});

constructor(
private readonly container: Container,
public readonly scope: IFluidObject,
Expand Down Expand Up @@ -270,18 +288,43 @@ export class ContainerContext implements IContainerContext {
return this.container.getAbsoluteUrl(relativeUrl);
}

private async load() {
if (this.codeDetails === undefined) {
const nullChaincode = new NullChaincode();
this._runtime = await nullChaincode.instantiateRuntime(this);
return;
/**
* Determines if the current code details of the context
* satisfy the incoming constraint code details
*/
public async satisfies(constraintCodeDetails: IFluidCodeDetails) {
const comparers: IFluidCodeDetailsComparer[] = [];

const maybeCompareCodeLoader = this.codeLoader;
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
}

const fluidModule = await PerformanceEvent.timedExecAsync(this.logger, { eventName: "CodeLoad" },
async () => this.codeLoader.load(this.codeDetails),
);
const maybeCompareExport = (await this.fluidModuleP).fluidExport;
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
}

const maybeFactory = fluidModule?.fluidExport?.IRuntimeFactory;
// if there are not comparers it is not possible to know
// if the current satisfy the incoming, so return false,
// as assuming they do not satisfy is safer .e.g we will
// reload, rather than potentially running with
// incompatible code
if (comparers.length === 0) {
return false;
}

for (const comparer of comparers) {
const satisfies = await comparer.satisfies(this.codeDetails, constraintCodeDetails);
if (satisfies === false) {
return false;
}
}
return true;
}

private async load() {
const maybeFactory = (await this.fluidModuleP).fluidExport.IRuntimeFactory;
if (maybeFactory === undefined) {
throw new Error(PackageNotFactoryError);
}
Expand Down
34 changes: 33 additions & 1 deletion packages/loader/core-interfaces/src/fluidPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export interface IFluidPackage {
}

/**
* Check if the package.json defines a Fluid module, which requires a `fluid` entry
* Check if the package.json defines a Fluid package
* @param pkg - the package json data to check if it is a Fluid package.
*/
export const isFluidPackage = (pkg: any): pkg is Readonly<IFluidPackage> =>
Expand Down Expand Up @@ -96,3 +96,35 @@ export const isFluidCodeDetails = (details: unknown): details is Readonly<IFluid
&& (typeof maybeCodeDetails?.package === "string" || isFluidPackage(maybeCodeDetails?.package))
&& (maybeCodeDetails?.config === undefined || typeof maybeCodeDetails?.config === "object");
};

export const IFluidCodeDetailsComparer: keyof IProvideFluidCodeDetailsComparer = "IFluidCodeDetailsComparer";

export interface IProvideFluidCodeDetailsComparer {
readonly IFluidCodeDetailsComparer: IFluidCodeDetailsComparer ;
}

/**
* Provides capability to compare Fluid code details.
*/
export interface IFluidCodeDetailsComparer extends IProvideFluidCodeDetailsComparer {

/**
* Determines if the `candidate` code details satisfy the constraints specified in `constraint` code details.
*
* Similar semantics to:
* https://github.com/npm/node-semver#usage
*/
satisfies(candidate: IFluidCodeDetails, constraint: IFluidCodeDetails): Promise<boolean>;

/**
* Return a number representing the ascending sort order of the `a` and `b` code details;
* `< 0` if `a < b`.
* `= 0` if `a === b`.
* `> 0` if `a > b`.
* `undefined` if `a` is not comparable to `b`.
*
* Similar semantics to:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Description
*/
compare(a: IFluidCodeDetails, b: IFluidCodeDetails): Promise<number | undefined>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface IContainerRuntime extends
readonly options: any;
readonly clientId: string | undefined;
readonly clientDetails: IClientDetails;
readonly codeDetails: IFluidCodeDetails;
readonly parentBranch: string | null;
readonly connected: boolean;
readonly leader: boolean;
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime/container-runtime/src/containerRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
IRequest,
IResponse,
IFluidHandle,
IFluidCodeDetails,
} from "@fluidframework/core-interfaces";
import {
IAudience,
Expand Down Expand Up @@ -612,6 +613,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>

public readonly IFluidHandleContext: IFluidHandleContext;

public get codeDetails(): IFluidCodeDetails {
return this.context.codeDetails ?? this.getQuorum().get("code") as IFluidCodeDetails;
}

// internal logger for ContainerRuntime
private readonly _logger: ITelemetryLogger;
// publicly visible logger, to be used by stores, summarize, etc.
Expand Down
Loading