@fluidframework/container-loader 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.6.4.0.191258
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 +162 -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.d.ts +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js +2 -2
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts +6 -6
- 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 +19 -15
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +59 -59
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +48 -38
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +447 -325
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +22 -70
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +24 -221
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +47 -16
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +21 -10
- 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 -9
- 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 +13 -0
- package/dist/disposal.d.ts.map +1 -0
- package/dist/disposal.js +25 -0
- package/dist/disposal.js.map +1 -0
- 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 +23 -5
- 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 +23 -0
- package/dist/noopHeuristic.d.ts.map +1 -0
- package/dist/noopHeuristic.js +90 -0
- package/dist/noopHeuristic.js.map +1 -0
- 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 +9 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +26 -7
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/quorum.d.ts +1 -14
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +1 -29
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +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.d.ts +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts +6 -6
- 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 +19 -15
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +36 -36
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +48 -38
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +414 -292
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +22 -70
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +24 -221
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +43 -12
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +21 -10
- 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 -9
- 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 +13 -0
- package/lib/disposal.d.ts.map +1 -0
- package/lib/disposal.js +21 -0
- package/lib/disposal.js.map +1 -0
- 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 +23 -5
- 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 +23 -0
- package/lib/noopHeuristic.d.ts.map +1 -0
- package/lib/{collabWindowTracker.js → noopHeuristic.js} +31 -42
- package/lib/noopHeuristic.js.map +1 -0
- 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 +9 -12
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +24 -6
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/quorum.d.ts +1 -14
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +0 -26
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +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 -28
- package/src/audience.ts +7 -1
- package/src/catchUpMonitor.ts +2 -2
- package/src/connectionManager.ts +76 -52
- package/src/connectionStateHandler.ts +46 -48
- package/src/container.ts +561 -326
- package/src/containerContext.ts +31 -349
- package/src/containerStorageAdapter.ts +49 -6
- package/src/contracts.ts +27 -13
- package/src/debugLogger.ts +113 -0
- package/src/deltaManager.ts +93 -36
- package/src/deltaQueue.ts +2 -1
- package/src/disposal.ts +25 -0
- package/src/error.ts +44 -0
- package/src/loader.ts +84 -36
- package/src/{collabWindowTracker.ts → noopHeuristic.ts} +38 -47
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +26 -16
- package/src/protocolTreeDocumentStorageService.ts +1 -1
- package/src/quorum.ts +1 -40
- package/src/retriableDocumentStorageService.ts +3 -4
- package/src/utils.ts +33 -8
- package/dist/collabWindowTracker.d.ts +0 -19
- package/dist/collabWindowTracker.d.ts.map +0 -1
- package/dist/collabWindowTracker.js +0 -101
- package/dist/collabWindowTracker.js.map +0 -1
- package/dist/deltaManagerProxy.d.ts +0 -42
- package/dist/deltaManagerProxy.d.ts.map +0 -1
- package/dist/deltaManagerProxy.js +0 -79
- package/dist/deltaManagerProxy.js.map +0 -1
- package/lib/collabWindowTracker.d.ts +0 -19
- package/lib/collabWindowTracker.d.ts.map +0 -1
- package/lib/collabWindowTracker.js.map +0 -1
- package/lib/deltaManagerProxy.d.ts +0 -42
- package/lib/deltaManagerProxy.d.ts.map +0 -1
- package/lib/deltaManagerProxy.js +0 -74
- package/lib/deltaManagerProxy.js.map +0 -1
- package/src/deltaManagerProxy.ts +0 -109
package/src/container.ts
CHANGED
|
@@ -7,48 +7,58 @@
|
|
|
7
7
|
import merge from "lodash/merge";
|
|
8
8
|
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { IRequest, IResponse, IFluidRouter, FluidObject } from "@fluidframework/core-interfaces";
|
|
10
|
+
import { assert, unreachableCase } from "@fluidframework/core-utils";
|
|
11
|
+
import { TypedEventEmitter, performance } from "@fluid-internal/client-utils";
|
|
13
12
|
import {
|
|
13
|
+
IEvent,
|
|
14
|
+
ITelemetryProperties,
|
|
15
|
+
TelemetryEventCategory,
|
|
16
|
+
IRequest,
|
|
17
|
+
IResponse,
|
|
18
|
+
IFluidRouter,
|
|
19
|
+
FluidObject,
|
|
20
|
+
LogLevel,
|
|
21
|
+
} from "@fluidframework/core-interfaces";
|
|
22
|
+
import {
|
|
23
|
+
AttachState,
|
|
24
|
+
ContainerWarning,
|
|
14
25
|
IAudience,
|
|
15
|
-
|
|
26
|
+
IBatchMessage,
|
|
27
|
+
ICodeDetailsLoader,
|
|
16
28
|
IContainer,
|
|
17
29
|
IContainerEvents,
|
|
18
|
-
IDeltaManager,
|
|
19
|
-
ICriticalContainerError,
|
|
20
|
-
ContainerWarning,
|
|
21
|
-
AttachState,
|
|
22
|
-
IThrottlingWarning,
|
|
23
|
-
ReadOnlyInfo,
|
|
24
30
|
IContainerLoadMode,
|
|
31
|
+
ICriticalContainerError,
|
|
32
|
+
IDeltaManager,
|
|
25
33
|
IFluidCodeDetails,
|
|
26
|
-
isFluidCodeDetails,
|
|
27
|
-
IBatchMessage,
|
|
28
|
-
ICodeDetailsLoader,
|
|
29
34
|
IHostLoader,
|
|
35
|
+
IFluidModuleWithDetails,
|
|
36
|
+
IProvideRuntimeFactory,
|
|
37
|
+
IProvideFluidCodeDetailsComparer,
|
|
38
|
+
IFluidCodeDetailsComparer,
|
|
39
|
+
IRuntime,
|
|
40
|
+
ReadOnlyInfo,
|
|
41
|
+
isFluidCodeDetails,
|
|
30
42
|
} from "@fluidframework/container-definitions";
|
|
31
|
-
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
32
43
|
import {
|
|
33
|
-
IAnyDriverError,
|
|
34
44
|
IDocumentService,
|
|
35
45
|
IDocumentServiceFactory,
|
|
36
46
|
IDocumentStorageService,
|
|
37
47
|
IResolvedUrl,
|
|
48
|
+
IThrottlingWarning,
|
|
38
49
|
IUrlResolver,
|
|
39
50
|
} from "@fluidframework/driver-definitions";
|
|
40
51
|
import {
|
|
41
52
|
readAndParse,
|
|
42
53
|
OnlineStatus,
|
|
43
54
|
isOnline,
|
|
44
|
-
combineAppAndProtocolSummary,
|
|
45
55
|
runWithRetry,
|
|
46
56
|
isCombinedAppAndProtocolSummary,
|
|
57
|
+
MessageType2,
|
|
47
58
|
} from "@fluidframework/driver-utils";
|
|
48
59
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
49
60
|
import {
|
|
50
61
|
IClient,
|
|
51
|
-
IClientConfiguration,
|
|
52
62
|
IClientDetails,
|
|
53
63
|
ICommittedProposal,
|
|
54
64
|
IDocumentAttributes,
|
|
@@ -67,23 +77,30 @@ import {
|
|
|
67
77
|
SummaryType,
|
|
68
78
|
} from "@fluidframework/protocol-definitions";
|
|
69
79
|
import {
|
|
70
|
-
|
|
80
|
+
createChildLogger,
|
|
71
81
|
EventEmitterWithErrorHandling,
|
|
72
82
|
PerformanceEvent,
|
|
73
83
|
raiseConnectedEvent,
|
|
74
|
-
TelemetryLogger,
|
|
75
84
|
connectedEventName,
|
|
76
85
|
normalizeError,
|
|
77
86
|
MonitoringContext,
|
|
78
|
-
|
|
87
|
+
createChildMonitoringContext,
|
|
79
88
|
wrapError,
|
|
80
89
|
ITelemetryLoggerExt,
|
|
90
|
+
formatTick,
|
|
91
|
+
GenericError,
|
|
92
|
+
UsageError,
|
|
81
93
|
} from "@fluidframework/telemetry-utils";
|
|
82
94
|
import { Audience } from "./audience";
|
|
83
95
|
import { ContainerContext } from "./containerContext";
|
|
84
|
-
import {
|
|
96
|
+
import {
|
|
97
|
+
ReconnectMode,
|
|
98
|
+
IConnectionManagerFactoryArgs,
|
|
99
|
+
getPackageName,
|
|
100
|
+
IConnectionDetailsInternal,
|
|
101
|
+
IConnectionStateChangeReason,
|
|
102
|
+
} from "./contracts";
|
|
85
103
|
import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
86
|
-
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
87
104
|
import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
|
|
88
105
|
import { pkgVersion } from "./packageVersion";
|
|
89
106
|
import {
|
|
@@ -93,20 +110,21 @@ import {
|
|
|
93
110
|
ISerializableBlobContents,
|
|
94
111
|
} from "./containerStorageAdapter";
|
|
95
112
|
import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
|
|
96
|
-
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
97
113
|
import {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} from "./
|
|
102
|
-
import {
|
|
114
|
+
combineAppAndProtocolSummary,
|
|
115
|
+
getProtocolSnapshotTree,
|
|
116
|
+
getSnapshotTreeFromSerializedContainer,
|
|
117
|
+
} from "./utils";
|
|
118
|
+
import { initQuorumValuesFromCodeDetails } from "./quorum";
|
|
119
|
+
import { NoopHeuristic } from "./noopHeuristic";
|
|
103
120
|
import { ConnectionManager } from "./connectionManager";
|
|
104
121
|
import { ConnectionState } from "./connectionState";
|
|
105
122
|
import {
|
|
106
|
-
OnlyValidTermValue,
|
|
107
123
|
IProtocolHandler,
|
|
124
|
+
OnlyValidTermValue,
|
|
108
125
|
ProtocolHandler,
|
|
109
126
|
ProtocolHandlerBuilder,
|
|
127
|
+
protocolHandlerShouldProcessSignal,
|
|
110
128
|
} from "./protocol";
|
|
111
129
|
|
|
112
130
|
const detachedContainerRefSeqNumber = 0;
|
|
@@ -114,6 +132,8 @@ const detachedContainerRefSeqNumber = 0;
|
|
|
114
132
|
const dirtyContainerEvent = "dirty";
|
|
115
133
|
const savedContainerEvent = "saved";
|
|
116
134
|
|
|
135
|
+
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
136
|
+
|
|
117
137
|
/**
|
|
118
138
|
* @internal
|
|
119
139
|
*/
|
|
@@ -135,6 +155,11 @@ export interface IContainerLoadProps {
|
|
|
135
155
|
* The pending state serialized from a pervious container instance
|
|
136
156
|
*/
|
|
137
157
|
readonly pendingLocalState?: IPendingContainerState;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Load the container to at least this sequence number.
|
|
161
|
+
*/
|
|
162
|
+
readonly loadToSequenceNumber?: number;
|
|
138
163
|
}
|
|
139
164
|
|
|
140
165
|
/**
|
|
@@ -190,6 +215,10 @@ export interface IContainerCreateProps {
|
|
|
190
215
|
*/
|
|
191
216
|
readonly detachedBlobStorage?: IDetachedBlobStorage;
|
|
192
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
|
+
*/
|
|
193
222
|
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
|
|
194
223
|
}
|
|
195
224
|
|
|
@@ -336,12 +365,15 @@ export interface IPendingContainerState {
|
|
|
336
365
|
|
|
337
366
|
const summarizerClientType = "summarizer";
|
|
338
367
|
|
|
368
|
+
interface IContainerLifecycleEvents extends IEvent {
|
|
369
|
+
(event: "runtimeInstantiated", listener: () => void): void;
|
|
370
|
+
(event: "disposed", listener: () => void): void;
|
|
371
|
+
}
|
|
372
|
+
|
|
339
373
|
export class Container
|
|
340
374
|
extends EventEmitterWithErrorHandling<IContainerEvents>
|
|
341
375
|
implements IContainer, IContainerExperimental
|
|
342
376
|
{
|
|
343
|
-
public static version = "^0.1.0";
|
|
344
|
-
|
|
345
377
|
/**
|
|
346
378
|
* Load an existing container.
|
|
347
379
|
* @internal
|
|
@@ -350,7 +382,8 @@ export class Container
|
|
|
350
382
|
loadProps: IContainerLoadProps,
|
|
351
383
|
createProps: IContainerCreateProps,
|
|
352
384
|
): Promise<Container> {
|
|
353
|
-
const { version, pendingLocalState, loadMode, resolvedUrl } =
|
|
385
|
+
const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } =
|
|
386
|
+
loadProps;
|
|
354
387
|
|
|
355
388
|
const container = new Container(createProps, loadProps);
|
|
356
389
|
|
|
@@ -379,7 +412,7 @@ export class Container
|
|
|
379
412
|
container.on("closed", onClosed);
|
|
380
413
|
|
|
381
414
|
container
|
|
382
|
-
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
415
|
+
.load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
|
|
383
416
|
.finally(() => {
|
|
384
417
|
container.removeListener("closed", onClosed);
|
|
385
418
|
})
|
|
@@ -393,7 +426,11 @@ export class Container
|
|
|
393
426
|
// Depending where error happens, we can be attempting to connect to web socket
|
|
394
427
|
// and continuously retrying (consider offline mode)
|
|
395
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.
|
|
396
432
|
container.close(err);
|
|
433
|
+
container.dispose(err);
|
|
397
434
|
onClosed(err);
|
|
398
435
|
},
|
|
399
436
|
);
|
|
@@ -452,9 +489,9 @@ export class Container
|
|
|
452
489
|
private readonly urlResolver: IUrlResolver;
|
|
453
490
|
private readonly serviceFactory: IDocumentServiceFactory;
|
|
454
491
|
private readonly codeLoader: ICodeDetailsLoader;
|
|
455
|
-
|
|
492
|
+
private readonly options: ILoaderOptions;
|
|
456
493
|
private readonly scope: FluidObject;
|
|
457
|
-
|
|
494
|
+
private readonly subLogger: ITelemetryLoggerExt;
|
|
458
495
|
private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
|
|
459
496
|
private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
|
|
460
497
|
|
|
@@ -504,29 +541,27 @@ export class Container
|
|
|
504
541
|
|
|
505
542
|
public get closed(): boolean {
|
|
506
543
|
return (
|
|
507
|
-
this._lifecycleState === "closing" ||
|
|
508
|
-
this._lifecycleState === "closed" ||
|
|
509
|
-
this._lifecycleState === "disposing" ||
|
|
510
|
-
this._lifecycleState === "disposed"
|
|
544
|
+
this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed
|
|
511
545
|
);
|
|
512
546
|
}
|
|
513
547
|
|
|
548
|
+
public get disposed(): boolean {
|
|
549
|
+
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
550
|
+
}
|
|
551
|
+
|
|
514
552
|
private _attachState = AttachState.Detached;
|
|
515
553
|
|
|
516
554
|
private readonly storageAdapter: ContainerStorageAdapter;
|
|
517
|
-
public get storage(): IDocumentStorageService {
|
|
518
|
-
return this.storageAdapter;
|
|
519
|
-
}
|
|
520
555
|
|
|
521
556
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
522
557
|
private service: IDocumentService | undefined;
|
|
523
558
|
|
|
524
|
-
private
|
|
525
|
-
private get
|
|
526
|
-
if (this.
|
|
527
|
-
throw new
|
|
559
|
+
private _runtime: IRuntime | undefined;
|
|
560
|
+
private get runtime() {
|
|
561
|
+
if (this._runtime === undefined) {
|
|
562
|
+
throw new Error("Attempted to access runtime before it was defined");
|
|
528
563
|
}
|
|
529
|
-
return this.
|
|
564
|
+
return this._runtime;
|
|
530
565
|
}
|
|
531
566
|
private _protocolHandler: IProtocolHandler | undefined;
|
|
532
567
|
private get protocolHandler() {
|
|
@@ -540,7 +575,6 @@ export class Container
|
|
|
540
575
|
private inboundQueuePausedFromInit = true;
|
|
541
576
|
private firstConnection = true;
|
|
542
577
|
private readonly connectionTransitionTimes: number[] = [];
|
|
543
|
-
private messageCountAfterDisconnection: number = 0;
|
|
544
578
|
private _loadedFromVersion: IVersion | undefined;
|
|
545
579
|
private attachStarted = false;
|
|
546
580
|
private _dirtyContainer = false;
|
|
@@ -551,10 +585,11 @@ export class Container
|
|
|
551
585
|
private lastVisible: number | undefined;
|
|
552
586
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
553
587
|
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
588
|
+
private readonly clientsWhoShouldHaveLeft = new Set<string>();
|
|
554
589
|
|
|
555
590
|
private setAutoReconnectTime = performance.now();
|
|
556
591
|
|
|
557
|
-
private
|
|
592
|
+
private noopHeuristic: NoopHeuristic | undefined;
|
|
558
593
|
|
|
559
594
|
private get connectionMode() {
|
|
560
595
|
return this._deltaManager.connectionManager.connectionMode;
|
|
@@ -579,18 +614,10 @@ export class Container
|
|
|
579
614
|
return this.service?.resolvedUrl;
|
|
580
615
|
}
|
|
581
616
|
|
|
582
|
-
public get loadedFromVersion(): IVersion | undefined {
|
|
583
|
-
return this._loadedFromVersion;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
617
|
public get readOnlyInfo(): ReadOnlyInfo {
|
|
587
618
|
return this._deltaManager.readOnlyInfo;
|
|
588
619
|
}
|
|
589
620
|
|
|
590
|
-
public get closeSignal(): AbortSignal {
|
|
591
|
-
return this._deltaManager.closeAbortController.signal;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
621
|
/**
|
|
595
622
|
* Tracks host requiring read-only mode.
|
|
596
623
|
*/
|
|
@@ -606,18 +633,10 @@ export class Container
|
|
|
606
633
|
return this.connectionStateHandler.connectionState;
|
|
607
634
|
}
|
|
608
635
|
|
|
609
|
-
|
|
636
|
+
private get connected(): boolean {
|
|
610
637
|
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
611
638
|
}
|
|
612
639
|
|
|
613
|
-
/**
|
|
614
|
-
* Service configuration details. If running in offline mode will be undefined otherwise will contain service
|
|
615
|
-
* configuration details returned as part of the initial connection.
|
|
616
|
-
*/
|
|
617
|
-
public get serviceConfiguration(): IClientConfiguration | undefined {
|
|
618
|
-
return this._deltaManager.serviceConfiguration;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
640
|
private _clientId: string | undefined;
|
|
622
641
|
|
|
623
642
|
/**
|
|
@@ -628,24 +647,12 @@ export class Container
|
|
|
628
647
|
return this._clientId;
|
|
629
648
|
}
|
|
630
649
|
|
|
631
|
-
/**
|
|
632
|
-
* The server provided claims of the client.
|
|
633
|
-
* Set once this.connected is true, otherwise undefined
|
|
634
|
-
*/
|
|
635
|
-
public get scopes(): string[] | undefined {
|
|
636
|
-
return this._deltaManager.connectionManager.scopes;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
public get clientDetails(): IClientDetails {
|
|
640
|
-
return this._deltaManager.clientDetails;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
650
|
private get offlineLoadEnabled(): boolean {
|
|
644
651
|
const enabled =
|
|
645
652
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
|
|
646
653
|
this.options?.enableOfflineLoad === true;
|
|
647
654
|
// summarizer will not have any pending state we want to save
|
|
648
|
-
return enabled && this.clientDetails.capabilities.interactive;
|
|
655
|
+
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
649
656
|
}
|
|
650
657
|
|
|
651
658
|
/**
|
|
@@ -656,15 +663,18 @@ export class Container
|
|
|
656
663
|
return this.getCodeDetailsFromQuorum();
|
|
657
664
|
}
|
|
658
665
|
|
|
666
|
+
private _loadedCodeDetails: IFluidCodeDetails | undefined;
|
|
659
667
|
/**
|
|
660
668
|
* Get the code details that were used to load the container.
|
|
661
669
|
* @returns The code details that were used to load the container if it is loaded, undefined if it is not yet
|
|
662
670
|
* loaded.
|
|
663
671
|
*/
|
|
664
672
|
public getLoadedCodeDetails(): IFluidCodeDetails | undefined {
|
|
665
|
-
return this.
|
|
673
|
+
return this._loadedCodeDetails;
|
|
666
674
|
}
|
|
667
675
|
|
|
676
|
+
private _loadedModule: IFluidModuleWithDetails | undefined;
|
|
677
|
+
|
|
668
678
|
/**
|
|
669
679
|
* Retrieves the audience associated with the document
|
|
670
680
|
*/
|
|
@@ -684,38 +694,33 @@ export class Container
|
|
|
684
694
|
/**
|
|
685
695
|
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
686
696
|
*/
|
|
687
|
-
public async getEntryPoint
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
// Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
|
|
691
|
-
if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
|
|
692
|
-
throw new UsageError("The container is disposing or disposed");
|
|
697
|
+
public async getEntryPoint(): Promise<FluidObject | undefined> {
|
|
698
|
+
if (this._disposed) {
|
|
699
|
+
throw new UsageError("The context is already disposed");
|
|
693
700
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const contextChangedHandler = () => {
|
|
697
|
-
resolve();
|
|
698
|
-
this.off("disposed", disposedHandler);
|
|
699
|
-
};
|
|
700
|
-
const disposedHandler = (error) => {
|
|
701
|
-
reject(error ?? "The Container is disposed");
|
|
702
|
-
this.off("contextChanged", contextChangedHandler);
|
|
703
|
-
};
|
|
704
|
-
this.once("contextChanged", contextChangedHandler);
|
|
705
|
-
this.once("disposed", disposedHandler);
|
|
706
|
-
});
|
|
707
|
-
// The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
|
|
708
|
-
// should have set this._context; making sure.
|
|
709
|
-
assert(
|
|
710
|
-
this._context !== undefined,
|
|
711
|
-
0x5a2 /* Context still not defined after contextChanged event */,
|
|
712
|
-
);
|
|
701
|
+
if (this._runtime !== undefined) {
|
|
702
|
+
return this._runtime.getEntryPoint?.();
|
|
713
703
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
704
|
+
return new Promise<FluidObject | undefined>((resolve, reject) => {
|
|
705
|
+
const runtimeInstantiatedHandler = () => {
|
|
706
|
+
assert(
|
|
707
|
+
this._runtime !== undefined,
|
|
708
|
+
0x5a3 /* runtimeInstantiated fired but runtime is still undefined */,
|
|
709
|
+
);
|
|
710
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
711
|
+
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
712
|
+
};
|
|
713
|
+
const disposedHandler = () => {
|
|
714
|
+
reject(new Error("ContainerContext was disposed"));
|
|
715
|
+
this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
716
|
+
};
|
|
717
|
+
this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
718
|
+
this._lifecycleEvents.once("disposed", disposedHandler);
|
|
719
|
+
});
|
|
717
720
|
}
|
|
718
721
|
|
|
722
|
+
private readonly _lifecycleEvents = new TypedEventEmitter<IContainerLifecycleEvents>();
|
|
723
|
+
|
|
719
724
|
/**
|
|
720
725
|
* @internal
|
|
721
726
|
*/
|
|
@@ -748,6 +753,7 @@ export class Container
|
|
|
748
753
|
|
|
749
754
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
750
755
|
const pendingLocalState = loadProps?.pendingLocalState;
|
|
756
|
+
this._clientId = pendingLocalState?.clientId;
|
|
751
757
|
|
|
752
758
|
this._canReconnect = canReconnect ?? true;
|
|
753
759
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
@@ -761,7 +767,19 @@ export class Container
|
|
|
761
767
|
this.scope = scope;
|
|
762
768
|
this.detachedBlobStorage = detachedBlobStorage;
|
|
763
769
|
this.protocolHandlerBuilder =
|
|
764
|
-
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
|
+
));
|
|
765
783
|
|
|
766
784
|
// Note that we capture the createProps here so we can replicate the creation call when we want to clone.
|
|
767
785
|
this.clone = async (
|
|
@@ -782,50 +800,56 @@ export class Container
|
|
|
782
800
|
}`;
|
|
783
801
|
// Need to use the property getter for docId because for detached flow we don't have the docId initially.
|
|
784
802
|
// We assign the id later so property getter is used.
|
|
785
|
-
this.subLogger =
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
//
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
+
},
|
|
813
837
|
},
|
|
814
838
|
});
|
|
815
839
|
|
|
816
840
|
// Prefix all events in this file with container-loader
|
|
817
|
-
this.mc =
|
|
841
|
+
this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
|
|
818
842
|
|
|
819
843
|
this._deltaManager = this.createDeltaManager();
|
|
820
844
|
|
|
821
845
|
this.connectionStateHandler = createConnectionStateHandler(
|
|
822
846
|
{
|
|
823
847
|
logger: this.mc.logger,
|
|
824
|
-
connectionStateChanged: (value, oldState, reason
|
|
848
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
825
849
|
if (value === ConnectionState.Connected) {
|
|
826
850
|
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
827
851
|
}
|
|
828
|
-
this.logConnectionStateChangeTelemetry(value, oldState, reason
|
|
852
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
829
853
|
if (this._lifecycleState === "loaded") {
|
|
830
854
|
this.propagateConnectionState(
|
|
831
855
|
false /* initial transition */,
|
|
@@ -867,10 +891,14 @@ export class Container
|
|
|
867
891
|
// Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
|
|
868
892
|
// to call this.applyForConnectedState("addMemberEvent") for "read" connections)
|
|
869
893
|
if (mode === "read") {
|
|
870
|
-
|
|
871
|
-
this.
|
|
894
|
+
const reason = { text: "NoJoinSignal" };
|
|
895
|
+
this.disconnectInternal(reason);
|
|
896
|
+
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
872
897
|
}
|
|
873
898
|
},
|
|
899
|
+
clientShouldHaveLeft: (clientId: string) => {
|
|
900
|
+
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
901
|
+
},
|
|
874
902
|
},
|
|
875
903
|
this.deltaManager,
|
|
876
904
|
pendingLocalState?.clientId,
|
|
@@ -907,8 +935,8 @@ export class Container
|
|
|
907
935
|
document !== null &&
|
|
908
936
|
typeof document.addEventListener === "function" &&
|
|
909
937
|
document.addEventListener !== null;
|
|
910
|
-
// keep track of last time page was visible for telemetry
|
|
911
|
-
if (isDomAvailable) {
|
|
938
|
+
// keep track of last time page was visible for telemetry (on interactive clients only)
|
|
939
|
+
if (isDomAvailable && interactive) {
|
|
912
940
|
this.lastVisible = document.hidden ? performance.now() : undefined;
|
|
913
941
|
this.visibilityEventHandler = () => {
|
|
914
942
|
if (document.hidden) {
|
|
@@ -994,6 +1022,11 @@ export class Container
|
|
|
994
1022
|
}
|
|
995
1023
|
} finally {
|
|
996
1024
|
this._lifecycleState = "closed";
|
|
1025
|
+
|
|
1026
|
+
// There is no user for summarizer, so we need to ensure dispose is called
|
|
1027
|
+
if (this.client.details.type === summarizerClientType) {
|
|
1028
|
+
this.dispose(error);
|
|
1029
|
+
}
|
|
997
1030
|
}
|
|
998
1031
|
}
|
|
999
1032
|
|
|
@@ -1010,7 +1043,8 @@ export class Container
|
|
|
1010
1043
|
this.mc.logger.sendTelemetryEvent(
|
|
1011
1044
|
{
|
|
1012
1045
|
eventName: "ContainerDispose",
|
|
1013
|
-
|
|
1046
|
+
// Only log error if container isn't closed
|
|
1047
|
+
category: !this.closed && error !== undefined ? "error" : "generic",
|
|
1014
1048
|
},
|
|
1015
1049
|
error,
|
|
1016
1050
|
);
|
|
@@ -1024,7 +1058,8 @@ export class Container
|
|
|
1024
1058
|
|
|
1025
1059
|
this.connectionStateHandler.dispose();
|
|
1026
1060
|
|
|
1027
|
-
|
|
1061
|
+
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
1062
|
+
this._runtime?.dispose(maybeError);
|
|
1028
1063
|
|
|
1029
1064
|
this.storageAdapter.dispose();
|
|
1030
1065
|
|
|
@@ -1047,45 +1082,69 @@ export class Container
|
|
|
1047
1082
|
}
|
|
1048
1083
|
} finally {
|
|
1049
1084
|
this._lifecycleState = "disposed";
|
|
1085
|
+
this._lifecycleEvents.emit("disposed");
|
|
1050
1086
|
}
|
|
1051
1087
|
}
|
|
1052
1088
|
|
|
1053
|
-
public closeAndGetPendingLocalState(): string {
|
|
1089
|
+
public async closeAndGetPendingLocalState(): Promise<string> {
|
|
1054
1090
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
1055
1091
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
1056
1092
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
1057
|
-
|
|
1093
|
+
this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
|
|
1094
|
+
const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
|
|
1058
1095
|
this.close();
|
|
1059
1096
|
return pendingState;
|
|
1060
1097
|
}
|
|
1061
1098
|
|
|
1062
|
-
public getPendingLocalState(): string {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1066
|
-
assert(
|
|
1067
|
-
this.attachState === AttachState.Attached,
|
|
1068
|
-
0x0d1 /* "Container should be attached before close" */,
|
|
1069
|
-
);
|
|
1070
|
-
assert(
|
|
1071
|
-
this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
1072
|
-
0x0d2 /* "resolved url should be valid Fluid url" */,
|
|
1073
|
-
);
|
|
1074
|
-
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
1075
|
-
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
1076
|
-
const pendingState: IPendingContainerState = {
|
|
1077
|
-
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
1078
|
-
baseSnapshot: this.baseSnapshot,
|
|
1079
|
-
snapshotBlobs: this.baseSnapshotBlobs,
|
|
1080
|
-
savedOps: this.savedOps,
|
|
1081
|
-
url: this.resolvedUrl.url,
|
|
1082
|
-
term: OnlyValidTermValue,
|
|
1083
|
-
clientId: this.clientId,
|
|
1084
|
-
};
|
|
1099
|
+
public async getPendingLocalState(): Promise<string> {
|
|
1100
|
+
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
1101
|
+
}
|
|
1085
1102
|
|
|
1086
|
-
|
|
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
|
+
};
|
|
1087
1144
|
|
|
1088
|
-
|
|
1145
|
+
return JSON.stringify(pendingState);
|
|
1146
|
+
},
|
|
1147
|
+
);
|
|
1089
1148
|
}
|
|
1090
1149
|
|
|
1091
1150
|
public get attachState(): AttachState {
|
|
@@ -1098,7 +1157,7 @@ export class Container
|
|
|
1098
1157
|
0x0d3 /* "Should only be called in detached container" */,
|
|
1099
1158
|
);
|
|
1100
1159
|
|
|
1101
|
-
const appSummary: ISummaryTree = this.
|
|
1160
|
+
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1102
1161
|
const protocolSummary = this.captureProtocolSummary();
|
|
1103
1162
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1104
1163
|
|
|
@@ -1111,7 +1170,10 @@ export class Container
|
|
|
1111
1170
|
return JSON.stringify(combinedSummary);
|
|
1112
1171
|
}
|
|
1113
1172
|
|
|
1114
|
-
public async attach(
|
|
1173
|
+
public async attach(
|
|
1174
|
+
request: IRequest,
|
|
1175
|
+
attachProps?: { deltaConnection?: "none" | "delayed" },
|
|
1176
|
+
): Promise<void> {
|
|
1115
1177
|
await PerformanceEvent.timedExecAsync(
|
|
1116
1178
|
this.mc.logger,
|
|
1117
1179
|
{ eventName: "Attach" },
|
|
@@ -1144,7 +1206,7 @@ export class Container
|
|
|
1144
1206
|
if (!hasAttachmentBlobs) {
|
|
1145
1207
|
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
1146
1208
|
// semantics around what the attach means as far as async code goes.
|
|
1147
|
-
const appSummary: ISummaryTree = this.
|
|
1209
|
+
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1148
1210
|
const protocolSummary = this.captureProtocolSummary();
|
|
1149
1211
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1150
1212
|
|
|
@@ -1153,6 +1215,7 @@ export class Container
|
|
|
1153
1215
|
// starting to attach the container to storage.
|
|
1154
1216
|
// Also, this should only be fired in detached container.
|
|
1155
1217
|
this._attachState = AttachState.Attaching;
|
|
1218
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
1156
1219
|
this.emit("attaching");
|
|
1157
1220
|
if (this.offlineLoadEnabled) {
|
|
1158
1221
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -1181,7 +1244,7 @@ export class Container
|
|
|
1181
1244
|
"containerAttach",
|
|
1182
1245
|
this.mc.logger,
|
|
1183
1246
|
{
|
|
1184
|
-
cancel: this.
|
|
1247
|
+
cancel: this._deltaManager.closeAbortController.signal,
|
|
1185
1248
|
}, // progress
|
|
1186
1249
|
);
|
|
1187
1250
|
}
|
|
@@ -1210,11 +1273,12 @@ export class Container
|
|
|
1210
1273
|
}
|
|
1211
1274
|
|
|
1212
1275
|
// take summary and upload
|
|
1213
|
-
const appSummary: ISummaryTree = this.
|
|
1276
|
+
const appSummary: ISummaryTree = this.runtime.createSummary(redirectTable);
|
|
1214
1277
|
const protocolSummary = this.captureProtocolSummary();
|
|
1215
1278
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1216
1279
|
|
|
1217
1280
|
this._attachState = AttachState.Attaching;
|
|
1281
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
1218
1282
|
this.emit("attaching");
|
|
1219
1283
|
if (this.offlineLoadEnabled) {
|
|
1220
1284
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -1231,13 +1295,17 @@ export class Container
|
|
|
1231
1295
|
}
|
|
1232
1296
|
|
|
1233
1297
|
this._attachState = AttachState.Attached;
|
|
1298
|
+
this.runtime.setAttachState(AttachState.Attached);
|
|
1234
1299
|
this.emit("attached");
|
|
1235
1300
|
|
|
1236
1301
|
if (!this.closed) {
|
|
1237
|
-
this.
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1302
|
+
this.handleDeltaConnectionArg(
|
|
1303
|
+
{
|
|
1304
|
+
fetchOpsFromStorage: false,
|
|
1305
|
+
reason: { text: "createDetached" },
|
|
1306
|
+
},
|
|
1307
|
+
attachProps?.deltaConnection,
|
|
1308
|
+
);
|
|
1241
1309
|
}
|
|
1242
1310
|
} catch (error) {
|
|
1243
1311
|
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
@@ -1255,12 +1323,12 @@ export class Container
|
|
|
1255
1323
|
return PerformanceEvent.timedExecAsync(
|
|
1256
1324
|
this.mc.logger,
|
|
1257
1325
|
{ eventName: "Request" },
|
|
1258
|
-
async () => this.
|
|
1326
|
+
async () => this.runtime.request(path),
|
|
1259
1327
|
{ end: true, cancel: "error" },
|
|
1260
1328
|
);
|
|
1261
1329
|
}
|
|
1262
1330
|
|
|
1263
|
-
private setAutoReconnectInternal(mode: ReconnectMode) {
|
|
1331
|
+
private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
|
|
1264
1332
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
1265
1333
|
|
|
1266
1334
|
if (currentMode === mode) {
|
|
@@ -1279,7 +1347,7 @@ export class Container
|
|
|
1279
1347
|
duration,
|
|
1280
1348
|
});
|
|
1281
1349
|
|
|
1282
|
-
this._deltaManager.connectionManager.setAutoReconnect(mode);
|
|
1350
|
+
this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
|
|
1283
1351
|
}
|
|
1284
1352
|
|
|
1285
1353
|
public connect() {
|
|
@@ -1291,7 +1359,10 @@ export class Container
|
|
|
1291
1359
|
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
1292
1360
|
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
1293
1361
|
// assuming that connect() is called quickly after initial container boot.
|
|
1294
|
-
this.connectInternal({
|
|
1362
|
+
this.connectInternal({
|
|
1363
|
+
reason: { text: "DocumentConnect" },
|
|
1364
|
+
fetchOpsFromStorage: false,
|
|
1365
|
+
});
|
|
1295
1366
|
}
|
|
1296
1367
|
}
|
|
1297
1368
|
|
|
@@ -1307,23 +1378,23 @@ export class Container
|
|
|
1307
1378
|
|
|
1308
1379
|
// Set Auto Reconnect Mode
|
|
1309
1380
|
const mode = ReconnectMode.Enabled;
|
|
1310
|
-
this.setAutoReconnectInternal(mode);
|
|
1381
|
+
this.setAutoReconnectInternal(mode, args.reason);
|
|
1311
1382
|
}
|
|
1312
1383
|
|
|
1313
1384
|
public disconnect() {
|
|
1314
1385
|
if (this.closed) {
|
|
1315
1386
|
throw new UsageError(`The Container is closed and cannot be disconnected`);
|
|
1316
1387
|
} else {
|
|
1317
|
-
this.disconnectInternal();
|
|
1388
|
+
this.disconnectInternal({ text: "DocumentDisconnect" });
|
|
1318
1389
|
}
|
|
1319
1390
|
}
|
|
1320
1391
|
|
|
1321
|
-
private disconnectInternal() {
|
|
1392
|
+
private disconnectInternal(reason: IConnectionStateChangeReason) {
|
|
1322
1393
|
assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
|
|
1323
1394
|
|
|
1324
1395
|
// Set Auto Reconnect Mode
|
|
1325
1396
|
const mode = ReconnectMode.Disabled;
|
|
1326
|
-
this.setAutoReconnectInternal(mode);
|
|
1397
|
+
this.setAutoReconnectInternal(mode, reason);
|
|
1327
1398
|
}
|
|
1328
1399
|
|
|
1329
1400
|
private resumeInternal(args: IConnectionArgs) {
|
|
@@ -1340,7 +1411,7 @@ export class Container
|
|
|
1340
1411
|
this.connectToDeltaStream(args);
|
|
1341
1412
|
}
|
|
1342
1413
|
|
|
1343
|
-
public async
|
|
1414
|
+
public readonly getAbsoluteUrl = async (relativeUrl: string): Promise<string | undefined> => {
|
|
1344
1415
|
if (this.resolvedUrl === undefined) {
|
|
1345
1416
|
return undefined;
|
|
1346
1417
|
}
|
|
@@ -1348,9 +1419,9 @@ export class Container
|
|
|
1348
1419
|
return this.urlResolver.getAbsoluteUrl(
|
|
1349
1420
|
this.resolvedUrl,
|
|
1350
1421
|
relativeUrl,
|
|
1351
|
-
getPackageName(this.
|
|
1422
|
+
getPackageName(this._loadedCodeDetails),
|
|
1352
1423
|
);
|
|
1353
|
-
}
|
|
1424
|
+
};
|
|
1354
1425
|
|
|
1355
1426
|
public async proposeCodeDetails(codeDetails: IFluidCodeDetails) {
|
|
1356
1427
|
if (!isFluidCodeDetails(codeDetails)) {
|
|
@@ -1381,7 +1452,7 @@ export class Container
|
|
|
1381
1452
|
this.deltaManager.inboundSignal.pause(),
|
|
1382
1453
|
]);
|
|
1383
1454
|
|
|
1384
|
-
if ((await this.
|
|
1455
|
+
if ((await this.satisfies(codeDetails)) === true) {
|
|
1385
1456
|
this.deltaManager.inbound.resume();
|
|
1386
1457
|
this.deltaManager.inboundSignal.resume();
|
|
1387
1458
|
return;
|
|
@@ -1392,6 +1463,47 @@ export class Container
|
|
|
1392
1463
|
this.close(error);
|
|
1393
1464
|
}
|
|
1394
1465
|
|
|
1466
|
+
/**
|
|
1467
|
+
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
1468
|
+
*/
|
|
1469
|
+
private async satisfies(constraintCodeDetails: IFluidCodeDetails) {
|
|
1470
|
+
// If we have no module, it can't satisfy anything.
|
|
1471
|
+
if (this._loadedModule === undefined) {
|
|
1472
|
+
return false;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
const comparers: IFluidCodeDetailsComparer[] = [];
|
|
1476
|
+
|
|
1477
|
+
const maybeCompareCodeLoader = this.codeLoader;
|
|
1478
|
+
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
1479
|
+
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const maybeCompareExport: Partial<IProvideFluidCodeDetailsComparer> | undefined =
|
|
1483
|
+
this._loadedModule?.module.fluidExport;
|
|
1484
|
+
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
|
|
1485
|
+
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
1489
|
+
// the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
|
|
1490
|
+
// rather than potentially running with incompatible code.
|
|
1491
|
+
if (comparers.length === 0) {
|
|
1492
|
+
return false;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
for (const comparer of comparers) {
|
|
1496
|
+
const satisfies = await comparer.satisfies(
|
|
1497
|
+
this._loadedModule?.details,
|
|
1498
|
+
constraintCodeDetails,
|
|
1499
|
+
);
|
|
1500
|
+
if (satisfies === false) {
|
|
1501
|
+
return false;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return true;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1395
1507
|
private async getVersion(version: string | null): Promise<IVersion | undefined> {
|
|
1396
1508
|
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
1397
1509
|
return versions[0];
|
|
@@ -1415,8 +1527,10 @@ export class Container
|
|
|
1415
1527
|
specifiedVersion: string | undefined,
|
|
1416
1528
|
loadMode: IContainerLoadMode,
|
|
1417
1529
|
resolvedUrl: IResolvedUrl,
|
|
1418
|
-
pendingLocalState
|
|
1530
|
+
pendingLocalState: IPendingContainerState | undefined,
|
|
1531
|
+
loadToSequenceNumber: number | undefined,
|
|
1419
1532
|
) {
|
|
1533
|
+
const timings: Record<string, number> = { phase1: performance.now() };
|
|
1420
1534
|
this.service = await this.serviceFactory.createDocumentService(
|
|
1421
1535
|
resolvedUrl,
|
|
1422
1536
|
this.subLogger,
|
|
@@ -1433,7 +1547,7 @@ export class Container
|
|
|
1433
1547
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
1434
1548
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
1435
1549
|
const connectionArgs: IConnectionArgs = {
|
|
1436
|
-
reason: "DocumentOpen",
|
|
1550
|
+
reason: { text: "DocumentOpen" },
|
|
1437
1551
|
mode: "write",
|
|
1438
1552
|
fetchOpsFromStorage: false,
|
|
1439
1553
|
};
|
|
@@ -1455,6 +1569,7 @@ export class Container
|
|
|
1455
1569
|
|
|
1456
1570
|
this._attachState = AttachState.Attached;
|
|
1457
1571
|
|
|
1572
|
+
timings.phase2 = performance.now();
|
|
1458
1573
|
// Fetch specified snapshot.
|
|
1459
1574
|
const { snapshot, versionId } =
|
|
1460
1575
|
pendingLocalState === undefined
|
|
@@ -1469,7 +1584,10 @@ export class Container
|
|
|
1469
1584
|
if (this.offlineLoadEnabled) {
|
|
1470
1585
|
this.baseSnapshot = snapshot;
|
|
1471
1586
|
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
1472
|
-
this.baseSnapshotBlobs = await getBlobContentsFromTree(
|
|
1587
|
+
this.baseSnapshotBlobs = await getBlobContentsFromTree(
|
|
1588
|
+
snapshot,
|
|
1589
|
+
this.storageAdapter,
|
|
1590
|
+
);
|
|
1473
1591
|
}
|
|
1474
1592
|
}
|
|
1475
1593
|
|
|
@@ -1486,6 +1604,57 @@ export class Container
|
|
|
1486
1604
|
|
|
1487
1605
|
let opsBeforeReturnP: Promise<void> | undefined;
|
|
1488
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
|
+
|
|
1489
1658
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
1490
1659
|
// Kick off any ops fetching if required.
|
|
1491
1660
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -1497,11 +1666,13 @@ export class Container
|
|
|
1497
1666
|
loadMode.deltaConnection !== "none" ? "all" : "none",
|
|
1498
1667
|
);
|
|
1499
1668
|
break;
|
|
1669
|
+
case "sequenceNumber":
|
|
1500
1670
|
case "cached":
|
|
1501
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
1502
|
-
break;
|
|
1503
1671
|
case "all":
|
|
1504
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
1672
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
1673
|
+
dmAttributes,
|
|
1674
|
+
loadMode.opsBeforeReturn,
|
|
1675
|
+
);
|
|
1505
1676
|
break;
|
|
1506
1677
|
default:
|
|
1507
1678
|
unreachableCase(loadMode.opsBeforeReturn);
|
|
@@ -1511,12 +1682,13 @@ export class Container
|
|
|
1511
1682
|
// Initialize the protocol handler
|
|
1512
1683
|
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
1513
1684
|
|
|
1685
|
+
timings.phase3 = performance.now();
|
|
1514
1686
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1515
|
-
await this.
|
|
1516
|
-
true, // existing
|
|
1687
|
+
await this.instantiateRuntime(
|
|
1517
1688
|
codeDetails,
|
|
1518
1689
|
snapshot,
|
|
1519
|
-
|
|
1690
|
+
// give runtime a dummy value so it knows we're loading from a stash blob
|
|
1691
|
+
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined,
|
|
1520
1692
|
);
|
|
1521
1693
|
|
|
1522
1694
|
// replay saved ops
|
|
@@ -1525,16 +1697,9 @@ export class Container
|
|
|
1525
1697
|
this.processRemoteMessage(message);
|
|
1526
1698
|
|
|
1527
1699
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
1528
|
-
await this.
|
|
1700
|
+
await this.runtime.notifyOpReplay?.(message);
|
|
1529
1701
|
}
|
|
1530
1702
|
pendingLocalState.savedOps = [];
|
|
1531
|
-
|
|
1532
|
-
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
1533
|
-
assert(
|
|
1534
|
-
this.clientId === undefined,
|
|
1535
|
-
0x5d6 /* Unexpected clientId when setting stashed clientId */,
|
|
1536
|
-
);
|
|
1537
|
-
this._clientId = pendingLocalState?.clientId;
|
|
1538
1703
|
}
|
|
1539
1704
|
|
|
1540
1705
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
@@ -1558,27 +1723,27 @@ export class Container
|
|
|
1558
1723
|
this._deltaManager.inbound.pause();
|
|
1559
1724
|
}
|
|
1560
1725
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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);
|
|
1566
1743
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
this.inboundQueuePausedFromInit,
|
|
1571
|
-
0x346 /* inboundQueuePausedFromInit should be true */,
|
|
1572
|
-
);
|
|
1573
|
-
this.inboundQueuePausedFromInit = false;
|
|
1574
|
-
this._deltaManager.inbound.resume();
|
|
1575
|
-
this._deltaManager.inboundSignal.resume();
|
|
1576
|
-
break;
|
|
1577
|
-
case "none":
|
|
1578
|
-
break;
|
|
1579
|
-
default:
|
|
1580
|
-
unreachableCase(loadMode.deltaConnection);
|
|
1581
|
-
}
|
|
1744
|
+
};
|
|
1745
|
+
this.on("op", opHandler);
|
|
1746
|
+
});
|
|
1582
1747
|
}
|
|
1583
1748
|
|
|
1584
1749
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
@@ -1592,7 +1757,15 @@ export class Container
|
|
|
1592
1757
|
|
|
1593
1758
|
// Internal context is fully loaded at this point
|
|
1594
1759
|
this.setLoaded();
|
|
1595
|
-
|
|
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
|
+
);
|
|
1596
1769
|
return {
|
|
1597
1770
|
sequenceNumber: attributes.sequenceNumber,
|
|
1598
1771
|
version: versionId,
|
|
@@ -1601,7 +1774,7 @@ export class Container
|
|
|
1601
1774
|
};
|
|
1602
1775
|
}
|
|
1603
1776
|
|
|
1604
|
-
private async createDetached(
|
|
1777
|
+
private async createDetached(codeDetails: IFluidCodeDetails) {
|
|
1605
1778
|
const attributes: IDocumentAttributes = {
|
|
1606
1779
|
sequenceNumber: detachedContainerRefSeqNumber,
|
|
1607
1780
|
term: OnlyValidTermValue,
|
|
@@ -1611,7 +1784,7 @@ export class Container
|
|
|
1611
1784
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1612
1785
|
|
|
1613
1786
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1614
|
-
const qValues = initQuorumValuesFromCodeDetails(
|
|
1787
|
+
const qValues = initQuorumValuesFromCodeDetails(codeDetails);
|
|
1615
1788
|
this.initializeProtocolState(
|
|
1616
1789
|
attributes,
|
|
1617
1790
|
{
|
|
@@ -1621,10 +1794,7 @@ export class Container
|
|
|
1621
1794
|
}, // IQuorumSnapShot
|
|
1622
1795
|
);
|
|
1623
1796
|
|
|
1624
|
-
|
|
1625
|
-
await this.instantiateContextDetached(
|
|
1626
|
-
false, // existing
|
|
1627
|
-
);
|
|
1797
|
+
await this.instantiateRuntime(codeDetails, undefined);
|
|
1628
1798
|
|
|
1629
1799
|
this.setLoaded();
|
|
1630
1800
|
}
|
|
@@ -1650,21 +1820,17 @@ export class Container
|
|
|
1650
1820
|
this.storageAdapter,
|
|
1651
1821
|
baseTree.blobs.quorumValues,
|
|
1652
1822
|
);
|
|
1653
|
-
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1654
1823
|
this.initializeProtocolState(
|
|
1655
1824
|
attributes,
|
|
1656
1825
|
{
|
|
1657
1826
|
members: [],
|
|
1658
1827
|
proposals: [],
|
|
1659
|
-
values:
|
|
1660
|
-
codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
1828
|
+
values: qValues,
|
|
1661
1829
|
}, // IQuorumSnapShot
|
|
1662
1830
|
);
|
|
1831
|
+
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1663
1832
|
|
|
1664
|
-
await this.
|
|
1665
|
-
true, // existing
|
|
1666
|
-
snapshotTree,
|
|
1667
|
-
);
|
|
1833
|
+
await this.instantiateRuntime(codeDetails, snapshotTree);
|
|
1668
1834
|
|
|
1669
1835
|
this.setLoaded();
|
|
1670
1836
|
}
|
|
@@ -1733,7 +1899,10 @@ export class Container
|
|
|
1733
1899
|
this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
|
|
1734
1900
|
);
|
|
1735
1901
|
|
|
1736
|
-
const protocolLogger =
|
|
1902
|
+
const protocolLogger = createChildLogger({
|
|
1903
|
+
logger: this.subLogger,
|
|
1904
|
+
namespace: "ProtocolHandler",
|
|
1905
|
+
});
|
|
1737
1906
|
|
|
1738
1907
|
protocol.quorum.on("error", (error) => {
|
|
1739
1908
|
protocolLogger.sendErrorEvent(error);
|
|
@@ -1844,7 +2013,7 @@ export class Container
|
|
|
1844
2013
|
const serviceProvider = () => this.service;
|
|
1845
2014
|
const deltaManager = new DeltaManager<ConnectionManager>(
|
|
1846
2015
|
serviceProvider,
|
|
1847
|
-
|
|
2016
|
+
createChildLogger({ logger: this.subLogger, namespace: "DeltaManager" }),
|
|
1848
2017
|
() => this.activeConnection(),
|
|
1849
2018
|
(props: IConnectionManagerFactoryArgs) =>
|
|
1850
2019
|
new ConnectionManager(
|
|
@@ -1852,7 +2021,7 @@ export class Container
|
|
|
1852
2021
|
() => this.isDirty,
|
|
1853
2022
|
this.client,
|
|
1854
2023
|
this._canReconnect,
|
|
1855
|
-
|
|
2024
|
+
createChildLogger({ logger: this.subLogger, namespace: "ConnectionManager" }),
|
|
1856
2025
|
props,
|
|
1857
2026
|
),
|
|
1858
2027
|
);
|
|
@@ -1868,18 +2037,18 @@ export class Container
|
|
|
1868
2037
|
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1869
2038
|
});
|
|
1870
2039
|
|
|
1871
|
-
deltaManager.on("establishingConnection", (reason:
|
|
2040
|
+
deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
|
|
1872
2041
|
this.connectionStateHandler.establishingConnection(reason);
|
|
1873
2042
|
});
|
|
1874
2043
|
|
|
1875
|
-
deltaManager.on("cancelEstablishingConnection", (reason:
|
|
2044
|
+
deltaManager.on("cancelEstablishingConnection", (reason: IConnectionStateChangeReason) => {
|
|
1876
2045
|
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1877
2046
|
});
|
|
1878
2047
|
|
|
1879
|
-
deltaManager.on("disconnect", (reason:
|
|
1880
|
-
this.
|
|
2048
|
+
deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
|
|
2049
|
+
this.noopHeuristic?.notifyDisconnect();
|
|
1881
2050
|
if (!this.closed) {
|
|
1882
|
-
this.connectionStateHandler.receivedDisconnectEvent(reason
|
|
2051
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1883
2052
|
}
|
|
1884
2053
|
});
|
|
1885
2054
|
|
|
@@ -1914,7 +2083,7 @@ export class Container
|
|
|
1914
2083
|
|
|
1915
2084
|
private async attachDeltaManagerOpHandler(
|
|
1916
2085
|
attributes: IDocumentAttributes,
|
|
1917
|
-
prefetchType?: "cached" | "all" | "none",
|
|
2086
|
+
prefetchType?: "sequenceNumber" | "cached" | "all" | "none",
|
|
1918
2087
|
) {
|
|
1919
2088
|
return this._deltaManager.attachOpHandler(
|
|
1920
2089
|
attributes.minimumSequenceNumber,
|
|
@@ -1932,8 +2101,7 @@ export class Container
|
|
|
1932
2101
|
private logConnectionStateChangeTelemetry(
|
|
1933
2102
|
value: ConnectionState,
|
|
1934
2103
|
oldState: ConnectionState,
|
|
1935
|
-
reason?:
|
|
1936
|
-
error?: IAnyDriverError,
|
|
2104
|
+
reason?: IConnectionStateChangeReason,
|
|
1937
2105
|
) {
|
|
1938
2106
|
// Log actual event
|
|
1939
2107
|
const time = performance.now();
|
|
@@ -1951,11 +2119,15 @@ export class Container
|
|
|
1951
2119
|
if (value === ConnectionState.Connected) {
|
|
1952
2120
|
durationFromDisconnected =
|
|
1953
2121
|
time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
1954
|
-
durationFromDisconnected =
|
|
2122
|
+
durationFromDisconnected = formatTick(durationFromDisconnected);
|
|
1955
2123
|
} else if (value === ConnectionState.CatchingUp) {
|
|
1956
2124
|
// This info is of most interesting while Catching Up.
|
|
1957
2125
|
checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
|
|
1958
|
-
|
|
2126
|
+
// Need to check that we have already loaded and fetched the snapshot.
|
|
2127
|
+
if (
|
|
2128
|
+
this.deltaManager.hasCheckpointSequenceNumber &&
|
|
2129
|
+
this._lifecycleState === "loaded"
|
|
2130
|
+
) {
|
|
1959
2131
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1960
2132
|
}
|
|
1961
2133
|
}
|
|
@@ -1968,7 +2140,7 @@ export class Container
|
|
|
1968
2140
|
from: ConnectionState[oldState],
|
|
1969
2141
|
duration,
|
|
1970
2142
|
durationFromDisconnected,
|
|
1971
|
-
reason,
|
|
2143
|
+
reason: reason?.text,
|
|
1972
2144
|
connectionInitiationReason,
|
|
1973
2145
|
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
1974
2146
|
clientId: this.clientId,
|
|
@@ -1981,9 +2153,10 @@ export class Container
|
|
|
1981
2153
|
: undefined,
|
|
1982
2154
|
checkpointSequenceNumber,
|
|
1983
2155
|
quorumSize: this._protocolHandler?.quorum.getMembers().size,
|
|
2156
|
+
isDirty: this.isDirty,
|
|
1984
2157
|
...this._deltaManager.connectionProps,
|
|
1985
2158
|
},
|
|
1986
|
-
error,
|
|
2159
|
+
reason?.error,
|
|
1987
2160
|
);
|
|
1988
2161
|
|
|
1989
2162
|
if (value === ConnectionState.Connected) {
|
|
@@ -1991,7 +2164,10 @@ export class Container
|
|
|
1991
2164
|
}
|
|
1992
2165
|
}
|
|
1993
2166
|
|
|
1994
|
-
private propagateConnectionState(
|
|
2167
|
+
private propagateConnectionState(
|
|
2168
|
+
initialTransition: boolean,
|
|
2169
|
+
disconnectedReason?: IConnectionStateChangeReason,
|
|
2170
|
+
) {
|
|
1995
2171
|
// When container loaded, we want to propagate initial connection state.
|
|
1996
2172
|
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1997
2173
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
@@ -2004,26 +2180,11 @@ export class Container
|
|
|
2004
2180
|
}
|
|
2005
2181
|
const state = this.connectionState === ConnectionState.Connected;
|
|
2006
2182
|
|
|
2007
|
-
const logOpsOnReconnect: boolean =
|
|
2008
|
-
this.connectionState === ConnectionState.Connected &&
|
|
2009
|
-
!this.firstConnection &&
|
|
2010
|
-
this.connectionMode === "write";
|
|
2011
|
-
if (logOpsOnReconnect) {
|
|
2012
|
-
this.messageCountAfterDisconnection = 0;
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
2183
|
// Both protocol and context should not be undefined if we got so far.
|
|
2016
2184
|
|
|
2017
2185
|
this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
|
|
2018
2186
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
2019
|
-
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
2020
|
-
|
|
2021
|
-
if (logOpsOnReconnect) {
|
|
2022
|
-
this.mc.logger.sendTelemetryEvent({
|
|
2023
|
-
eventName: "OpsSentOnReconnect",
|
|
2024
|
-
count: this.messageCountAfterDisconnection,
|
|
2025
|
-
});
|
|
2026
|
-
}
|
|
2187
|
+
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
|
|
2027
2188
|
}
|
|
2028
2189
|
|
|
2029
2190
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
@@ -2099,8 +2260,7 @@ export class Container
|
|
|
2099
2260
|
return -1;
|
|
2100
2261
|
}
|
|
2101
2262
|
|
|
2102
|
-
this.
|
|
2103
|
-
this.collabWindowTracker?.stopSequenceNumberUpdate();
|
|
2263
|
+
this.noopHeuristic?.notifyMessageSent();
|
|
2104
2264
|
return this._deltaManager.submit(
|
|
2105
2265
|
type,
|
|
2106
2266
|
contents,
|
|
@@ -2121,35 +2281,41 @@ export class Container
|
|
|
2121
2281
|
const result = this.protocolHandler.processMessage(message, local);
|
|
2122
2282
|
|
|
2123
2283
|
// Forward messages to the loaded runtime for processing
|
|
2124
|
-
this.
|
|
2284
|
+
this.runtime.process(message, local);
|
|
2125
2285
|
|
|
2126
2286
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
2127
2287
|
if (this.activeConnection()) {
|
|
2128
|
-
if (this.
|
|
2288
|
+
if (this.noopHeuristic === undefined) {
|
|
2289
|
+
const serviceConfiguration = this.deltaManager.serviceConfiguration;
|
|
2129
2290
|
// Note that config from first connection will be used for this container's lifetime.
|
|
2130
2291
|
// That means that if relay service changes settings, such changes will impact only newly booted
|
|
2131
2292
|
// clients.
|
|
2132
2293
|
// All existing will continue to use settings they got earlier.
|
|
2133
2294
|
assert(
|
|
2134
|
-
|
|
2295
|
+
serviceConfiguration !== undefined,
|
|
2135
2296
|
0x2e4 /* "there should be service config for active connection" */,
|
|
2136
2297
|
);
|
|
2137
|
-
this.
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
this.activeConnection(),
|
|
2141
|
-
0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */,
|
|
2142
|
-
);
|
|
2143
|
-
this.submitMessage(type);
|
|
2144
|
-
},
|
|
2145
|
-
this.serviceConfiguration.noopTimeFrequency,
|
|
2146
|
-
this.serviceConfiguration.noopCountFrequency,
|
|
2298
|
+
this.noopHeuristic = new NoopHeuristic(
|
|
2299
|
+
serviceConfiguration.noopTimeFrequency,
|
|
2300
|
+
serviceConfiguration.noopCountFrequency,
|
|
2147
2301
|
);
|
|
2302
|
+
this.noopHeuristic.on("wantsNoop", () => {
|
|
2303
|
+
// On disconnect we notify the heuristic which should prevent it from wanting a noop.
|
|
2304
|
+
// Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
|
|
2305
|
+
// running the microtask that the heuristic queued in response.
|
|
2306
|
+
assert(
|
|
2307
|
+
this.activeConnection(),
|
|
2308
|
+
0x241 /* "Trying to send noop without active connection" */,
|
|
2309
|
+
);
|
|
2310
|
+
this.submitMessage(MessageType.NoOp);
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
this.noopHeuristic.notifyMessageProcessed(message);
|
|
2314
|
+
// The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
|
|
2315
|
+
if (result.immediateNoOp === true) {
|
|
2316
|
+
// ADO:1385: Remove cast and use MessageType once definition changes propagate
|
|
2317
|
+
this.submitMessage(MessageType2.Accept as unknown as MessageType);
|
|
2148
2318
|
}
|
|
2149
|
-
this.collabWindowTracker.scheduleSequenceNumberUpdate(
|
|
2150
|
-
message,
|
|
2151
|
-
result.immediateNoOp === true,
|
|
2152
|
-
);
|
|
2153
2319
|
}
|
|
2154
2320
|
|
|
2155
2321
|
this.emit("op", message);
|
|
@@ -2161,11 +2327,11 @@ export class Container
|
|
|
2161
2327
|
|
|
2162
2328
|
private processSignal(message: ISignalMessage) {
|
|
2163
2329
|
// No clientId indicates a system signal message.
|
|
2164
|
-
if (message
|
|
2330
|
+
if (protocolHandlerShouldProcessSignal(message)) {
|
|
2165
2331
|
this.protocolHandler.processSignal(message);
|
|
2166
2332
|
} else {
|
|
2167
2333
|
const local = this.clientId === message.clientId;
|
|
2168
|
-
this.
|
|
2334
|
+
this.runtime.processSignal(message, local);
|
|
2169
2335
|
}
|
|
2170
2336
|
}
|
|
2171
2337
|
|
|
@@ -2195,35 +2361,54 @@ export class Container
|
|
|
2195
2361
|
return { snapshot, versionId: version?.id };
|
|
2196
2362
|
}
|
|
2197
2363
|
|
|
2198
|
-
private async
|
|
2199
|
-
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
2200
|
-
if (codeDetails === undefined) {
|
|
2201
|
-
throw new Error("pkg should be provided in create flow!!");
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
await this.instantiateContext(existing, codeDetails, snapshot);
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
private async instantiateContext(
|
|
2208
|
-
existing: boolean,
|
|
2364
|
+
private async instantiateRuntime(
|
|
2209
2365
|
codeDetails: IFluidCodeDetails,
|
|
2210
|
-
snapshot
|
|
2366
|
+
snapshot: ISnapshotTree | undefined,
|
|
2211
2367
|
pendingLocalState?: unknown,
|
|
2212
2368
|
) {
|
|
2213
|
-
assert(this.
|
|
2369
|
+
assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
2214
2370
|
|
|
2215
2371
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
2216
2372
|
// are set. Global requests will still go directly to the loader
|
|
2217
2373
|
const maybeLoader: FluidObject<IHostLoader> = this.scope;
|
|
2218
2374
|
const loader = new RelativeLoader(this, maybeLoader.ILoader);
|
|
2219
|
-
|
|
2220
|
-
|
|
2375
|
+
|
|
2376
|
+
const loadCodeResult = await PerformanceEvent.timedExecAsync(
|
|
2377
|
+
this.subLogger,
|
|
2378
|
+
{ eventName: "CodeLoad" },
|
|
2379
|
+
async () => this.codeLoader.load(codeDetails),
|
|
2380
|
+
);
|
|
2381
|
+
|
|
2382
|
+
this._loadedModule = {
|
|
2383
|
+
module: loadCodeResult.module,
|
|
2384
|
+
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
2385
|
+
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
2386
|
+
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
2387
|
+
details: loadCodeResult.details ?? codeDetails,
|
|
2388
|
+
};
|
|
2389
|
+
|
|
2390
|
+
const fluidExport: FluidObject<IProvideRuntimeFactory> | undefined =
|
|
2391
|
+
this._loadedModule.module.fluidExport;
|
|
2392
|
+
const runtimeFactory = fluidExport?.IRuntimeFactory;
|
|
2393
|
+
if (runtimeFactory === undefined) {
|
|
2394
|
+
throw new Error(packageNotFactoryError);
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
const getSpecifiedCodeDetails = () =>
|
|
2398
|
+
(this.protocolHandler.quorum.get("code") ??
|
|
2399
|
+
this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
2400
|
+
|
|
2401
|
+
const existing = snapshot !== undefined;
|
|
2402
|
+
|
|
2403
|
+
const context = new ContainerContext(
|
|
2404
|
+
this.options,
|
|
2221
2405
|
this.scope,
|
|
2222
|
-
this.codeLoader,
|
|
2223
|
-
codeDetails,
|
|
2224
2406
|
snapshot,
|
|
2225
|
-
|
|
2226
|
-
|
|
2407
|
+
this._loadedFromVersion,
|
|
2408
|
+
this._deltaManager,
|
|
2409
|
+
this.storageAdapter,
|
|
2410
|
+
this.protocolHandler.quorum,
|
|
2411
|
+
this.protocolHandler.audience,
|
|
2227
2412
|
loader,
|
|
2228
2413
|
(type, contents, batch, metadata) =>
|
|
2229
2414
|
this.submitContainerMessage(type, contents, batch, metadata),
|
|
@@ -2234,22 +2419,36 @@ export class Container
|
|
|
2234
2419
|
(message) => this.submitSignal(message),
|
|
2235
2420
|
(error?: ICriticalContainerError) => this.dispose(error),
|
|
2236
2421
|
(error?: ICriticalContainerError) => this.close(error),
|
|
2237
|
-
|
|
2238
|
-
|
|
2422
|
+
this.updateDirtyContainerState,
|
|
2423
|
+
this.getAbsoluteUrl,
|
|
2424
|
+
() => this.resolvedUrl?.id,
|
|
2425
|
+
() => this.clientId,
|
|
2426
|
+
() => this.attachState,
|
|
2427
|
+
() => this.connected,
|
|
2428
|
+
getSpecifiedCodeDetails,
|
|
2429
|
+
this._deltaManager.clientDetails,
|
|
2239
2430
|
existing,
|
|
2431
|
+
this.subLogger,
|
|
2240
2432
|
pendingLocalState,
|
|
2241
2433
|
);
|
|
2242
2434
|
|
|
2243
|
-
this.
|
|
2435
|
+
this._runtime = await PerformanceEvent.timedExecAsync(
|
|
2436
|
+
this.subLogger,
|
|
2437
|
+
{ eventName: "InstantiateRuntime" },
|
|
2438
|
+
async () => runtimeFactory.instantiateRuntime(context, existing),
|
|
2439
|
+
);
|
|
2440
|
+
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
2441
|
+
|
|
2442
|
+
this._loadedCodeDetails = codeDetails;
|
|
2244
2443
|
}
|
|
2245
2444
|
|
|
2246
|
-
private updateDirtyContainerState(dirty: boolean) {
|
|
2445
|
+
private readonly updateDirtyContainerState = (dirty: boolean) => {
|
|
2247
2446
|
if (this._dirtyContainer === dirty) {
|
|
2248
2447
|
return;
|
|
2249
2448
|
}
|
|
2250
2449
|
this._dirtyContainer = dirty;
|
|
2251
2450
|
this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
|
|
2252
|
-
}
|
|
2451
|
+
};
|
|
2253
2452
|
|
|
2254
2453
|
/**
|
|
2255
2454
|
* Set the connected state of the ContainerContext
|
|
@@ -2258,21 +2457,49 @@ export class Container
|
|
|
2258
2457
|
* @param readonly - Is the container in readonly mode?
|
|
2259
2458
|
*/
|
|
2260
2459
|
private setContextConnectedState(state: boolean, readonly: boolean): void {
|
|
2261
|
-
if (this.
|
|
2460
|
+
if (this._runtime?.disposed === false) {
|
|
2262
2461
|
/**
|
|
2263
2462
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
2264
2463
|
* ops getting through to the DeltaManager.
|
|
2265
2464
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
2266
2465
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
2267
2466
|
*/
|
|
2268
|
-
this.
|
|
2467
|
+
this.runtime.setConnectionState(state && !readonly, this.clientId);
|
|
2468
|
+
}
|
|
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);
|
|
2269
2496
|
}
|
|
2270
2497
|
}
|
|
2271
2498
|
}
|
|
2272
2499
|
|
|
2273
2500
|
/**
|
|
2274
2501
|
* IContainer interface that includes experimental features still under development.
|
|
2275
|
-
* @
|
|
2502
|
+
* @experimental
|
|
2276
2503
|
*/
|
|
2277
2504
|
export interface IContainerExperimental extends IContainer {
|
|
2278
2505
|
/**
|
|
@@ -2283,5 +2510,13 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2283
2510
|
* @experimental misuse of this API can result in duplicate op submission and potential document corruption
|
|
2284
2511
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2285
2512
|
*/
|
|
2286
|
-
getPendingLocalState(): string
|
|
2513
|
+
getPendingLocalState?(): Promise<string>;
|
|
2514
|
+
|
|
2515
|
+
/**
|
|
2516
|
+
* Closes the container and returns serialized local state intended to be
|
|
2517
|
+
* given to a newly loaded container.
|
|
2518
|
+
* @experimental
|
|
2519
|
+
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2520
|
+
*/
|
|
2521
|
+
closeAndGetPendingLocalState?(): Promise<string>;
|
|
2287
2522
|
}
|