Skip to content

Commit d5af295

Browse files
(compat) Add new interface for layer incomatibility to make it simpler for consumption (#25706)
Also a new `@alpha` interface `ILayerIncompatibilityError` of error type `FluidErrorTypes.usageError`. It has properties that consumers can use to get additional details about the incompatiblity. When layer incompatibility is detected, this error will be thrown. The properties have comments that explain what these properties are for. Also, added a telemetry called `LayerIncompatibilityError` which will be logged everytime layer incompatibility is detected. [AB#43990](https://dev.azure.com/fluidframework/235294da-091d-4c29-84fc-cdfc3d90890b/_workitems/edit/43990)
1 parent d1c4c0a commit d5af295

File tree

22 files changed

+603
-424
lines changed

22 files changed

+603
-424
lines changed

packages/common/client-utils/src/indexBrowser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export {
2929
export { createEmitter } from "./events/index.js";
3030

3131
export {
32+
type FluidLayer,
3233
checkLayerCompatibility,
3334
type LayerCompatCheckResult,
3435
type ILayerCompatDetails,

packages/common/client-utils/src/indexNode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export {
2929
export { createEmitter } from "./events/index.js";
3030

3131
export {
32+
type FluidLayer,
3233
checkLayerCompatibility,
3334
type LayerCompatCheckResult,
3435
type ILayerCompatDetails,

packages/common/client-utils/src/layerCompat.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
* Licensed under the MIT License.
44
*/
55

6+
/**
7+
* The different Fluid layers in a client.
8+
* @internal
9+
*/
10+
export type FluidLayer = "loader" | "driver" | "runtime" | "dataStore";
11+
612
/**
713
* Result of a layer compatibility check - whether a layer is compatible with another layer.
814
* @internal

packages/common/core-interfaces/src/error.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,58 @@ export interface IThrottlingWarning extends IErrorBase {
128128
readonly errorType: typeof FluidErrorTypes.throttlingError;
129129
readonly retryAfterSeconds: number;
130130
}
131+
132+
/**
133+
* Symbol used to identify an error of type {@link ILayerIncompatibilityError}.
134+
* @legacy @alpha
135+
*/
136+
export const layerIncompatibilityErrorSymbol: unique symbol = Symbol(
137+
"LayerIncompatibilityError",
138+
);
139+
140+
/**
141+
* Usage error indicating that two Fluid layers are incompatible. For instance, if the Loader layer is
142+
* not compatible with the Runtime layer, the container will be disposed with this error.
143+
* @legacy @alpha
144+
*/
145+
export interface ILayerIncompatibilityError extends IErrorBase {
146+
/**
147+
* {@inheritDoc IErrorBase.errorType}
148+
*/
149+
readonly errorType: typeof FluidErrorTypes.usageError;
150+
151+
/**
152+
* Symbol used to identify this error as a layer incompatibility error.
153+
*/
154+
readonly [layerIncompatibilityErrorSymbol]: true;
155+
/**
156+
* The layer that is reporting the incompatibility.
157+
*/
158+
readonly layer: string;
159+
/**
160+
* The layer that is incompatible with the reporting layer.
161+
*/
162+
readonly incompatibleLayer: string;
163+
/**
164+
* The package version of the reporting layer.
165+
*/
166+
readonly layerVersion: string;
167+
/**
168+
* The package version of the incompatible layer.
169+
*/
170+
readonly incompatibleLayerVersion: string;
171+
/**
172+
* The number of months of compatibility requirements between the two layers as per the layer compatibility policy.
173+
*/
174+
readonly compatibilityRequirementsInMonths: number;
175+
/**
176+
* The minimum actual difference in months between the release of the two layers.
177+
* Note that for layers with package versions older than 2.63.0, the actual difference may be higher than this value
178+
* because the difference reported is capped as per 2.63.0 where the compatibility enforcement was introduced.
179+
*/
180+
readonly actualDifferenceInMonths: number;
181+
/**
182+
* Additional details about the incompatibility to be used for debugging purposes.
183+
*/
184+
readonly details: string;
185+
}

packages/common/core-interfaces/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ export type { BrandedType } from "./brandedType.js";
77

88
export type { IDisposable } from "./disposable.js";
99

10-
export type { IErrorBase, IGenericError, IUsageError, IThrottlingWarning } from "./error.js";
10+
export type {
11+
IErrorBase,
12+
IGenericError,
13+
IUsageError,
14+
IThrottlingWarning,
15+
ILayerIncompatibilityError,
16+
} from "./error.js";
17+
export { layerIncompatibilityErrorSymbol } from "./error.js";
1118
export { FluidErrorTypes } from "./error.js";
1219

1320
export type {

packages/loader/container-loader/src/container.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,7 @@ export class Container
810810
validateDriverCompatibility(
811811
maybeDriverCompatDetails.ILayerCompatDetails,
812812
(error) => {} /* disposeFn */, // There is nothing to dispose here, so just ignore the error.
813+
subLogger,
813814
);
814815

815816
this.connectionTransitionTimes[ConnectionState.Disconnected] = performanceNow();
@@ -2481,7 +2482,10 @@ export class Container
24812482

24822483
// Validate that the Runtime is compatible with this Loader.
24832484
const maybeRuntimeCompatDetails = runtime as FluidObject<ILayerCompatDetails>;
2484-
validateRuntimeCompatibility(maybeRuntimeCompatDetails.ILayerCompatDetails);
2485+
validateRuntimeCompatibility(
2486+
maybeRuntimeCompatDetails.ILayerCompatDetails,
2487+
this.mc.logger,
2488+
);
24852489

24862490
this._runtime = runtime;
24872491
this._lifecycleEvents.emit("runtimeInstantiated");

packages/loader/container-loader/src/loaderLayerCompatState.ts

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
* Licensed under the MIT License.
44
*/
55

6-
import {
7-
checkLayerCompatibility,
8-
type ILayerCompatDetails,
9-
type ILayerCompatSupportRequirements,
6+
import type {
7+
ILayerCompatDetails,
8+
ILayerCompatSupportRequirements,
109
} from "@fluid-internal/client-utils";
1110
import type { ICriticalContainerError } from "@fluidframework/container-definitions";
12-
import { UsageError } from "@fluidframework/telemetry-utils/internal";
11+
import {
12+
validateLayerCompatibility,
13+
type ITelemetryLoggerExt,
14+
} from "@fluidframework/telemetry-utils/internal";
1315

1416
import { pkgVersion } from "./packageVersion.js";
1517

@@ -78,25 +80,17 @@ export const driverSupportRequirementsForLoader: ILayerCompatSupportRequirements
7880
*/
7981
export function validateRuntimeCompatibility(
8082
maybeRuntimeCompatDetails: ILayerCompatDetails | undefined,
83+
logger: ITelemetryLoggerExt,
8184
): void {
82-
const layerCheckResult = checkLayerCompatibility(
85+
validateLayerCompatibility(
86+
"loader",
87+
"runtime",
88+
loaderCompatDetailsForRuntime,
8389
runtimeSupportRequirementsForLoader,
8490
maybeRuntimeCompatDetails,
91+
() => {} /* disposeFn - no op. This will be handled by the caller */,
92+
logger,
8593
);
86-
if (!layerCheckResult.isCompatible) {
87-
const error = new UsageError("Loader is not compatible with Runtime", {
88-
errorDetails: JSON.stringify({
89-
loaderVersion: loaderCompatDetailsForRuntime.pkgVersion,
90-
runtimeVersion: maybeRuntimeCompatDetails?.pkgVersion,
91-
loaderGeneration: loaderCompatDetailsForRuntime.generation,
92-
runtimeGeneration: maybeRuntimeCompatDetails?.generation,
93-
minSupportedGeneration: runtimeSupportRequirementsForLoader.minSupportedGeneration,
94-
isGenerationCompatible: layerCheckResult.isGenerationCompatible,
95-
unsupportedFeatures: layerCheckResult.unsupportedFeatures,
96-
}),
97-
});
98-
throw error;
99-
}
10094
}
10195

10296
/**
@@ -106,24 +100,15 @@ export function validateRuntimeCompatibility(
106100
export function validateDriverCompatibility(
107101
maybeDriverCompatDetails: ILayerCompatDetails | undefined,
108102
disposeFn: (error?: ICriticalContainerError) => void,
103+
logger: ITelemetryLoggerExt,
109104
): void {
110-
const layerCheckResult = checkLayerCompatibility(
105+
validateLayerCompatibility(
106+
"loader",
107+
"driver",
108+
loaderCompatDetailsForRuntime,
111109
driverSupportRequirementsForLoader,
112110
maybeDriverCompatDetails,
111+
disposeFn,
112+
logger,
113113
);
114-
if (!layerCheckResult.isCompatible) {
115-
const error = new UsageError("Loader is not compatible with Driver", {
116-
errorDetails: JSON.stringify({
117-
loaderVersion: loaderCoreCompatDetails.pkgVersion,
118-
driverVersion: maybeDriverCompatDetails?.pkgVersion,
119-
loaderGeneration: loaderCoreCompatDetails.generation,
120-
driverGeneration: maybeDriverCompatDetails?.generation,
121-
minSupportedGeneration: driverSupportRequirementsForLoader.minSupportedGeneration,
122-
isGenerationCompatible: layerCheckResult.isGenerationCompatible,
123-
unsupportedFeatures: layerCheckResult.unsupportedFeatures,
124-
}),
125-
});
126-
disposeFn(error);
127-
throw error;
128-
}
129114
}

0 commit comments

Comments
 (0)