@fluidframework/container-loader 2.0.0-dev.5.3.2.178189 → 2.0.0-dev.6.4.0.191457
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/CHANGELOG.md +131 -0
- package/README.md +10 -6
- package/dist/audience.d.ts +1 -0
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +5 -3
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.js +2 -2
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts +5 -5
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +97 -93
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +15 -14
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +50 -52
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +20 -9
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +327 -277
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +2 -7
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +2 -14
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +12 -13
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +21 -8
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -3
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.d.ts +30 -0
- package/dist/debugLogger.d.ts.map +1 -0
- package/dist/debugLogger.js +95 -0
- package/dist/debugLogger.js.map +1 -0
- package/dist/deltaManager.d.ts +21 -10
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +114 -66
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.d.ts +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +10 -10
- package/dist/deltaQueue.js.map +1 -1
- package/dist/disposal.d.ts +2 -2
- package/dist/disposal.d.ts.map +1 -1
- package/dist/disposal.js +1 -1
- package/dist/disposal.js.map +1 -1
- package/dist/error.d.ts +23 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +32 -0
- package/dist/error.js.map +1 -0
- package/dist/loader.d.ts +22 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +82 -51
- package/dist/loader.js.map +1 -1
- package/dist/noopHeuristic.d.ts +2 -2
- package/dist/noopHeuristic.d.ts.map +1 -1
- package/dist/noopHeuristic.js +6 -5
- package/dist/noopHeuristic.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +4 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +25 -4
- package/dist/protocol.js.map +1 -1
- package/dist/quorum.d.ts +4 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +1 -13
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +4 -4
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts +8 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +30 -11
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts +1 -0
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +4 -2
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.js +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts +5 -5
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +74 -67
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +15 -14
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +27 -29
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +20 -9
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +288 -238
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +2 -7
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +2 -14
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +5 -6
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +21 -8
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +3 -3
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.d.ts +30 -0
- package/lib/debugLogger.d.ts.map +1 -0
- package/lib/debugLogger.js +91 -0
- package/lib/debugLogger.js.map +1 -0
- package/lib/deltaManager.d.ts +21 -10
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +88 -37
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.d.ts +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +3 -3
- package/lib/deltaQueue.js.map +1 -1
- package/lib/disposal.d.ts +2 -2
- package/lib/disposal.d.ts.map +1 -1
- package/lib/disposal.js +1 -1
- package/lib/disposal.js.map +1 -1
- package/lib/error.d.ts +23 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +28 -0
- package/lib/error.js.map +1 -0
- package/lib/loader.d.ts +22 -3
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +82 -51
- package/lib/loader.js.map +1 -1
- package/lib/noopHeuristic.d.ts +2 -2
- package/lib/noopHeuristic.d.ts.map +1 -1
- package/lib/noopHeuristic.js +2 -1
- package/lib/noopHeuristic.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +4 -2
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +25 -4
- package/lib/protocol.js.map +1 -1
- package/lib/quorum.d.ts +4 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +0 -11
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +2 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts +8 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +25 -7
- package/lib/utils.js.map +1 -1
- package/package.json +26 -32
- package/src/audience.ts +7 -1
- package/src/catchUpMonitor.ts +1 -1
- package/src/connectionManager.ts +75 -51
- package/src/connectionStateHandler.ts +31 -38
- package/src/container.ts +335 -240
- package/src/containerContext.ts +0 -16
- package/src/containerStorageAdapter.ts +2 -1
- package/src/contracts.ts +27 -11
- package/src/debugLogger.ts +113 -0
- package/src/deltaManager.ts +84 -34
- package/src/deltaQueue.ts +2 -1
- package/src/disposal.ts +2 -2
- package/src/error.ts +44 -0
- package/src/loader.ts +83 -35
- package/src/noopHeuristic.ts +3 -2
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +33 -2
- package/src/quorum.ts +0 -10
- package/src/retriableDocumentStorageService.ts +2 -4
- package/src/utils.ts +33 -8
package/src/container.ts
CHANGED
|
@@ -7,62 +7,54 @@
|
|
|
7
7
|
import merge from "lodash/merge";
|
|
8
8
|
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
TypedEventEmitter,
|
|
13
|
-
assert,
|
|
14
|
-
performance,
|
|
15
|
-
unreachableCase,
|
|
16
|
-
} from "@fluidframework/common-utils";
|
|
10
|
+
import { assert, unreachableCase } from "@fluidframework/core-utils";
|
|
11
|
+
import { TypedEventEmitter, performance } from "@fluid-internal/client-utils";
|
|
17
12
|
import {
|
|
13
|
+
IEvent,
|
|
18
14
|
ITelemetryProperties,
|
|
19
15
|
TelemetryEventCategory,
|
|
20
16
|
IRequest,
|
|
21
17
|
IResponse,
|
|
22
18
|
IFluidRouter,
|
|
23
19
|
FluidObject,
|
|
20
|
+
LogLevel,
|
|
24
21
|
} from "@fluidframework/core-interfaces";
|
|
25
22
|
import {
|
|
23
|
+
AttachState,
|
|
24
|
+
ContainerWarning,
|
|
26
25
|
IAudience,
|
|
27
|
-
|
|
26
|
+
IBatchMessage,
|
|
27
|
+
ICodeDetailsLoader,
|
|
28
28
|
IContainer,
|
|
29
29
|
IContainerEvents,
|
|
30
|
-
IDeltaManager,
|
|
31
|
-
ICriticalContainerError,
|
|
32
|
-
ContainerWarning,
|
|
33
|
-
AttachState,
|
|
34
|
-
IThrottlingWarning,
|
|
35
|
-
ReadOnlyInfo,
|
|
36
30
|
IContainerLoadMode,
|
|
31
|
+
ICriticalContainerError,
|
|
32
|
+
IDeltaManager,
|
|
37
33
|
IFluidCodeDetails,
|
|
38
|
-
isFluidCodeDetails,
|
|
39
|
-
IBatchMessage,
|
|
40
|
-
ICodeDetailsLoader,
|
|
41
34
|
IHostLoader,
|
|
42
35
|
IFluidModuleWithDetails,
|
|
43
36
|
IProvideRuntimeFactory,
|
|
44
37
|
IProvideFluidCodeDetailsComparer,
|
|
45
38
|
IFluidCodeDetailsComparer,
|
|
46
39
|
IRuntime,
|
|
40
|
+
ReadOnlyInfo,
|
|
41
|
+
isFluidCodeDetails,
|
|
47
42
|
} from "@fluidframework/container-definitions";
|
|
48
|
-
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
49
43
|
import {
|
|
50
|
-
IAnyDriverError,
|
|
51
44
|
IDocumentService,
|
|
52
45
|
IDocumentServiceFactory,
|
|
53
46
|
IDocumentStorageService,
|
|
54
47
|
IResolvedUrl,
|
|
48
|
+
IThrottlingWarning,
|
|
55
49
|
IUrlResolver,
|
|
56
50
|
} from "@fluidframework/driver-definitions";
|
|
57
51
|
import {
|
|
58
52
|
readAndParse,
|
|
59
53
|
OnlineStatus,
|
|
60
54
|
isOnline,
|
|
61
|
-
combineAppAndProtocolSummary,
|
|
62
55
|
runWithRetry,
|
|
63
56
|
isCombinedAppAndProtocolSummary,
|
|
64
57
|
MessageType2,
|
|
65
|
-
canBeCoalescedByService,
|
|
66
58
|
} from "@fluidframework/driver-utils";
|
|
67
59
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
68
60
|
import {
|
|
@@ -85,21 +77,29 @@ import {
|
|
|
85
77
|
SummaryType,
|
|
86
78
|
} from "@fluidframework/protocol-definitions";
|
|
87
79
|
import {
|
|
88
|
-
|
|
80
|
+
createChildLogger,
|
|
89
81
|
EventEmitterWithErrorHandling,
|
|
90
82
|
PerformanceEvent,
|
|
91
83
|
raiseConnectedEvent,
|
|
92
|
-
TelemetryLogger,
|
|
93
84
|
connectedEventName,
|
|
94
85
|
normalizeError,
|
|
95
86
|
MonitoringContext,
|
|
96
|
-
|
|
87
|
+
createChildMonitoringContext,
|
|
97
88
|
wrapError,
|
|
98
89
|
ITelemetryLoggerExt,
|
|
90
|
+
formatTick,
|
|
91
|
+
GenericError,
|
|
92
|
+
UsageError,
|
|
99
93
|
} from "@fluidframework/telemetry-utils";
|
|
100
94
|
import { Audience } from "./audience";
|
|
101
95
|
import { ContainerContext } from "./containerContext";
|
|
102
|
-
import {
|
|
96
|
+
import {
|
|
97
|
+
ReconnectMode,
|
|
98
|
+
IConnectionManagerFactoryArgs,
|
|
99
|
+
getPackageName,
|
|
100
|
+
IConnectionDetailsInternal,
|
|
101
|
+
IConnectionStateChangeReason,
|
|
102
|
+
} from "./contracts";
|
|
103
103
|
import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
104
104
|
import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
|
|
105
105
|
import { pkgVersion } from "./packageVersion";
|
|
@@ -110,8 +110,12 @@ import {
|
|
|
110
110
|
ISerializableBlobContents,
|
|
111
111
|
} from "./containerStorageAdapter";
|
|
112
112
|
import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
|
|
113
|
-
import {
|
|
114
|
-
|
|
113
|
+
import {
|
|
114
|
+
combineAppAndProtocolSummary,
|
|
115
|
+
getProtocolSnapshotTree,
|
|
116
|
+
getSnapshotTreeFromSerializedContainer,
|
|
117
|
+
} from "./utils";
|
|
118
|
+
import { initQuorumValuesFromCodeDetails } from "./quorum";
|
|
115
119
|
import { NoopHeuristic } from "./noopHeuristic";
|
|
116
120
|
import { ConnectionManager } from "./connectionManager";
|
|
117
121
|
import { ConnectionState } from "./connectionState";
|
|
@@ -151,6 +155,11 @@ export interface IContainerLoadProps {
|
|
|
151
155
|
* The pending state serialized from a pervious container instance
|
|
152
156
|
*/
|
|
153
157
|
readonly pendingLocalState?: IPendingContainerState;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Load the container to at least this sequence number.
|
|
161
|
+
*/
|
|
162
|
+
readonly loadToSequenceNumber?: number;
|
|
154
163
|
}
|
|
155
164
|
|
|
156
165
|
/**
|
|
@@ -206,6 +215,10 @@ export interface IContainerCreateProps {
|
|
|
206
215
|
*/
|
|
207
216
|
readonly detachedBlobStorage?: IDetachedBlobStorage;
|
|
208
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Optional property for allowing the container to use a custom
|
|
220
|
+
* protocol implementation for handling the quorum and/or the audience.
|
|
221
|
+
*/
|
|
209
222
|
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
|
|
210
223
|
}
|
|
211
224
|
|
|
@@ -369,7 +382,8 @@ export class Container
|
|
|
369
382
|
loadProps: IContainerLoadProps,
|
|
370
383
|
createProps: IContainerCreateProps,
|
|
371
384
|
): Promise<Container> {
|
|
372
|
-
const { version, pendingLocalState, loadMode, resolvedUrl } =
|
|
385
|
+
const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } =
|
|
386
|
+
loadProps;
|
|
373
387
|
|
|
374
388
|
const container = new Container(createProps, loadProps);
|
|
375
389
|
|
|
@@ -398,7 +412,7 @@ export class Container
|
|
|
398
412
|
container.on("closed", onClosed);
|
|
399
413
|
|
|
400
414
|
container
|
|
401
|
-
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
415
|
+
.load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
|
|
402
416
|
.finally(() => {
|
|
403
417
|
container.removeListener("closed", onClosed);
|
|
404
418
|
})
|
|
@@ -412,7 +426,11 @@ export class Container
|
|
|
412
426
|
// Depending where error happens, we can be attempting to connect to web socket
|
|
413
427
|
// and continuously retrying (consider offline mode)
|
|
414
428
|
// Host has no container to close, so it's prudent to do it here
|
|
429
|
+
// Note: We could only dispose the container instead of just close but that would
|
|
430
|
+
// the telemetry where users sometimes search for ContainerClose event to look
|
|
431
|
+
// for load failures. So not removing this at this time.
|
|
415
432
|
container.close(err);
|
|
433
|
+
container.dispose(err);
|
|
416
434
|
onClosed(err);
|
|
417
435
|
},
|
|
418
436
|
);
|
|
@@ -473,7 +491,7 @@ export class Container
|
|
|
473
491
|
private readonly codeLoader: ICodeDetailsLoader;
|
|
474
492
|
private readonly options: ILoaderOptions;
|
|
475
493
|
private readonly scope: FluidObject;
|
|
476
|
-
private readonly subLogger:
|
|
494
|
+
private readonly subLogger: ITelemetryLoggerExt;
|
|
477
495
|
private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
|
|
478
496
|
private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
|
|
479
497
|
|
|
@@ -523,13 +541,14 @@ export class Container
|
|
|
523
541
|
|
|
524
542
|
public get closed(): boolean {
|
|
525
543
|
return (
|
|
526
|
-
this._lifecycleState === "closing" ||
|
|
527
|
-
this._lifecycleState === "closed" ||
|
|
528
|
-
this._lifecycleState === "disposing" ||
|
|
529
|
-
this._lifecycleState === "disposed"
|
|
544
|
+
this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed
|
|
530
545
|
);
|
|
531
546
|
}
|
|
532
547
|
|
|
548
|
+
public get disposed(): boolean {
|
|
549
|
+
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
550
|
+
}
|
|
551
|
+
|
|
533
552
|
private _attachState = AttachState.Detached;
|
|
534
553
|
|
|
535
554
|
private readonly storageAdapter: ContainerStorageAdapter;
|
|
@@ -556,7 +575,6 @@ export class Container
|
|
|
556
575
|
private inboundQueuePausedFromInit = true;
|
|
557
576
|
private firstConnection = true;
|
|
558
577
|
private readonly connectionTransitionTimes: number[] = [];
|
|
559
|
-
private messageCountAfterDisconnection: number = 0;
|
|
560
578
|
private _loadedFromVersion: IVersion | undefined;
|
|
561
579
|
private attachStarted = false;
|
|
562
580
|
private _dirtyContainer = false;
|
|
@@ -735,6 +753,7 @@ export class Container
|
|
|
735
753
|
|
|
736
754
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
737
755
|
const pendingLocalState = loadProps?.pendingLocalState;
|
|
756
|
+
this._clientId = pendingLocalState?.clientId;
|
|
738
757
|
|
|
739
758
|
this._canReconnect = canReconnect ?? true;
|
|
740
759
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
@@ -748,7 +767,19 @@ export class Container
|
|
|
748
767
|
this.scope = scope;
|
|
749
768
|
this.detachedBlobStorage = detachedBlobStorage;
|
|
750
769
|
this.protocolHandlerBuilder =
|
|
751
|
-
protocolHandlerBuilder ??
|
|
770
|
+
protocolHandlerBuilder ??
|
|
771
|
+
((
|
|
772
|
+
attributes: IDocumentAttributes,
|
|
773
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
774
|
+
sendProposal: (key: string, value: any) => number,
|
|
775
|
+
) =>
|
|
776
|
+
new ProtocolHandler(
|
|
777
|
+
attributes,
|
|
778
|
+
quorumSnapshot,
|
|
779
|
+
sendProposal,
|
|
780
|
+
new Audience(),
|
|
781
|
+
(clientId: string) => this.clientsWhoShouldHaveLeft.has(clientId),
|
|
782
|
+
));
|
|
752
783
|
|
|
753
784
|
// Note that we capture the createProps here so we can replicate the creation call when we want to clone.
|
|
754
785
|
this.clone = async (
|
|
@@ -769,53 +800,56 @@ export class Container
|
|
|
769
800
|
}`;
|
|
770
801
|
// Need to use the property getter for docId because for detached flow we don't have the docId initially.
|
|
771
802
|
// We assign the id later so property getter is used.
|
|
772
|
-
this.subLogger =
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
//
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
this.deltaManager?.lastMessage?.
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
+
this.subLogger = createChildLogger({
|
|
804
|
+
logger: subLogger,
|
|
805
|
+
properties: {
|
|
806
|
+
all: {
|
|
807
|
+
clientType, // Differentiating summarizer container from main container
|
|
808
|
+
containerId: uuid(),
|
|
809
|
+
docId: () => this.resolvedUrl?.id,
|
|
810
|
+
containerAttachState: () => this._attachState,
|
|
811
|
+
containerLifecycleState: () => this._lifecycleState,
|
|
812
|
+
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
813
|
+
serializedContainer: pendingLocalState !== undefined,
|
|
814
|
+
},
|
|
815
|
+
// we need to be judicious with our logging here to avoid generating too much data
|
|
816
|
+
// all data logged here should be broadly applicable, and not specific to a
|
|
817
|
+
// specific error or class of errors
|
|
818
|
+
error: {
|
|
819
|
+
// load information to associate errors with the specific load point
|
|
820
|
+
dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
|
|
821
|
+
dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
|
|
822
|
+
dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
|
|
823
|
+
containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
|
|
824
|
+
containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
|
|
825
|
+
// message information to associate errors with the specific execution state
|
|
826
|
+
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
827
|
+
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
828
|
+
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
829
|
+
dmLastMsqSeqClientId: () =>
|
|
830
|
+
this.deltaManager?.lastMessage?.clientId === null
|
|
831
|
+
? "null"
|
|
832
|
+
: this.deltaManager?.lastMessage?.clientId,
|
|
833
|
+
dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
|
|
834
|
+
connectionStateDuration: () =>
|
|
835
|
+
performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
836
|
+
},
|
|
803
837
|
},
|
|
804
838
|
});
|
|
805
839
|
|
|
806
840
|
// Prefix all events in this file with container-loader
|
|
807
|
-
this.mc =
|
|
841
|
+
this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
|
|
808
842
|
|
|
809
843
|
this._deltaManager = this.createDeltaManager();
|
|
810
844
|
|
|
811
845
|
this.connectionStateHandler = createConnectionStateHandler(
|
|
812
846
|
{
|
|
813
847
|
logger: this.mc.logger,
|
|
814
|
-
connectionStateChanged: (value, oldState, reason
|
|
848
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
815
849
|
if (value === ConnectionState.Connected) {
|
|
816
850
|
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
817
851
|
}
|
|
818
|
-
this.logConnectionStateChangeTelemetry(value, oldState, reason
|
|
852
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
819
853
|
if (this._lifecycleState === "loaded") {
|
|
820
854
|
this.propagateConnectionState(
|
|
821
855
|
false /* initial transition */,
|
|
@@ -857,8 +891,9 @@ export class Container
|
|
|
857
891
|
// Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
|
|
858
892
|
// to call this.applyForConnectedState("addMemberEvent") for "read" connections)
|
|
859
893
|
if (mode === "read") {
|
|
860
|
-
|
|
861
|
-
this.
|
|
894
|
+
const reason = { text: "NoJoinSignal" };
|
|
895
|
+
this.disconnectInternal(reason);
|
|
896
|
+
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
862
897
|
}
|
|
863
898
|
},
|
|
864
899
|
clientShouldHaveLeft: (clientId: string) => {
|
|
@@ -900,8 +935,8 @@ export class Container
|
|
|
900
935
|
document !== null &&
|
|
901
936
|
typeof document.addEventListener === "function" &&
|
|
902
937
|
document.addEventListener !== null;
|
|
903
|
-
// keep track of last time page was visible for telemetry
|
|
904
|
-
if (isDomAvailable) {
|
|
938
|
+
// keep track of last time page was visible for telemetry (on interactive clients only)
|
|
939
|
+
if (isDomAvailable && interactive) {
|
|
905
940
|
this.lastVisible = document.hidden ? performance.now() : undefined;
|
|
906
941
|
this.visibilityEventHandler = () => {
|
|
907
942
|
if (document.hidden) {
|
|
@@ -1051,47 +1086,65 @@ export class Container
|
|
|
1051
1086
|
}
|
|
1052
1087
|
}
|
|
1053
1088
|
|
|
1054
|
-
public closeAndGetPendingLocalState(): string {
|
|
1089
|
+
public async closeAndGetPendingLocalState(): Promise<string> {
|
|
1055
1090
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
1056
1091
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
1057
1092
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
1058
|
-
|
|
1093
|
+
this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
|
|
1094
|
+
const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
|
|
1059
1095
|
this.close();
|
|
1060
1096
|
return pendingState;
|
|
1061
1097
|
}
|
|
1062
1098
|
|
|
1063
|
-
public getPendingLocalState(): string {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
}
|
|
1067
|
-
if (this.closed || this._disposed) {
|
|
1068
|
-
throw new UsageError(
|
|
1069
|
-
"Pending state cannot be retried if the container is closed or disposed",
|
|
1070
|
-
);
|
|
1071
|
-
}
|
|
1072
|
-
assert(
|
|
1073
|
-
this.attachState === AttachState.Attached,
|
|
1074
|
-
0x0d1 /* "Container should be attached before close" */,
|
|
1075
|
-
);
|
|
1076
|
-
assert(
|
|
1077
|
-
this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
1078
|
-
0x0d2 /* "resolved url should be valid Fluid url" */,
|
|
1079
|
-
);
|
|
1080
|
-
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
1081
|
-
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
1082
|
-
const pendingState: IPendingContainerState = {
|
|
1083
|
-
pendingRuntimeState: this.runtime.getPendingLocalState(),
|
|
1084
|
-
baseSnapshot: this.baseSnapshot,
|
|
1085
|
-
snapshotBlobs: this.baseSnapshotBlobs,
|
|
1086
|
-
savedOps: this.savedOps,
|
|
1087
|
-
url: this.resolvedUrl.url,
|
|
1088
|
-
term: OnlyValidTermValue,
|
|
1089
|
-
clientId: this.clientId,
|
|
1090
|
-
};
|
|
1091
|
-
|
|
1092
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "GetPendingLocalState" });
|
|
1099
|
+
public async getPendingLocalState(): Promise<string> {
|
|
1100
|
+
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
1101
|
+
}
|
|
1093
1102
|
|
|
1094
|
-
|
|
1103
|
+
private async getPendingLocalStateCore(props: { notifyImminentClosure: boolean }) {
|
|
1104
|
+
return PerformanceEvent.timedExecAsync(
|
|
1105
|
+
this.mc.logger,
|
|
1106
|
+
{
|
|
1107
|
+
eventName: "getPendingLocalState",
|
|
1108
|
+
notifyImminentClosure: props.notifyImminentClosure,
|
|
1109
|
+
savedOpsSize: this.savedOps.length,
|
|
1110
|
+
clientId: this.clientId,
|
|
1111
|
+
},
|
|
1112
|
+
async () => {
|
|
1113
|
+
if (!this.offlineLoadEnabled) {
|
|
1114
|
+
throw new UsageError(
|
|
1115
|
+
"Can't get pending local state unless offline load is enabled",
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
if (this.closed || this._disposed) {
|
|
1119
|
+
throw new UsageError(
|
|
1120
|
+
"Pending state cannot be retried if the container is closed or disposed",
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
assert(
|
|
1124
|
+
this.attachState === AttachState.Attached,
|
|
1125
|
+
0x0d1 /* "Container should be attached before close" */,
|
|
1126
|
+
);
|
|
1127
|
+
assert(
|
|
1128
|
+
this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
1129
|
+
0x0d2 /* "resolved url should be valid Fluid url" */,
|
|
1130
|
+
);
|
|
1131
|
+
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
1132
|
+
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
1133
|
+
const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
|
|
1134
|
+
const pendingState: IPendingContainerState = {
|
|
1135
|
+
pendingRuntimeState,
|
|
1136
|
+
baseSnapshot: this.baseSnapshot,
|
|
1137
|
+
snapshotBlobs: this.baseSnapshotBlobs,
|
|
1138
|
+
savedOps: this.savedOps,
|
|
1139
|
+
url: this.resolvedUrl.url,
|
|
1140
|
+
term: OnlyValidTermValue,
|
|
1141
|
+
// no need to save this if there is no pending runtime state
|
|
1142
|
+
clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
return JSON.stringify(pendingState);
|
|
1146
|
+
},
|
|
1147
|
+
);
|
|
1095
1148
|
}
|
|
1096
1149
|
|
|
1097
1150
|
public get attachState(): AttachState {
|
|
@@ -1117,7 +1170,10 @@ export class Container
|
|
|
1117
1170
|
return JSON.stringify(combinedSummary);
|
|
1118
1171
|
}
|
|
1119
1172
|
|
|
1120
|
-
public async attach(
|
|
1173
|
+
public async attach(
|
|
1174
|
+
request: IRequest,
|
|
1175
|
+
attachProps?: { deltaConnection?: "none" | "delayed" },
|
|
1176
|
+
): Promise<void> {
|
|
1121
1177
|
await PerformanceEvent.timedExecAsync(
|
|
1122
1178
|
this.mc.logger,
|
|
1123
1179
|
{ eventName: "Attach" },
|
|
@@ -1243,10 +1299,13 @@ export class Container
|
|
|
1243
1299
|
this.emit("attached");
|
|
1244
1300
|
|
|
1245
1301
|
if (!this.closed) {
|
|
1246
|
-
this.
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1302
|
+
this.handleDeltaConnectionArg(
|
|
1303
|
+
{
|
|
1304
|
+
fetchOpsFromStorage: false,
|
|
1305
|
+
reason: { text: "createDetached" },
|
|
1306
|
+
},
|
|
1307
|
+
attachProps?.deltaConnection,
|
|
1308
|
+
);
|
|
1250
1309
|
}
|
|
1251
1310
|
} catch (error) {
|
|
1252
1311
|
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
@@ -1269,7 +1328,7 @@ export class Container
|
|
|
1269
1328
|
);
|
|
1270
1329
|
}
|
|
1271
1330
|
|
|
1272
|
-
private setAutoReconnectInternal(mode: ReconnectMode) {
|
|
1331
|
+
private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
|
|
1273
1332
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
1274
1333
|
|
|
1275
1334
|
if (currentMode === mode) {
|
|
@@ -1288,7 +1347,7 @@ export class Container
|
|
|
1288
1347
|
duration,
|
|
1289
1348
|
});
|
|
1290
1349
|
|
|
1291
|
-
this._deltaManager.connectionManager.setAutoReconnect(mode);
|
|
1350
|
+
this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
|
|
1292
1351
|
}
|
|
1293
1352
|
|
|
1294
1353
|
public connect() {
|
|
@@ -1300,7 +1359,10 @@ export class Container
|
|
|
1300
1359
|
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
1301
1360
|
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
1302
1361
|
// assuming that connect() is called quickly after initial container boot.
|
|
1303
|
-
this.connectInternal({
|
|
1362
|
+
this.connectInternal({
|
|
1363
|
+
reason: { text: "DocumentConnect" },
|
|
1364
|
+
fetchOpsFromStorage: false,
|
|
1365
|
+
});
|
|
1304
1366
|
}
|
|
1305
1367
|
}
|
|
1306
1368
|
|
|
@@ -1316,23 +1378,23 @@ export class Container
|
|
|
1316
1378
|
|
|
1317
1379
|
// Set Auto Reconnect Mode
|
|
1318
1380
|
const mode = ReconnectMode.Enabled;
|
|
1319
|
-
this.setAutoReconnectInternal(mode);
|
|
1381
|
+
this.setAutoReconnectInternal(mode, args.reason);
|
|
1320
1382
|
}
|
|
1321
1383
|
|
|
1322
1384
|
public disconnect() {
|
|
1323
1385
|
if (this.closed) {
|
|
1324
1386
|
throw new UsageError(`The Container is closed and cannot be disconnected`);
|
|
1325
1387
|
} else {
|
|
1326
|
-
this.disconnectInternal();
|
|
1388
|
+
this.disconnectInternal({ text: "DocumentDisconnect" });
|
|
1327
1389
|
}
|
|
1328
1390
|
}
|
|
1329
1391
|
|
|
1330
|
-
private disconnectInternal() {
|
|
1392
|
+
private disconnectInternal(reason: IConnectionStateChangeReason) {
|
|
1331
1393
|
assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
|
|
1332
1394
|
|
|
1333
1395
|
// Set Auto Reconnect Mode
|
|
1334
1396
|
const mode = ReconnectMode.Disabled;
|
|
1335
|
-
this.setAutoReconnectInternal(mode);
|
|
1397
|
+
this.setAutoReconnectInternal(mode, reason);
|
|
1336
1398
|
}
|
|
1337
1399
|
|
|
1338
1400
|
private resumeInternal(args: IConnectionArgs) {
|
|
@@ -1465,8 +1527,10 @@ export class Container
|
|
|
1465
1527
|
specifiedVersion: string | undefined,
|
|
1466
1528
|
loadMode: IContainerLoadMode,
|
|
1467
1529
|
resolvedUrl: IResolvedUrl,
|
|
1468
|
-
pendingLocalState
|
|
1530
|
+
pendingLocalState: IPendingContainerState | undefined,
|
|
1531
|
+
loadToSequenceNumber: number | undefined,
|
|
1469
1532
|
) {
|
|
1533
|
+
const timings: Record<string, number> = { phase1: performance.now() };
|
|
1470
1534
|
this.service = await this.serviceFactory.createDocumentService(
|
|
1471
1535
|
resolvedUrl,
|
|
1472
1536
|
this.subLogger,
|
|
@@ -1483,7 +1547,7 @@ export class Container
|
|
|
1483
1547
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
1484
1548
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
1485
1549
|
const connectionArgs: IConnectionArgs = {
|
|
1486
|
-
reason: "DocumentOpen",
|
|
1550
|
+
reason: { text: "DocumentOpen" },
|
|
1487
1551
|
mode: "write",
|
|
1488
1552
|
fetchOpsFromStorage: false,
|
|
1489
1553
|
};
|
|
@@ -1505,6 +1569,7 @@ export class Container
|
|
|
1505
1569
|
|
|
1506
1570
|
this._attachState = AttachState.Attached;
|
|
1507
1571
|
|
|
1572
|
+
timings.phase2 = performance.now();
|
|
1508
1573
|
// Fetch specified snapshot.
|
|
1509
1574
|
const { snapshot, versionId } =
|
|
1510
1575
|
pendingLocalState === undefined
|
|
@@ -1539,6 +1604,57 @@ export class Container
|
|
|
1539
1604
|
|
|
1540
1605
|
let opsBeforeReturnP: Promise<void> | undefined;
|
|
1541
1606
|
|
|
1607
|
+
if (loadMode.pauseAfterLoad === true) {
|
|
1608
|
+
// If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
|
|
1609
|
+
if (loadMode.opsBeforeReturn === "sequenceNumber") {
|
|
1610
|
+
assert(
|
|
1611
|
+
loadToSequenceNumber !== undefined,
|
|
1612
|
+
0x727 /* sequenceNumber should be defined */,
|
|
1613
|
+
);
|
|
1614
|
+
// Note: It is possible that we think the latest snapshot is newer than the specified sequence number
|
|
1615
|
+
// due to saved ops that may be replayed after the snapshot.
|
|
1616
|
+
// https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
|
|
1617
|
+
if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
|
|
1618
|
+
throw new Error(
|
|
1619
|
+
"Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.",
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// Force readonly mode - this will ensure we don't receive an error for the lack of join op
|
|
1625
|
+
this.forceReadonly(true);
|
|
1626
|
+
|
|
1627
|
+
// We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
|
|
1628
|
+
const opHandler = () => {
|
|
1629
|
+
if (loadToSequenceNumber === undefined) {
|
|
1630
|
+
// If there is no specified sequence number, pause after the inbound queue is empty.
|
|
1631
|
+
if (this.deltaManager.inbound.length !== 0) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
} else {
|
|
1635
|
+
// If there is a specified sequence number, keep processing until we reach it.
|
|
1636
|
+
if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Pause op processing once we have processed the desired number of ops.
|
|
1642
|
+
void this.deltaManager.inbound.pause();
|
|
1643
|
+
void this.deltaManager.outbound.pause();
|
|
1644
|
+
this.off("op", opHandler);
|
|
1645
|
+
};
|
|
1646
|
+
if (
|
|
1647
|
+
(loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
|
|
1648
|
+
this.deltaManager.lastSequenceNumber === loadToSequenceNumber
|
|
1649
|
+
) {
|
|
1650
|
+
// If we have already reached the desired sequence number, call opHandler() to pause immediately.
|
|
1651
|
+
opHandler();
|
|
1652
|
+
} else {
|
|
1653
|
+
// If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
|
|
1654
|
+
this.on("op", opHandler);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1542
1658
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
1543
1659
|
// Kick off any ops fetching if required.
|
|
1544
1660
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -1550,11 +1666,13 @@ export class Container
|
|
|
1550
1666
|
loadMode.deltaConnection !== "none" ? "all" : "none",
|
|
1551
1667
|
);
|
|
1552
1668
|
break;
|
|
1669
|
+
case "sequenceNumber":
|
|
1553
1670
|
case "cached":
|
|
1554
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
1555
|
-
break;
|
|
1556
1671
|
case "all":
|
|
1557
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
1672
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
1673
|
+
dmAttributes,
|
|
1674
|
+
loadMode.opsBeforeReturn,
|
|
1675
|
+
);
|
|
1558
1676
|
break;
|
|
1559
1677
|
default:
|
|
1560
1678
|
unreachableCase(loadMode.opsBeforeReturn);
|
|
@@ -1564,12 +1682,13 @@ export class Container
|
|
|
1564
1682
|
// Initialize the protocol handler
|
|
1565
1683
|
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
1566
1684
|
|
|
1685
|
+
timings.phase3 = performance.now();
|
|
1567
1686
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1568
|
-
await this.
|
|
1569
|
-
true, // existing
|
|
1687
|
+
await this.instantiateRuntime(
|
|
1570
1688
|
codeDetails,
|
|
1571
1689
|
snapshot,
|
|
1572
|
-
|
|
1690
|
+
// give runtime a dummy value so it knows we're loading from a stash blob
|
|
1691
|
+
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined,
|
|
1573
1692
|
);
|
|
1574
1693
|
|
|
1575
1694
|
// replay saved ops
|
|
@@ -1581,13 +1700,6 @@ export class Container
|
|
|
1581
1700
|
await this.runtime.notifyOpReplay?.(message);
|
|
1582
1701
|
}
|
|
1583
1702
|
pendingLocalState.savedOps = [];
|
|
1584
|
-
|
|
1585
|
-
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
1586
|
-
assert(
|
|
1587
|
-
this.clientId === undefined,
|
|
1588
|
-
0x5d6 /* Unexpected clientId when setting stashed clientId */,
|
|
1589
|
-
);
|
|
1590
|
-
this._clientId = pendingLocalState?.clientId;
|
|
1591
1703
|
}
|
|
1592
1704
|
|
|
1593
1705
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
@@ -1611,27 +1723,27 @@ export class Container
|
|
|
1611
1723
|
this._deltaManager.inbound.pause();
|
|
1612
1724
|
}
|
|
1613
1725
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1726
|
+
this.handleDeltaConnectionArg(
|
|
1727
|
+
connectionArgs,
|
|
1728
|
+
loadMode.deltaConnection,
|
|
1729
|
+
pendingLocalState !== undefined,
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
|
|
1734
|
+
if (
|
|
1735
|
+
loadToSequenceNumber !== undefined &&
|
|
1736
|
+
this.deltaManager.lastSequenceNumber < loadToSequenceNumber
|
|
1737
|
+
) {
|
|
1738
|
+
await new Promise<void>((resolve, reject) => {
|
|
1739
|
+
const opHandler = (message: ISequencedDocumentMessage) => {
|
|
1740
|
+
if (message.sequenceNumber >= loadToSequenceNumber) {
|
|
1741
|
+
resolve();
|
|
1742
|
+
this.off("op", opHandler);
|
|
1619
1743
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
this.inboundQueuePausedFromInit,
|
|
1624
|
-
0x346 /* inboundQueuePausedFromInit should be true */,
|
|
1625
|
-
);
|
|
1626
|
-
this.inboundQueuePausedFromInit = false;
|
|
1627
|
-
this._deltaManager.inbound.resume();
|
|
1628
|
-
this._deltaManager.inboundSignal.resume();
|
|
1629
|
-
break;
|
|
1630
|
-
case "none":
|
|
1631
|
-
break;
|
|
1632
|
-
default:
|
|
1633
|
-
unreachableCase(loadMode.deltaConnection);
|
|
1634
|
-
}
|
|
1744
|
+
};
|
|
1745
|
+
this.on("op", opHandler);
|
|
1746
|
+
});
|
|
1635
1747
|
}
|
|
1636
1748
|
|
|
1637
1749
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
@@ -1645,7 +1757,15 @@ export class Container
|
|
|
1645
1757
|
|
|
1646
1758
|
// Internal context is fully loaded at this point
|
|
1647
1759
|
this.setLoaded();
|
|
1648
|
-
|
|
1760
|
+
timings.end = performance.now();
|
|
1761
|
+
this.subLogger.sendTelemetryEvent(
|
|
1762
|
+
{
|
|
1763
|
+
eventName: "LoadStagesTimings",
|
|
1764
|
+
details: JSON.stringify(timings),
|
|
1765
|
+
},
|
|
1766
|
+
undefined,
|
|
1767
|
+
LogLevel.verbose,
|
|
1768
|
+
);
|
|
1649
1769
|
return {
|
|
1650
1770
|
sequenceNumber: attributes.sequenceNumber,
|
|
1651
1771
|
version: versionId,
|
|
@@ -1654,7 +1774,7 @@ export class Container
|
|
|
1654
1774
|
};
|
|
1655
1775
|
}
|
|
1656
1776
|
|
|
1657
|
-
private async createDetached(
|
|
1777
|
+
private async createDetached(codeDetails: IFluidCodeDetails) {
|
|
1658
1778
|
const attributes: IDocumentAttributes = {
|
|
1659
1779
|
sequenceNumber: detachedContainerRefSeqNumber,
|
|
1660
1780
|
term: OnlyValidTermValue,
|
|
@@ -1664,7 +1784,7 @@ export class Container
|
|
|
1664
1784
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1665
1785
|
|
|
1666
1786
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1667
|
-
const qValues = initQuorumValuesFromCodeDetails(
|
|
1787
|
+
const qValues = initQuorumValuesFromCodeDetails(codeDetails);
|
|
1668
1788
|
this.initializeProtocolState(
|
|
1669
1789
|
attributes,
|
|
1670
1790
|
{
|
|
@@ -1674,10 +1794,7 @@ export class Container
|
|
|
1674
1794
|
}, // IQuorumSnapShot
|
|
1675
1795
|
);
|
|
1676
1796
|
|
|
1677
|
-
|
|
1678
|
-
await this.instantiateContextDetached(
|
|
1679
|
-
false, // existing
|
|
1680
|
-
);
|
|
1797
|
+
await this.instantiateRuntime(codeDetails, undefined);
|
|
1681
1798
|
|
|
1682
1799
|
this.setLoaded();
|
|
1683
1800
|
}
|
|
@@ -1703,21 +1820,17 @@ export class Container
|
|
|
1703
1820
|
this.storageAdapter,
|
|
1704
1821
|
baseTree.blobs.quorumValues,
|
|
1705
1822
|
);
|
|
1706
|
-
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1707
1823
|
this.initializeProtocolState(
|
|
1708
1824
|
attributes,
|
|
1709
1825
|
{
|
|
1710
1826
|
members: [],
|
|
1711
1827
|
proposals: [],
|
|
1712
|
-
values:
|
|
1713
|
-
codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
1828
|
+
values: qValues,
|
|
1714
1829
|
}, // IQuorumSnapShot
|
|
1715
1830
|
);
|
|
1831
|
+
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1716
1832
|
|
|
1717
|
-
await this.
|
|
1718
|
-
true, // existing
|
|
1719
|
-
snapshotTree,
|
|
1720
|
-
);
|
|
1833
|
+
await this.instantiateRuntime(codeDetails, snapshotTree);
|
|
1721
1834
|
|
|
1722
1835
|
this.setLoaded();
|
|
1723
1836
|
}
|
|
@@ -1786,7 +1899,10 @@ export class Container
|
|
|
1786
1899
|
this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
|
|
1787
1900
|
);
|
|
1788
1901
|
|
|
1789
|
-
const protocolLogger =
|
|
1902
|
+
const protocolLogger = createChildLogger({
|
|
1903
|
+
logger: this.subLogger,
|
|
1904
|
+
namespace: "ProtocolHandler",
|
|
1905
|
+
});
|
|
1790
1906
|
|
|
1791
1907
|
protocol.quorum.on("error", (error) => {
|
|
1792
1908
|
protocolLogger.sendErrorEvent(error);
|
|
@@ -1897,7 +2013,7 @@ export class Container
|
|
|
1897
2013
|
const serviceProvider = () => this.service;
|
|
1898
2014
|
const deltaManager = new DeltaManager<ConnectionManager>(
|
|
1899
2015
|
serviceProvider,
|
|
1900
|
-
|
|
2016
|
+
createChildLogger({ logger: this.subLogger, namespace: "DeltaManager" }),
|
|
1901
2017
|
() => this.activeConnection(),
|
|
1902
2018
|
(props: IConnectionManagerFactoryArgs) =>
|
|
1903
2019
|
new ConnectionManager(
|
|
@@ -1905,7 +2021,7 @@ export class Container
|
|
|
1905
2021
|
() => this.isDirty,
|
|
1906
2022
|
this.client,
|
|
1907
2023
|
this._canReconnect,
|
|
1908
|
-
|
|
2024
|
+
createChildLogger({ logger: this.subLogger, namespace: "ConnectionManager" }),
|
|
1909
2025
|
props,
|
|
1910
2026
|
),
|
|
1911
2027
|
);
|
|
@@ -1921,18 +2037,18 @@ export class Container
|
|
|
1921
2037
|
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1922
2038
|
});
|
|
1923
2039
|
|
|
1924
|
-
deltaManager.on("establishingConnection", (reason:
|
|
2040
|
+
deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
|
|
1925
2041
|
this.connectionStateHandler.establishingConnection(reason);
|
|
1926
2042
|
});
|
|
1927
2043
|
|
|
1928
|
-
deltaManager.on("cancelEstablishingConnection", (reason:
|
|
2044
|
+
deltaManager.on("cancelEstablishingConnection", (reason: IConnectionStateChangeReason) => {
|
|
1929
2045
|
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1930
2046
|
});
|
|
1931
2047
|
|
|
1932
|
-
deltaManager.on("disconnect", (reason:
|
|
2048
|
+
deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
|
|
1933
2049
|
this.noopHeuristic?.notifyDisconnect();
|
|
1934
2050
|
if (!this.closed) {
|
|
1935
|
-
this.connectionStateHandler.receivedDisconnectEvent(reason
|
|
2051
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1936
2052
|
}
|
|
1937
2053
|
});
|
|
1938
2054
|
|
|
@@ -1967,7 +2083,7 @@ export class Container
|
|
|
1967
2083
|
|
|
1968
2084
|
private async attachDeltaManagerOpHandler(
|
|
1969
2085
|
attributes: IDocumentAttributes,
|
|
1970
|
-
prefetchType?: "cached" | "all" | "none",
|
|
2086
|
+
prefetchType?: "sequenceNumber" | "cached" | "all" | "none",
|
|
1971
2087
|
) {
|
|
1972
2088
|
return this._deltaManager.attachOpHandler(
|
|
1973
2089
|
attributes.minimumSequenceNumber,
|
|
@@ -1985,8 +2101,7 @@ export class Container
|
|
|
1985
2101
|
private logConnectionStateChangeTelemetry(
|
|
1986
2102
|
value: ConnectionState,
|
|
1987
2103
|
oldState: ConnectionState,
|
|
1988
|
-
reason?:
|
|
1989
|
-
error?: IAnyDriverError,
|
|
2104
|
+
reason?: IConnectionStateChangeReason,
|
|
1990
2105
|
) {
|
|
1991
2106
|
// Log actual event
|
|
1992
2107
|
const time = performance.now();
|
|
@@ -2004,7 +2119,7 @@ export class Container
|
|
|
2004
2119
|
if (value === ConnectionState.Connected) {
|
|
2005
2120
|
durationFromDisconnected =
|
|
2006
2121
|
time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
2007
|
-
durationFromDisconnected =
|
|
2122
|
+
durationFromDisconnected = formatTick(durationFromDisconnected);
|
|
2008
2123
|
} else if (value === ConnectionState.CatchingUp) {
|
|
2009
2124
|
// This info is of most interesting while Catching Up.
|
|
2010
2125
|
checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
|
|
@@ -2025,7 +2140,7 @@ export class Container
|
|
|
2025
2140
|
from: ConnectionState[oldState],
|
|
2026
2141
|
duration,
|
|
2027
2142
|
durationFromDisconnected,
|
|
2028
|
-
reason,
|
|
2143
|
+
reason: reason?.text,
|
|
2029
2144
|
connectionInitiationReason,
|
|
2030
2145
|
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
2031
2146
|
clientId: this.clientId,
|
|
@@ -2038,9 +2153,10 @@ export class Container
|
|
|
2038
2153
|
: undefined,
|
|
2039
2154
|
checkpointSequenceNumber,
|
|
2040
2155
|
quorumSize: this._protocolHandler?.quorum.getMembers().size,
|
|
2156
|
+
isDirty: this.isDirty,
|
|
2041
2157
|
...this._deltaManager.connectionProps,
|
|
2042
2158
|
},
|
|
2043
|
-
error,
|
|
2159
|
+
reason?.error,
|
|
2044
2160
|
);
|
|
2045
2161
|
|
|
2046
2162
|
if (value === ConnectionState.Connected) {
|
|
@@ -2048,7 +2164,10 @@ export class Container
|
|
|
2048
2164
|
}
|
|
2049
2165
|
}
|
|
2050
2166
|
|
|
2051
|
-
private propagateConnectionState(
|
|
2167
|
+
private propagateConnectionState(
|
|
2168
|
+
initialTransition: boolean,
|
|
2169
|
+
disconnectedReason?: IConnectionStateChangeReason,
|
|
2170
|
+
) {
|
|
2052
2171
|
// When container loaded, we want to propagate initial connection state.
|
|
2053
2172
|
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
2054
2173
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
@@ -2061,26 +2180,11 @@ export class Container
|
|
|
2061
2180
|
}
|
|
2062
2181
|
const state = this.connectionState === ConnectionState.Connected;
|
|
2063
2182
|
|
|
2064
|
-
const logOpsOnReconnect: boolean =
|
|
2065
|
-
this.connectionState === ConnectionState.Connected &&
|
|
2066
|
-
!this.firstConnection &&
|
|
2067
|
-
this.connectionMode === "write";
|
|
2068
|
-
if (logOpsOnReconnect) {
|
|
2069
|
-
this.messageCountAfterDisconnection = 0;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
2183
|
// Both protocol and context should not be undefined if we got so far.
|
|
2073
2184
|
|
|
2074
2185
|
this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
|
|
2075
2186
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
2076
|
-
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
2077
|
-
|
|
2078
|
-
if (logOpsOnReconnect) {
|
|
2079
|
-
this.mc.logger.sendTelemetryEvent({
|
|
2080
|
-
eventName: "OpsSentOnReconnect",
|
|
2081
|
-
count: this.messageCountAfterDisconnection,
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2187
|
+
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
|
|
2084
2188
|
}
|
|
2085
2189
|
|
|
2086
2190
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
@@ -2156,7 +2260,6 @@ export class Container
|
|
|
2156
2260
|
return -1;
|
|
2157
2261
|
}
|
|
2158
2262
|
|
|
2159
|
-
this.messageCountAfterDisconnection += 1;
|
|
2160
2263
|
this.noopHeuristic?.notifyMessageSent();
|
|
2161
2264
|
return this._deltaManager.submit(
|
|
2162
2265
|
type,
|
|
@@ -2174,28 +2277,6 @@ export class Container
|
|
|
2174
2277
|
}
|
|
2175
2278
|
const local = this.clientId === message.clientId;
|
|
2176
2279
|
|
|
2177
|
-
// Check and report if we're getting messages from a clientId that we previously
|
|
2178
|
-
// flagged should have left, or from a client that's not in the quorum but should be
|
|
2179
|
-
if (message.clientId != null) {
|
|
2180
|
-
const client = this.protocolHandler.quorum.getMember(message.clientId);
|
|
2181
|
-
|
|
2182
|
-
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
2183
|
-
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
2184
|
-
throw new Error("Remote message's clientId is missing from the quorum");
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
// Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
|
|
2188
|
-
// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
|
|
2189
|
-
// document we don't need to blow up aggressively.
|
|
2190
|
-
if (
|
|
2191
|
-
this.clientsWhoShouldHaveLeft.has(message.clientId) &&
|
|
2192
|
-
!canBeCoalescedByService(message)
|
|
2193
|
-
) {
|
|
2194
|
-
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
2195
|
-
throw new Error("Remote message's clientId already should have left");
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
2280
|
// Allow the protocol handler to process the message
|
|
2200
2281
|
const result = this.protocolHandler.processMessage(message, local);
|
|
2201
2282
|
|
|
@@ -2280,17 +2361,7 @@ export class Container
|
|
|
2280
2361
|
return { snapshot, versionId: version?.id };
|
|
2281
2362
|
}
|
|
2282
2363
|
|
|
2283
|
-
private async
|
|
2284
|
-
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
2285
|
-
if (codeDetails === undefined) {
|
|
2286
|
-
throw new Error("pkg should be provided in create flow!!");
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
await this.instantiateContext(existing, codeDetails, snapshot);
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
private async instantiateContext(
|
|
2293
|
-
existing: boolean,
|
|
2364
|
+
private async instantiateRuntime(
|
|
2294
2365
|
codeDetails: IFluidCodeDetails,
|
|
2295
2366
|
snapshot: ISnapshotTree | undefined,
|
|
2296
2367
|
pendingLocalState?: unknown,
|
|
@@ -2327,6 +2398,8 @@ export class Container
|
|
|
2327
2398
|
(this.protocolHandler.quorum.get("code") ??
|
|
2328
2399
|
this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
2329
2400
|
|
|
2401
|
+
const existing = snapshot !== undefined;
|
|
2402
|
+
|
|
2330
2403
|
const context = new ContainerContext(
|
|
2331
2404
|
this.options,
|
|
2332
2405
|
this.scope,
|
|
@@ -2350,7 +2423,6 @@ export class Container
|
|
|
2350
2423
|
this.getAbsoluteUrl,
|
|
2351
2424
|
() => this.resolvedUrl?.id,
|
|
2352
2425
|
() => this.clientId,
|
|
2353
|
-
() => this._deltaManager.serviceConfiguration,
|
|
2354
2426
|
() => this.attachState,
|
|
2355
2427
|
() => this.connected,
|
|
2356
2428
|
getSpecifiedCodeDetails,
|
|
@@ -2359,9 +2431,6 @@ export class Container
|
|
|
2359
2431
|
this.subLogger,
|
|
2360
2432
|
pendingLocalState,
|
|
2361
2433
|
);
|
|
2362
|
-
this._lifecycleEvents.once("disposed", () => {
|
|
2363
|
-
context.dispose();
|
|
2364
|
-
});
|
|
2365
2434
|
|
|
2366
2435
|
this._runtime = await PerformanceEvent.timedExecAsync(
|
|
2367
2436
|
this.subLogger,
|
|
@@ -2371,8 +2440,6 @@ export class Container
|
|
|
2371
2440
|
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
2372
2441
|
|
|
2373
2442
|
this._loadedCodeDetails = codeDetails;
|
|
2374
|
-
|
|
2375
|
-
this.emit("contextChanged", codeDetails);
|
|
2376
2443
|
}
|
|
2377
2444
|
|
|
2378
2445
|
private readonly updateDirtyContainerState = (dirty: boolean) => {
|
|
@@ -2400,6 +2467,34 @@ export class Container
|
|
|
2400
2467
|
this.runtime.setConnectionState(state && !readonly, this.clientId);
|
|
2401
2468
|
}
|
|
2402
2469
|
}
|
|
2470
|
+
|
|
2471
|
+
private handleDeltaConnectionArg(
|
|
2472
|
+
connectionArgs: IConnectionArgs,
|
|
2473
|
+
deltaConnectionArg?: "none" | "delayed",
|
|
2474
|
+
canConnect: boolean = true,
|
|
2475
|
+
) {
|
|
2476
|
+
switch (deltaConnectionArg) {
|
|
2477
|
+
case undefined:
|
|
2478
|
+
if (canConnect) {
|
|
2479
|
+
// connect to delta stream now since we did not before
|
|
2480
|
+
this.connectToDeltaStream(connectionArgs);
|
|
2481
|
+
}
|
|
2482
|
+
// intentional fallthrough
|
|
2483
|
+
case "delayed":
|
|
2484
|
+
assert(
|
|
2485
|
+
this.inboundQueuePausedFromInit,
|
|
2486
|
+
0x346 /* inboundQueuePausedFromInit should be true */,
|
|
2487
|
+
);
|
|
2488
|
+
this.inboundQueuePausedFromInit = false;
|
|
2489
|
+
this._deltaManager.inbound.resume();
|
|
2490
|
+
this._deltaManager.inboundSignal.resume();
|
|
2491
|
+
break;
|
|
2492
|
+
case "none":
|
|
2493
|
+
break;
|
|
2494
|
+
default:
|
|
2495
|
+
unreachableCase(deltaConnectionArg);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2403
2498
|
}
|
|
2404
2499
|
|
|
2405
2500
|
/**
|
|
@@ -2415,7 +2510,7 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2415
2510
|
* @experimental misuse of this API can result in duplicate op submission and potential document corruption
|
|
2416
2511
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2417
2512
|
*/
|
|
2418
|
-
getPendingLocalState?(): string
|
|
2513
|
+
getPendingLocalState?(): Promise<string>;
|
|
2419
2514
|
|
|
2420
2515
|
/**
|
|
2421
2516
|
* Closes the container and returns serialized local state intended to be
|
|
@@ -2423,5 +2518,5 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2423
2518
|
* @experimental
|
|
2424
2519
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2425
2520
|
*/
|
|
2426
|
-
closeAndGetPendingLocalState(): string
|
|
2521
|
+
closeAndGetPendingLocalState?(): Promise<string>;
|
|
2427
2522
|
}
|