@fluidframework/container-loader 2.63.0-358419 → 2.63.0-359286
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api-report/container-loader.legacy.alpha.api.md +1 -9
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +64 -49
- package/dist/connectionManager.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +3 -5
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +10 -22
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +3 -1
- package/dist/containerContext.js.map +1 -1
- package/dist/contracts.d.ts +5 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/createAndLoadContainerUtils.d.ts +1 -33
- package/dist/createAndLoadContainerUtils.d.ts.map +1 -1
- package/dist/createAndLoadContainerUtils.js +1 -1
- package/dist/createAndLoadContainerUtils.js.map +1 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +56 -55
- package/dist/deltaManager.js.map +1 -1
- package/dist/frozenServices.d.ts +2 -0
- package/dist/frozenServices.d.ts.map +1 -1
- package/dist/frozenServices.js +20 -12
- package/dist/frozenServices.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +51 -8
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +11 -12
- package/dist/protocol.js.map +1 -1
- package/dist/serializedStateManager.d.ts +4 -3
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js +63 -23
- package/dist/serializedStateManager.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +18 -3
- package/lib/connectionManager.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +3 -5
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +10 -22
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +3 -1
- package/lib/containerContext.js.map +1 -1
- package/lib/contracts.d.ts +5 -1
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/createAndLoadContainerUtils.d.ts +1 -33
- package/lib/createAndLoadContainerUtils.d.ts.map +1 -1
- package/lib/createAndLoadContainerUtils.js +1 -1
- package/lib/createAndLoadContainerUtils.js.map +1 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +2 -1
- package/lib/deltaManager.js.map +1 -1
- package/lib/frozenServices.d.ts +2 -0
- package/lib/frozenServices.d.ts.map +1 -1
- package/lib/frozenServices.js +20 -12
- package/lib/frozenServices.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +51 -8
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +5 -6
- package/lib/protocol.js.map +1 -1
- package/lib/serializedStateManager.d.ts +4 -3
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js +63 -23
- package/lib/serializedStateManager.js.map +1 -1
- package/package.json +12 -12
- package/src/connectionManager.ts +31 -14
- package/src/container.ts +9 -13
- package/src/containerContext.ts +34 -34
- package/src/contracts.ts +4 -1
- package/src/createAndLoadContainerUtils.ts +3 -42
- package/src/deltaManager.ts +12 -5
- package/src/frozenServices.ts +24 -12
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +67 -10
- package/src/serializedStateManager.ts +60 -29
package/src/container.ts
CHANGED
|
@@ -140,9 +140,10 @@ import { NoopHeuristic } from "./noopHeuristic.js";
|
|
|
140
140
|
import { pkgVersion } from "./packageVersion.js";
|
|
141
141
|
import type { IQuorumSnapshot } from "./protocol/index.js";
|
|
142
142
|
import {
|
|
143
|
-
type
|
|
143
|
+
type InternalProtocolHandlerBuilder,
|
|
144
144
|
ProtocolHandler,
|
|
145
145
|
type ProtocolHandlerBuilder,
|
|
146
|
+
type ProtocolHandlerInternal,
|
|
146
147
|
protocolHandlerShouldProcessSignal,
|
|
147
148
|
} from "./protocol.js";
|
|
148
149
|
import { initQuorumValuesFromCodeDetails } from "./quorum.js";
|
|
@@ -495,7 +496,7 @@ export class Container
|
|
|
495
496
|
private readonly scope: FluidObject;
|
|
496
497
|
private readonly subLogger: ITelemetryLoggerExt;
|
|
497
498
|
private readonly detachedBlobStorage: MemoryDetachedBlobStorage | undefined;
|
|
498
|
-
private readonly protocolHandlerBuilder:
|
|
499
|
+
private readonly protocolHandlerBuilder: InternalProtocolHandlerBuilder;
|
|
499
500
|
private readonly client: IClient;
|
|
500
501
|
|
|
501
502
|
private readonly mc: MonitoringContext;
|
|
@@ -597,8 +598,8 @@ export class Container
|
|
|
597
598
|
}
|
|
598
599
|
return this._runtime;
|
|
599
600
|
}
|
|
600
|
-
private _protocolHandler:
|
|
601
|
-
private get protocolHandler():
|
|
601
|
+
private _protocolHandler: ProtocolHandlerInternal | undefined;
|
|
602
|
+
private get protocolHandler(): ProtocolHandlerInternal {
|
|
602
603
|
if (this._protocolHandler === undefined) {
|
|
603
604
|
throw new Error("Attempted to access protocolHandler before it was defined");
|
|
604
605
|
}
|
|
@@ -830,7 +831,7 @@ export class Container
|
|
|
830
831
|
attributes: IDocumentAttributes,
|
|
831
832
|
quorumSnapshot: IQuorumSnapshot,
|
|
832
833
|
sendProposal: (key: string, value: unknown) => number,
|
|
833
|
-
):
|
|
834
|
+
): ProtocolHandlerInternal =>
|
|
834
835
|
new ProtocolHandler(
|
|
835
836
|
attributes,
|
|
836
837
|
quorumSnapshot,
|
|
@@ -1641,10 +1642,9 @@ export class Container
|
|
|
1641
1642
|
const timings: Record<string, number> = { phase1: performanceNow() };
|
|
1642
1643
|
this.service = await this.createDocumentService(resolvedUrl, { mode: "load" });
|
|
1643
1644
|
|
|
1644
|
-
// Except in cases where
|
|
1645
|
+
// Except in cases where its requested by feature gate, the container will connect in "read" mode
|
|
1645
1646
|
const mode =
|
|
1646
|
-
this.mc.config.getBoolean("Fluid.Container.ForceWriteConnection") === true
|
|
1647
|
-
(pendingLocalState?.savedOps.length ?? 0) > 0
|
|
1647
|
+
this.mc.config.getBoolean("Fluid.Container.ForceWriteConnection") === true
|
|
1648
1648
|
? "write"
|
|
1649
1649
|
: "read";
|
|
1650
1650
|
const connectionArgs: IConnectionArgs = {
|
|
@@ -1668,14 +1668,10 @@ export class Container
|
|
|
1668
1668
|
timings.phase2 = performanceNow();
|
|
1669
1669
|
|
|
1670
1670
|
// Fetch specified snapshot.
|
|
1671
|
-
const { baseSnapshot, version } =
|
|
1671
|
+
const { baseSnapshot, version, attributes } =
|
|
1672
1672
|
await this.serializedStateManager.fetchSnapshot(specifiedVersion);
|
|
1673
1673
|
const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(baseSnapshot);
|
|
1674
1674
|
this._loadedFromVersion = version;
|
|
1675
|
-
const attributes: IDocumentAttributes = await getDocumentAttributes(
|
|
1676
|
-
this.storageAdapter,
|
|
1677
|
-
baseSnapshotTree,
|
|
1678
|
-
);
|
|
1679
1675
|
|
|
1680
1676
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
1681
1677
|
const lastProcessedSequenceNumber =
|
package/src/containerContext.ts
CHANGED
|
@@ -39,52 +39,50 @@ import { loaderCompatDetailsForRuntime } from "./loaderLayerCompatState.js";
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Configuration object for ContainerContext constructor.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* A large subset of properties are from {@link IContainerContext}. Select
|
|
45
|
+
* properties are explicitly omitted so that, as {@link ContainerContext} is
|
|
46
|
+
* extended, newly added properties are not overlooked (but may also be
|
|
47
|
+
* explicitly omitted here by adding to the list).
|
|
42
48
|
*/
|
|
43
|
-
export interface IContainerContextConfig
|
|
49
|
+
export interface IContainerContextConfig
|
|
50
|
+
extends Readonly<
|
|
51
|
+
Required<
|
|
52
|
+
Omit<
|
|
53
|
+
IContainerContext,
|
|
54
|
+
| "clientId"
|
|
55
|
+
| "connected"
|
|
56
|
+
| "attachState"
|
|
57
|
+
| "getLoadedFromVersion"
|
|
58
|
+
| "supportedFeatures"
|
|
59
|
+
| "id"
|
|
60
|
+
| "snapshotWithContents"
|
|
61
|
+
>
|
|
62
|
+
>
|
|
63
|
+
> {
|
|
64
|
+
// This overrides IContainerContext.options with specific type.
|
|
44
65
|
readonly options: ILoaderOptions;
|
|
45
|
-
readonly scope: FluidObject;
|
|
46
|
-
readonly baseSnapshot: ISnapshotTree | undefined;
|
|
47
66
|
readonly version: IVersion | undefined;
|
|
48
|
-
readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>;
|
|
49
|
-
readonly storage: IContainerStorageService;
|
|
50
|
-
readonly quorum: IQuorumClients;
|
|
51
|
-
readonly audience: IAudience;
|
|
52
|
-
readonly loader: ILoader;
|
|
53
|
-
readonly submitFn: (
|
|
54
|
-
type: MessageType,
|
|
55
|
-
contents: unknown,
|
|
56
|
-
batch: boolean,
|
|
57
|
-
appData: unknown,
|
|
58
|
-
) => number;
|
|
59
|
-
readonly submitSummaryFn: (
|
|
60
|
-
summaryOp: ISummaryContent,
|
|
61
|
-
referenceSequenceNumber?: number,
|
|
62
|
-
) => number;
|
|
63
|
-
readonly submitBatchFn: (batch: IBatchMessage[], referenceSequenceNumber?: number) => number;
|
|
64
|
-
readonly submitSignalFn: (
|
|
65
|
-
content: unknown | ISignalEnvelope,
|
|
66
|
-
targetClientId?: string,
|
|
67
|
-
) => void;
|
|
68
|
-
readonly disposeFn: (error?: ICriticalContainerError) => void;
|
|
69
|
-
readonly closeFn: (error?: ICriticalContainerError) => void;
|
|
70
|
-
readonly updateDirtyContainerState: (dirty: boolean) => void;
|
|
71
|
-
readonly getAbsoluteUrl: (relativeUrl: string) => Promise<string | undefined>;
|
|
72
67
|
readonly getContainerDiagnosticId: () => string | undefined;
|
|
73
68
|
readonly getClientId: () => string | undefined;
|
|
74
69
|
readonly getAttachState: () => AttachState;
|
|
75
70
|
readonly getConnected: () => boolean;
|
|
76
|
-
readonly getConnectionState: () => ConnectionState;
|
|
77
|
-
readonly clientDetails: IClientDetails;
|
|
78
71
|
readonly existing: boolean;
|
|
79
72
|
readonly taggedLogger: ITelemetryLoggerExt;
|
|
80
|
-
|
|
81
|
-
readonly snapshotWithContents
|
|
73
|
+
// This "overrides" IContainerContext.snapshotWithContents to be required but allow `undefined`.
|
|
74
|
+
readonly snapshotWithContents: IContainerContext["snapshotWithContents"] | undefined;
|
|
82
75
|
}
|
|
83
76
|
|
|
84
77
|
/**
|
|
85
78
|
* {@inheritDoc @fluidframework/container-definitions#IContainerContext}
|
|
86
79
|
*/
|
|
87
|
-
export class ContainerContext
|
|
80
|
+
export class ContainerContext
|
|
81
|
+
implements
|
|
82
|
+
Required<Omit<IContainerContext, "snapshotWithContents">>,
|
|
83
|
+
Pick<IContainerContext, "snapshotWithContents">,
|
|
84
|
+
IProvideLayerCompatDetails
|
|
85
|
+
{
|
|
88
86
|
/**
|
|
89
87
|
* @deprecated - This has been replaced by ILayerCompatDetails.
|
|
90
88
|
*/
|
|
@@ -139,7 +137,7 @@ export class ContainerContext implements IContainerContext, IProvideLayerCompatD
|
|
|
139
137
|
public readonly existing: boolean;
|
|
140
138
|
public readonly taggedLogger: ITelemetryLoggerExt;
|
|
141
139
|
public readonly pendingLocalState: unknown;
|
|
142
|
-
public readonly snapshotWithContents
|
|
140
|
+
public readonly snapshotWithContents?: ISnapshot;
|
|
143
141
|
|
|
144
142
|
public readonly getConnectionState: () => ConnectionState;
|
|
145
143
|
|
|
@@ -197,7 +195,9 @@ export class ContainerContext implements IContainerContext, IProvideLayerCompatD
|
|
|
197
195
|
this.existing = config.existing;
|
|
198
196
|
this.taggedLogger = config.taggedLogger;
|
|
199
197
|
this.pendingLocalState = config.pendingLocalState;
|
|
200
|
-
|
|
198
|
+
if (config.snapshotWithContents !== undefined) {
|
|
199
|
+
this.snapshotWithContents = config.snapshotWithContents;
|
|
200
|
+
}
|
|
201
201
|
|
|
202
202
|
this.getConnectionState = config.getConnectionState;
|
|
203
203
|
this._getClientId = config.getClientId;
|
package/src/contracts.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type IConnectionDetails,
|
|
13
13
|
} from "@fluidframework/container-definitions/internal";
|
|
14
14
|
import type { IErrorBase, ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
|
|
15
|
+
import type { JsonString } from "@fluidframework/core-interfaces/internal";
|
|
15
16
|
import type { ConnectionMode, IClientDetails } from "@fluidframework/driver-definitions";
|
|
16
17
|
import type {
|
|
17
18
|
IContainerPackageInfo,
|
|
@@ -145,7 +146,9 @@ export interface IConnectionManagerFactoryArgs {
|
|
|
145
146
|
* Called by connection manager for each incoming signal.
|
|
146
147
|
* May be called before connectHandler is called (due to initial signals on socket connection)
|
|
147
148
|
*/
|
|
148
|
-
readonly signalHandler: (
|
|
149
|
+
readonly signalHandler: (
|
|
150
|
+
signals: ISignalMessage<{ type: never; content: JsonString<unknown> }>[],
|
|
151
|
+
) => void;
|
|
149
152
|
|
|
150
153
|
/**
|
|
151
154
|
* Called when connection manager experiences delay in connecting to relay service.
|
|
@@ -179,51 +179,12 @@ export async function loadExistingContainer(
|
|
|
179
179
|
* Properties required to load a frozen container from pending state.
|
|
180
180
|
* @legacy @alpha
|
|
181
181
|
*/
|
|
182
|
-
export interface ILoadFrozenContainerFromPendingStateProps
|
|
183
|
-
|
|
184
|
-
* The code loader handles loading the necessary code for running a container once it is loaded.
|
|
185
|
-
*/
|
|
186
|
-
readonly codeLoader: ICodeDetailsLoader;
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* The url resolver used by the loader for resolving external urls into Fluid urls.
|
|
190
|
-
*/
|
|
191
|
-
readonly urlResolver: IUrlResolver;
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* The request to resolve the container.
|
|
195
|
-
*/
|
|
196
|
-
readonly request: IRequest;
|
|
197
|
-
|
|
182
|
+
export interface ILoadFrozenContainerFromPendingStateProps
|
|
183
|
+
extends ILoadExistingContainerProps {
|
|
198
184
|
/**
|
|
199
185
|
* Pending local state to be applied to the container.
|
|
200
186
|
*/
|
|
201
187
|
readonly pendingLocalState: string;
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* A property bag of options/policies used by various layers to control features.
|
|
205
|
-
*/
|
|
206
|
-
readonly options?: IContainerPolicies | undefined;
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Scope is provided to all container and is a set of shared services for container's to integrate with their host environment.
|
|
210
|
-
*/
|
|
211
|
-
readonly scope?: FluidObject | undefined;
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* The logger that all telemetry should be pushed to.
|
|
215
|
-
*/
|
|
216
|
-
readonly logger?: ITelemetryBaseLogger | undefined;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* The configuration provider which may be used to control features.
|
|
220
|
-
*/
|
|
221
|
-
readonly configProvider?: IConfigProviderBase | undefined;
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Client details provided in the override will be merged over the default client.
|
|
225
|
-
*/
|
|
226
|
-
readonly clientDetailsOverride?: IClientDetails | undefined;
|
|
227
188
|
}
|
|
228
189
|
|
|
229
190
|
/**
|
|
@@ -236,6 +197,6 @@ export async function loadFrozenContainerFromPendingState(
|
|
|
236
197
|
): Promise<IContainer> {
|
|
237
198
|
return loadExistingContainer({
|
|
238
199
|
...props,
|
|
239
|
-
documentServiceFactory: new FrozenDocumentServiceFactory(),
|
|
200
|
+
documentServiceFactory: new FrozenDocumentServiceFactory(props.documentServiceFactory),
|
|
240
201
|
});
|
|
241
202
|
}
|
package/src/deltaManager.ts
CHANGED
|
@@ -16,7 +16,8 @@ import type {
|
|
|
16
16
|
ITelemetryBaseEvent,
|
|
17
17
|
ITelemetryBaseProperties,
|
|
18
18
|
} from "@fluidframework/core-interfaces";
|
|
19
|
-
import
|
|
19
|
+
import { JsonParse } from "@fluidframework/core-interfaces/internal";
|
|
20
|
+
import type { IThrottlingWarning, JsonString } from "@fluidframework/core-interfaces/internal";
|
|
20
21
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
21
22
|
import type { ConnectionMode } from "@fluidframework/driver-definitions";
|
|
22
23
|
import {
|
|
@@ -210,7 +211,9 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
210
211
|
private initSequenceNumber: number = 0;
|
|
211
212
|
|
|
212
213
|
private readonly _inbound: DeltaQueue<ISequencedDocumentMessage>;
|
|
213
|
-
private readonly _inboundSignal: DeltaQueue<
|
|
214
|
+
private readonly _inboundSignal: DeltaQueue<
|
|
215
|
+
ISignalMessage<{ type: never; content: JsonString<unknown> }>
|
|
216
|
+
>;
|
|
214
217
|
|
|
215
218
|
private _closed = false;
|
|
216
219
|
private _disposed = false;
|
|
@@ -433,7 +436,9 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
433
436
|
this.close(normalizeError(error));
|
|
434
437
|
}
|
|
435
438
|
},
|
|
436
|
-
signalHandler: (
|
|
439
|
+
signalHandler: (
|
|
440
|
+
signals: ISignalMessage<{ type: never; content: JsonString<unknown> }>[],
|
|
441
|
+
) => {
|
|
437
442
|
for (const signal of signals) {
|
|
438
443
|
this._inboundSignal.push(signal);
|
|
439
444
|
}
|
|
@@ -474,14 +479,16 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
474
479
|
});
|
|
475
480
|
|
|
476
481
|
// Inbound signal queue
|
|
477
|
-
this._inboundSignal = new DeltaQueue<
|
|
482
|
+
this._inboundSignal = new DeltaQueue<
|
|
483
|
+
ISignalMessage<{ type: never; content: JsonString<unknown> }>
|
|
484
|
+
>((message) => {
|
|
478
485
|
if (this.handler === undefined) {
|
|
479
486
|
throw new Error("Attempted to process an inbound signal without a handler attached");
|
|
480
487
|
}
|
|
481
488
|
|
|
482
489
|
this.handler.processSignal({
|
|
483
490
|
...message,
|
|
484
|
-
content:
|
|
491
|
+
content: JsonParse(message.content),
|
|
485
492
|
});
|
|
486
493
|
});
|
|
487
494
|
|
package/src/frozenServices.ts
CHANGED
|
@@ -29,8 +29,13 @@ import {
|
|
|
29
29
|
import type { IConnectionStateChangeReason } from "./contracts.js";
|
|
30
30
|
|
|
31
31
|
export class FrozenDocumentServiceFactory implements IDocumentServiceFactory {
|
|
32
|
+
constructor(private readonly documentServiceFactory?: IDocumentServiceFactory) {}
|
|
33
|
+
|
|
32
34
|
async createDocumentService(resolvedUrl: IResolvedUrl): Promise<IDocumentService> {
|
|
33
|
-
return new FrozenDocumentService(
|
|
35
|
+
return new FrozenDocumentService(
|
|
36
|
+
resolvedUrl,
|
|
37
|
+
await this.documentServiceFactory?.createDocumentService(resolvedUrl),
|
|
38
|
+
);
|
|
34
39
|
}
|
|
35
40
|
async createContainer(): Promise<IDocumentService> {
|
|
36
41
|
throw new Error("The FrozenDocumentServiceFactory cannot be used to create containers.");
|
|
@@ -41,7 +46,10 @@ class FrozenDocumentService
|
|
|
41
46
|
extends TypedEventEmitter<IDocumentServiceEvents>
|
|
42
47
|
implements IDocumentService
|
|
43
48
|
{
|
|
44
|
-
constructor(
|
|
49
|
+
constructor(
|
|
50
|
+
public readonly resolvedUrl: IResolvedUrl,
|
|
51
|
+
private readonly documentService?: IDocumentService,
|
|
52
|
+
) {
|
|
45
53
|
super();
|
|
46
54
|
}
|
|
47
55
|
|
|
@@ -49,7 +57,7 @@ class FrozenDocumentService
|
|
|
49
57
|
storageOnly: true,
|
|
50
58
|
};
|
|
51
59
|
async connectToStorage(): Promise<IDocumentStorageService> {
|
|
52
|
-
return
|
|
60
|
+
return new FrozenDocumentStorageService(await this.documentService?.connectToStorage());
|
|
53
61
|
}
|
|
54
62
|
async connectToDeltaStorage(): Promise<IDocumentDeltaStorageService> {
|
|
55
63
|
return frozenDocumentDeltaStorageService;
|
|
@@ -63,15 +71,19 @@ class FrozenDocumentService
|
|
|
63
71
|
const frozenDocumentStorageServiceHandler = (): never => {
|
|
64
72
|
throw new Error("Operations are not supported on the FrozenDocumentStorageService.");
|
|
65
73
|
};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
class FrozenDocumentStorageService implements IDocumentStorageService {
|
|
75
|
+
constructor(private readonly documentStorageService?: IDocumentStorageService) {}
|
|
76
|
+
|
|
77
|
+
getSnapshotTree = frozenDocumentStorageServiceHandler;
|
|
78
|
+
getSnapshot = frozenDocumentStorageServiceHandler;
|
|
79
|
+
getVersions = frozenDocumentStorageServiceHandler;
|
|
80
|
+
createBlob = frozenDocumentStorageServiceHandler;
|
|
81
|
+
readBlob =
|
|
82
|
+
this.documentStorageService?.readBlob.bind(this.documentStorageService) ??
|
|
83
|
+
frozenDocumentStorageServiceHandler;
|
|
84
|
+
uploadSummaryWithContext = frozenDocumentStorageServiceHandler;
|
|
85
|
+
downloadSummary = frozenDocumentStorageServiceHandler;
|
|
86
|
+
}
|
|
75
87
|
|
|
76
88
|
const frozenDocumentDeltaStorageService: IDocumentDeltaStorageService = {
|
|
77
89
|
fetchMessages: () => ({
|
package/src/packageVersion.ts
CHANGED
package/src/protocol.ts
CHANGED
|
@@ -21,12 +21,39 @@ import {
|
|
|
21
21
|
} from "./protocol/index.js";
|
|
22
22
|
|
|
23
23
|
// ADO: #1986: Start using enum from protocol-base.
|
|
24
|
-
export
|
|
25
|
-
ClientJoin
|
|
26
|
-
ClientLeave
|
|
27
|
-
Clear
|
|
24
|
+
export const SignalType = {
|
|
25
|
+
ClientJoin: "join", // same value as MessageType.ClientJoin,
|
|
26
|
+
ClientLeave: "leave", // same value as MessageType.ClientLeave,
|
|
27
|
+
Clear: "clear", // used only by client for synthetic signals
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
interface SystemSignalContent {
|
|
31
|
+
type: (typeof SignalType)[keyof typeof SignalType];
|
|
32
|
+
content?: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface InboundSystemSignal<TSignalContent extends SystemSignalContent>
|
|
36
|
+
extends ISignalMessage<{ type: never; content: TSignalContent }> {
|
|
37
|
+
// eslint-disable-next-line @rushstack/no-new-null -- `null` is used in JSON protocol to indicate system message
|
|
38
|
+
readonly clientId: null;
|
|
28
39
|
}
|
|
29
40
|
|
|
41
|
+
type ClientJoinSignal = InboundSystemSignal<{
|
|
42
|
+
type: typeof SignalType.ClientJoin;
|
|
43
|
+
content: ISignalClient;
|
|
44
|
+
}>;
|
|
45
|
+
|
|
46
|
+
type ClientLeaveSignal = InboundSystemSignal<{
|
|
47
|
+
type: typeof SignalType.ClientLeave;
|
|
48
|
+
content: string; // clientId of leaving client
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
type ClearClientsSignal = InboundSystemSignal<{
|
|
52
|
+
type: typeof SignalType.Clear;
|
|
53
|
+
}>;
|
|
54
|
+
|
|
55
|
+
type AudienceSignal = ClientJoinSignal | ClientLeaveSignal | ClearClientsSignal;
|
|
56
|
+
|
|
30
57
|
/**
|
|
31
58
|
* Function to be used for creating a protocol handler.
|
|
32
59
|
* @legacy @beta
|
|
@@ -47,7 +74,35 @@ export interface IProtocolHandler extends IBaseProtocolHandler {
|
|
|
47
74
|
processSignal(message: ISignalMessage);
|
|
48
75
|
}
|
|
49
76
|
|
|
50
|
-
|
|
77
|
+
/**
|
|
78
|
+
* More specific version of {@link IProtocolHandler} with narrower call
|
|
79
|
+
* constraints for {@link IProtocolHandler.processSignal}.
|
|
80
|
+
*/
|
|
81
|
+
export interface ProtocolHandlerInternal extends IProtocolHandler {
|
|
82
|
+
/**
|
|
83
|
+
* Process the audience related signal.
|
|
84
|
+
* @privateRemarks
|
|
85
|
+
* Internally, only {@link AudienceSignal} messages need handling.
|
|
86
|
+
*/
|
|
87
|
+
processSignal(message: AudienceSignal): void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Function to be used for creating a protocol handler.
|
|
92
|
+
*
|
|
93
|
+
* @remarks This is the same are {@link ProtocolHandlerBuilder} but
|
|
94
|
+
* returns the {@link ProtocolHandlerInternal} which has narrower
|
|
95
|
+
* expectations for `processSignal`.
|
|
96
|
+
*/
|
|
97
|
+
export type InternalProtocolHandlerBuilder = (
|
|
98
|
+
attributes: IDocumentAttributes,
|
|
99
|
+
snapshot: IQuorumSnapshot,
|
|
100
|
+
// TODO: use a real type (breaking change)
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
sendProposal: (key: string, value: any) => number,
|
|
103
|
+
) => ProtocolHandlerInternal;
|
|
104
|
+
|
|
105
|
+
export class ProtocolHandler extends ProtocolOpHandler implements ProtocolHandlerInternal {
|
|
51
106
|
constructor(
|
|
52
107
|
attributes: IDocumentAttributes,
|
|
53
108
|
quorumSnapshot: IQuorumSnapshot,
|
|
@@ -104,8 +159,8 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
104
159
|
return super.processMessage(message, local);
|
|
105
160
|
}
|
|
106
161
|
|
|
107
|
-
public processSignal(message:
|
|
108
|
-
const innerContent = message.content
|
|
162
|
+
public processSignal(message: AudienceSignal): void {
|
|
163
|
+
const innerContent = message.content;
|
|
109
164
|
switch (innerContent.type) {
|
|
110
165
|
case SignalType.Clear: {
|
|
111
166
|
const members = this.audience.getMembers();
|
|
@@ -117,7 +172,7 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
117
172
|
break;
|
|
118
173
|
}
|
|
119
174
|
case SignalType.ClientJoin: {
|
|
120
|
-
const newClient = innerContent.content
|
|
175
|
+
const newClient = innerContent.content;
|
|
121
176
|
// Ignore write clients - quorum will control such clients.
|
|
122
177
|
if (newClient.client.mode === "read") {
|
|
123
178
|
this.audience.addMember(newClient.clientId, newClient.client);
|
|
@@ -125,7 +180,7 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
125
180
|
break;
|
|
126
181
|
}
|
|
127
182
|
case SignalType.ClientLeave: {
|
|
128
|
-
const leftClientId = innerContent.content
|
|
183
|
+
const leftClientId = innerContent.content;
|
|
129
184
|
// Ignore write clients - quorum will control such clients.
|
|
130
185
|
if (this.audience.getMember(leftClientId)?.mode === "read") {
|
|
131
186
|
this.audience.removeMember(leftClientId);
|
|
@@ -144,7 +199,9 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
144
199
|
* The protocol handler should strictly handle only ClientJoin, ClientLeave
|
|
145
200
|
* and Clear signal types.
|
|
146
201
|
*/
|
|
147
|
-
export function protocolHandlerShouldProcessSignal(
|
|
202
|
+
export function protocolHandlerShouldProcessSignal(
|
|
203
|
+
message: ISignalMessage,
|
|
204
|
+
): message is AudienceSignal {
|
|
148
205
|
// Signal originates from server
|
|
149
206
|
if (message.clientId === null) {
|
|
150
207
|
const innerContent = message.content as { content: unknown; type: string };
|
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
IEventProvider,
|
|
10
10
|
IEvent,
|
|
11
11
|
ITelemetryBaseLogger,
|
|
12
|
-
Tagged,
|
|
13
12
|
} from "@fluidframework/core-interfaces";
|
|
14
13
|
import { Timer, assert } from "@fluidframework/core-utils/internal";
|
|
15
14
|
import {
|
|
@@ -28,7 +27,6 @@ import {
|
|
|
28
27
|
PerformanceEvent,
|
|
29
28
|
UsageError,
|
|
30
29
|
createChildMonitoringContext,
|
|
31
|
-
type TelemetryEventPropertyTypeExt,
|
|
32
30
|
} from "@fluidframework/telemetry-utils/internal";
|
|
33
31
|
|
|
34
32
|
import {
|
|
@@ -135,6 +133,27 @@ interface ISerializerEvent extends IEvent {
|
|
|
135
133
|
(event: "saved", listener: (dirty: boolean) => void): void;
|
|
136
134
|
}
|
|
137
135
|
|
|
136
|
+
class RefreshPromiseTracker {
|
|
137
|
+
public get hasPromise(): boolean {
|
|
138
|
+
return this.#promise !== undefined;
|
|
139
|
+
}
|
|
140
|
+
public get Promise(): Promise<number> | undefined {
|
|
141
|
+
return this.#promise;
|
|
142
|
+
}
|
|
143
|
+
constructor(private readonly catchHandler: (error: Error) => void) {}
|
|
144
|
+
|
|
145
|
+
#promise: Promise<number> | undefined;
|
|
146
|
+
setPromise(p: Promise<number>): void {
|
|
147
|
+
if (this.hasPromise) {
|
|
148
|
+
throw new Error("Cannot set promise while promise exists");
|
|
149
|
+
}
|
|
150
|
+
this.#promise = p.finally(() => {
|
|
151
|
+
this.#promise = undefined;
|
|
152
|
+
});
|
|
153
|
+
p.catch(this.catchHandler);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
138
157
|
/**
|
|
139
158
|
* Helper class to manage the state of the container needed for proper serialization.
|
|
140
159
|
*
|
|
@@ -147,7 +166,16 @@ export class SerializedStateManager {
|
|
|
147
166
|
private readonly mc: MonitoringContext;
|
|
148
167
|
private snapshot: ISnapshotInfo | undefined;
|
|
149
168
|
private latestSnapshot: ISnapshotInfo | undefined;
|
|
150
|
-
private
|
|
169
|
+
private readonly refreshTracker = new RefreshPromiseTracker(
|
|
170
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
171
|
+
(error) =>
|
|
172
|
+
this.mc.logger.sendErrorEvent(
|
|
173
|
+
{
|
|
174
|
+
eventName: "RefreshLatestSnapshotFailed",
|
|
175
|
+
},
|
|
176
|
+
error,
|
|
177
|
+
),
|
|
178
|
+
);
|
|
151
179
|
private readonly lastSavedOpSequenceNumber: number = 0;
|
|
152
180
|
private readonly refreshTimer: Timer;
|
|
153
181
|
private readonly snapshotRefreshTimeoutMs: number = 60 * 60 * 24 * 1000;
|
|
@@ -199,8 +227,8 @@ export class SerializedStateManager {
|
|
|
199
227
|
* only intended to be used for testing purposes.
|
|
200
228
|
* @returns The snapshot sequence number associated with the latest fetched snapshot
|
|
201
229
|
*/
|
|
202
|
-
public get refreshSnapshotP(): Promise<number> | undefined {
|
|
203
|
-
return this.
|
|
230
|
+
public get refreshSnapshotP(): Promise<number | undefined> | undefined {
|
|
231
|
+
return this.refreshTracker.Promise;
|
|
204
232
|
}
|
|
205
233
|
|
|
206
234
|
/**
|
|
@@ -226,6 +254,7 @@ export class SerializedStateManager {
|
|
|
226
254
|
public async fetchSnapshot(specifiedVersion: string | undefined): Promise<{
|
|
227
255
|
baseSnapshot: ISnapshot | ISnapshotTree;
|
|
228
256
|
version: IVersion | undefined;
|
|
257
|
+
attributes: IDocumentAttributes;
|
|
229
258
|
}> {
|
|
230
259
|
if (this.pendingLocalState === undefined) {
|
|
231
260
|
const { baseSnapshot, version } = await getSnapshot(
|
|
@@ -235,18 +264,24 @@ export class SerializedStateManager {
|
|
|
235
264
|
specifiedVersion,
|
|
236
265
|
);
|
|
237
266
|
const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(baseSnapshot);
|
|
267
|
+
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshotTree);
|
|
238
268
|
// non-interactive clients will not have any pending state we want to save
|
|
239
269
|
if (this.offlineLoadEnabled) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
this.
|
|
243
|
-
baseSnapshot
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
270
|
+
// we defer getting the blobs to not impact the container load flow
|
|
271
|
+
// only getPendingState depends on the resolution of this promise
|
|
272
|
+
this.refreshTracker.setPromise(
|
|
273
|
+
getBlobContentsFromTree(baseSnapshot, this.storageAdapter).then((snapshotBlobs) => {
|
|
274
|
+
this.snapshot = {
|
|
275
|
+
baseSnapshot: baseSnapshotTree,
|
|
276
|
+
snapshotBlobs,
|
|
277
|
+
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
278
|
+
};
|
|
279
|
+
this.refreshTimer.start();
|
|
280
|
+
return attributes.sequenceNumber;
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
248
283
|
}
|
|
249
|
-
return { baseSnapshot, version };
|
|
284
|
+
return { baseSnapshot, version, attributes };
|
|
250
285
|
} else {
|
|
251
286
|
const { baseSnapshot, snapshotBlobs } = this.pendingLocalState;
|
|
252
287
|
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshot);
|
|
@@ -268,30 +303,18 @@ export class SerializedStateManager {
|
|
|
268
303
|
ops: [],
|
|
269
304
|
snapshotFormatV: 1,
|
|
270
305
|
};
|
|
271
|
-
return { baseSnapshot: iSnapshot, version: undefined };
|
|
306
|
+
return { baseSnapshot: iSnapshot, version: undefined, attributes };
|
|
272
307
|
}
|
|
273
308
|
}
|
|
274
309
|
|
|
275
310
|
private tryRefreshSnapshot(): void {
|
|
276
311
|
if (
|
|
277
312
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") === true &&
|
|
278
|
-
this.
|
|
313
|
+
!this.refreshTracker.hasPromise &&
|
|
279
314
|
this.latestSnapshot === undefined
|
|
280
315
|
) {
|
|
281
316
|
// Don't block on the refresh snapshot call - it is for the next time we serialize, not booting this incarnation
|
|
282
|
-
this.
|
|
283
|
-
this._refreshSnapshotP
|
|
284
|
-
.catch(
|
|
285
|
-
(error: TelemetryEventPropertyTypeExt | Tagged<TelemetryEventPropertyTypeExt>) => {
|
|
286
|
-
this.mc.logger.sendTelemetryEvent({
|
|
287
|
-
eventName: "RefreshLatestSnapshotFailed",
|
|
288
|
-
error,
|
|
289
|
-
});
|
|
290
|
-
},
|
|
291
|
-
)
|
|
292
|
-
.finally(() => {
|
|
293
|
-
this._refreshSnapshotP = undefined;
|
|
294
|
-
});
|
|
317
|
+
this.refreshTracker.setPromise(this.refreshLatestSnapshot(this.supportGetSnapshotApi()));
|
|
295
318
|
}
|
|
296
319
|
}
|
|
297
320
|
|
|
@@ -439,6 +462,14 @@ export class SerializedStateManager {
|
|
|
439
462
|
if (!this.offlineLoadEnabled) {
|
|
440
463
|
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
441
464
|
}
|
|
465
|
+
if (this.snapshot === undefined && this.refreshTracker.hasPromise) {
|
|
466
|
+
// we deferred the initial download of the snapshot to not block
|
|
467
|
+
// the container load flow, so if it is not resolved
|
|
468
|
+
// and we don't have a snapshot, we will wait for the download
|
|
469
|
+
// to finish.
|
|
470
|
+
await this.refreshTracker.Promise;
|
|
471
|
+
}
|
|
472
|
+
|
|
442
473
|
assert(this.snapshot !== undefined, 0x8e5 /* no base data */);
|
|
443
474
|
const pendingRuntimeState = await runtime.getPendingLocalState({
|
|
444
475
|
notifyImminentClosure: false,
|