@fluidframework/container-loader 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.1.0.148229
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 +7 -4
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +43 -11
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +4 -4
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +7 -0
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +44 -4
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +152 -93
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +18 -8
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +47 -4
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +41 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +87 -11
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +3 -2
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +4 -2
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +10 -22
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js +14 -50
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +10 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +22 -16
- 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 +6 -2
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js +7 -4
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +44 -12
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +4 -4
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +7 -0
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +44 -4
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +155 -96
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +18 -8
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +48 -5
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +41 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +85 -11
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +2 -2
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +3 -2
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +4 -2
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +10 -22
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js +14 -50
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +10 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +21 -16
- package/lib/loader.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/protocolTreeDocumentStorageService.d.ts +6 -2
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js +7 -4
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +2 -1
- package/lib/utils.js.map +1 -1
- package/package.json +64 -56
- package/src/connectionManager.ts +48 -17
- package/src/connectionStateHandler.ts +17 -5
- package/src/container.ts +224 -116
- package/src/containerContext.ts +74 -11
- package/src/containerStorageAdapter.ts +113 -9
- package/src/contracts.ts +2 -2
- package/src/deltaManager.ts +9 -4
- package/src/deltaManagerProxy.ts +18 -73
- package/src/index.ts +2 -3
- package/src/loader.ts +28 -26
- package/src/packageVersion.ts +1 -1
- package/src/protocolTreeDocumentStorageService.ts +6 -3
- package/src/utils.ts +7 -4
package/src/container.ts
CHANGED
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
TelemetryEventCategory,
|
|
13
13
|
} from "@fluidframework/common-definitions";
|
|
14
14
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
15
|
-
import { IRequest, IResponse, IFluidRouter } from "@fluidframework/core-interfaces";
|
|
15
|
+
import { IRequest, IResponse, IFluidRouter, FluidObject } from "@fluidframework/core-interfaces";
|
|
16
16
|
import {
|
|
17
17
|
IAudience,
|
|
18
|
-
|
|
18
|
+
IConnectionDetailsInternal,
|
|
19
19
|
IContainer,
|
|
20
20
|
IContainerEvents,
|
|
21
21
|
IDeltaManager,
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
combineAppAndProtocolSummary,
|
|
45
45
|
runWithRetry,
|
|
46
46
|
isFluidResolvedUrl,
|
|
47
|
+
isCombinedAppAndProtocolSummary,
|
|
47
48
|
} from "@fluidframework/driver-utils";
|
|
48
49
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
49
50
|
import {
|
|
@@ -53,7 +54,6 @@ import {
|
|
|
53
54
|
ICommittedProposal,
|
|
54
55
|
IDocumentAttributes,
|
|
55
56
|
IDocumentMessage,
|
|
56
|
-
IProtocolState,
|
|
57
57
|
IQuorumClients,
|
|
58
58
|
IQuorumProposals,
|
|
59
59
|
ISequencedClient,
|
|
@@ -74,7 +74,6 @@ import {
|
|
|
74
74
|
raiseConnectedEvent,
|
|
75
75
|
TelemetryLogger,
|
|
76
76
|
connectedEventName,
|
|
77
|
-
disconnectedEventName,
|
|
78
77
|
normalizeError,
|
|
79
78
|
MonitoringContext,
|
|
80
79
|
loggerToMonitoringContext,
|
|
@@ -87,7 +86,12 @@ import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
|
87
86
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
88
87
|
import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
|
|
89
88
|
import { pkgVersion } from "./packageVersion";
|
|
90
|
-
import {
|
|
89
|
+
import {
|
|
90
|
+
ContainerStorageAdapter,
|
|
91
|
+
getBlobContentsFromTree,
|
|
92
|
+
getBlobContentsFromTreeWithBlobContents,
|
|
93
|
+
ISerializableBlobContents,
|
|
94
|
+
} from "./containerStorageAdapter";
|
|
91
95
|
import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
|
|
92
96
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
93
97
|
import {
|
|
@@ -105,6 +109,10 @@ const detachedContainerRefSeqNumber = 0;
|
|
|
105
109
|
const dirtyContainerEvent = "dirty";
|
|
106
110
|
const savedContainerEvent = "saved";
|
|
107
111
|
|
|
112
|
+
/**
|
|
113
|
+
* @deprecated this is an internal interface and will not longer be exported in future versions
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
108
116
|
export interface IContainerLoadOptions {
|
|
109
117
|
/**
|
|
110
118
|
* Disables the Container from reconnecting if false, allows reconnect otherwise.
|
|
@@ -125,6 +133,10 @@ export interface IContainerLoadOptions {
|
|
|
125
133
|
loadMode?: IContainerLoadMode;
|
|
126
134
|
}
|
|
127
135
|
|
|
136
|
+
/**
|
|
137
|
+
* @deprecated this is an internal interface and will not longer be exported in future versions
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
128
140
|
export interface IContainerConfig {
|
|
129
141
|
resolvedUrl?: IFluidResolvedUrl;
|
|
130
142
|
canReconnect?: boolean;
|
|
@@ -255,17 +267,36 @@ export async function ReportIfTooLong(
|
|
|
255
267
|
/**
|
|
256
268
|
* State saved by a container at close time, to be used to load a new instance
|
|
257
269
|
* of the container to the same state
|
|
270
|
+
* @deprecated this is an internal interface and will not longer be exported in future versions
|
|
271
|
+
* @internal
|
|
258
272
|
*/
|
|
259
273
|
export interface IPendingContainerState {
|
|
260
274
|
pendingRuntimeState: unknown;
|
|
275
|
+
/**
|
|
276
|
+
* Snapshot from which container initially loaded.
|
|
277
|
+
*/
|
|
278
|
+
baseSnapshot: ISnapshotTree;
|
|
279
|
+
/**
|
|
280
|
+
* Serializable blobs from the base snapshot. Used to load offline since
|
|
281
|
+
* storage is not available.
|
|
282
|
+
*/
|
|
283
|
+
snapshotBlobs: ISerializableBlobContents;
|
|
284
|
+
/**
|
|
285
|
+
* All ops since base snapshot sequence number up to the latest op
|
|
286
|
+
* seen when the container was closed. Used to apply stashed (saved pending)
|
|
287
|
+
* ops at the same sequence number at which they were made.
|
|
288
|
+
*/
|
|
289
|
+
savedOps: ISequencedDocumentMessage[];
|
|
261
290
|
url: string;
|
|
262
|
-
protocol: IProtocolState;
|
|
263
291
|
term: number;
|
|
264
292
|
clientId?: string;
|
|
265
293
|
}
|
|
266
294
|
|
|
267
295
|
const summarizerClientType = "summarizer";
|
|
268
296
|
|
|
297
|
+
/**
|
|
298
|
+
* @deprecated - In the next release Container will no longer be exported, IContainer should be used in its place.
|
|
299
|
+
*/
|
|
269
300
|
export class Container
|
|
270
301
|
extends EventEmitterWithErrorHandling<IContainerEvents>
|
|
271
302
|
implements IContainer
|
|
@@ -274,6 +305,7 @@ export class Container
|
|
|
274
305
|
|
|
275
306
|
/**
|
|
276
307
|
* Load an existing container.
|
|
308
|
+
* @internal
|
|
277
309
|
*/
|
|
278
310
|
public static async load(
|
|
279
311
|
loader: Loader,
|
|
@@ -434,9 +466,9 @@ export class Container
|
|
|
434
466
|
|
|
435
467
|
private _attachState = AttachState.Detached;
|
|
436
468
|
|
|
437
|
-
private readonly
|
|
469
|
+
private readonly storageAdapter: ContainerStorageAdapter;
|
|
438
470
|
public get storage(): IDocumentStorageService {
|
|
439
|
-
return this.
|
|
471
|
+
return this.storageAdapter;
|
|
440
472
|
}
|
|
441
473
|
|
|
442
474
|
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
@@ -467,6 +499,9 @@ export class Container
|
|
|
467
499
|
private _resolvedUrl: IFluidResolvedUrl | undefined;
|
|
468
500
|
private attachStarted = false;
|
|
469
501
|
private _dirtyContainer = false;
|
|
502
|
+
private readonly savedOps: ISequencedDocumentMessage[] = [];
|
|
503
|
+
private baseSnapshot?: ISnapshotTree;
|
|
504
|
+
private baseSnapshotBlobs?: ISerializableBlobContents;
|
|
470
505
|
|
|
471
506
|
private lastVisible: number | undefined;
|
|
472
507
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
@@ -549,6 +584,14 @@ export class Container
|
|
|
549
584
|
return this._deltaManager.clientDetails;
|
|
550
585
|
}
|
|
551
586
|
|
|
587
|
+
private get offlineLoadEnabled(): boolean {
|
|
588
|
+
// summarizer will not have any pending state we want to save
|
|
589
|
+
return (
|
|
590
|
+
(this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false) &&
|
|
591
|
+
this.clientDetails.capabilities.interactive
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
552
595
|
/**
|
|
553
596
|
* Get the code details that are currently specified for the container.
|
|
554
597
|
* @returns The current code details if any are specified, undefined if none are specified.
|
|
@@ -596,6 +639,44 @@ export class Container
|
|
|
596
639
|
return this.loader.services.codeLoader;
|
|
597
640
|
}
|
|
598
641
|
|
|
642
|
+
/**
|
|
643
|
+
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
644
|
+
*/
|
|
645
|
+
public async getEntryPoint?(): Promise<FluidObject | undefined> {
|
|
646
|
+
// Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
|
|
647
|
+
// allow it since they mean a kind of read-only state for the Container.
|
|
648
|
+
// Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
|
|
649
|
+
if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
|
|
650
|
+
throw new UsageError("The container is disposing or disposed");
|
|
651
|
+
}
|
|
652
|
+
while (this._context === undefined) {
|
|
653
|
+
await new Promise<void>((resolve, reject) => {
|
|
654
|
+
const contextChangedHandler = () => {
|
|
655
|
+
resolve();
|
|
656
|
+
this.off("disposed", disposedHandler);
|
|
657
|
+
};
|
|
658
|
+
const disposedHandler = (error) => {
|
|
659
|
+
reject(error ?? "The Container is disposed");
|
|
660
|
+
this.off("contextChanged", contextChangedHandler);
|
|
661
|
+
};
|
|
662
|
+
this.once("contextChanged", contextChangedHandler);
|
|
663
|
+
this.once("disposed", disposedHandler);
|
|
664
|
+
});
|
|
665
|
+
// The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
|
|
666
|
+
// should have set this._context; making sure.
|
|
667
|
+
assert(
|
|
668
|
+
this._context !== undefined,
|
|
669
|
+
0x5a2 /* Context still not defined after contextChanged event */,
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
// Disable lint rule for the sake of more complete stack traces
|
|
673
|
+
// eslint-disable-next-line no-return-await
|
|
674
|
+
return await this._context.getEntryPoint?.();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* @internal
|
|
679
|
+
*/
|
|
599
680
|
constructor(
|
|
600
681
|
private readonly loader: Loader,
|
|
601
682
|
config: IContainerConfig,
|
|
@@ -650,6 +731,7 @@ export class Container
|
|
|
650
731
|
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
651
732
|
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
652
733
|
dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId,
|
|
734
|
+
dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
|
|
653
735
|
connectionStateDuration: () =>
|
|
654
736
|
performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
655
737
|
},
|
|
@@ -658,18 +740,12 @@ export class Container
|
|
|
658
740
|
// Prefix all events in this file with container-loader
|
|
659
741
|
this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
|
|
660
742
|
|
|
661
|
-
const summarizeProtocolTree =
|
|
662
|
-
this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
|
|
663
|
-
this.loader.services.options.summarizeProtocolTree;
|
|
664
|
-
|
|
665
743
|
this.options = {
|
|
666
744
|
...this.loader.services.options,
|
|
667
|
-
summarizeProtocolTree,
|
|
668
745
|
};
|
|
669
746
|
|
|
670
747
|
this._deltaManager = this.createDeltaManager();
|
|
671
748
|
|
|
672
|
-
this._clientId = config.serializedContainerState?.clientId;
|
|
673
749
|
this.connectionStateHandler = createConnectionStateHandler(
|
|
674
750
|
{
|
|
675
751
|
logger: this.mc.logger,
|
|
@@ -725,19 +801,33 @@ export class Container
|
|
|
725
801
|
},
|
|
726
802
|
},
|
|
727
803
|
this.deltaManager,
|
|
728
|
-
|
|
804
|
+
config.serializedContainerState?.clientId,
|
|
729
805
|
);
|
|
730
806
|
|
|
731
807
|
this.on(savedContainerEvent, () => {
|
|
732
808
|
this.connectionStateHandler.containerSaved();
|
|
733
809
|
});
|
|
734
810
|
|
|
735
|
-
|
|
811
|
+
// We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
|
|
812
|
+
// non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
|
|
813
|
+
// using this callback and fix them up.
|
|
814
|
+
const addProtocolSummaryIfMissing = (summaryTree: ISummaryTree) =>
|
|
815
|
+
isCombinedAppAndProtocolSummary(summaryTree) === true
|
|
816
|
+
? summaryTree
|
|
817
|
+
: combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
|
|
818
|
+
|
|
819
|
+
// Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
|
|
820
|
+
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
821
|
+
const forceEnableSummarizeProtocolTree =
|
|
822
|
+
this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
|
|
823
|
+
this.loader.services.options.summarizeProtocolTree;
|
|
824
|
+
|
|
825
|
+
this.storageAdapter = new ContainerStorageAdapter(
|
|
736
826
|
this.loader.services.detachedBlobStorage,
|
|
737
827
|
this.mc.logger,
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
828
|
+
config.serializedContainerState?.snapshotBlobs,
|
|
829
|
+
addProtocolSummaryIfMissing,
|
|
830
|
+
forceEnableSummarizeProtocolTree,
|
|
741
831
|
);
|
|
742
832
|
|
|
743
833
|
const isDomAvailable =
|
|
@@ -760,43 +850,6 @@ export class Container
|
|
|
760
850
|
};
|
|
761
851
|
document.addEventListener("visibilitychange", this.visibilityEventHandler);
|
|
762
852
|
}
|
|
763
|
-
|
|
764
|
-
// We observed that most users of platform do not check Container.connected event on load, causing bugs.
|
|
765
|
-
// As such, we are raising events when new listener pops up.
|
|
766
|
-
// Note that we can raise both "disconnected" & "connect" events at the same time,
|
|
767
|
-
// if we are in connecting stage.
|
|
768
|
-
this.on("newListener", (event: string, listener: (...args: any[]) => void) => {
|
|
769
|
-
// Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
|
|
770
|
-
Promise.resolve()
|
|
771
|
-
.then(() => {
|
|
772
|
-
switch (event) {
|
|
773
|
-
case dirtyContainerEvent:
|
|
774
|
-
if (this._dirtyContainer) {
|
|
775
|
-
listener();
|
|
776
|
-
}
|
|
777
|
-
break;
|
|
778
|
-
case savedContainerEvent:
|
|
779
|
-
if (!this._dirtyContainer) {
|
|
780
|
-
listener();
|
|
781
|
-
}
|
|
782
|
-
break;
|
|
783
|
-
case connectedEventName:
|
|
784
|
-
if (this.connected) {
|
|
785
|
-
listener(this.clientId);
|
|
786
|
-
}
|
|
787
|
-
break;
|
|
788
|
-
case disconnectedEventName:
|
|
789
|
-
if (!this.connected) {
|
|
790
|
-
listener();
|
|
791
|
-
}
|
|
792
|
-
break;
|
|
793
|
-
default:
|
|
794
|
-
}
|
|
795
|
-
})
|
|
796
|
-
.catch((error) => {
|
|
797
|
-
this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
|
|
798
|
-
});
|
|
799
|
-
});
|
|
800
853
|
}
|
|
801
854
|
|
|
802
855
|
/**
|
|
@@ -840,10 +893,15 @@ export class Container
|
|
|
840
893
|
try {
|
|
841
894
|
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
842
895
|
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
896
|
+
// Log generic events instead of error events if container is in loading state, as most errors are not really FF errors
|
|
897
|
+
// which can pollute telemetry for real bugs
|
|
843
898
|
this.mc.logger.sendTelemetryEvent(
|
|
844
899
|
{
|
|
845
900
|
eventName: "ContainerClose",
|
|
846
|
-
category:
|
|
901
|
+
category:
|
|
902
|
+
this._lifecycleState !== "loading" && error !== undefined
|
|
903
|
+
? "error"
|
|
904
|
+
: "generic",
|
|
847
905
|
},
|
|
848
906
|
error,
|
|
849
907
|
);
|
|
@@ -856,7 +914,7 @@ export class Container
|
|
|
856
914
|
|
|
857
915
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
858
916
|
|
|
859
|
-
this.
|
|
917
|
+
this.storageAdapter.dispose();
|
|
860
918
|
|
|
861
919
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
862
920
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
@@ -889,7 +947,7 @@ export class Container
|
|
|
889
947
|
this.mc.logger.sendTelemetryEvent(
|
|
890
948
|
{
|
|
891
949
|
eventName: "ContainerDispose",
|
|
892
|
-
category:
|
|
950
|
+
category: "generic",
|
|
893
951
|
},
|
|
894
952
|
error,
|
|
895
953
|
);
|
|
@@ -905,7 +963,7 @@ export class Container
|
|
|
905
963
|
|
|
906
964
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
907
965
|
|
|
908
|
-
this.
|
|
966
|
+
this.storageAdapter.dispose();
|
|
909
967
|
|
|
910
968
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
911
969
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
@@ -933,6 +991,9 @@ export class Container
|
|
|
933
991
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
934
992
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
935
993
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
994
|
+
if (!this.offlineLoadEnabled) {
|
|
995
|
+
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
996
|
+
}
|
|
936
997
|
assert(
|
|
937
998
|
this.attachState === AttachState.Attached,
|
|
938
999
|
0x0d1 /* "Container should be attached before close" */,
|
|
@@ -946,10 +1007,14 @@ export class Container
|
|
|
946
1007
|
this._protocolHandler.attributes.term !== undefined,
|
|
947
1008
|
0x37e /* Must have a valid protocol handler instance */,
|
|
948
1009
|
);
|
|
1010
|
+
assert(!!this.baseSnapshot, "no base snapshot");
|
|
1011
|
+
assert(!!this.baseSnapshotBlobs, "no snapshot blobs");
|
|
949
1012
|
const pendingState: IPendingContainerState = {
|
|
950
1013
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
1014
|
+
baseSnapshot: this.baseSnapshot,
|
|
1015
|
+
snapshotBlobs: this.baseSnapshotBlobs,
|
|
1016
|
+
savedOps: this.savedOps,
|
|
951
1017
|
url: this.resolvedUrl.url,
|
|
952
|
-
protocol: this.protocolHandler.getProtocolState(),
|
|
953
1018
|
term: this._protocolHandler.attributes.term,
|
|
954
1019
|
clientId: this.clientId,
|
|
955
1020
|
};
|
|
@@ -1031,9 +1096,13 @@ export class Container
|
|
|
1031
1096
|
// starting to attach the container to storage.
|
|
1032
1097
|
// Also, this should only be fired in detached container.
|
|
1033
1098
|
this._attachState = AttachState.Attaching;
|
|
1034
|
-
this.
|
|
1035
|
-
|
|
1036
|
-
|
|
1099
|
+
this.emit("attaching");
|
|
1100
|
+
if (this.offlineLoadEnabled) {
|
|
1101
|
+
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
1102
|
+
this.baseSnapshot = snapshot;
|
|
1103
|
+
this.baseSnapshotBlobs =
|
|
1104
|
+
getBlobContentsFromTreeWithBlobContents(snapshot);
|
|
1105
|
+
}
|
|
1037
1106
|
}
|
|
1038
1107
|
|
|
1039
1108
|
// Actually go and create the resolved document
|
|
@@ -1062,7 +1131,7 @@ export class Container
|
|
|
1062
1131
|
const resolvedUrl = this.service.resolvedUrl;
|
|
1063
1132
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
1064
1133
|
this._resolvedUrl = resolvedUrl;
|
|
1065
|
-
await this.
|
|
1134
|
+
await this.storageAdapter.connectToService(this.service);
|
|
1066
1135
|
|
|
1067
1136
|
if (hasAttachmentBlobs) {
|
|
1068
1137
|
// upload blobs to storage
|
|
@@ -1082,7 +1151,7 @@ export class Container
|
|
|
1082
1151
|
for (const id of newIds) {
|
|
1083
1152
|
const blob =
|
|
1084
1153
|
await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
1085
|
-
const response = await this.
|
|
1154
|
+
const response = await this.storageAdapter.createBlob(blob);
|
|
1086
1155
|
redirectTable.set(id, response.id);
|
|
1087
1156
|
}
|
|
1088
1157
|
}
|
|
@@ -1093,11 +1162,15 @@ export class Container
|
|
|
1093
1162
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1094
1163
|
|
|
1095
1164
|
this._attachState = AttachState.Attaching;
|
|
1096
|
-
this.
|
|
1097
|
-
|
|
1098
|
-
|
|
1165
|
+
this.emit("attaching");
|
|
1166
|
+
if (this.offlineLoadEnabled) {
|
|
1167
|
+
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
1168
|
+
this.baseSnapshot = snapshot;
|
|
1169
|
+
this.baseSnapshotBlobs =
|
|
1170
|
+
getBlobContentsFromTreeWithBlobContents(snapshot);
|
|
1171
|
+
}
|
|
1099
1172
|
|
|
1100
|
-
await this.
|
|
1173
|
+
await this.storageAdapter.uploadSummaryWithContext(summary, {
|
|
1101
1174
|
referenceSequenceNumber: 0,
|
|
1102
1175
|
ackHandle: undefined,
|
|
1103
1176
|
proposalHandle: undefined,
|
|
@@ -1272,7 +1345,7 @@ export class Container
|
|
|
1272
1345
|
}
|
|
1273
1346
|
|
|
1274
1347
|
private async getVersion(version: string | null): Promise<IVersion | undefined> {
|
|
1275
|
-
const versions = await this.
|
|
1348
|
+
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
1276
1349
|
return versions[0];
|
|
1277
1350
|
}
|
|
1278
1351
|
|
|
@@ -1329,15 +1402,15 @@ export class Container
|
|
|
1329
1402
|
|
|
1330
1403
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
1331
1404
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
1332
|
-
if (loadMode.deltaConnection === undefined) {
|
|
1405
|
+
if (loadMode.deltaConnection === undefined && !pendingLocalState) {
|
|
1333
1406
|
this.connectToDeltaStream(connectionArgs);
|
|
1334
1407
|
}
|
|
1335
1408
|
|
|
1336
1409
|
if (!pendingLocalState) {
|
|
1337
|
-
await this.
|
|
1410
|
+
await this.storageAdapter.connectToService(this.service);
|
|
1338
1411
|
} else {
|
|
1339
1412
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
1340
|
-
this.
|
|
1413
|
+
this.storageAdapter.connectToService(this.service).catch((error) => {
|
|
1341
1414
|
this.close(error);
|
|
1342
1415
|
this.dispose?.(error);
|
|
1343
1416
|
});
|
|
@@ -1349,20 +1422,30 @@ export class Container
|
|
|
1349
1422
|
const { snapshot, versionId } =
|
|
1350
1423
|
pendingLocalState === undefined
|
|
1351
1424
|
? await this.fetchSnapshotTree(specifiedVersion)
|
|
1352
|
-
: { snapshot:
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1425
|
+
: { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
|
|
1426
|
+
|
|
1427
|
+
if (pendingLocalState) {
|
|
1428
|
+
this.baseSnapshot = pendingLocalState.baseSnapshot;
|
|
1429
|
+
this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
|
|
1430
|
+
} else {
|
|
1431
|
+
assert(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
1432
|
+
if (this.offlineLoadEnabled) {
|
|
1433
|
+
this.baseSnapshot = snapshot;
|
|
1434
|
+
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
1435
|
+
this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storage);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const attributes: IDocumentAttributes = await this.getDocumentAttributes(
|
|
1440
|
+
this.storageAdapter,
|
|
1441
|
+
snapshot,
|
|
1356
1442
|
);
|
|
1357
1443
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
minimumSequenceNumber: pendingLocalState.protocol.minimumSequenceNumber,
|
|
1364
|
-
term: pendingLocalState.term,
|
|
1365
|
-
};
|
|
1444
|
+
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
1445
|
+
const sequenceNumber =
|
|
1446
|
+
pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
|
|
1447
|
+
const dmAttributes =
|
|
1448
|
+
sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
|
|
1366
1449
|
|
|
1367
1450
|
let opsBeforeReturnP: Promise<void> | undefined;
|
|
1368
1451
|
|
|
@@ -1373,15 +1456,15 @@ export class Container
|
|
|
1373
1456
|
// Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
|
|
1374
1457
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1375
1458
|
this.attachDeltaManagerOpHandler(
|
|
1376
|
-
|
|
1459
|
+
dmAttributes,
|
|
1377
1460
|
loadMode.deltaConnection !== "none" ? "all" : "none",
|
|
1378
1461
|
);
|
|
1379
1462
|
break;
|
|
1380
1463
|
case "cached":
|
|
1381
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
1464
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
1382
1465
|
break;
|
|
1383
1466
|
case "all":
|
|
1384
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
1467
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
|
|
1385
1468
|
break;
|
|
1386
1469
|
default:
|
|
1387
1470
|
unreachableCase(loadMode.opsBeforeReturn);
|
|
@@ -1389,22 +1472,7 @@ export class Container
|
|
|
1389
1472
|
|
|
1390
1473
|
// ...load in the existing quorum
|
|
1391
1474
|
// Initialize the protocol handler
|
|
1392
|
-
|
|
1393
|
-
await this.initializeProtocolStateFromSnapshot(
|
|
1394
|
-
attributes,
|
|
1395
|
-
this.storageService,
|
|
1396
|
-
snapshot,
|
|
1397
|
-
);
|
|
1398
|
-
} else {
|
|
1399
|
-
this.initializeProtocolState(
|
|
1400
|
-
attributes,
|
|
1401
|
-
{
|
|
1402
|
-
members: pendingLocalState.protocol.members,
|
|
1403
|
-
proposals: pendingLocalState.protocol.proposals,
|
|
1404
|
-
values: pendingLocalState.protocol.values,
|
|
1405
|
-
}, // pending IQuorumSnapshot
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1475
|
+
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
1408
1476
|
|
|
1409
1477
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1410
1478
|
await this.instantiateContext(
|
|
@@ -1414,6 +1482,24 @@ export class Container
|
|
|
1414
1482
|
pendingLocalState?.pendingRuntimeState,
|
|
1415
1483
|
);
|
|
1416
1484
|
|
|
1485
|
+
// replay saved ops
|
|
1486
|
+
if (pendingLocalState) {
|
|
1487
|
+
for (const message of pendingLocalState.savedOps) {
|
|
1488
|
+
this.processRemoteMessage(message);
|
|
1489
|
+
|
|
1490
|
+
// allow runtime to apply stashed ops at this op's sequence number
|
|
1491
|
+
await this.context.notifyOpReplay(message);
|
|
1492
|
+
}
|
|
1493
|
+
pendingLocalState.savedOps = [];
|
|
1494
|
+
|
|
1495
|
+
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
1496
|
+
assert(
|
|
1497
|
+
this.clientId === undefined,
|
|
1498
|
+
"Unexpected clientId when setting stashed clientId",
|
|
1499
|
+
);
|
|
1500
|
+
this._clientId = pendingLocalState?.clientId;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1417
1503
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
1418
1504
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
1419
1505
|
if (!this.closed) {
|
|
@@ -1437,6 +1523,11 @@ export class Container
|
|
|
1437
1523
|
|
|
1438
1524
|
switch (loadMode.deltaConnection) {
|
|
1439
1525
|
case undefined:
|
|
1526
|
+
if (pendingLocalState) {
|
|
1527
|
+
// connect to delta stream now since we did not before
|
|
1528
|
+
this.connectToDeltaStream(connectionArgs);
|
|
1529
|
+
}
|
|
1530
|
+
// intentional fallthrough
|
|
1440
1531
|
case "delayed":
|
|
1441
1532
|
assert(
|
|
1442
1533
|
this.inboundQueuePausedFromInit,
|
|
@@ -1512,15 +1603,15 @@ export class Container
|
|
|
1512
1603
|
}
|
|
1513
1604
|
|
|
1514
1605
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
1515
|
-
this.
|
|
1516
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1606
|
+
this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1607
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
|
|
1517
1608
|
|
|
1518
1609
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1519
1610
|
|
|
1520
1611
|
// Initialize the protocol handler
|
|
1521
1612
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1522
1613
|
const qValues = await readAndParse<[string, ICommittedProposal][]>(
|
|
1523
|
-
this.
|
|
1614
|
+
this.storageAdapter,
|
|
1524
1615
|
baseTree.blobs.quorumValues,
|
|
1525
1616
|
);
|
|
1526
1617
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
@@ -1744,7 +1835,7 @@ export class Container
|
|
|
1744
1835
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1745
1836
|
deltaManager.inboundSignal.pause();
|
|
1746
1837
|
|
|
1747
|
-
deltaManager.on("connect", (details:
|
|
1838
|
+
deltaManager.on("connect", (details: IConnectionDetailsInternal, _opsBehind?: number) => {
|
|
1748
1839
|
assert(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
|
|
1749
1840
|
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1750
1841
|
});
|
|
@@ -1923,7 +2014,7 @@ export class Container
|
|
|
1923
2014
|
}
|
|
1924
2015
|
|
|
1925
2016
|
/** @returns clientSequenceNumber of last message in a batch */
|
|
1926
|
-
private submitBatch(batch: IBatchMessage[]): number {
|
|
2017
|
+
private submitBatch(batch: IBatchMessage[], referenceSequenceNumber?: number): number {
|
|
1927
2018
|
let clientSequenceNumber = -1;
|
|
1928
2019
|
for (const message of batch) {
|
|
1929
2020
|
clientSequenceNumber = this.submitMessage(
|
|
@@ -1932,13 +2023,14 @@ export class Container
|
|
|
1932
2023
|
true, // batch
|
|
1933
2024
|
message.metadata,
|
|
1934
2025
|
message.compression,
|
|
2026
|
+
referenceSequenceNumber,
|
|
1935
2027
|
);
|
|
1936
2028
|
}
|
|
1937
2029
|
this._deltaManager.flush();
|
|
1938
2030
|
return clientSequenceNumber;
|
|
1939
2031
|
}
|
|
1940
2032
|
|
|
1941
|
-
private submitSummaryMessage(summary: ISummaryContent) {
|
|
2033
|
+
private submitSummaryMessage(summary: ISummaryContent, referenceSequenceNumber?: number) {
|
|
1942
2034
|
// github #6451: this is only needed for staging so the server
|
|
1943
2035
|
// know when the protocol tree is included
|
|
1944
2036
|
// this can be removed once all clients send
|
|
@@ -1946,11 +2038,14 @@ export class Container
|
|
|
1946
2038
|
if (summary.details === undefined) {
|
|
1947
2039
|
summary.details = {};
|
|
1948
2040
|
}
|
|
1949
|
-
summary.details.includesProtocolTree = this.
|
|
2041
|
+
summary.details.includesProtocolTree = this.storageAdapter.summarizeProtocolTree;
|
|
1950
2042
|
return this.submitMessage(
|
|
1951
2043
|
MessageType.Summarize,
|
|
1952
2044
|
JSON.stringify(summary),
|
|
1953
2045
|
false /* batch */,
|
|
2046
|
+
undefined /* metadata */,
|
|
2047
|
+
undefined /* compression */,
|
|
2048
|
+
referenceSequenceNumber,
|
|
1954
2049
|
);
|
|
1955
2050
|
}
|
|
1956
2051
|
|
|
@@ -1960,6 +2055,7 @@ export class Container
|
|
|
1960
2055
|
batch?: boolean,
|
|
1961
2056
|
metadata?: any,
|
|
1962
2057
|
compression?: string,
|
|
2058
|
+
referenceSequenceNumber?: number,
|
|
1963
2059
|
): number {
|
|
1964
2060
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1965
2061
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
@@ -1968,10 +2064,20 @@ export class Container
|
|
|
1968
2064
|
|
|
1969
2065
|
this.messageCountAfterDisconnection += 1;
|
|
1970
2066
|
this.collabWindowTracker?.stopSequenceNumberUpdate();
|
|
1971
|
-
return this._deltaManager.submit(
|
|
2067
|
+
return this._deltaManager.submit(
|
|
2068
|
+
type,
|
|
2069
|
+
contents,
|
|
2070
|
+
batch,
|
|
2071
|
+
metadata,
|
|
2072
|
+
compression,
|
|
2073
|
+
referenceSequenceNumber,
|
|
2074
|
+
);
|
|
1972
2075
|
}
|
|
1973
2076
|
|
|
1974
2077
|
private processRemoteMessage(message: ISequencedDocumentMessage) {
|
|
2078
|
+
if (this.offlineLoadEnabled) {
|
|
2079
|
+
this.savedOps.push(message);
|
|
2080
|
+
}
|
|
1975
2081
|
const local = this.clientId === message.clientId;
|
|
1976
2082
|
|
|
1977
2083
|
// Allow the protocol handler to process the message
|
|
@@ -2044,7 +2150,7 @@ export class Container
|
|
|
2044
2150
|
});
|
|
2045
2151
|
}
|
|
2046
2152
|
this._loadedFromVersion = version;
|
|
2047
|
-
const snapshot = (await this.
|
|
2153
|
+
const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
|
|
2048
2154
|
|
|
2049
2155
|
if (snapshot === undefined && version !== undefined) {
|
|
2050
2156
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
@@ -2083,8 +2189,10 @@ export class Container
|
|
|
2083
2189
|
loader,
|
|
2084
2190
|
(type, contents, batch, metadata) =>
|
|
2085
2191
|
this.submitContainerMessage(type, contents, batch, metadata),
|
|
2086
|
-
(summaryOp: ISummaryContent) =>
|
|
2087
|
-
|
|
2192
|
+
(summaryOp: ISummaryContent, referenceSequenceNumber?: number) =>
|
|
2193
|
+
this.submitSummaryMessage(summaryOp, referenceSequenceNumber),
|
|
2194
|
+
(batch: IBatchMessage[], referenceSequenceNumber?: number) =>
|
|
2195
|
+
this.submitBatch(batch, referenceSequenceNumber),
|
|
2088
2196
|
(message) => this.submitSignal(message),
|
|
2089
2197
|
(error?: ICriticalContainerError) => this.dispose?.(error),
|
|
2090
2198
|
(error?: ICriticalContainerError) => this.close(error),
|