@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/dist/container.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
4
|
* Licensed under the MIT License.
|
|
5
5
|
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
6
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
10
|
exports.Container = exports.ReportIfTooLong = exports.waitContainerToCatchUp = void 0;
|
|
8
11
|
const uuid_1 = require("uuid");
|
|
@@ -13,6 +16,7 @@ const container_definitions_1 = require("@fluidframework/container-definitions")
|
|
|
13
16
|
const driver_utils_1 = require("@fluidframework/driver-utils");
|
|
14
17
|
const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
|
|
15
18
|
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
|
|
19
|
+
const structured_clone_1 = __importDefault(require("@ungap/structured-clone"));
|
|
16
20
|
const audience_1 = require("./audience");
|
|
17
21
|
const containerContext_1 = require("./containerContext");
|
|
18
22
|
const contracts_1 = require("./contracts");
|
|
@@ -27,11 +31,11 @@ const noopHeuristic_1 = require("./noopHeuristic");
|
|
|
27
31
|
const connectionManager_1 = require("./connectionManager");
|
|
28
32
|
const connectionState_1 = require("./connectionState");
|
|
29
33
|
const protocol_1 = require("./protocol");
|
|
34
|
+
const attachment_1 = require("./attachment");
|
|
30
35
|
const detachedContainerRefSeqNumber = 0;
|
|
31
36
|
const dirtyContainerEvent = "dirty";
|
|
32
37
|
const savedContainerEvent = "saved";
|
|
33
38
|
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
34
|
-
const hasBlobsSummaryTree = ".hasAttachmentBlobs";
|
|
35
39
|
/**
|
|
36
40
|
* Waits until container connects to delta storage and gets up-to-date.
|
|
37
41
|
*
|
|
@@ -185,11 +189,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
185
189
|
static async rehydrateDetachedFromSnapshot(createProps, snapshot) {
|
|
186
190
|
const container = new Container(createProps);
|
|
187
191
|
return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
throw new telemetry_utils_1.UsageError("Cannot rehydrate detached container. Incorrect format");
|
|
191
|
-
}
|
|
192
|
-
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
192
|
+
const detachedContainerState = (0, utils_1.getDetachedContainerStateFromSerializedContainer)(snapshot);
|
|
193
|
+
await container.rehydrateDetachedFromSnapshot(detachedContainerState);
|
|
193
194
|
return container;
|
|
194
195
|
}, { start: true, end: true, cancel: "generic" });
|
|
195
196
|
}
|
|
@@ -240,6 +241,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
240
241
|
get readOnlyInfo() {
|
|
241
242
|
return this._deltaManager.readOnlyInfo;
|
|
242
243
|
}
|
|
244
|
+
get containerMetadata() {
|
|
245
|
+
return this._containerMetadata;
|
|
246
|
+
}
|
|
243
247
|
/**
|
|
244
248
|
* Sends signal to runtime (and data stores) to be read-only.
|
|
245
249
|
* Hosts may have read only views, indicating to data stores that no edits are allowed.
|
|
@@ -361,24 +365,103 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
361
365
|
* disposed: Container has been disposed
|
|
362
366
|
*/
|
|
363
367
|
this._lifecycleState = "loading";
|
|
364
|
-
this._attachState = container_definitions_1.AttachState.Detached;
|
|
365
368
|
/** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
|
|
366
369
|
this.inboundQueuePausedFromInit = true;
|
|
367
370
|
this.firstConnection = true;
|
|
368
371
|
this.connectionTransitionTimes = [];
|
|
369
|
-
this.attachStarted = false;
|
|
370
372
|
this._dirtyContainer = false;
|
|
371
373
|
this.savedOps = [];
|
|
374
|
+
this.attachmentData = { state: container_definitions_1.AttachState.Detached };
|
|
372
375
|
this.clientsWhoShouldHaveLeft = new Set();
|
|
376
|
+
this._containerMetadata = {};
|
|
373
377
|
this.setAutoReconnectTime = client_utils_1.performance.now();
|
|
374
378
|
this._lifecycleEvents = new client_utils_1.TypedEventEmitter();
|
|
375
379
|
this._disposed = false;
|
|
380
|
+
this.attach = (0, utils_1.runSingle)(async (request, attachProps) => {
|
|
381
|
+
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
382
|
+
if (this._lifecycleState !== "loaded" ||
|
|
383
|
+
this.attachmentData.state === container_definitions_1.AttachState.Attached) {
|
|
384
|
+
// pre-0.58 error message: containerNotValidForAttach
|
|
385
|
+
throw new telemetry_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}] and [${this.attachState}]`);
|
|
386
|
+
}
|
|
387
|
+
const normalizeErrorAndClose = (error) => {
|
|
388
|
+
const newError = (0, telemetry_utils_1.normalizeError)(error);
|
|
389
|
+
this.close(newError);
|
|
390
|
+
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
391
|
+
newError.addTelemetryProperties({
|
|
392
|
+
resolvedUrl: this.service?.resolvedUrl?.url,
|
|
393
|
+
});
|
|
394
|
+
return newError;
|
|
395
|
+
};
|
|
396
|
+
const setAttachmentData = (attachmentData) => {
|
|
397
|
+
const previousState = this.attachmentData.state;
|
|
398
|
+
this.attachmentData = attachmentData;
|
|
399
|
+
const state = this.attachmentData.state;
|
|
400
|
+
if (state !== previousState && state !== container_definitions_1.AttachState.Detached) {
|
|
401
|
+
try {
|
|
402
|
+
this.runtime.setAttachState(state);
|
|
403
|
+
this.emit(state.toLocaleLowerCase());
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
throw normalizeErrorAndClose(error);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const createAttachmentSummary = (redirectTable) => {
|
|
411
|
+
try {
|
|
412
|
+
(0, core_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
413
|
+
return (0, utils_1.combineAppAndProtocolSummary)(this.runtime.createSummary(redirectTable), this.captureProtocolSummary());
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
throw normalizeErrorAndClose(error);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const createOrGetStorageService = async (summary) => {
|
|
420
|
+
// Actually go and create the resolved document
|
|
421
|
+
if (this.service === undefined) {
|
|
422
|
+
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
423
|
+
(0, core_utils_1.assert)(this.client.details.type !== summarizerClientType &&
|
|
424
|
+
createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
|
|
425
|
+
this.service = await (0, driver_utils_1.runWithRetry)(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
|
|
426
|
+
cancel: this._deltaManager.closeAbortController.signal,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
this.storageAdapter.connectToService(this.service);
|
|
430
|
+
return this.storageAdapter;
|
|
431
|
+
};
|
|
432
|
+
let attachP = (0, attachment_1.runRetriableAttachProcess)({
|
|
433
|
+
initialAttachmentData: this.attachmentData,
|
|
434
|
+
offlineLoadEnabled: this.offlineLoadEnabled,
|
|
435
|
+
detachedBlobStorage: this.detachedBlobStorage,
|
|
436
|
+
setAttachmentData,
|
|
437
|
+
createAttachmentSummary,
|
|
438
|
+
createOrGetStorageService,
|
|
439
|
+
});
|
|
440
|
+
// only enable the new behavior if the config is set
|
|
441
|
+
if (this.mc.config.getBoolean("Fluid.Container.RetryOnAttachFailure") !== true) {
|
|
442
|
+
attachP = attachP.catch((error) => {
|
|
443
|
+
throw normalizeErrorAndClose(error);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
await attachP;
|
|
447
|
+
if (!this.closed) {
|
|
448
|
+
this.handleDeltaConnectionArg({
|
|
449
|
+
fetchOpsFromStorage: false,
|
|
450
|
+
reason: { text: "createDetached" },
|
|
451
|
+
}, attachProps?.deltaConnection);
|
|
452
|
+
}
|
|
453
|
+
}, { start: true, end: true, cancel: "generic" });
|
|
454
|
+
});
|
|
376
455
|
this.getAbsoluteUrl = async (relativeUrl) => {
|
|
377
456
|
if (this.resolvedUrl === undefined) {
|
|
378
457
|
return undefined;
|
|
379
458
|
}
|
|
380
459
|
return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)(this._loadedCodeDetails));
|
|
381
460
|
};
|
|
461
|
+
this.metadataUpdateHandler = (metadata) => {
|
|
462
|
+
this._containerMetadata = { ...this._containerMetadata, ...metadata };
|
|
463
|
+
this.emit("metadataUpdate", metadata);
|
|
464
|
+
};
|
|
382
465
|
this.updateDirtyContainerState = (dirty) => {
|
|
383
466
|
if (this._dirtyContainer === dirty) {
|
|
384
467
|
return;
|
|
@@ -426,7 +509,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
426
509
|
clientType,
|
|
427
510
|
containerId: this._containerId,
|
|
428
511
|
docId: () => this.resolvedUrl?.id,
|
|
429
|
-
containerAttachState: () => this.
|
|
512
|
+
containerAttachState: () => this.attachState,
|
|
430
513
|
containerLifecycleState: () => this._lifecycleState,
|
|
431
514
|
containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
|
|
432
515
|
serializedContainer: pendingLocalState !== undefined,
|
|
@@ -577,6 +660,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
577
660
|
: "generic",
|
|
578
661
|
}, error);
|
|
579
662
|
this._lifecycleState = "closing";
|
|
663
|
+
// Back-compat for Old driver
|
|
664
|
+
if (this.service?.off !== undefined) {
|
|
665
|
+
this.service?.off("metadataUpdate", this.metadataUpdateHandler);
|
|
666
|
+
}
|
|
580
667
|
this._protocolHandler?.close();
|
|
581
668
|
this.connectionStateHandler.dispose();
|
|
582
669
|
}
|
|
@@ -664,15 +751,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
664
751
|
if (this.closed || this._disposed) {
|
|
665
752
|
throw new telemetry_utils_1.UsageError("Pending state cannot be retried if the container is closed or disposed");
|
|
666
753
|
}
|
|
667
|
-
(0, core_utils_1.assert)(this.
|
|
754
|
+
(0, core_utils_1.assert)(this.attachmentData.state === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
668
755
|
(0, core_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
669
|
-
(0, core_utils_1.assert)(
|
|
670
|
-
(0, core_utils_1.assert)(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
756
|
+
(0, core_utils_1.assert)(this.attachmentData.snapshot !== undefined, 0x5d5 /* no base data */);
|
|
671
757
|
const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
|
|
672
758
|
const pendingState = {
|
|
673
759
|
pendingRuntimeState,
|
|
674
|
-
baseSnapshot: this.
|
|
675
|
-
snapshotBlobs: this.
|
|
760
|
+
baseSnapshot: this.attachmentData.snapshot.tree,
|
|
761
|
+
snapshotBlobs: this.attachmentData.snapshot.blobs,
|
|
676
762
|
savedOps: this.savedOps,
|
|
677
763
|
url: this.resolvedUrl.url,
|
|
678
764
|
// no need to save this if there is no pending runtime state
|
|
@@ -682,119 +768,21 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
682
768
|
});
|
|
683
769
|
}
|
|
684
770
|
get attachState() {
|
|
685
|
-
return this.
|
|
771
|
+
return this.attachmentData.state;
|
|
686
772
|
}
|
|
687
773
|
serialize() {
|
|
688
774
|
(0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
|
|
689
775
|
const appSummary = this.runtime.createSummary();
|
|
690
776
|
const protocolSummary = this.captureProtocolSummary();
|
|
691
777
|
const combinedSummary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
async attach(request, attachProps) {
|
|
701
|
-
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
702
|
-
if (this._lifecycleState !== "loaded") {
|
|
703
|
-
// pre-0.58 error message: containerNotValidForAttach
|
|
704
|
-
throw new telemetry_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
705
|
-
}
|
|
706
|
-
// If container is already attached or attach is in progress, throw an error.
|
|
707
|
-
(0, core_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
708
|
-
this.attachStarted = true;
|
|
709
|
-
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
710
|
-
const hasAttachmentBlobs = this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
|
|
711
|
-
try {
|
|
712
|
-
(0, core_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
713
|
-
let summary;
|
|
714
|
-
if (!hasAttachmentBlobs) {
|
|
715
|
-
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
716
|
-
// semantics around what the attach means as far as async code goes.
|
|
717
|
-
const appSummary = this.runtime.createSummary();
|
|
718
|
-
const protocolSummary = this.captureProtocolSummary();
|
|
719
|
-
summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
720
|
-
// Set the state as attaching as we are starting the process of attaching container.
|
|
721
|
-
// This should be fired after taking the summary because it is the place where we are
|
|
722
|
-
// starting to attach the container to storage.
|
|
723
|
-
// Also, this should only be fired in detached container.
|
|
724
|
-
this._attachState = container_definitions_1.AttachState.Attaching;
|
|
725
|
-
this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
|
|
726
|
-
this.emit("attaching");
|
|
727
|
-
if (this.offlineLoadEnabled) {
|
|
728
|
-
const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
|
|
729
|
-
this.baseSnapshot = snapshot;
|
|
730
|
-
this.baseSnapshotBlobs =
|
|
731
|
-
(0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
// Actually go and create the resolved document
|
|
735
|
-
if (this.service === undefined) {
|
|
736
|
-
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
737
|
-
(0, core_utils_1.assert)(this.client.details.type !== summarizerClientType &&
|
|
738
|
-
createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
|
|
739
|
-
this.service = await (0, driver_utils_1.runWithRetry)(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
|
|
740
|
-
cancel: this._deltaManager.closeAbortController.signal,
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
this.storageAdapter.connectToService(this.service);
|
|
744
|
-
if (hasAttachmentBlobs) {
|
|
745
|
-
// upload blobs to storage
|
|
746
|
-
(0, core_utils_1.assert)(!!this.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
747
|
-
// build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
|
|
748
|
-
// support blob handles that only know about the local IDs
|
|
749
|
-
const redirectTable = new Map();
|
|
750
|
-
// if new blobs are added while uploading, upload them too
|
|
751
|
-
while (redirectTable.size < this.detachedBlobStorage.size) {
|
|
752
|
-
const newIds = this.detachedBlobStorage
|
|
753
|
-
.getBlobIds()
|
|
754
|
-
.filter((id) => !redirectTable.has(id));
|
|
755
|
-
for (const id of newIds) {
|
|
756
|
-
const blob = await this.detachedBlobStorage.readBlob(id);
|
|
757
|
-
const response = await this.storageAdapter.createBlob(blob);
|
|
758
|
-
redirectTable.set(id, response.id);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
// take summary and upload
|
|
762
|
-
const appSummary = this.runtime.createSummary(redirectTable);
|
|
763
|
-
const protocolSummary = this.captureProtocolSummary();
|
|
764
|
-
summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
765
|
-
this._attachState = container_definitions_1.AttachState.Attaching;
|
|
766
|
-
this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
|
|
767
|
-
this.emit("attaching");
|
|
768
|
-
if (this.offlineLoadEnabled) {
|
|
769
|
-
const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
|
|
770
|
-
this.baseSnapshot = snapshot;
|
|
771
|
-
this.baseSnapshotBlobs =
|
|
772
|
-
(0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
|
|
773
|
-
}
|
|
774
|
-
await this.storageAdapter.uploadSummaryWithContext(summary, {
|
|
775
|
-
referenceSequenceNumber: 0,
|
|
776
|
-
ackHandle: undefined,
|
|
777
|
-
proposalHandle: undefined,
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
this._attachState = container_definitions_1.AttachState.Attached;
|
|
781
|
-
this.runtime.setAttachState(container_definitions_1.AttachState.Attached);
|
|
782
|
-
this.emit("attached");
|
|
783
|
-
if (!this.closed) {
|
|
784
|
-
this.handleDeltaConnectionArg({
|
|
785
|
-
fetchOpsFromStorage: false,
|
|
786
|
-
reason: { text: "createDetached" },
|
|
787
|
-
}, attachProps?.deltaConnection);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
catch (error) {
|
|
791
|
-
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
792
|
-
const newError = (0, telemetry_utils_1.normalizeError)(error);
|
|
793
|
-
newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
|
|
794
|
-
this.close(newError);
|
|
795
|
-
throw newError;
|
|
796
|
-
}
|
|
797
|
-
}, { start: true, end: true, cancel: "generic" });
|
|
778
|
+
const { tree: snapshot, blobs } = (0, utils_1.getSnapshotTreeAndBlobsFromSerializedContainer)(combinedSummary);
|
|
779
|
+
const detachedContainerState = {
|
|
780
|
+
attached: false,
|
|
781
|
+
baseSnapshot: snapshot,
|
|
782
|
+
snapshotBlobs: blobs,
|
|
783
|
+
hasAttachmentBlobs: !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
|
|
784
|
+
};
|
|
785
|
+
return JSON.stringify(detachedContainerState);
|
|
798
786
|
}
|
|
799
787
|
setAutoReconnectInternal(mode, reason) {
|
|
800
788
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
@@ -816,7 +804,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
816
804
|
if (this.closed) {
|
|
817
805
|
throw new telemetry_utils_1.UsageError(`The Container is closed and cannot be connected`);
|
|
818
806
|
}
|
|
819
|
-
else if (this.
|
|
807
|
+
else if (this.attachState !== container_definitions_1.AttachState.Attached) {
|
|
820
808
|
throw new telemetry_utils_1.UsageError(`The Container is not attached and cannot be connected`);
|
|
821
809
|
}
|
|
822
810
|
else if (!this.connected) {
|
|
@@ -831,7 +819,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
831
819
|
}
|
|
832
820
|
connectInternal(args) {
|
|
833
821
|
(0, core_utils_1.assert)(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
|
|
834
|
-
(0, core_utils_1.assert)(this.
|
|
822
|
+
(0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x2c6 /* "Attempting to connect() a container that is not attached" */);
|
|
835
823
|
// Resume processing ops and connect to delta stream
|
|
836
824
|
this.resumeInternal(args);
|
|
837
825
|
// Set Auto Reconnect Mode
|
|
@@ -935,6 +923,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
935
923
|
}
|
|
936
924
|
this._deltaManager.connect(args);
|
|
937
925
|
}
|
|
926
|
+
async createDocumentService(serviceProvider) {
|
|
927
|
+
const service = await serviceProvider();
|
|
928
|
+
// Back-compat for Old driver
|
|
929
|
+
if (service.on !== undefined) {
|
|
930
|
+
service.on("metadataUpdate", this.metadataUpdateHandler);
|
|
931
|
+
}
|
|
932
|
+
return service;
|
|
933
|
+
}
|
|
938
934
|
/**
|
|
939
935
|
* Load container.
|
|
940
936
|
*
|
|
@@ -942,7 +938,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
942
938
|
*/
|
|
943
939
|
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
|
|
944
940
|
const timings = { phase1: client_utils_1.performance.now() };
|
|
945
|
-
this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
|
|
941
|
+
this.service = await this.createDocumentService(async () => this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType));
|
|
946
942
|
// Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
|
|
947
943
|
const mode = this.mc.config.getBoolean("Fluid.Container.ForceWriteConnection") === true ||
|
|
948
944
|
(pendingLocalState?.savedOps.length ?? 0) > 0
|
|
@@ -959,25 +955,37 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
959
955
|
this.connectToDeltaStream(connectionArgs);
|
|
960
956
|
}
|
|
961
957
|
this.storageAdapter.connectToService(this.service);
|
|
962
|
-
this.
|
|
958
|
+
this.attachmentData = {
|
|
959
|
+
state: container_definitions_1.AttachState.Attached,
|
|
960
|
+
};
|
|
963
961
|
timings.phase2 = client_utils_1.performance.now();
|
|
964
962
|
// Fetch specified snapshot.
|
|
965
963
|
const { snapshot, versionId } = pendingLocalState === undefined
|
|
966
|
-
? await this.
|
|
964
|
+
? await this.fetchSnapshot(specifiedVersion)
|
|
967
965
|
: { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
|
|
966
|
+
const snapshotTree = (0, driver_utils_1.isInstanceOfISnapshot)(snapshot)
|
|
967
|
+
? snapshot.snapshotTree
|
|
968
|
+
: snapshot;
|
|
968
969
|
if (pendingLocalState) {
|
|
969
|
-
this.
|
|
970
|
-
|
|
970
|
+
this.attachmentData = {
|
|
971
|
+
state: container_definitions_1.AttachState.Attached,
|
|
972
|
+
snapshot: {
|
|
973
|
+
tree: pendingLocalState.baseSnapshot,
|
|
974
|
+
blobs: pendingLocalState.snapshotBlobs,
|
|
975
|
+
},
|
|
976
|
+
};
|
|
971
977
|
}
|
|
972
978
|
else {
|
|
973
|
-
(0, core_utils_1.assert)(
|
|
979
|
+
(0, core_utils_1.assert)(snapshotTree !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
974
980
|
if (this.offlineLoadEnabled) {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
981
|
+
const blobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshotTree, this.storageAdapter);
|
|
982
|
+
this.attachmentData = {
|
|
983
|
+
state: container_definitions_1.AttachState.Attached,
|
|
984
|
+
snapshot: { tree: snapshotTree, blobs },
|
|
985
|
+
};
|
|
978
986
|
}
|
|
979
987
|
}
|
|
980
|
-
const attributes = await this.getDocumentAttributes(this.storageAdapter,
|
|
988
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
|
|
981
989
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
982
990
|
const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
|
|
983
991
|
const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
|
|
@@ -1042,12 +1050,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1042
1050
|
}
|
|
1043
1051
|
// ...load in the existing quorum
|
|
1044
1052
|
// Initialize the protocol handler
|
|
1045
|
-
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter,
|
|
1053
|
+
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshotTree);
|
|
1046
1054
|
timings.phase3 = client_utils_1.performance.now();
|
|
1047
1055
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1048
|
-
await this.instantiateRuntime(codeDetails,
|
|
1056
|
+
await this.instantiateRuntime(codeDetails, snapshotTree,
|
|
1049
1057
|
// give runtime a dummy value so it knows we're loading from a stash blob
|
|
1050
|
-
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined);
|
|
1058
|
+
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined, (0, driver_utils_1.isInstanceOfISnapshot)(snapshot) ? snapshot : undefined);
|
|
1051
1059
|
// replay saved ops
|
|
1052
1060
|
if (pendingLocalState) {
|
|
1053
1061
|
for (const message of pendingLocalState.savedOps) {
|
|
@@ -1123,18 +1131,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1123
1131
|
await this.instantiateRuntime(codeDetails, undefined);
|
|
1124
1132
|
this.setLoaded();
|
|
1125
1133
|
}
|
|
1126
|
-
async rehydrateDetachedFromSnapshot(
|
|
1127
|
-
if (
|
|
1134
|
+
async rehydrateDetachedFromSnapshot({ attached, baseSnapshot, snapshotBlobs, hasAttachmentBlobs, }) {
|
|
1135
|
+
if (hasAttachmentBlobs) {
|
|
1128
1136
|
(0, core_utils_1.assert)(!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
|
|
1129
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
1130
|
-
delete detachedContainerSnapshot.tree[hasBlobsSummaryTree];
|
|
1131
1137
|
}
|
|
1132
|
-
const
|
|
1133
|
-
this.storageAdapter.
|
|
1134
|
-
const attributes = await this.getDocumentAttributes(this.storageAdapter,
|
|
1138
|
+
const snapshotTreeWithBlobContents = (0, utils_1.combineSnapshotTreeAndSnapshotBlobs)(baseSnapshot, snapshotBlobs);
|
|
1139
|
+
this.storageAdapter.loadSnapshotFromSnapshotBlobs(snapshotBlobs);
|
|
1140
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTreeWithBlobContents);
|
|
1135
1141
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1136
1142
|
// Initialize the protocol handler
|
|
1137
|
-
const baseTree = (0, utils_1.getProtocolSnapshotTree)(
|
|
1143
|
+
const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTreeWithBlobContents);
|
|
1138
1144
|
const qValues = await (0, driver_utils_1.readAndParse)(this.storageAdapter, baseTree.blobs.quorumValues);
|
|
1139
1145
|
this.initializeProtocolState(attributes, {
|
|
1140
1146
|
members: [],
|
|
@@ -1142,7 +1148,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1142
1148
|
values: qValues,
|
|
1143
1149
|
});
|
|
1144
1150
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1145
|
-
await this.instantiateRuntime(codeDetails,
|
|
1151
|
+
await this.instantiateRuntime(codeDetails, snapshotTreeWithBlobContents);
|
|
1146
1152
|
this.setLoaded();
|
|
1147
1153
|
}
|
|
1148
1154
|
async getDocumentAttributes(storage, tree) {
|
|
@@ -1243,7 +1249,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1243
1249
|
return pkg;
|
|
1244
1250
|
}
|
|
1245
1251
|
static setupClient(containerId, options, clientDetailsOverride) {
|
|
1246
|
-
const loaderOptionsClient =
|
|
1252
|
+
const loaderOptionsClient = (0, structured_clone_1.default)(options?.client);
|
|
1247
1253
|
const client = loaderOptionsClient !== undefined
|
|
1248
1254
|
? loaderOptionsClient
|
|
1249
1255
|
: {
|
|
@@ -1516,7 +1522,29 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1516
1522
|
}
|
|
1517
1523
|
return { snapshot, versionId: version?.id };
|
|
1518
1524
|
}
|
|
1519
|
-
async
|
|
1525
|
+
async fetchSnapshot(specifiedVersion) {
|
|
1526
|
+
if (this.mc.config.getBoolean("Fluid.Container.FetchSnapshotUsingGetSnapshotApi") ===
|
|
1527
|
+
true &&
|
|
1528
|
+
this.service?.policies?.supportGetSnapshotApi === true) {
|
|
1529
|
+
const snapshot = await this.storageAdapter.getSnapshot({
|
|
1530
|
+
versionId: specifiedVersion,
|
|
1531
|
+
});
|
|
1532
|
+
const version = {
|
|
1533
|
+
id: snapshot.snapshotTree.id ?? "",
|
|
1534
|
+
treeId: snapshot.snapshotTree.id ?? "",
|
|
1535
|
+
};
|
|
1536
|
+
this._loadedFromVersion = version;
|
|
1537
|
+
if (snapshot === undefined && specifiedVersion !== undefined) {
|
|
1538
|
+
this.mc.logger.sendErrorEvent({
|
|
1539
|
+
eventName: "getSnapshotTreeFailed",
|
|
1540
|
+
id: version.id,
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
return { snapshot, versionId: version.id };
|
|
1544
|
+
}
|
|
1545
|
+
return this.fetchSnapshotTree(specifiedVersion);
|
|
1546
|
+
}
|
|
1547
|
+
async instantiateRuntime(codeDetails, snapshotTree, pendingLocalState, snapshot) {
|
|
1520
1548
|
(0, core_utils_1.assert)(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1521
1549
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1522
1550
|
// are set. Global requests will still go directly to the loader
|
|
@@ -1537,8 +1565,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1537
1565
|
}
|
|
1538
1566
|
const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
|
|
1539
1567
|
this.protocolHandler.quorum.get("code2"));
|
|
1540
|
-
const existing =
|
|
1541
|
-
const context = new containerContext_1.ContainerContext(this.options, this.scope,
|
|
1568
|
+
const existing = snapshotTree !== undefined;
|
|
1569
|
+
const context = new containerContext_1.ContainerContext(this.options, this.scope, snapshotTree, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, this.protocolHandler.audience, loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (content, targetClientId) => this.submitSignal(content, targetClientId), (error) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => this.resolvedUrl?.id, () => this.clientId, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState, snapshot);
|
|
1542
1570
|
this._runtime = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
|
|
1543
1571
|
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
1544
1572
|
this._loadedCodeDetails = codeDetails;
|