@fluidframework/container-loader 2.0.0-dev-rc.1.0.0.228517 → 2.0.0-dev-rc.1.0.0.232845
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/README.md +3 -3
- package/dist/attachment.d.ts +116 -0
- package/dist/attachment.d.ts.map +1 -0
- package/dist/attachment.js +82 -0
- package/dist/attachment.js.map +1 -0
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +1 -2
- package/dist/connectionManager.js.map +1 -1
- package/dist/container-loader-alpha.d.ts +1 -1
- package/dist/container-loader-untrimmed.d.ts +1 -1
- package/dist/container.d.ts +21 -8
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +177 -149
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +3 -2
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +2 -1
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +3 -3
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +10 -11
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/loader.d.ts +1 -1
- package/dist/loader.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/protocolTreeDocumentStorageService.d.ts +1 -0
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js +1 -0
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +2 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +8 -0
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/utils.d.ts +13 -7
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +98 -29
- package/dist/utils.js.map +1 -1
- package/lib/attachment.d.mts +112 -0
- package/lib/attachment.d.mts.map +1 -0
- package/lib/attachment.mjs +74 -0
- package/lib/attachment.mjs.map +1 -0
- package/lib/connectionManager.d.mts.map +1 -1
- package/lib/connectionManager.mjs +2 -5
- package/lib/connectionManager.mjs.map +1 -1
- package/lib/container-loader-alpha.d.mts +1 -1
- package/lib/container-loader-untrimmed.d.mts +1 -1
- package/lib/container.d.mts +21 -8
- package/lib/container.d.mts.map +1 -1
- package/lib/container.mjs +176 -151
- package/lib/container.mjs.map +1 -1
- package/lib/containerContext.d.mts +3 -2
- package/lib/containerContext.d.mts.map +1 -1
- package/lib/containerContext.mjs +2 -1
- package/lib/containerContext.mjs.map +1 -1
- package/lib/containerStorageAdapter.d.mts +3 -3
- package/lib/containerStorageAdapter.d.mts.map +1 -1
- package/lib/containerStorageAdapter.mjs +10 -11
- package/lib/containerStorageAdapter.mjs.map +1 -1
- package/lib/loader.d.mts +1 -1
- package/lib/loader.mjs.map +1 -1
- package/lib/packageVersion.d.mts +1 -1
- package/lib/packageVersion.mjs +1 -1
- package/lib/packageVersion.mjs.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.mts +1 -0
- package/lib/protocolTreeDocumentStorageService.d.mts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.mjs +1 -0
- package/lib/protocolTreeDocumentStorageService.mjs.map +1 -1
- package/lib/retriableDocumentStorageService.d.mts +2 -1
- package/lib/retriableDocumentStorageService.d.mts.map +1 -1
- package/lib/retriableDocumentStorageService.mjs +9 -1
- package/lib/retriableDocumentStorageService.mjs.map +1 -1
- package/lib/utils.d.mts +13 -7
- package/lib/utils.d.mts.map +1 -1
- package/lib/utils.mjs +96 -29
- package/lib/utils.mjs.map +1 -1
- package/package.json +21 -15
- package/src/attachment.ts +219 -0
- package/src/connectionManager.ts +2 -4
- package/src/container.ts +260 -191
- package/src/containerContext.ts +2 -1
- package/src/containerStorageAdapter.ts +15 -12
- package/src/loader.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/protocolTreeDocumentStorageService.ts +1 -0
- package/src/retriableDocumentStorageService.ts +18 -1
- package/src/utils.ts +128 -36
- /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
package/src/container.ts
CHANGED
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
IEvent,
|
|
11
11
|
ITelemetryProperties,
|
|
12
12
|
TelemetryEventCategory,
|
|
13
|
-
IRequest,
|
|
14
13
|
FluidObject,
|
|
15
14
|
LogLevel,
|
|
15
|
+
IRequest,
|
|
16
16
|
} from "@fluidframework/core-interfaces";
|
|
17
17
|
import {
|
|
18
18
|
AttachState,
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
IDocumentServiceFactory,
|
|
42
42
|
IDocumentStorageService,
|
|
43
43
|
IResolvedUrl,
|
|
44
|
+
ISnapshot,
|
|
44
45
|
IThrottlingWarning,
|
|
45
46
|
IUrlResolver,
|
|
46
47
|
} from "@fluidframework/driver-definitions";
|
|
@@ -48,9 +49,10 @@ import {
|
|
|
48
49
|
readAndParse,
|
|
49
50
|
OnlineStatus,
|
|
50
51
|
isOnline,
|
|
51
|
-
runWithRetry,
|
|
52
52
|
isCombinedAppAndProtocolSummary,
|
|
53
53
|
MessageType2,
|
|
54
|
+
isInstanceOfISnapshot,
|
|
55
|
+
runWithRetry,
|
|
54
56
|
} from "@fluidframework/driver-utils";
|
|
55
57
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
56
58
|
import {
|
|
@@ -86,7 +88,9 @@ import {
|
|
|
86
88
|
formatTick,
|
|
87
89
|
GenericError,
|
|
88
90
|
UsageError,
|
|
91
|
+
IFluidErrorBase,
|
|
89
92
|
} from "@fluidframework/telemetry-utils";
|
|
93
|
+
import structuredClone from "@ungap/structured-clone";
|
|
90
94
|
import { Audience } from "./audience";
|
|
91
95
|
import { ContainerContext } from "./containerContext";
|
|
92
96
|
import {
|
|
@@ -102,14 +106,17 @@ import { pkgVersion } from "./packageVersion";
|
|
|
102
106
|
import {
|
|
103
107
|
ContainerStorageAdapter,
|
|
104
108
|
getBlobContentsFromTree,
|
|
105
|
-
getBlobContentsFromTreeWithBlobContents,
|
|
106
109
|
ISerializableBlobContents,
|
|
107
110
|
} from "./containerStorageAdapter";
|
|
108
111
|
import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
|
|
109
112
|
import {
|
|
113
|
+
ISnapshotTreeWithBlobContents,
|
|
110
114
|
combineAppAndProtocolSummary,
|
|
111
115
|
getProtocolSnapshotTree,
|
|
112
|
-
|
|
116
|
+
getSnapshotTreeAndBlobsFromSerializedContainer,
|
|
117
|
+
combineSnapshotTreeAndSnapshotBlobs,
|
|
118
|
+
getDetachedContainerStateFromSerializedContainer,
|
|
119
|
+
runSingle,
|
|
113
120
|
} from "./utils";
|
|
114
121
|
import { initQuorumValuesFromCodeDetails } from "./quorum";
|
|
115
122
|
import { NoopHeuristic } from "./noopHeuristic";
|
|
@@ -121,6 +128,7 @@ import {
|
|
|
121
128
|
ProtocolHandlerBuilder,
|
|
122
129
|
protocolHandlerShouldProcessSignal,
|
|
123
130
|
} from "./protocol";
|
|
131
|
+
import { AttachProcessProps, AttachmentData, runRetriableAttachProcess } from "./attachment";
|
|
124
132
|
|
|
125
133
|
const detachedContainerRefSeqNumber = 0;
|
|
126
134
|
|
|
@@ -129,8 +137,6 @@ const savedContainerEvent = "saved";
|
|
|
129
137
|
|
|
130
138
|
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
131
139
|
|
|
132
|
-
const hasBlobsSummaryTree = ".hasAttachmentBlobs";
|
|
133
|
-
|
|
134
140
|
/**
|
|
135
141
|
* @internal
|
|
136
142
|
*/
|
|
@@ -360,6 +366,18 @@ export interface IPendingContainerState {
|
|
|
360
366
|
clientId?: string;
|
|
361
367
|
}
|
|
362
368
|
|
|
369
|
+
/**
|
|
370
|
+
* State saved by a container in detached state, to be used to load a new instance
|
|
371
|
+
* of the container to the same state (rehydrate)
|
|
372
|
+
* @internal
|
|
373
|
+
*/
|
|
374
|
+
export interface IPendingDetachedContainerState {
|
|
375
|
+
attached: boolean;
|
|
376
|
+
baseSnapshot: ISnapshotTree;
|
|
377
|
+
snapshotBlobs: ISerializableBlobContents;
|
|
378
|
+
hasAttachmentBlobs: boolean;
|
|
379
|
+
}
|
|
380
|
+
|
|
363
381
|
const summarizerClientType = "summarizer";
|
|
364
382
|
|
|
365
383
|
interface IContainerLifecycleEvents extends IEvent {
|
|
@@ -471,12 +489,9 @@ export class Container
|
|
|
471
489
|
container.mc.logger,
|
|
472
490
|
{ eventName: "RehydrateDetachedFromSnapshot" },
|
|
473
491
|
async (_event) => {
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
492
|
+
const detachedContainerState: IPendingDetachedContainerState =
|
|
493
|
+
getDetachedContainerStateFromSerializedContainer(snapshot);
|
|
494
|
+
await container.rehydrateDetachedFromSnapshot(detachedContainerState);
|
|
480
495
|
return container;
|
|
481
496
|
},
|
|
482
497
|
{ start: true, end: true, cancel: "generic" },
|
|
@@ -551,8 +566,6 @@ export class Container
|
|
|
551
566
|
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
552
567
|
}
|
|
553
568
|
|
|
554
|
-
private _attachState = AttachState.Detached;
|
|
555
|
-
|
|
556
569
|
private readonly storageAdapter: ContainerStorageAdapter;
|
|
557
570
|
|
|
558
571
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
@@ -578,17 +591,16 @@ export class Container
|
|
|
578
591
|
private firstConnection = true;
|
|
579
592
|
private readonly connectionTransitionTimes: number[] = [];
|
|
580
593
|
private _loadedFromVersion: IVersion | undefined;
|
|
581
|
-
private attachStarted = false;
|
|
582
594
|
private _dirtyContainer = false;
|
|
583
595
|
private readonly savedOps: ISequencedDocumentMessage[] = [];
|
|
584
|
-
private
|
|
585
|
-
private baseSnapshotBlobs?: ISerializableBlobContents;
|
|
596
|
+
private attachmentData: AttachmentData = { state: AttachState.Detached };
|
|
586
597
|
private readonly _containerId: string;
|
|
587
598
|
|
|
588
599
|
private lastVisible: number | undefined;
|
|
589
600
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
590
601
|
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
591
602
|
private readonly clientsWhoShouldHaveLeft = new Set<string>();
|
|
603
|
+
private _containerMetadata: Readonly<Record<string, string>> = {};
|
|
592
604
|
|
|
593
605
|
private setAutoReconnectTime = performance.now();
|
|
594
606
|
|
|
@@ -617,6 +629,10 @@ export class Container
|
|
|
617
629
|
return this._deltaManager.readOnlyInfo;
|
|
618
630
|
}
|
|
619
631
|
|
|
632
|
+
public get containerMetadata(): Record<string, string> {
|
|
633
|
+
return this._containerMetadata;
|
|
634
|
+
}
|
|
635
|
+
|
|
620
636
|
/**
|
|
621
637
|
* Sends signal to runtime (and data stores) to be read-only.
|
|
622
638
|
* Hosts may have read only views, indicating to data stores that no edits are allowed.
|
|
@@ -829,7 +845,7 @@ export class Container
|
|
|
829
845
|
clientType, // Differentiating summarizer container from main container
|
|
830
846
|
containerId: this._containerId,
|
|
831
847
|
docId: () => this.resolvedUrl?.id,
|
|
832
|
-
containerAttachState: () => this.
|
|
848
|
+
containerAttachState: () => this.attachState,
|
|
833
849
|
containerLifecycleState: () => this._lifecycleState,
|
|
834
850
|
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
835
851
|
serializedContainer: pendingLocalState !== undefined,
|
|
@@ -1030,6 +1046,11 @@ export class Container
|
|
|
1030
1046
|
|
|
1031
1047
|
this._lifecycleState = "closing";
|
|
1032
1048
|
|
|
1049
|
+
// Back-compat for Old driver
|
|
1050
|
+
if (this.service?.off !== undefined) {
|
|
1051
|
+
this.service?.off("metadataUpdate", this.metadataUpdateHandler);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1033
1054
|
this._protocolHandler?.close();
|
|
1034
1055
|
|
|
1035
1056
|
this.connectionStateHandler.dispose();
|
|
@@ -1147,20 +1168,19 @@ export class Container
|
|
|
1147
1168
|
);
|
|
1148
1169
|
}
|
|
1149
1170
|
assert(
|
|
1150
|
-
this.
|
|
1171
|
+
this.attachmentData.state === AttachState.Attached,
|
|
1151
1172
|
0x0d1 /* "Container should be attached before close" */,
|
|
1152
1173
|
);
|
|
1153
1174
|
assert(
|
|
1154
1175
|
this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
1155
1176
|
0x0d2 /* "resolved url should be valid Fluid url" */,
|
|
1156
1177
|
);
|
|
1157
|
-
assert(
|
|
1158
|
-
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
1178
|
+
assert(this.attachmentData.snapshot !== undefined, 0x5d5 /* no base data */);
|
|
1159
1179
|
const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
|
|
1160
1180
|
const pendingState: IPendingContainerState = {
|
|
1161
1181
|
pendingRuntimeState,
|
|
1162
|
-
baseSnapshot: this.
|
|
1163
|
-
snapshotBlobs: this.
|
|
1182
|
+
baseSnapshot: this.attachmentData.snapshot.tree,
|
|
1183
|
+
snapshotBlobs: this.attachmentData.snapshot.blobs,
|
|
1164
1184
|
savedOps: this.savedOps,
|
|
1165
1185
|
url: this.resolvedUrl.url,
|
|
1166
1186
|
// no need to save this if there is no pending runtime state
|
|
@@ -1173,7 +1193,7 @@ export class Container
|
|
|
1173
1193
|
}
|
|
1174
1194
|
|
|
1175
1195
|
public get attachState(): AttachState {
|
|
1176
|
-
return this.
|
|
1196
|
+
return this.attachmentData.state;
|
|
1177
1197
|
}
|
|
1178
1198
|
|
|
1179
1199
|
public serialize(): string {
|
|
@@ -1186,142 +1206,129 @@ export class Container
|
|
|
1186
1206
|
const protocolSummary = this.captureProtocolSummary();
|
|
1187
1207
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1188
1208
|
|
|
1189
|
-
|
|
1190
|
-
combinedSummary
|
|
1191
|
-
type: SummaryType.Blob,
|
|
1192
|
-
content: "true",
|
|
1193
|
-
};
|
|
1194
|
-
}
|
|
1195
|
-
return JSON.stringify(combinedSummary);
|
|
1196
|
-
}
|
|
1209
|
+
const { tree: snapshot, blobs } =
|
|
1210
|
+
getSnapshotTreeAndBlobsFromSerializedContainer(combinedSummary);
|
|
1197
1211
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
try {
|
|
1225
|
-
assert(
|
|
1226
|
-
this.deltaManager.inbound.length === 0,
|
|
1227
|
-
0x0d6 /* "Inbound queue should be empty when attaching" */,
|
|
1228
|
-
);
|
|
1229
|
-
|
|
1230
|
-
let summary: ISummaryTree;
|
|
1231
|
-
if (!hasAttachmentBlobs) {
|
|
1232
|
-
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
1233
|
-
// semantics around what the attach means as far as async code goes.
|
|
1234
|
-
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1235
|
-
const protocolSummary = this.captureProtocolSummary();
|
|
1236
|
-
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1237
|
-
|
|
1238
|
-
// Set the state as attaching as we are starting the process of attaching container.
|
|
1239
|
-
// This should be fired after taking the summary because it is the place where we are
|
|
1240
|
-
// starting to attach the container to storage.
|
|
1241
|
-
// Also, this should only be fired in detached container.
|
|
1242
|
-
this._attachState = AttachState.Attaching;
|
|
1243
|
-
this.runtime.setAttachState(AttachState.Attaching);
|
|
1244
|
-
this.emit("attaching");
|
|
1245
|
-
if (this.offlineLoadEnabled) {
|
|
1246
|
-
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
1247
|
-
this.baseSnapshot = snapshot;
|
|
1248
|
-
this.baseSnapshotBlobs =
|
|
1249
|
-
getBlobContentsFromTreeWithBlobContents(snapshot);
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// Actually go and create the resolved document
|
|
1254
|
-
if (this.service === undefined) {
|
|
1255
|
-
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
1256
|
-
assert(
|
|
1257
|
-
this.client.details.type !== summarizerClientType &&
|
|
1258
|
-
createNewResolvedUrl !== undefined,
|
|
1259
|
-
0x2c4 /* "client should not be summarizer before container is created" */,
|
|
1260
|
-
);
|
|
1261
|
-
this.service = await runWithRetry(
|
|
1262
|
-
async () =>
|
|
1263
|
-
this.serviceFactory.createContainer(
|
|
1264
|
-
summary,
|
|
1265
|
-
createNewResolvedUrl,
|
|
1266
|
-
this.subLogger,
|
|
1267
|
-
false, // clientIsSummarizer
|
|
1268
|
-
),
|
|
1269
|
-
"containerAttach",
|
|
1270
|
-
this.mc.logger,
|
|
1271
|
-
{
|
|
1272
|
-
cancel: this._deltaManager.closeAbortController.signal,
|
|
1273
|
-
}, // progress
|
|
1212
|
+
const detachedContainerState: IPendingDetachedContainerState = {
|
|
1213
|
+
attached: false,
|
|
1214
|
+
baseSnapshot: snapshot,
|
|
1215
|
+
snapshotBlobs: blobs,
|
|
1216
|
+
hasAttachmentBlobs: !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
|
|
1217
|
+
};
|
|
1218
|
+
return JSON.stringify(detachedContainerState);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
public readonly attach = runSingle(
|
|
1222
|
+
async (
|
|
1223
|
+
request: IRequest,
|
|
1224
|
+
attachProps?: { deltaConnection?: "none" | "delayed" },
|
|
1225
|
+
): Promise<void> => {
|
|
1226
|
+
await PerformanceEvent.timedExecAsync(
|
|
1227
|
+
this.mc.logger,
|
|
1228
|
+
{ eventName: "Attach" },
|
|
1229
|
+
async () => {
|
|
1230
|
+
if (
|
|
1231
|
+
this._lifecycleState !== "loaded" ||
|
|
1232
|
+
this.attachmentData.state === AttachState.Attached
|
|
1233
|
+
) {
|
|
1234
|
+
// pre-0.58 error message: containerNotValidForAttach
|
|
1235
|
+
throw new UsageError(
|
|
1236
|
+
`The Container is not in a valid state for attach [${this._lifecycleState}] and [${this.attachState}]`,
|
|
1274
1237
|
);
|
|
1275
1238
|
}
|
|
1276
|
-
this.storageAdapter.connectToService(this.service);
|
|
1277
1239
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1240
|
+
const normalizeErrorAndClose = (error: unknown): IFluidErrorBase => {
|
|
1241
|
+
const newError = normalizeError(error);
|
|
1242
|
+
this.close(newError);
|
|
1243
|
+
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
1244
|
+
newError.addTelemetryProperties({
|
|
1245
|
+
resolvedUrl: this.service?.resolvedUrl?.url,
|
|
1246
|
+
});
|
|
1247
|
+
return newError;
|
|
1248
|
+
};
|
|
1284
1249
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1250
|
+
const setAttachmentData: AttachProcessProps["setAttachmentData"] = (
|
|
1251
|
+
attachmentData,
|
|
1252
|
+
) => {
|
|
1253
|
+
const previousState = this.attachmentData.state;
|
|
1254
|
+
this.attachmentData = attachmentData;
|
|
1255
|
+
const state = this.attachmentData.state;
|
|
1256
|
+
if (state !== previousState && state !== AttachState.Detached) {
|
|
1257
|
+
try {
|
|
1258
|
+
this.runtime.setAttachState(state);
|
|
1259
|
+
this.emit(state.toLocaleLowerCase());
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
throw normalizeErrorAndClose(error);
|
|
1297
1262
|
}
|
|
1298
1263
|
}
|
|
1264
|
+
};
|
|
1299
1265
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1266
|
+
const createAttachmentSummary: AttachProcessProps["createAttachmentSummary"] = (
|
|
1267
|
+
redirectTable?: Map<string, string>,
|
|
1268
|
+
) => {
|
|
1269
|
+
try {
|
|
1270
|
+
assert(
|
|
1271
|
+
this.deltaManager.inbound.length === 0,
|
|
1272
|
+
0x0d6 /* "Inbound queue should be empty when attaching" */,
|
|
1273
|
+
);
|
|
1274
|
+
return combineAppAndProtocolSummary(
|
|
1275
|
+
this.runtime.createSummary(redirectTable),
|
|
1276
|
+
this.captureProtocolSummary(),
|
|
1277
|
+
);
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
throw normalizeErrorAndClose(error);
|
|
1313
1280
|
}
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
const createOrGetStorageService: AttachProcessProps["createOrGetStorageService"] =
|
|
1284
|
+
async (summary) => {
|
|
1285
|
+
// Actually go and create the resolved document
|
|
1286
|
+
if (this.service === undefined) {
|
|
1287
|
+
const createNewResolvedUrl =
|
|
1288
|
+
await this.urlResolver.resolve(request);
|
|
1289
|
+
assert(
|
|
1290
|
+
this.client.details.type !== summarizerClientType &&
|
|
1291
|
+
createNewResolvedUrl !== undefined,
|
|
1292
|
+
0x2c4 /* "client should not be summarizer before container is created" */,
|
|
1293
|
+
);
|
|
1294
|
+
this.service = await runWithRetry(
|
|
1295
|
+
async () =>
|
|
1296
|
+
this.serviceFactory.createContainer(
|
|
1297
|
+
summary,
|
|
1298
|
+
createNewResolvedUrl,
|
|
1299
|
+
this.subLogger,
|
|
1300
|
+
false, // clientIsSummarizer
|
|
1301
|
+
),
|
|
1302
|
+
"containerAttach",
|
|
1303
|
+
this.mc.logger,
|
|
1304
|
+
{
|
|
1305
|
+
cancel: this._deltaManager.closeAbortController.signal,
|
|
1306
|
+
}, // progress
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
this.storageAdapter.connectToService(this.service);
|
|
1310
|
+
return this.storageAdapter;
|
|
1311
|
+
};
|
|
1312
|
+
|
|
1313
|
+
let attachP = runRetriableAttachProcess({
|
|
1314
|
+
initialAttachmentData: this.attachmentData,
|
|
1315
|
+
offlineLoadEnabled: this.offlineLoadEnabled,
|
|
1316
|
+
detachedBlobStorage: this.detachedBlobStorage,
|
|
1317
|
+
setAttachmentData,
|
|
1318
|
+
createAttachmentSummary,
|
|
1319
|
+
createOrGetStorageService,
|
|
1320
|
+
});
|
|
1314
1321
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1322
|
+
// only enable the new behavior if the config is set
|
|
1323
|
+
if (
|
|
1324
|
+
this.mc.config.getBoolean("Fluid.Container.RetryOnAttachFailure") !== true
|
|
1325
|
+
) {
|
|
1326
|
+
attachP = attachP.catch((error) => {
|
|
1327
|
+
throw normalizeErrorAndClose(error);
|
|
1319
1328
|
});
|
|
1320
1329
|
}
|
|
1321
1330
|
|
|
1322
|
-
|
|
1323
|
-
this.runtime.setAttachState(AttachState.Attached);
|
|
1324
|
-
this.emit("attached");
|
|
1331
|
+
await attachP;
|
|
1325
1332
|
|
|
1326
1333
|
if (!this.closed) {
|
|
1327
1334
|
this.handleDeltaConnectionArg(
|
|
@@ -1332,17 +1339,11 @@ export class Container
|
|
|
1332
1339
|
attachProps?.deltaConnection,
|
|
1333
1340
|
);
|
|
1334
1341
|
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
throw newError;
|
|
1341
|
-
}
|
|
1342
|
-
},
|
|
1343
|
-
{ start: true, end: true, cancel: "generic" },
|
|
1344
|
-
);
|
|
1345
|
-
}
|
|
1342
|
+
},
|
|
1343
|
+
{ start: true, end: true, cancel: "generic" },
|
|
1344
|
+
);
|
|
1345
|
+
},
|
|
1346
|
+
);
|
|
1346
1347
|
|
|
1347
1348
|
private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
|
|
1348
1349
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
@@ -1369,7 +1370,7 @@ export class Container
|
|
|
1369
1370
|
public connect() {
|
|
1370
1371
|
if (this.closed) {
|
|
1371
1372
|
throw new UsageError(`The Container is closed and cannot be connected`);
|
|
1372
|
-
} else if (this.
|
|
1373
|
+
} else if (this.attachState !== AttachState.Attached) {
|
|
1373
1374
|
throw new UsageError(`The Container is not attached and cannot be connected`);
|
|
1374
1375
|
} else if (!this.connected) {
|
|
1375
1376
|
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
@@ -1385,7 +1386,7 @@ export class Container
|
|
|
1385
1386
|
private connectInternal(args: IConnectionArgs) {
|
|
1386
1387
|
assert(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
|
|
1387
1388
|
assert(
|
|
1388
|
-
this.
|
|
1389
|
+
this.attachState === AttachState.Attached,
|
|
1389
1390
|
0x2c6 /* "Attempting to connect() a container that is not attached" */,
|
|
1390
1391
|
);
|
|
1391
1392
|
|
|
@@ -1534,6 +1535,22 @@ export class Container
|
|
|
1534
1535
|
this._deltaManager.connect(args);
|
|
1535
1536
|
}
|
|
1536
1537
|
|
|
1538
|
+
private readonly metadataUpdateHandler = (metadata: Record<string, string>) => {
|
|
1539
|
+
this._containerMetadata = { ...this._containerMetadata, ...metadata };
|
|
1540
|
+
this.emit("metadataUpdate", metadata);
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
private async createDocumentService(
|
|
1544
|
+
serviceProvider: () => Promise<IDocumentService>,
|
|
1545
|
+
): Promise<IDocumentService> {
|
|
1546
|
+
const service = await serviceProvider();
|
|
1547
|
+
// Back-compat for Old driver
|
|
1548
|
+
if (service.on !== undefined) {
|
|
1549
|
+
service.on("metadataUpdate", this.metadataUpdateHandler);
|
|
1550
|
+
}
|
|
1551
|
+
return service;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1537
1554
|
/**
|
|
1538
1555
|
* Load container.
|
|
1539
1556
|
*
|
|
@@ -1547,10 +1564,12 @@ export class Container
|
|
|
1547
1564
|
loadToSequenceNumber: number | undefined,
|
|
1548
1565
|
) {
|
|
1549
1566
|
const timings: Record<string, number> = { phase1: performance.now() };
|
|
1550
|
-
this.service = await this.
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1567
|
+
this.service = await this.createDocumentService(async () =>
|
|
1568
|
+
this.serviceFactory.createDocumentService(
|
|
1569
|
+
resolvedUrl,
|
|
1570
|
+
this.subLogger,
|
|
1571
|
+
this.client.details.type === summarizerClientType,
|
|
1572
|
+
),
|
|
1554
1573
|
);
|
|
1555
1574
|
|
|
1556
1575
|
// Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
|
|
@@ -1573,33 +1592,42 @@ export class Container
|
|
|
1573
1592
|
|
|
1574
1593
|
this.storageAdapter.connectToService(this.service);
|
|
1575
1594
|
|
|
1576
|
-
this.
|
|
1595
|
+
this.attachmentData = {
|
|
1596
|
+
state: AttachState.Attached,
|
|
1597
|
+
};
|
|
1577
1598
|
|
|
1578
1599
|
timings.phase2 = performance.now();
|
|
1579
1600
|
// Fetch specified snapshot.
|
|
1580
1601
|
const { snapshot, versionId } =
|
|
1581
1602
|
pendingLocalState === undefined
|
|
1582
|
-
? await this.
|
|
1603
|
+
? await this.fetchSnapshot(specifiedVersion)
|
|
1583
1604
|
: { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
|
|
1584
1605
|
|
|
1606
|
+
const snapshotTree: ISnapshotTree | undefined = isInstanceOfISnapshot(snapshot)
|
|
1607
|
+
? snapshot.snapshotTree
|
|
1608
|
+
: snapshot;
|
|
1585
1609
|
if (pendingLocalState) {
|
|
1586
|
-
this.
|
|
1587
|
-
|
|
1610
|
+
this.attachmentData = {
|
|
1611
|
+
state: AttachState.Attached,
|
|
1612
|
+
snapshot: {
|
|
1613
|
+
tree: pendingLocalState.baseSnapshot,
|
|
1614
|
+
blobs: pendingLocalState.snapshotBlobs,
|
|
1615
|
+
},
|
|
1616
|
+
};
|
|
1588
1617
|
} else {
|
|
1589
|
-
assert(
|
|
1618
|
+
assert(snapshotTree !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
1590
1619
|
if (this.offlineLoadEnabled) {
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
snapshot,
|
|
1595
|
-
|
|
1596
|
-
);
|
|
1620
|
+
const blobs = await getBlobContentsFromTree(snapshotTree, this.storageAdapter);
|
|
1621
|
+
this.attachmentData = {
|
|
1622
|
+
state: AttachState.Attached,
|
|
1623
|
+
snapshot: { tree: snapshotTree, blobs },
|
|
1624
|
+
};
|
|
1597
1625
|
}
|
|
1598
1626
|
}
|
|
1599
1627
|
|
|
1600
1628
|
const attributes: IDocumentAttributes = await this.getDocumentAttributes(
|
|
1601
1629
|
this.storageAdapter,
|
|
1602
|
-
|
|
1630
|
+
snapshotTree,
|
|
1603
1631
|
);
|
|
1604
1632
|
|
|
1605
1633
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
@@ -1686,15 +1714,20 @@ export class Container
|
|
|
1686
1714
|
|
|
1687
1715
|
// ...load in the existing quorum
|
|
1688
1716
|
// Initialize the protocol handler
|
|
1689
|
-
await this.initializeProtocolStateFromSnapshot(
|
|
1717
|
+
await this.initializeProtocolStateFromSnapshot(
|
|
1718
|
+
attributes,
|
|
1719
|
+
this.storageAdapter,
|
|
1720
|
+
snapshotTree,
|
|
1721
|
+
);
|
|
1690
1722
|
|
|
1691
1723
|
timings.phase3 = performance.now();
|
|
1692
1724
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1693
1725
|
await this.instantiateRuntime(
|
|
1694
1726
|
codeDetails,
|
|
1695
|
-
|
|
1727
|
+
snapshotTree,
|
|
1696
1728
|
// give runtime a dummy value so it knows we're loading from a stash blob
|
|
1697
1729
|
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined,
|
|
1730
|
+
isInstanceOfISnapshot(snapshot) ? snapshot : undefined,
|
|
1698
1731
|
);
|
|
1699
1732
|
|
|
1700
1733
|
// replay saved ops
|
|
@@ -1807,24 +1840,30 @@ export class Container
|
|
|
1807
1840
|
this.setLoaded();
|
|
1808
1841
|
}
|
|
1809
1842
|
|
|
1810
|
-
private async rehydrateDetachedFromSnapshot(
|
|
1811
|
-
|
|
1843
|
+
private async rehydrateDetachedFromSnapshot({
|
|
1844
|
+
attached,
|
|
1845
|
+
baseSnapshot,
|
|
1846
|
+
snapshotBlobs,
|
|
1847
|
+
hasAttachmentBlobs,
|
|
1848
|
+
}: IPendingDetachedContainerState) {
|
|
1849
|
+
if (hasAttachmentBlobs) {
|
|
1812
1850
|
assert(
|
|
1813
1851
|
!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
|
|
1814
1852
|
0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
|
|
1815
1853
|
);
|
|
1816
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
1817
|
-
delete detachedContainerSnapshot.tree[hasBlobsSummaryTree];
|
|
1818
1854
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
this.storageAdapter.
|
|
1822
|
-
const attributes = await this.getDocumentAttributes(
|
|
1855
|
+
const snapshotTreeWithBlobContents: ISnapshotTreeWithBlobContents =
|
|
1856
|
+
combineSnapshotTreeAndSnapshotBlobs(baseSnapshot, snapshotBlobs);
|
|
1857
|
+
this.storageAdapter.loadSnapshotFromSnapshotBlobs(snapshotBlobs);
|
|
1858
|
+
const attributes = await this.getDocumentAttributes(
|
|
1859
|
+
this.storageAdapter,
|
|
1860
|
+
snapshotTreeWithBlobContents,
|
|
1861
|
+
);
|
|
1823
1862
|
|
|
1824
1863
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1825
1864
|
|
|
1826
1865
|
// Initialize the protocol handler
|
|
1827
|
-
const baseTree = getProtocolSnapshotTree(
|
|
1866
|
+
const baseTree = getProtocolSnapshotTree(snapshotTreeWithBlobContents);
|
|
1828
1867
|
const qValues = await readAndParse<[string, ICommittedProposal][]>(
|
|
1829
1868
|
this.storageAdapter,
|
|
1830
1869
|
baseTree.blobs.quorumValues,
|
|
@@ -1839,7 +1878,7 @@ export class Container
|
|
|
1839
1878
|
);
|
|
1840
1879
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1841
1880
|
|
|
1842
|
-
await this.instantiateRuntime(codeDetails,
|
|
1881
|
+
await this.instantiateRuntime(codeDetails, snapshotTreeWithBlobContents);
|
|
1843
1882
|
|
|
1844
1883
|
this.setLoaded();
|
|
1845
1884
|
}
|
|
@@ -2383,10 +2422,39 @@ export class Container
|
|
|
2383
2422
|
return { snapshot, versionId: version?.id };
|
|
2384
2423
|
}
|
|
2385
2424
|
|
|
2425
|
+
private async fetchSnapshot(
|
|
2426
|
+
specifiedVersion: string | undefined,
|
|
2427
|
+
): Promise<{ snapshot?: ISnapshot | ISnapshotTree; versionId?: string }> {
|
|
2428
|
+
if (
|
|
2429
|
+
this.mc.config.getBoolean("Fluid.Container.FetchSnapshotUsingGetSnapshotApi") ===
|
|
2430
|
+
true &&
|
|
2431
|
+
this.service?.policies?.supportGetSnapshotApi === true
|
|
2432
|
+
) {
|
|
2433
|
+
const snapshot = await this.storageAdapter.getSnapshot({
|
|
2434
|
+
versionId: specifiedVersion,
|
|
2435
|
+
});
|
|
2436
|
+
const version: IVersion = {
|
|
2437
|
+
id: snapshot.snapshotTree.id ?? "",
|
|
2438
|
+
treeId: snapshot.snapshotTree.id ?? "",
|
|
2439
|
+
};
|
|
2440
|
+
this._loadedFromVersion = version;
|
|
2441
|
+
|
|
2442
|
+
if (snapshot === undefined && specifiedVersion !== undefined) {
|
|
2443
|
+
this.mc.logger.sendErrorEvent({
|
|
2444
|
+
eventName: "getSnapshotTreeFailed",
|
|
2445
|
+
id: version.id,
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
return { snapshot, versionId: version.id };
|
|
2449
|
+
}
|
|
2450
|
+
return this.fetchSnapshotTree(specifiedVersion);
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2386
2453
|
private async instantiateRuntime(
|
|
2387
2454
|
codeDetails: IFluidCodeDetails,
|
|
2388
|
-
|
|
2455
|
+
snapshotTree: ISnapshotTree | undefined,
|
|
2389
2456
|
pendingLocalState?: unknown,
|
|
2457
|
+
snapshot?: ISnapshot,
|
|
2390
2458
|
) {
|
|
2391
2459
|
assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
2392
2460
|
|
|
@@ -2420,12 +2488,12 @@ export class Container
|
|
|
2420
2488
|
(this.protocolHandler.quorum.get("code") ??
|
|
2421
2489
|
this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
2422
2490
|
|
|
2423
|
-
const existing =
|
|
2491
|
+
const existing = snapshotTree !== undefined;
|
|
2424
2492
|
|
|
2425
2493
|
const context = new ContainerContext(
|
|
2426
2494
|
this.options,
|
|
2427
2495
|
this.scope,
|
|
2428
|
-
|
|
2496
|
+
snapshotTree,
|
|
2429
2497
|
this._loadedFromVersion,
|
|
2430
2498
|
this._deltaManager,
|
|
2431
2499
|
this.storageAdapter,
|
|
@@ -2452,6 +2520,7 @@ export class Container
|
|
|
2452
2520
|
existing,
|
|
2453
2521
|
this.subLogger,
|
|
2454
2522
|
pendingLocalState,
|
|
2523
|
+
snapshot,
|
|
2455
2524
|
);
|
|
2456
2525
|
|
|
2457
2526
|
this._runtime = await PerformanceEvent.timedExecAsync(
|