@fluidframework/container-loader 2.0.0-dev.4.4.0.162574 → 2.0.0-dev.5.3.2.178189
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 +59 -0
- package/README.md +27 -3
- package/dist/catchUpMonitor.d.ts +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts +3 -2
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +32 -13
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +18 -3
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +34 -9
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +99 -70
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +260 -218
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +24 -67
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +28 -217
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +3 -3
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +29 -6
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +9 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +22 -9
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +42 -31
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.d.ts +2 -3
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +2 -3
- 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/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +9 -8
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +47 -61
- package/dist/loader.js.map +1 -1
- package/dist/noopHeuristic.d.ts +23 -0
- package/dist/noopHeuristic.d.ts.map +1 -0
- package/dist/{collabWindowTracker.js → noopHeuristic.js} +30 -42
- 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 +7 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +17 -19
- 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 -17
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +1 -17
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +3 -2
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +8 -1
- package/dist/utils.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts +3 -2
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +33 -14
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +18 -3
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +35 -10
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +99 -70
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +264 -222
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +24 -67
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +28 -217
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +3 -3
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +29 -6
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +9 -3
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +22 -9
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +44 -33
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.d.ts +2 -3
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +2 -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/index.d.ts +1 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +9 -8
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +47 -61
- 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} +30 -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 +7 -12
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +15 -18
- 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 -17
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +1 -16
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +3 -2
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts +2 -0
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +7 -1
- package/lib/utils.js.map +1 -1
- package/package.json +22 -20
- package/src/catchUpMonitor.ts +1 -1
- package/src/connectionManager.ts +40 -22
- package/src/connectionStateHandler.ts +66 -17
- package/src/container.ts +464 -292
- package/src/containerContext.ts +33 -341
- package/src/containerStorageAdapter.ts +40 -10
- package/src/contracts.ts +11 -3
- package/src/deltaManager.ts +74 -45
- package/src/deltaQueue.ts +2 -3
- package/src/disposal.ts +25 -0
- package/src/index.ts +1 -8
- package/src/loader.ts +85 -83
- package/src/{collabWindowTracker.ts → noopHeuristic.ts} +37 -47
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +18 -39
- package/src/protocolTreeDocumentStorageService.ts +1 -1
- package/src/quorum.ts +2 -31
- package/src/retriableDocumentStorageService.ts +4 -2
- package/src/utils.ts +15 -1
- package/dist/collabWindowTracker.d.ts +0 -19
- package/dist/collabWindowTracker.d.ts.map +0 -1
- 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,13 +7,21 @@
|
|
|
7
7
|
import merge from "lodash/merge";
|
|
8
8
|
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
|
+
import { IEvent } from "@fluidframework/common-definitions";
|
|
11
|
+
import {
|
|
12
|
+
TypedEventEmitter,
|
|
13
|
+
assert,
|
|
14
|
+
performance,
|
|
15
|
+
unreachableCase,
|
|
16
|
+
} from "@fluidframework/common-utils";
|
|
10
17
|
import {
|
|
11
|
-
ITelemetryLogger,
|
|
12
18
|
ITelemetryProperties,
|
|
13
19
|
TelemetryEventCategory,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
IRequest,
|
|
21
|
+
IResponse,
|
|
22
|
+
IFluidRouter,
|
|
23
|
+
FluidObject,
|
|
24
|
+
} from "@fluidframework/core-interfaces";
|
|
17
25
|
import {
|
|
18
26
|
IAudience,
|
|
19
27
|
IConnectionDetailsInternal,
|
|
@@ -29,29 +37,36 @@ import {
|
|
|
29
37
|
IFluidCodeDetails,
|
|
30
38
|
isFluidCodeDetails,
|
|
31
39
|
IBatchMessage,
|
|
40
|
+
ICodeDetailsLoader,
|
|
41
|
+
IHostLoader,
|
|
42
|
+
IFluidModuleWithDetails,
|
|
43
|
+
IProvideRuntimeFactory,
|
|
44
|
+
IProvideFluidCodeDetailsComparer,
|
|
45
|
+
IFluidCodeDetailsComparer,
|
|
46
|
+
IRuntime,
|
|
32
47
|
} from "@fluidframework/container-definitions";
|
|
33
48
|
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
34
49
|
import {
|
|
35
50
|
IAnyDriverError,
|
|
36
51
|
IDocumentService,
|
|
52
|
+
IDocumentServiceFactory,
|
|
37
53
|
IDocumentStorageService,
|
|
38
|
-
IFluidResolvedUrl,
|
|
39
54
|
IResolvedUrl,
|
|
55
|
+
IUrlResolver,
|
|
40
56
|
} from "@fluidframework/driver-definitions";
|
|
41
57
|
import {
|
|
42
58
|
readAndParse,
|
|
43
59
|
OnlineStatus,
|
|
44
60
|
isOnline,
|
|
45
|
-
ensureFluidResolvedUrl,
|
|
46
61
|
combineAppAndProtocolSummary,
|
|
47
62
|
runWithRetry,
|
|
48
|
-
isFluidResolvedUrl,
|
|
49
63
|
isCombinedAppAndProtocolSummary,
|
|
64
|
+
MessageType2,
|
|
65
|
+
canBeCoalescedByService,
|
|
50
66
|
} from "@fluidframework/driver-utils";
|
|
51
67
|
import { IQuorumSnapshot } from "@fluidframework/protocol-base";
|
|
52
68
|
import {
|
|
53
69
|
IClient,
|
|
54
|
-
IClientConfiguration,
|
|
55
70
|
IClientDetails,
|
|
56
71
|
ICommittedProposal,
|
|
57
72
|
IDocumentAttributes,
|
|
@@ -80,13 +95,13 @@ import {
|
|
|
80
95
|
MonitoringContext,
|
|
81
96
|
loggerToMonitoringContext,
|
|
82
97
|
wrapError,
|
|
98
|
+
ITelemetryLoggerExt,
|
|
83
99
|
} from "@fluidframework/telemetry-utils";
|
|
84
100
|
import { Audience } from "./audience";
|
|
85
101
|
import { ContainerContext } from "./containerContext";
|
|
86
102
|
import { ReconnectMode, IConnectionManagerFactoryArgs, getPackageName } from "./contracts";
|
|
87
103
|
import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
88
|
-
import {
|
|
89
|
-
import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
|
|
104
|
+
import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
|
|
90
105
|
import { pkgVersion } from "./packageVersion";
|
|
91
106
|
import {
|
|
92
107
|
ContainerStorageAdapter,
|
|
@@ -96,19 +111,16 @@ import {
|
|
|
96
111
|
} from "./containerStorageAdapter";
|
|
97
112
|
import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
|
|
98
113
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
99
|
-
import {
|
|
100
|
-
|
|
101
|
-
getCodeDetailsFromQuorumValues,
|
|
102
|
-
QuorumProxy,
|
|
103
|
-
} from "./quorum";
|
|
104
|
-
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
114
|
+
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
|
|
115
|
+
import { NoopHeuristic } from "./noopHeuristic";
|
|
105
116
|
import { ConnectionManager } from "./connectionManager";
|
|
106
117
|
import { ConnectionState } from "./connectionState";
|
|
107
118
|
import {
|
|
108
|
-
OnlyValidTermValue,
|
|
109
119
|
IProtocolHandler,
|
|
120
|
+
OnlyValidTermValue,
|
|
110
121
|
ProtocolHandler,
|
|
111
122
|
ProtocolHandlerBuilder,
|
|
123
|
+
protocolHandlerShouldProcessSignal,
|
|
112
124
|
} from "./protocol";
|
|
113
125
|
|
|
114
126
|
const detachedContainerRefSeqNumber = 0;
|
|
@@ -116,45 +128,85 @@ const detachedContainerRefSeqNumber = 0;
|
|
|
116
128
|
const dirtyContainerEvent = "dirty";
|
|
117
129
|
const savedContainerEvent = "saved";
|
|
118
130
|
|
|
131
|
+
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
132
|
+
|
|
119
133
|
/**
|
|
120
|
-
* @deprecated this is an internal interface and will not longer be exported in future versions
|
|
121
134
|
* @internal
|
|
122
135
|
*/
|
|
123
|
-
export interface
|
|
124
|
-
/**
|
|
125
|
-
* Disables the Container from reconnecting if false, allows reconnect otherwise.
|
|
126
|
-
*/
|
|
127
|
-
canReconnect?: boolean;
|
|
136
|
+
export interface IContainerLoadProps {
|
|
128
137
|
/**
|
|
129
|
-
*
|
|
138
|
+
* The resolved url of the container being loaded
|
|
130
139
|
*/
|
|
131
|
-
|
|
132
|
-
resolvedUrl: IFluidResolvedUrl;
|
|
140
|
+
readonly resolvedUrl: IResolvedUrl;
|
|
133
141
|
/**
|
|
134
142
|
* Control which snapshot version to load from. See IParsedUrl for detailed information.
|
|
135
143
|
*/
|
|
136
|
-
version: string | undefined;
|
|
144
|
+
readonly version: string | undefined;
|
|
137
145
|
/**
|
|
138
146
|
* Loads the Container in paused state if true, unpaused otherwise.
|
|
139
147
|
*/
|
|
140
|
-
loadMode?: IContainerLoadMode;
|
|
148
|
+
readonly loadMode?: IContainerLoadMode;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The pending state serialized from a pervious container instance
|
|
152
|
+
*/
|
|
153
|
+
readonly pendingLocalState?: IPendingContainerState;
|
|
141
154
|
}
|
|
142
155
|
|
|
143
156
|
/**
|
|
144
|
-
* @deprecated this is an internal interface and will not longer be exported in future versions
|
|
145
157
|
* @internal
|
|
146
158
|
*/
|
|
147
|
-
export interface
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
export interface IContainerCreateProps {
|
|
160
|
+
/**
|
|
161
|
+
* Disables the Container from reconnecting if false, allows reconnect otherwise.
|
|
162
|
+
*/
|
|
163
|
+
readonly canReconnect?: boolean;
|
|
150
164
|
/**
|
|
151
165
|
* Client details provided in the override will be merged over the default client.
|
|
152
166
|
*/
|
|
153
|
-
clientDetailsOverride?: IClientDetails;
|
|
167
|
+
readonly clientDetailsOverride?: IClientDetails;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The url resolver used by the loader for resolving external urls
|
|
171
|
+
* into Fluid urls such that the container specified by the
|
|
172
|
+
* external url can be loaded.
|
|
173
|
+
*/
|
|
174
|
+
readonly urlResolver: IUrlResolver;
|
|
175
|
+
/**
|
|
176
|
+
* The document service factory take the Fluid url provided
|
|
177
|
+
* by the resolved url and constructs all the necessary services
|
|
178
|
+
* for communication with the container's server.
|
|
179
|
+
*/
|
|
180
|
+
readonly documentServiceFactory: IDocumentServiceFactory;
|
|
181
|
+
/**
|
|
182
|
+
* The code loader handles loading the necessary code
|
|
183
|
+
* for running a container once it is loaded.
|
|
184
|
+
*/
|
|
185
|
+
readonly codeLoader: ICodeDetailsLoader;
|
|
186
|
+
|
|
154
187
|
/**
|
|
155
|
-
*
|
|
188
|
+
* A property bag of options used by various layers
|
|
189
|
+
* to control features
|
|
156
190
|
*/
|
|
157
|
-
|
|
191
|
+
readonly options: ILoaderOptions;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Scope is provided to all container and is a set of shared
|
|
195
|
+
* services for container's to integrate with their host environment.
|
|
196
|
+
*/
|
|
197
|
+
readonly scope: FluidObject;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The logger downstream consumers should construct their loggers from
|
|
201
|
+
*/
|
|
202
|
+
readonly subLogger: ITelemetryLoggerExt;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Blobs storage for detached containers.
|
|
206
|
+
*/
|
|
207
|
+
readonly detachedBlobStorage?: IDetachedBlobStorage;
|
|
208
|
+
|
|
209
|
+
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
|
|
158
210
|
}
|
|
159
211
|
|
|
160
212
|
/**
|
|
@@ -260,7 +312,7 @@ const getCodeProposal =
|
|
|
260
312
|
* @param action - functor to call and measure
|
|
261
313
|
*/
|
|
262
314
|
export async function ReportIfTooLong(
|
|
263
|
-
logger:
|
|
315
|
+
logger: ITelemetryLoggerExt,
|
|
264
316
|
eventName: string,
|
|
265
317
|
action: () => Promise<ITelemetryProperties>,
|
|
266
318
|
) {
|
|
@@ -274,7 +326,6 @@ export async function ReportIfTooLong(
|
|
|
274
326
|
/**
|
|
275
327
|
* State saved by a container at close time, to be used to load a new instance
|
|
276
328
|
* of the container to the same state
|
|
277
|
-
* @deprecated this is an internal interface and will not longer be exported in future versions
|
|
278
329
|
* @internal
|
|
279
330
|
*/
|
|
280
331
|
export interface IPendingContainerState {
|
|
@@ -301,34 +352,29 @@ export interface IPendingContainerState {
|
|
|
301
352
|
|
|
302
353
|
const summarizerClientType = "summarizer";
|
|
303
354
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
355
|
+
interface IContainerLifecycleEvents extends IEvent {
|
|
356
|
+
(event: "runtimeInstantiated", listener: () => void): void;
|
|
357
|
+
(event: "disposed", listener: () => void): void;
|
|
358
|
+
}
|
|
359
|
+
|
|
307
360
|
export class Container
|
|
308
361
|
extends EventEmitterWithErrorHandling<IContainerEvents>
|
|
309
362
|
implements IContainer, IContainerExperimental
|
|
310
363
|
{
|
|
311
|
-
public static version = "^0.1.0";
|
|
312
|
-
|
|
313
364
|
/**
|
|
314
365
|
* Load an existing container.
|
|
315
366
|
* @internal
|
|
316
367
|
*/
|
|
317
368
|
public static async load(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
pendingLocalState?: IPendingContainerState,
|
|
321
|
-
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
369
|
+
loadProps: IContainerLoadProps,
|
|
370
|
+
createProps: IContainerCreateProps,
|
|
322
371
|
): Promise<Container> {
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
serializedContainerState: pendingLocalState,
|
|
330
|
-
},
|
|
331
|
-
protocolHandlerBuilder,
|
|
372
|
+
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
373
|
+
|
|
374
|
+
const container = new Container(createProps, loadProps);
|
|
375
|
+
|
|
376
|
+
const disableRecordHeapSize = container.mc.config.getBoolean(
|
|
377
|
+
"Fluid.Loader.DisableRecordHeapSize",
|
|
332
378
|
);
|
|
333
379
|
|
|
334
380
|
return PerformanceEvent.timedExecAsync(
|
|
@@ -336,14 +382,12 @@ export class Container
|
|
|
336
382
|
{ eventName: "Load" },
|
|
337
383
|
async (event) =>
|
|
338
384
|
new Promise<Container>((resolve, reject) => {
|
|
339
|
-
const version = loadOptions.version;
|
|
340
|
-
|
|
341
385
|
const defaultMode: IContainerLoadMode = { opsBeforeReturn: "cached" };
|
|
342
386
|
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
343
387
|
// to return container, so ignore this value and use undefined for opsBeforeReturn
|
|
344
388
|
const mode: IContainerLoadMode = pendingLocalState
|
|
345
|
-
? { ...(
|
|
346
|
-
:
|
|
389
|
+
? { ...(loadMode ?? defaultMode), opsBeforeReturn: undefined }
|
|
390
|
+
: loadMode ?? defaultMode;
|
|
347
391
|
|
|
348
392
|
const onClosed = (err?: ICriticalContainerError) => {
|
|
349
393
|
// pre-0.58 error message: containerClosedWithoutErrorDuringLoad
|
|
@@ -354,13 +398,13 @@ export class Container
|
|
|
354
398
|
container.on("closed", onClosed);
|
|
355
399
|
|
|
356
400
|
container
|
|
357
|
-
.load(version, mode, pendingLocalState)
|
|
401
|
+
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
358
402
|
.finally(() => {
|
|
359
403
|
container.removeListener("closed", onClosed);
|
|
360
404
|
})
|
|
361
405
|
.then(
|
|
362
406
|
(props) => {
|
|
363
|
-
event.end({ ...props, ...
|
|
407
|
+
event.end({ ...props, ...loadMode });
|
|
364
408
|
resolve(container);
|
|
365
409
|
},
|
|
366
410
|
(error) => {
|
|
@@ -374,6 +418,7 @@ export class Container
|
|
|
374
418
|
);
|
|
375
419
|
}),
|
|
376
420
|
{ start: true, end: true, cancel: "generic" },
|
|
421
|
+
disableRecordHeapSize !== true /* recordHeapSize */,
|
|
377
422
|
);
|
|
378
423
|
}
|
|
379
424
|
|
|
@@ -381,11 +426,10 @@ export class Container
|
|
|
381
426
|
* Create a new container in a detached state.
|
|
382
427
|
*/
|
|
383
428
|
public static async createDetached(
|
|
384
|
-
|
|
429
|
+
createProps: IContainerCreateProps,
|
|
385
430
|
codeDetails: IFluidCodeDetails,
|
|
386
|
-
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
387
431
|
): Promise<Container> {
|
|
388
|
-
const container = new Container(
|
|
432
|
+
const container = new Container(createProps);
|
|
389
433
|
|
|
390
434
|
return PerformanceEvent.timedExecAsync(
|
|
391
435
|
container.mc.logger,
|
|
@@ -403,11 +447,10 @@ export class Container
|
|
|
403
447
|
* snapshot from a previous detached container.
|
|
404
448
|
*/
|
|
405
449
|
public static async rehydrateDetachedFromSnapshot(
|
|
406
|
-
|
|
450
|
+
createProps: IContainerCreateProps,
|
|
407
451
|
snapshot: string,
|
|
408
|
-
protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
409
452
|
): Promise<Container> {
|
|
410
|
-
const container = new Container(
|
|
453
|
+
const container = new Container(createProps);
|
|
411
454
|
|
|
412
455
|
return PerformanceEvent.timedExecAsync(
|
|
413
456
|
container.mc.logger,
|
|
@@ -421,14 +464,30 @@ export class Container
|
|
|
421
464
|
);
|
|
422
465
|
}
|
|
423
466
|
|
|
424
|
-
public subLogger: TelemetryLogger;
|
|
425
|
-
|
|
426
467
|
// Tells if container can reconnect on losing fist connection
|
|
427
468
|
// If false, container gets closed on loss of connection.
|
|
428
|
-
private readonly _canReconnect: boolean
|
|
469
|
+
private readonly _canReconnect: boolean;
|
|
470
|
+
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
471
|
+
private readonly urlResolver: IUrlResolver;
|
|
472
|
+
private readonly serviceFactory: IDocumentServiceFactory;
|
|
473
|
+
private readonly codeLoader: ICodeDetailsLoader;
|
|
474
|
+
private readonly options: ILoaderOptions;
|
|
475
|
+
private readonly scope: FluidObject;
|
|
476
|
+
private readonly subLogger: TelemetryLogger;
|
|
477
|
+
private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
|
|
478
|
+
private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
|
|
429
479
|
|
|
430
480
|
private readonly mc: MonitoringContext;
|
|
431
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Used by the RelativeLoader to spawn a new Container for the same document. Used to create the summarizing client.
|
|
484
|
+
* @internal
|
|
485
|
+
*/
|
|
486
|
+
public readonly clone: (
|
|
487
|
+
loadProps: IContainerLoadProps,
|
|
488
|
+
createParamOverrides: Partial<IContainerCreateProps>,
|
|
489
|
+
) => Promise<Container>;
|
|
490
|
+
|
|
432
491
|
/**
|
|
433
492
|
* Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
|
|
434
493
|
*
|
|
@@ -474,20 +533,16 @@ export class Container
|
|
|
474
533
|
private _attachState = AttachState.Detached;
|
|
475
534
|
|
|
476
535
|
private readonly storageAdapter: ContainerStorageAdapter;
|
|
477
|
-
public get storage(): IDocumentStorageService {
|
|
478
|
-
return this.storageAdapter;
|
|
479
|
-
}
|
|
480
536
|
|
|
481
|
-
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
482
537
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
483
538
|
private service: IDocumentService | undefined;
|
|
484
539
|
|
|
485
|
-
private
|
|
486
|
-
private get
|
|
487
|
-
if (this.
|
|
488
|
-
throw new
|
|
540
|
+
private _runtime: IRuntime | undefined;
|
|
541
|
+
private get runtime() {
|
|
542
|
+
if (this._runtime === undefined) {
|
|
543
|
+
throw new Error("Attempted to access runtime before it was defined");
|
|
489
544
|
}
|
|
490
|
-
return this.
|
|
545
|
+
return this._runtime;
|
|
491
546
|
}
|
|
492
547
|
private _protocolHandler: IProtocolHandler | undefined;
|
|
493
548
|
private get protocolHandler() {
|
|
@@ -503,7 +558,6 @@ export class Container
|
|
|
503
558
|
private readonly connectionTransitionTimes: number[] = [];
|
|
504
559
|
private messageCountAfterDisconnection: number = 0;
|
|
505
560
|
private _loadedFromVersion: IVersion | undefined;
|
|
506
|
-
private _resolvedUrl: IFluidResolvedUrl | undefined;
|
|
507
561
|
private attachStarted = false;
|
|
508
562
|
private _dirtyContainer = false;
|
|
509
563
|
private readonly savedOps: ISequencedDocumentMessage[] = [];
|
|
@@ -513,10 +567,11 @@ export class Container
|
|
|
513
567
|
private lastVisible: number | undefined;
|
|
514
568
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
515
569
|
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
570
|
+
private readonly clientsWhoShouldHaveLeft = new Set<string>();
|
|
516
571
|
|
|
517
572
|
private setAutoReconnectTime = performance.now();
|
|
518
573
|
|
|
519
|
-
private
|
|
574
|
+
private noopHeuristic: NoopHeuristic | undefined;
|
|
520
575
|
|
|
521
576
|
private get connectionMode() {
|
|
522
577
|
return this._deltaManager.connectionManager.connectionMode;
|
|
@@ -527,21 +582,24 @@ export class Container
|
|
|
527
582
|
}
|
|
528
583
|
|
|
529
584
|
public get resolvedUrl(): IResolvedUrl | undefined {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
585
|
+
/**
|
|
586
|
+
* All attached containers will have a document service,
|
|
587
|
+
* this is required, as attached containers are attached to
|
|
588
|
+
* a service. Detached containers will neither have a document
|
|
589
|
+
* service or a resolved url as they only exist locally.
|
|
590
|
+
* in order to create a document service a resolved url must
|
|
591
|
+
* first be obtained, this is how the container is identified.
|
|
592
|
+
* Because of this, the document service's resolved url
|
|
593
|
+
* is always the same as the containers, as we had to
|
|
594
|
+
* obtain the resolved url, and then create the service from it.
|
|
595
|
+
*/
|
|
596
|
+
return this.service?.resolvedUrl;
|
|
535
597
|
}
|
|
536
598
|
|
|
537
599
|
public get readOnlyInfo(): ReadOnlyInfo {
|
|
538
600
|
return this._deltaManager.readOnlyInfo;
|
|
539
601
|
}
|
|
540
602
|
|
|
541
|
-
public get closeSignal(): AbortSignal {
|
|
542
|
-
return this._deltaManager.closeAbortController.signal;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
603
|
/**
|
|
546
604
|
* Tracks host requiring read-only mode.
|
|
547
605
|
*/
|
|
@@ -557,18 +615,10 @@ export class Container
|
|
|
557
615
|
return this.connectionStateHandler.connectionState;
|
|
558
616
|
}
|
|
559
617
|
|
|
560
|
-
|
|
618
|
+
private get connected(): boolean {
|
|
561
619
|
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
562
620
|
}
|
|
563
621
|
|
|
564
|
-
/**
|
|
565
|
-
* Service configuration details. If running in offline mode will be undefined otherwise will contain service
|
|
566
|
-
* configuration details returned as part of the initial connection.
|
|
567
|
-
*/
|
|
568
|
-
public get serviceConfiguration(): IClientConfiguration | undefined {
|
|
569
|
-
return this._deltaManager.serviceConfiguration;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
622
|
private _clientId: string | undefined;
|
|
573
623
|
|
|
574
624
|
/**
|
|
@@ -579,24 +629,12 @@ export class Container
|
|
|
579
629
|
return this._clientId;
|
|
580
630
|
}
|
|
581
631
|
|
|
582
|
-
/**
|
|
583
|
-
* The server provided claims of the client.
|
|
584
|
-
* Set once this.connected is true, otherwise undefined
|
|
585
|
-
*/
|
|
586
|
-
public get scopes(): string[] | undefined {
|
|
587
|
-
return this._deltaManager.connectionManager.scopes;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
public get clientDetails(): IClientDetails {
|
|
591
|
-
return this._deltaManager.clientDetails;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
632
|
private get offlineLoadEnabled(): boolean {
|
|
595
633
|
const enabled =
|
|
596
634
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
|
|
597
635
|
this.options?.enableOfflineLoad === true;
|
|
598
636
|
// summarizer will not have any pending state we want to save
|
|
599
|
-
return enabled && this.clientDetails.capabilities.interactive;
|
|
637
|
+
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
600
638
|
}
|
|
601
639
|
|
|
602
640
|
/**
|
|
@@ -607,15 +645,18 @@ export class Container
|
|
|
607
645
|
return this.getCodeDetailsFromQuorum();
|
|
608
646
|
}
|
|
609
647
|
|
|
648
|
+
private _loadedCodeDetails: IFluidCodeDetails | undefined;
|
|
610
649
|
/**
|
|
611
650
|
* Get the code details that were used to load the container.
|
|
612
651
|
* @returns The code details that were used to load the container if it is loaded, undefined if it is not yet
|
|
613
652
|
* loaded.
|
|
614
653
|
*/
|
|
615
654
|
public getLoadedCodeDetails(): IFluidCodeDetails | undefined {
|
|
616
|
-
return this.
|
|
655
|
+
return this._loadedCodeDetails;
|
|
617
656
|
}
|
|
618
657
|
|
|
658
|
+
private _loadedModule: IFluidModuleWithDetails | undefined;
|
|
659
|
+
|
|
619
660
|
/**
|
|
620
661
|
* Retrieves the audience associated with the document
|
|
621
662
|
*/
|
|
@@ -632,62 +673,42 @@ export class Container
|
|
|
632
673
|
return this._dirtyContainer;
|
|
633
674
|
}
|
|
634
675
|
|
|
635
|
-
private get serviceFactory() {
|
|
636
|
-
return this.loader.services.documentServiceFactory;
|
|
637
|
-
}
|
|
638
|
-
private get urlResolver() {
|
|
639
|
-
return this.loader.services.urlResolver;
|
|
640
|
-
}
|
|
641
|
-
public readonly options: ILoaderOptions;
|
|
642
|
-
private get scope() {
|
|
643
|
-
return this.loader.services.scope;
|
|
644
|
-
}
|
|
645
|
-
private get codeLoader() {
|
|
646
|
-
return this.loader.services.codeLoader;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
676
|
/**
|
|
650
677
|
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
651
678
|
*/
|
|
652
|
-
public async getEntryPoint
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
// Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
|
|
656
|
-
if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
|
|
657
|
-
throw new UsageError("The container is disposing or disposed");
|
|
679
|
+
public async getEntryPoint(): Promise<FluidObject | undefined> {
|
|
680
|
+
if (this._disposed) {
|
|
681
|
+
throw new UsageError("The context is already disposed");
|
|
658
682
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const contextChangedHandler = () => {
|
|
662
|
-
resolve();
|
|
663
|
-
this.off("disposed", disposedHandler);
|
|
664
|
-
};
|
|
665
|
-
const disposedHandler = (error) => {
|
|
666
|
-
reject(error ?? "The Container is disposed");
|
|
667
|
-
this.off("contextChanged", contextChangedHandler);
|
|
668
|
-
};
|
|
669
|
-
this.once("contextChanged", contextChangedHandler);
|
|
670
|
-
this.once("disposed", disposedHandler);
|
|
671
|
-
});
|
|
672
|
-
// The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
|
|
673
|
-
// should have set this._context; making sure.
|
|
674
|
-
assert(
|
|
675
|
-
this._context !== undefined,
|
|
676
|
-
0x5a2 /* Context still not defined after contextChanged event */,
|
|
677
|
-
);
|
|
683
|
+
if (this._runtime !== undefined) {
|
|
684
|
+
return this._runtime.getEntryPoint?.();
|
|
678
685
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
686
|
+
return new Promise<FluidObject | undefined>((resolve, reject) => {
|
|
687
|
+
const runtimeInstantiatedHandler = () => {
|
|
688
|
+
assert(
|
|
689
|
+
this._runtime !== undefined,
|
|
690
|
+
0x5a3 /* runtimeInstantiated fired but runtime is still undefined */,
|
|
691
|
+
);
|
|
692
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
693
|
+
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
694
|
+
};
|
|
695
|
+
const disposedHandler = () => {
|
|
696
|
+
reject(new Error("ContainerContext was disposed"));
|
|
697
|
+
this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
698
|
+
};
|
|
699
|
+
this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
700
|
+
this._lifecycleEvents.once("disposed", disposedHandler);
|
|
701
|
+
});
|
|
682
702
|
}
|
|
683
703
|
|
|
704
|
+
private readonly _lifecycleEvents = new TypedEventEmitter<IContainerLifecycleEvents>();
|
|
705
|
+
|
|
684
706
|
/**
|
|
685
707
|
* @internal
|
|
686
708
|
*/
|
|
687
709
|
constructor(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
private readonly protocolHandlerBuilder?: ProtocolHandlerBuilder,
|
|
710
|
+
createProps: IContainerCreateProps,
|
|
711
|
+
loadProps?: Pick<IContainerLoadProps, "pendingLocalState">,
|
|
691
712
|
) {
|
|
692
713
|
super((name, error) => {
|
|
693
714
|
this.mc.logger.sendErrorEvent(
|
|
@@ -699,11 +720,46 @@ export class Container
|
|
|
699
720
|
);
|
|
700
721
|
});
|
|
701
722
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
723
|
+
const {
|
|
724
|
+
canReconnect,
|
|
725
|
+
clientDetailsOverride,
|
|
726
|
+
urlResolver,
|
|
727
|
+
documentServiceFactory,
|
|
728
|
+
codeLoader,
|
|
729
|
+
options,
|
|
730
|
+
scope,
|
|
731
|
+
subLogger,
|
|
732
|
+
detachedBlobStorage,
|
|
733
|
+
protocolHandlerBuilder,
|
|
734
|
+
} = createProps;
|
|
735
|
+
|
|
736
|
+
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
737
|
+
const pendingLocalState = loadProps?.pendingLocalState;
|
|
738
|
+
|
|
739
|
+
this._canReconnect = canReconnect ?? true;
|
|
740
|
+
this.clientDetailsOverride = clientDetailsOverride;
|
|
741
|
+
this.urlResolver = urlResolver;
|
|
742
|
+
this.serviceFactory = documentServiceFactory;
|
|
743
|
+
this.codeLoader = codeLoader;
|
|
744
|
+
// Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
|
|
745
|
+
// all clients that were loaded from the same loader (including summarizer clients).
|
|
746
|
+
// Tracking alternative ways to handle this in AB#4129.
|
|
747
|
+
this.options = { ...options };
|
|
748
|
+
this.scope = scope;
|
|
749
|
+
this.detachedBlobStorage = detachedBlobStorage;
|
|
750
|
+
this.protocolHandlerBuilder =
|
|
751
|
+
protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
752
|
+
|
|
753
|
+
// Note that we capture the createProps here so we can replicate the creation call when we want to clone.
|
|
754
|
+
this.clone = async (
|
|
755
|
+
_loadProps: IContainerLoadProps,
|
|
756
|
+
createParamOverrides: Partial<IContainerCreateProps>,
|
|
757
|
+
) => {
|
|
758
|
+
return Container.load(_loadProps, {
|
|
759
|
+
...createProps,
|
|
760
|
+
...createParamOverrides,
|
|
761
|
+
});
|
|
762
|
+
};
|
|
707
763
|
|
|
708
764
|
// Create logger for data stores to use
|
|
709
765
|
const type = this.client.details.type;
|
|
@@ -713,15 +769,15 @@ export class Container
|
|
|
713
769
|
}`;
|
|
714
770
|
// Need to use the property getter for docId because for detached flow we don't have the docId initially.
|
|
715
771
|
// We assign the id later so property getter is used.
|
|
716
|
-
this.subLogger = ChildLogger.create(
|
|
772
|
+
this.subLogger = ChildLogger.create(subLogger, undefined, {
|
|
717
773
|
all: {
|
|
718
774
|
clientType, // Differentiating summarizer container from main container
|
|
719
775
|
containerId: uuid(),
|
|
720
|
-
docId: () => this.
|
|
776
|
+
docId: () => this.resolvedUrl?.id,
|
|
721
777
|
containerAttachState: () => this._attachState,
|
|
722
778
|
containerLifecycleState: () => this._lifecycleState,
|
|
723
779
|
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
724
|
-
serializedContainer:
|
|
780
|
+
serializedContainer: pendingLocalState !== undefined,
|
|
725
781
|
},
|
|
726
782
|
// we need to be judicious with our logging here to avoid generating too much data
|
|
727
783
|
// all data logged here should be broadly applicable, and not specific to a
|
|
@@ -731,13 +787,16 @@ export class Container
|
|
|
731
787
|
dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
|
|
732
788
|
dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
|
|
733
789
|
dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
|
|
734
|
-
containerLoadedFromVersionId: () => this.
|
|
735
|
-
containerLoadedFromVersionDate: () => this.
|
|
790
|
+
containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
|
|
791
|
+
containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
|
|
736
792
|
// message information to associate errors with the specific execution state
|
|
737
793
|
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
738
794
|
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
739
795
|
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
740
|
-
dmLastMsqSeqClientId: () =>
|
|
796
|
+
dmLastMsqSeqClientId: () =>
|
|
797
|
+
this.deltaManager?.lastMessage?.clientId === null
|
|
798
|
+
? "null"
|
|
799
|
+
: this.deltaManager?.lastMessage?.clientId,
|
|
741
800
|
dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
|
|
742
801
|
connectionStateDuration: () =>
|
|
743
802
|
performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
@@ -747,13 +806,6 @@ export class Container
|
|
|
747
806
|
// Prefix all events in this file with container-loader
|
|
748
807
|
this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
|
|
749
808
|
|
|
750
|
-
// Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
|
|
751
|
-
// all clients that were loaded from the same loader (including summarizer clients).
|
|
752
|
-
// Tracking alternative ways to handle this in AB#4129.
|
|
753
|
-
this.options = {
|
|
754
|
-
...this.loader.services.options,
|
|
755
|
-
};
|
|
756
|
-
|
|
757
809
|
this._deltaManager = this.createDeltaManager();
|
|
758
810
|
|
|
759
811
|
this.connectionStateHandler = createConnectionStateHandler(
|
|
@@ -774,7 +826,7 @@ export class Container
|
|
|
774
826
|
}
|
|
775
827
|
},
|
|
776
828
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
777
|
-
maxClientLeaveWaitTime:
|
|
829
|
+
maxClientLeaveWaitTime: options.maxClientLeaveWaitTime,
|
|
778
830
|
logConnectionIssue: (
|
|
779
831
|
eventName: string,
|
|
780
832
|
category: TelemetryEventCategory,
|
|
@@ -809,9 +861,12 @@ export class Container
|
|
|
809
861
|
this.connect();
|
|
810
862
|
}
|
|
811
863
|
},
|
|
864
|
+
clientShouldHaveLeft: (clientId: string) => {
|
|
865
|
+
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
866
|
+
},
|
|
812
867
|
},
|
|
813
868
|
this.deltaManager,
|
|
814
|
-
|
|
869
|
+
pendingLocalState?.clientId,
|
|
815
870
|
);
|
|
816
871
|
|
|
817
872
|
this.on(savedContainerEvent, () => {
|
|
@@ -830,12 +885,12 @@ export class Container
|
|
|
830
885
|
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
831
886
|
const forceEnableSummarizeProtocolTree =
|
|
832
887
|
this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
|
|
833
|
-
|
|
888
|
+
options.summarizeProtocolTree;
|
|
834
889
|
|
|
835
890
|
this.storageAdapter = new ContainerStorageAdapter(
|
|
836
|
-
|
|
891
|
+
detachedBlobStorage,
|
|
837
892
|
this.mc.logger,
|
|
838
|
-
|
|
893
|
+
pendingLocalState?.snapshotBlobs,
|
|
839
894
|
addProtocolSummaryIfMissing,
|
|
840
895
|
forceEnableSummarizeProtocolTree,
|
|
841
896
|
);
|
|
@@ -869,8 +924,8 @@ export class Container
|
|
|
869
924
|
return this.protocolHandler.quorum;
|
|
870
925
|
}
|
|
871
926
|
|
|
872
|
-
public dispose
|
|
873
|
-
this._deltaManager.
|
|
927
|
+
public dispose(error?: ICriticalContainerError) {
|
|
928
|
+
this._deltaManager.dispose(error);
|
|
874
929
|
this.verifyClosed();
|
|
875
930
|
}
|
|
876
931
|
|
|
@@ -921,15 +976,6 @@ export class Container
|
|
|
921
976
|
this._protocolHandler?.close();
|
|
922
977
|
|
|
923
978
|
this.connectionStateHandler.dispose();
|
|
924
|
-
|
|
925
|
-
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
926
|
-
|
|
927
|
-
this.storageAdapter.dispose();
|
|
928
|
-
|
|
929
|
-
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
930
|
-
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
931
|
-
// Driver need to ensure all caches are cleared on critical errors
|
|
932
|
-
this.service?.dispose(error);
|
|
933
979
|
} catch (exception) {
|
|
934
980
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
935
981
|
}
|
|
@@ -941,6 +987,11 @@ export class Container
|
|
|
941
987
|
}
|
|
942
988
|
} finally {
|
|
943
989
|
this._lifecycleState = "closed";
|
|
990
|
+
|
|
991
|
+
// There is no user for summarizer, so we need to ensure dispose is called
|
|
992
|
+
if (this.client.details.type === summarizerClientType) {
|
|
993
|
+
this.dispose(error);
|
|
994
|
+
}
|
|
944
995
|
}
|
|
945
996
|
}
|
|
946
997
|
|
|
@@ -957,7 +1008,8 @@ export class Container
|
|
|
957
1008
|
this.mc.logger.sendTelemetryEvent(
|
|
958
1009
|
{
|
|
959
1010
|
eventName: "ContainerDispose",
|
|
960
|
-
|
|
1011
|
+
// Only log error if container isn't closed
|
|
1012
|
+
category: !this.closed && error !== undefined ? "error" : "generic",
|
|
961
1013
|
},
|
|
962
1014
|
error,
|
|
963
1015
|
);
|
|
@@ -971,7 +1023,8 @@ export class Container
|
|
|
971
1023
|
|
|
972
1024
|
this.connectionStateHandler.dispose();
|
|
973
1025
|
|
|
974
|
-
|
|
1026
|
+
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
1027
|
+
this._runtime?.dispose(maybeError);
|
|
975
1028
|
|
|
976
1029
|
this.storageAdapter.dispose();
|
|
977
1030
|
|
|
@@ -994,6 +1047,7 @@ export class Container
|
|
|
994
1047
|
}
|
|
995
1048
|
} finally {
|
|
996
1049
|
this._lifecycleState = "disposed";
|
|
1050
|
+
this._lifecycleEvents.emit("disposed");
|
|
997
1051
|
}
|
|
998
1052
|
}
|
|
999
1053
|
|
|
@@ -1010,6 +1064,11 @@ export class Container
|
|
|
1010
1064
|
if (!this.offlineLoadEnabled) {
|
|
1011
1065
|
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
1012
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
|
+
}
|
|
1013
1072
|
assert(
|
|
1014
1073
|
this.attachState === AttachState.Attached,
|
|
1015
1074
|
0x0d1 /* "Container should be attached before close" */,
|
|
@@ -1021,7 +1080,7 @@ export class Container
|
|
|
1021
1080
|
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
1022
1081
|
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
1023
1082
|
const pendingState: IPendingContainerState = {
|
|
1024
|
-
pendingRuntimeState: this.
|
|
1083
|
+
pendingRuntimeState: this.runtime.getPendingLocalState(),
|
|
1025
1084
|
baseSnapshot: this.baseSnapshot,
|
|
1026
1085
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
1027
1086
|
savedOps: this.savedOps,
|
|
@@ -1045,14 +1104,11 @@ export class Container
|
|
|
1045
1104
|
0x0d3 /* "Should only be called in detached container" */,
|
|
1046
1105
|
);
|
|
1047
1106
|
|
|
1048
|
-
const appSummary: ISummaryTree = this.
|
|
1107
|
+
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1049
1108
|
const protocolSummary = this.captureProtocolSummary();
|
|
1050
1109
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1051
1110
|
|
|
1052
|
-
if (
|
|
1053
|
-
this.loader.services.detachedBlobStorage &&
|
|
1054
|
-
this.loader.services.detachedBlobStorage.size > 0
|
|
1055
|
-
) {
|
|
1111
|
+
if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
|
|
1056
1112
|
combinedSummary.tree[".hasAttachmentBlobs"] = {
|
|
1057
1113
|
type: SummaryType.Blob,
|
|
1058
1114
|
content: "true",
|
|
@@ -1082,8 +1138,7 @@ export class Container
|
|
|
1082
1138
|
|
|
1083
1139
|
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
1084
1140
|
const hasAttachmentBlobs =
|
|
1085
|
-
this.
|
|
1086
|
-
this.loader.services.detachedBlobStorage.size > 0;
|
|
1141
|
+
this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
|
|
1087
1142
|
|
|
1088
1143
|
try {
|
|
1089
1144
|
assert(
|
|
@@ -1095,7 +1150,7 @@ export class Container
|
|
|
1095
1150
|
if (!hasAttachmentBlobs) {
|
|
1096
1151
|
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
1097
1152
|
// semantics around what the attach means as far as async code goes.
|
|
1098
|
-
const appSummary: ISummaryTree = this.
|
|
1153
|
+
const appSummary: ISummaryTree = this.runtime.createSummary();
|
|
1099
1154
|
const protocolSummary = this.captureProtocolSummary();
|
|
1100
1155
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1101
1156
|
|
|
@@ -1104,6 +1159,7 @@ export class Container
|
|
|
1104
1159
|
// starting to attach the container to storage.
|
|
1105
1160
|
// Also, this should only be fired in detached container.
|
|
1106
1161
|
this._attachState = AttachState.Attaching;
|
|
1162
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
1107
1163
|
this.emit("attaching");
|
|
1108
1164
|
if (this.offlineLoadEnabled) {
|
|
1109
1165
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -1114,11 +1170,11 @@ export class Container
|
|
|
1114
1170
|
}
|
|
1115
1171
|
|
|
1116
1172
|
// Actually go and create the resolved document
|
|
1117
|
-
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
1118
|
-
ensureFluidResolvedUrl(createNewResolvedUrl);
|
|
1119
1173
|
if (this.service === undefined) {
|
|
1174
|
+
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
1120
1175
|
assert(
|
|
1121
|
-
this.client.details.type !== summarizerClientType
|
|
1176
|
+
this.client.details.type !== summarizerClientType &&
|
|
1177
|
+
createNewResolvedUrl !== undefined,
|
|
1122
1178
|
0x2c4 /* "client should not be summarizer before container is created" */,
|
|
1123
1179
|
);
|
|
1124
1180
|
this.service = await runWithRetry(
|
|
@@ -1132,19 +1188,16 @@ export class Container
|
|
|
1132
1188
|
"containerAttach",
|
|
1133
1189
|
this.mc.logger,
|
|
1134
1190
|
{
|
|
1135
|
-
cancel: this.
|
|
1191
|
+
cancel: this._deltaManager.closeAbortController.signal,
|
|
1136
1192
|
}, // progress
|
|
1137
1193
|
);
|
|
1138
1194
|
}
|
|
1139
|
-
const resolvedUrl = this.service.resolvedUrl;
|
|
1140
|
-
ensureFluidResolvedUrl(resolvedUrl);
|
|
1141
|
-
this._resolvedUrl = resolvedUrl;
|
|
1142
1195
|
await this.storageAdapter.connectToService(this.service);
|
|
1143
1196
|
|
|
1144
1197
|
if (hasAttachmentBlobs) {
|
|
1145
1198
|
// upload blobs to storage
|
|
1146
1199
|
assert(
|
|
1147
|
-
!!this.
|
|
1200
|
+
!!this.detachedBlobStorage,
|
|
1148
1201
|
0x24e /* "assertion for type narrowing" */,
|
|
1149
1202
|
);
|
|
1150
1203
|
|
|
@@ -1152,24 +1205,24 @@ export class Container
|
|
|
1152
1205
|
// support blob handles that only know about the local IDs
|
|
1153
1206
|
const redirectTable = new Map<string, string>();
|
|
1154
1207
|
// if new blobs are added while uploading, upload them too
|
|
1155
|
-
while (redirectTable.size < this.
|
|
1156
|
-
const newIds = this.
|
|
1208
|
+
while (redirectTable.size < this.detachedBlobStorage.size) {
|
|
1209
|
+
const newIds = this.detachedBlobStorage
|
|
1157
1210
|
.getBlobIds()
|
|
1158
1211
|
.filter((id) => !redirectTable.has(id));
|
|
1159
1212
|
for (const id of newIds) {
|
|
1160
|
-
const blob =
|
|
1161
|
-
await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
1213
|
+
const blob = await this.detachedBlobStorage.readBlob(id);
|
|
1162
1214
|
const response = await this.storageAdapter.createBlob(blob);
|
|
1163
1215
|
redirectTable.set(id, response.id);
|
|
1164
1216
|
}
|
|
1165
1217
|
}
|
|
1166
1218
|
|
|
1167
1219
|
// take summary and upload
|
|
1168
|
-
const appSummary: ISummaryTree = this.
|
|
1220
|
+
const appSummary: ISummaryTree = this.runtime.createSummary(redirectTable);
|
|
1169
1221
|
const protocolSummary = this.captureProtocolSummary();
|
|
1170
1222
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1171
1223
|
|
|
1172
1224
|
this._attachState = AttachState.Attaching;
|
|
1225
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
1173
1226
|
this.emit("attaching");
|
|
1174
1227
|
if (this.offlineLoadEnabled) {
|
|
1175
1228
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -1186,6 +1239,7 @@ export class Container
|
|
|
1186
1239
|
}
|
|
1187
1240
|
|
|
1188
1241
|
this._attachState = AttachState.Attached;
|
|
1242
|
+
this.runtime.setAttachState(AttachState.Attached);
|
|
1189
1243
|
this.emit("attached");
|
|
1190
1244
|
|
|
1191
1245
|
if (!this.closed) {
|
|
@@ -1197,12 +1251,8 @@ export class Container
|
|
|
1197
1251
|
} catch (error) {
|
|
1198
1252
|
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
1199
1253
|
const newError = normalizeError(error);
|
|
1200
|
-
|
|
1201
|
-
if (isFluidResolvedUrl(resolvedUrl)) {
|
|
1202
|
-
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
1203
|
-
}
|
|
1254
|
+
newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
|
|
1204
1255
|
this.close(newError);
|
|
1205
|
-
this.dispose?.(newError);
|
|
1206
1256
|
throw newError;
|
|
1207
1257
|
}
|
|
1208
1258
|
},
|
|
@@ -1214,7 +1264,7 @@ export class Container
|
|
|
1214
1264
|
return PerformanceEvent.timedExecAsync(
|
|
1215
1265
|
this.mc.logger,
|
|
1216
1266
|
{ eventName: "Request" },
|
|
1217
|
-
async () => this.
|
|
1267
|
+
async () => this.runtime.request(path),
|
|
1218
1268
|
{ end: true, cancel: "error" },
|
|
1219
1269
|
);
|
|
1220
1270
|
}
|
|
@@ -1299,7 +1349,7 @@ export class Container
|
|
|
1299
1349
|
this.connectToDeltaStream(args);
|
|
1300
1350
|
}
|
|
1301
1351
|
|
|
1302
|
-
public async
|
|
1352
|
+
public readonly getAbsoluteUrl = async (relativeUrl: string): Promise<string | undefined> => {
|
|
1303
1353
|
if (this.resolvedUrl === undefined) {
|
|
1304
1354
|
return undefined;
|
|
1305
1355
|
}
|
|
@@ -1307,9 +1357,9 @@ export class Container
|
|
|
1307
1357
|
return this.urlResolver.getAbsoluteUrl(
|
|
1308
1358
|
this.resolvedUrl,
|
|
1309
1359
|
relativeUrl,
|
|
1310
|
-
getPackageName(this.
|
|
1360
|
+
getPackageName(this._loadedCodeDetails),
|
|
1311
1361
|
);
|
|
1312
|
-
}
|
|
1362
|
+
};
|
|
1313
1363
|
|
|
1314
1364
|
public async proposeCodeDetails(codeDetails: IFluidCodeDetails) {
|
|
1315
1365
|
if (!isFluidCodeDetails(codeDetails)) {
|
|
@@ -1340,7 +1390,7 @@ export class Container
|
|
|
1340
1390
|
this.deltaManager.inboundSignal.pause(),
|
|
1341
1391
|
]);
|
|
1342
1392
|
|
|
1343
|
-
if ((await this.
|
|
1393
|
+
if ((await this.satisfies(codeDetails)) === true) {
|
|
1344
1394
|
this.deltaManager.inbound.resume();
|
|
1345
1395
|
this.deltaManager.inboundSignal.resume();
|
|
1346
1396
|
return;
|
|
@@ -1349,7 +1399,47 @@ export class Container
|
|
|
1349
1399
|
// pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
|
|
1350
1400
|
const error = new GenericError("Existing context does not satisfy incoming proposal");
|
|
1351
1401
|
this.close(error);
|
|
1352
|
-
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
1406
|
+
*/
|
|
1407
|
+
private async satisfies(constraintCodeDetails: IFluidCodeDetails) {
|
|
1408
|
+
// If we have no module, it can't satisfy anything.
|
|
1409
|
+
if (this._loadedModule === undefined) {
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
const comparers: IFluidCodeDetailsComparer[] = [];
|
|
1414
|
+
|
|
1415
|
+
const maybeCompareCodeLoader = this.codeLoader;
|
|
1416
|
+
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
1417
|
+
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
const maybeCompareExport: Partial<IProvideFluidCodeDetailsComparer> | undefined =
|
|
1421
|
+
this._loadedModule?.module.fluidExport;
|
|
1422
|
+
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
|
|
1423
|
+
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
1427
|
+
// the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
|
|
1428
|
+
// rather than potentially running with incompatible code.
|
|
1429
|
+
if (comparers.length === 0) {
|
|
1430
|
+
return false;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
for (const comparer of comparers) {
|
|
1434
|
+
const satisfies = await comparer.satisfies(
|
|
1435
|
+
this._loadedModule?.details,
|
|
1436
|
+
constraintCodeDetails,
|
|
1437
|
+
);
|
|
1438
|
+
if (satisfies === false) {
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return true;
|
|
1353
1443
|
}
|
|
1354
1444
|
|
|
1355
1445
|
private async getVersion(version: string | null): Promise<IVersion | undefined> {
|
|
@@ -1357,15 +1447,7 @@ export class Container
|
|
|
1357
1447
|
return versions[0];
|
|
1358
1448
|
}
|
|
1359
1449
|
|
|
1360
|
-
private recordConnectStartTime() {
|
|
1361
|
-
if (this.connectionTransitionTimes[ConnectionState.Disconnected] === undefined) {
|
|
1362
|
-
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
1450
|
private connectToDeltaStream(args: IConnectionArgs) {
|
|
1367
|
-
this.recordConnectStartTime();
|
|
1368
|
-
|
|
1369
1451
|
// All agents need "write" access, including summarizer.
|
|
1370
1452
|
if (!this._canReconnect || !this.client.details.capabilities.interactive) {
|
|
1371
1453
|
args.mode = "write";
|
|
@@ -1382,13 +1464,11 @@ export class Container
|
|
|
1382
1464
|
private async load(
|
|
1383
1465
|
specifiedVersion: string | undefined,
|
|
1384
1466
|
loadMode: IContainerLoadMode,
|
|
1467
|
+
resolvedUrl: IResolvedUrl,
|
|
1385
1468
|
pendingLocalState?: IPendingContainerState,
|
|
1386
1469
|
) {
|
|
1387
|
-
if (this._resolvedUrl === undefined) {
|
|
1388
|
-
throw new Error("Attempting to load without a resolved url");
|
|
1389
|
-
}
|
|
1390
1470
|
this.service = await this.serviceFactory.createDocumentService(
|
|
1391
|
-
|
|
1471
|
+
resolvedUrl,
|
|
1392
1472
|
this.subLogger,
|
|
1393
1473
|
this.client.details.type === summarizerClientType,
|
|
1394
1474
|
);
|
|
@@ -1420,7 +1500,6 @@ export class Container
|
|
|
1420
1500
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
1421
1501
|
this.storageAdapter.connectToService(this.service).catch((error) => {
|
|
1422
1502
|
this.close(error);
|
|
1423
|
-
this.dispose?.(error);
|
|
1424
1503
|
});
|
|
1425
1504
|
}
|
|
1426
1505
|
|
|
@@ -1440,7 +1519,10 @@ export class Container
|
|
|
1440
1519
|
if (this.offlineLoadEnabled) {
|
|
1441
1520
|
this.baseSnapshot = snapshot;
|
|
1442
1521
|
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
1443
|
-
this.baseSnapshotBlobs = await getBlobContentsFromTree(
|
|
1522
|
+
this.baseSnapshotBlobs = await getBlobContentsFromTree(
|
|
1523
|
+
snapshot,
|
|
1524
|
+
this.storageAdapter,
|
|
1525
|
+
);
|
|
1444
1526
|
}
|
|
1445
1527
|
}
|
|
1446
1528
|
|
|
@@ -1496,7 +1578,7 @@ export class Container
|
|
|
1496
1578
|
this.processRemoteMessage(message);
|
|
1497
1579
|
|
|
1498
1580
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
1499
|
-
await this.
|
|
1581
|
+
await this.runtime.notifyOpReplay?.(message);
|
|
1500
1582
|
}
|
|
1501
1583
|
pendingLocalState.savedOps = [];
|
|
1502
1584
|
|
|
@@ -1603,8 +1685,7 @@ export class Container
|
|
|
1603
1685
|
private async rehydrateDetachedFromSnapshot(detachedContainerSnapshot: ISummaryTree) {
|
|
1604
1686
|
if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
|
|
1605
1687
|
assert(
|
|
1606
|
-
!!this.
|
|
1607
|
-
this.loader.services.detachedBlobStorage.size > 0,
|
|
1688
|
+
!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
|
|
1608
1689
|
0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
|
|
1609
1690
|
);
|
|
1610
1691
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
@@ -1701,10 +1782,7 @@ export class Container
|
|
|
1701
1782
|
attributes: IDocumentAttributes,
|
|
1702
1783
|
quorumSnapshot: IQuorumSnapshot,
|
|
1703
1784
|
): void {
|
|
1704
|
-
const protocolHandlerBuilder
|
|
1705
|
-
this.protocolHandlerBuilder ??
|
|
1706
|
-
((...args) => new ProtocolHandler(...args, new Audience()));
|
|
1707
|
-
const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) =>
|
|
1785
|
+
const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) =>
|
|
1708
1786
|
this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
|
|
1709
1787
|
);
|
|
1710
1788
|
|
|
@@ -1733,7 +1811,6 @@ export class Container
|
|
|
1733
1811
|
this.processCodeProposal().catch((error) => {
|
|
1734
1812
|
const normalizedError = normalizeError(error);
|
|
1735
1813
|
this.close(normalizedError);
|
|
1736
|
-
this.dispose?.(normalizedError);
|
|
1737
1814
|
throw error;
|
|
1738
1815
|
});
|
|
1739
1816
|
}
|
|
@@ -1844,8 +1921,16 @@ export class Container
|
|
|
1844
1921
|
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1845
1922
|
});
|
|
1846
1923
|
|
|
1924
|
+
deltaManager.on("establishingConnection", (reason: string) => {
|
|
1925
|
+
this.connectionStateHandler.establishingConnection(reason);
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1928
|
+
deltaManager.on("cancelEstablishingConnection", (reason: string) => {
|
|
1929
|
+
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1930
|
+
});
|
|
1931
|
+
|
|
1847
1932
|
deltaManager.on("disconnect", (reason: string, error?: IAnyDriverError) => {
|
|
1848
|
-
this.
|
|
1933
|
+
this.noopHeuristic?.notifyDisconnect();
|
|
1849
1934
|
if (!this.closed) {
|
|
1850
1935
|
this.connectionStateHandler.receivedDisconnectEvent(reason, error);
|
|
1851
1936
|
}
|
|
@@ -1920,10 +2005,14 @@ export class Container
|
|
|
1920
2005
|
durationFromDisconnected =
|
|
1921
2006
|
time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
1922
2007
|
durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
|
|
1923
|
-
} else {
|
|
1924
|
-
// This info is of most
|
|
2008
|
+
} else if (value === ConnectionState.CatchingUp) {
|
|
2009
|
+
// This info is of most interesting while Catching Up.
|
|
1925
2010
|
checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
|
|
1926
|
-
|
|
2011
|
+
// Need to check that we have already loaded and fetched the snapshot.
|
|
2012
|
+
if (
|
|
2013
|
+
this.deltaManager.hasCheckpointSequenceNumber &&
|
|
2014
|
+
this._lifecycleState === "loaded"
|
|
2015
|
+
) {
|
|
1927
2016
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1928
2017
|
}
|
|
1929
2018
|
}
|
|
@@ -2013,7 +2102,6 @@ export class Container
|
|
|
2013
2102
|
{ messageType: type },
|
|
2014
2103
|
);
|
|
2015
2104
|
this.close(newError);
|
|
2016
|
-
this.dispose?.(newError);
|
|
2017
2105
|
return -1;
|
|
2018
2106
|
}
|
|
2019
2107
|
}
|
|
@@ -2069,7 +2157,7 @@ export class Container
|
|
|
2069
2157
|
}
|
|
2070
2158
|
|
|
2071
2159
|
this.messageCountAfterDisconnection += 1;
|
|
2072
|
-
this.
|
|
2160
|
+
this.noopHeuristic?.notifyMessageSent();
|
|
2073
2161
|
return this._deltaManager.submit(
|
|
2074
2162
|
type,
|
|
2075
2163
|
contents,
|
|
@@ -2086,39 +2174,67 @@ export class Container
|
|
|
2086
2174
|
}
|
|
2087
2175
|
const local = this.clientId === message.clientId;
|
|
2088
2176
|
|
|
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
|
+
|
|
2089
2199
|
// Allow the protocol handler to process the message
|
|
2090
2200
|
const result = this.protocolHandler.processMessage(message, local);
|
|
2091
2201
|
|
|
2092
2202
|
// Forward messages to the loaded runtime for processing
|
|
2093
|
-
this.
|
|
2203
|
+
this.runtime.process(message, local);
|
|
2094
2204
|
|
|
2095
2205
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
2096
2206
|
if (this.activeConnection()) {
|
|
2097
|
-
if (this.
|
|
2207
|
+
if (this.noopHeuristic === undefined) {
|
|
2208
|
+
const serviceConfiguration = this.deltaManager.serviceConfiguration;
|
|
2098
2209
|
// Note that config from first connection will be used for this container's lifetime.
|
|
2099
2210
|
// That means that if relay service changes settings, such changes will impact only newly booted
|
|
2100
2211
|
// clients.
|
|
2101
2212
|
// All existing will continue to use settings they got earlier.
|
|
2102
2213
|
assert(
|
|
2103
|
-
|
|
2214
|
+
serviceConfiguration !== undefined,
|
|
2104
2215
|
0x2e4 /* "there should be service config for active connection" */,
|
|
2105
2216
|
);
|
|
2106
|
-
this.
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
this.activeConnection(),
|
|
2110
|
-
0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */,
|
|
2111
|
-
);
|
|
2112
|
-
this.submitMessage(type);
|
|
2113
|
-
},
|
|
2114
|
-
this.serviceConfiguration.noopTimeFrequency,
|
|
2115
|
-
this.serviceConfiguration.noopCountFrequency,
|
|
2217
|
+
this.noopHeuristic = new NoopHeuristic(
|
|
2218
|
+
serviceConfiguration.noopTimeFrequency,
|
|
2219
|
+
serviceConfiguration.noopCountFrequency,
|
|
2116
2220
|
);
|
|
2221
|
+
this.noopHeuristic.on("wantsNoop", () => {
|
|
2222
|
+
// On disconnect we notify the heuristic which should prevent it from wanting a noop.
|
|
2223
|
+
// Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
|
|
2224
|
+
// running the microtask that the heuristic queued in response.
|
|
2225
|
+
assert(
|
|
2226
|
+
this.activeConnection(),
|
|
2227
|
+
0x241 /* "Trying to send noop without active connection" */,
|
|
2228
|
+
);
|
|
2229
|
+
this.submitMessage(MessageType.NoOp);
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
this.noopHeuristic.notifyMessageProcessed(message);
|
|
2233
|
+
// The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
|
|
2234
|
+
if (result.immediateNoOp === true) {
|
|
2235
|
+
// ADO:1385: Remove cast and use MessageType once definition changes propagate
|
|
2236
|
+
this.submitMessage(MessageType2.Accept as unknown as MessageType);
|
|
2117
2237
|
}
|
|
2118
|
-
this.collabWindowTracker.scheduleSequenceNumberUpdate(
|
|
2119
|
-
message,
|
|
2120
|
-
result.immediateNoOp === true,
|
|
2121
|
-
);
|
|
2122
2238
|
}
|
|
2123
2239
|
|
|
2124
2240
|
this.emit("op", message);
|
|
@@ -2130,11 +2246,11 @@ export class Container
|
|
|
2130
2246
|
|
|
2131
2247
|
private processSignal(message: ISignalMessage) {
|
|
2132
2248
|
// No clientId indicates a system signal message.
|
|
2133
|
-
if (message
|
|
2249
|
+
if (protocolHandlerShouldProcessSignal(message)) {
|
|
2134
2250
|
this.protocolHandler.processSignal(message);
|
|
2135
2251
|
} else {
|
|
2136
2252
|
const local = this.clientId === message.clientId;
|
|
2137
|
-
this.
|
|
2253
|
+
this.runtime.processSignal(message, local);
|
|
2138
2254
|
}
|
|
2139
2255
|
}
|
|
2140
2256
|
|
|
@@ -2176,22 +2292,50 @@ export class Container
|
|
|
2176
2292
|
private async instantiateContext(
|
|
2177
2293
|
existing: boolean,
|
|
2178
2294
|
codeDetails: IFluidCodeDetails,
|
|
2179
|
-
snapshot
|
|
2295
|
+
snapshot: ISnapshotTree | undefined,
|
|
2180
2296
|
pendingLocalState?: unknown,
|
|
2181
2297
|
) {
|
|
2182
|
-
assert(this.
|
|
2298
|
+
assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
2183
2299
|
|
|
2184
2300
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
2185
2301
|
// are set. Global requests will still go directly to the loader
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2302
|
+
const maybeLoader: FluidObject<IHostLoader> = this.scope;
|
|
2303
|
+
const loader = new RelativeLoader(this, maybeLoader.ILoader);
|
|
2304
|
+
|
|
2305
|
+
const loadCodeResult = await PerformanceEvent.timedExecAsync(
|
|
2306
|
+
this.subLogger,
|
|
2307
|
+
{ eventName: "CodeLoad" },
|
|
2308
|
+
async () => this.codeLoader.load(codeDetails),
|
|
2309
|
+
);
|
|
2310
|
+
|
|
2311
|
+
this._loadedModule = {
|
|
2312
|
+
module: loadCodeResult.module,
|
|
2313
|
+
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
2314
|
+
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
2315
|
+
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
2316
|
+
details: loadCodeResult.details ?? codeDetails,
|
|
2317
|
+
};
|
|
2318
|
+
|
|
2319
|
+
const fluidExport: FluidObject<IProvideRuntimeFactory> | undefined =
|
|
2320
|
+
this._loadedModule.module.fluidExport;
|
|
2321
|
+
const runtimeFactory = fluidExport?.IRuntimeFactory;
|
|
2322
|
+
if (runtimeFactory === undefined) {
|
|
2323
|
+
throw new Error(packageNotFactoryError);
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
const getSpecifiedCodeDetails = () =>
|
|
2327
|
+
(this.protocolHandler.quorum.get("code") ??
|
|
2328
|
+
this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
2329
|
+
|
|
2330
|
+
const context = new ContainerContext(
|
|
2331
|
+
this.options,
|
|
2189
2332
|
this.scope,
|
|
2190
|
-
this.codeLoader,
|
|
2191
|
-
codeDetails,
|
|
2192
2333
|
snapshot,
|
|
2193
|
-
|
|
2194
|
-
|
|
2334
|
+
this._loadedFromVersion,
|
|
2335
|
+
this._deltaManager,
|
|
2336
|
+
this.storageAdapter,
|
|
2337
|
+
this.protocolHandler.quorum,
|
|
2338
|
+
this.protocolHandler.audience,
|
|
2195
2339
|
loader,
|
|
2196
2340
|
(type, contents, batch, metadata) =>
|
|
2197
2341
|
this.submitContainerMessage(type, contents, batch, metadata),
|
|
@@ -2200,24 +2344,44 @@ export class Container
|
|
|
2200
2344
|
(batch: IBatchMessage[], referenceSequenceNumber?: number) =>
|
|
2201
2345
|
this.submitBatch(batch, referenceSequenceNumber),
|
|
2202
2346
|
(message) => this.submitSignal(message),
|
|
2203
|
-
(error?: ICriticalContainerError) => this.dispose
|
|
2347
|
+
(error?: ICriticalContainerError) => this.dispose(error),
|
|
2204
2348
|
(error?: ICriticalContainerError) => this.close(error),
|
|
2205
|
-
|
|
2206
|
-
|
|
2349
|
+
this.updateDirtyContainerState,
|
|
2350
|
+
this.getAbsoluteUrl,
|
|
2351
|
+
() => this.resolvedUrl?.id,
|
|
2352
|
+
() => this.clientId,
|
|
2353
|
+
() => this._deltaManager.serviceConfiguration,
|
|
2354
|
+
() => this.attachState,
|
|
2355
|
+
() => this.connected,
|
|
2356
|
+
getSpecifiedCodeDetails,
|
|
2357
|
+
this._deltaManager.clientDetails,
|
|
2207
2358
|
existing,
|
|
2359
|
+
this.subLogger,
|
|
2208
2360
|
pendingLocalState,
|
|
2209
2361
|
);
|
|
2362
|
+
this._lifecycleEvents.once("disposed", () => {
|
|
2363
|
+
context.dispose();
|
|
2364
|
+
});
|
|
2365
|
+
|
|
2366
|
+
this._runtime = await PerformanceEvent.timedExecAsync(
|
|
2367
|
+
this.subLogger,
|
|
2368
|
+
{ eventName: "InstantiateRuntime" },
|
|
2369
|
+
async () => runtimeFactory.instantiateRuntime(context, existing),
|
|
2370
|
+
);
|
|
2371
|
+
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
2372
|
+
|
|
2373
|
+
this._loadedCodeDetails = codeDetails;
|
|
2210
2374
|
|
|
2211
2375
|
this.emit("contextChanged", codeDetails);
|
|
2212
2376
|
}
|
|
2213
2377
|
|
|
2214
|
-
private updateDirtyContainerState(dirty: boolean) {
|
|
2378
|
+
private readonly updateDirtyContainerState = (dirty: boolean) => {
|
|
2215
2379
|
if (this._dirtyContainer === dirty) {
|
|
2216
2380
|
return;
|
|
2217
2381
|
}
|
|
2218
2382
|
this._dirtyContainer = dirty;
|
|
2219
2383
|
this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
|
|
2220
|
-
}
|
|
2384
|
+
};
|
|
2221
2385
|
|
|
2222
2386
|
/**
|
|
2223
2387
|
* Set the connected state of the ContainerContext
|
|
@@ -2226,21 +2390,21 @@ export class Container
|
|
|
2226
2390
|
* @param readonly - Is the container in readonly mode?
|
|
2227
2391
|
*/
|
|
2228
2392
|
private setContextConnectedState(state: boolean, readonly: boolean): void {
|
|
2229
|
-
if (this.
|
|
2393
|
+
if (this._runtime?.disposed === false) {
|
|
2230
2394
|
/**
|
|
2231
2395
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
2232
2396
|
* ops getting through to the DeltaManager.
|
|
2233
2397
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
2234
2398
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
2235
2399
|
*/
|
|
2236
|
-
this.
|
|
2400
|
+
this.runtime.setConnectionState(state && !readonly, this.clientId);
|
|
2237
2401
|
}
|
|
2238
2402
|
}
|
|
2239
2403
|
}
|
|
2240
2404
|
|
|
2241
2405
|
/**
|
|
2242
2406
|
* IContainer interface that includes experimental features still under development.
|
|
2243
|
-
* @
|
|
2407
|
+
* @experimental
|
|
2244
2408
|
*/
|
|
2245
2409
|
export interface IContainerExperimental extends IContainer {
|
|
2246
2410
|
/**
|
|
@@ -2251,5 +2415,13 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2251
2415
|
* @experimental misuse of this API can result in duplicate op submission and potential document corruption
|
|
2252
2416
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2253
2417
|
*/
|
|
2254
|
-
getPendingLocalState(): string;
|
|
2418
|
+
getPendingLocalState?(): string;
|
|
2419
|
+
|
|
2420
|
+
/**
|
|
2421
|
+
* Closes the container and returns serialized local state intended to be
|
|
2422
|
+
* given to a newly loaded container.
|
|
2423
|
+
* @experimental
|
|
2424
|
+
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2425
|
+
*/
|
|
2426
|
+
closeAndGetPendingLocalState(): string;
|
|
2255
2427
|
}
|