@fluidframework/container-loader 2.0.0-internal.5.3.2 → 2.0.0-internal.6.0.0
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 +85 -0
- package/README.md +6 -3
- package/dist/audience.d.ts +1 -0
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +3 -1
- package/dist/audience.js.map +1 -1
- package/dist/connectionManager.d.ts +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +30 -36
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +2 -1
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +9 -16
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +12 -8
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +199 -156
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +2 -12
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +1 -20
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.js +3 -5
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +11 -2
- 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 +16 -4
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +79 -33
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.js +1 -2
- package/dist/deltaQueue.js.map +1 -1
- package/dist/loader.d.ts +12 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +73 -47
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -3
- package/dist/protocol.js.map +1 -1
- package/dist/quorum.d.ts +4 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +1 -13
- package/dist/quorum.js.map +1 -1
- package/dist/utils.d.ts +8 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -6
- 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 +3 -1
- package/lib/audience.js.map +1 -1
- package/lib/connectionManager.d.ts +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +32 -35
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +2 -1
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +9 -16
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +12 -8
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +200 -157
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +2 -12
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +1 -20
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.js +3 -5
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +11 -2
- 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 +16 -4
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +77 -28
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.js +1 -2
- package/lib/deltaQueue.js.map +1 -1
- package/lib/loader.d.ts +12 -0
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +73 -47
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +2 -3
- package/lib/protocol.js.map +1 -1
- package/lib/quorum.d.ts +4 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +0 -11
- package/lib/quorum.js.map +1 -1
- package/lib/utils.d.ts +8 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +22 -5
- package/lib/utils.js.map +1 -1
- package/package.json +14 -14
- package/src/audience.ts +6 -0
- package/src/connectionManager.ts +13 -14
- package/src/connectionStateHandler.ts +3 -2
- package/src/container.ts +178 -120
- package/src/containerContext.ts +0 -24
- package/src/contracts.ts +16 -5
- package/src/debugLogger.ts +113 -0
- package/src/deltaManager.ts +50 -9
- package/src/loader.ts +53 -30
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +0 -1
- package/src/quorum.ts +0 -10
- package/src/utils.ts +29 -0
package/lib/container.js
CHANGED
|
@@ -8,19 +8,19 @@ import { v4 as uuid } from "uuid";
|
|
|
8
8
|
import { TypedEventEmitter, assert, performance, unreachableCase, } from "@fluidframework/common-utils";
|
|
9
9
|
import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
|
|
10
10
|
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
11
|
-
import { readAndParse, OnlineStatus, isOnline,
|
|
11
|
+
import { readAndParse, OnlineStatus, isOnline, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, canBeCoalescedByService, } from "@fluidframework/driver-utils";
|
|
12
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
|
-
import {
|
|
13
|
+
import { createChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, connectedEventName, normalizeError, createChildMonitoringContext, wrapError, formatTick, } from "@fluidframework/telemetry-utils";
|
|
14
14
|
import { Audience } from "./audience";
|
|
15
15
|
import { ContainerContext } from "./containerContext";
|
|
16
|
-
import { ReconnectMode, getPackageName } from "./contracts";
|
|
16
|
+
import { ReconnectMode, getPackageName, } from "./contracts";
|
|
17
17
|
import { DeltaManager } from "./deltaManager";
|
|
18
18
|
import { RelativeLoader } from "./loader";
|
|
19
19
|
import { pkgVersion } from "./packageVersion";
|
|
20
20
|
import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTreeWithBlobContents, } from "./containerStorageAdapter";
|
|
21
21
|
import { createConnectionStateHandler } from "./connectionStateHandler";
|
|
22
|
-
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
23
|
-
import { initQuorumValuesFromCodeDetails
|
|
22
|
+
import { combineAppAndProtocolSummary, getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer, } from "./utils";
|
|
23
|
+
import { initQuorumValuesFromCodeDetails } from "./quorum";
|
|
24
24
|
import { NoopHeuristic } from "./noopHeuristic";
|
|
25
25
|
import { ConnectionManager } from "./connectionManager";
|
|
26
26
|
import { ConnectionState } from "./connectionState";
|
|
@@ -104,7 +104,7 @@ export async function waitContainerToCatchUp(container) {
|
|
|
104
104
|
}
|
|
105
105
|
const getCodeProposal =
|
|
106
106
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
107
|
-
(quorum) =>
|
|
107
|
+
(quorum) => quorum.get("code") ?? quorum.get("code2");
|
|
108
108
|
/**
|
|
109
109
|
* Helper function to report to telemetry cases where operation takes longer than expected (200ms)
|
|
110
110
|
* @param logger - logger to use
|
|
@@ -124,7 +124,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
124
124
|
* @internal
|
|
125
125
|
*/
|
|
126
126
|
constructor(createProps, loadProps) {
|
|
127
|
-
var _a;
|
|
128
127
|
super((name, error) => {
|
|
129
128
|
this.mc.logger.sendErrorEvent({
|
|
130
129
|
eventName: "ContainerEventHandlerException",
|
|
@@ -152,7 +151,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
152
151
|
this.inboundQueuePausedFromInit = true;
|
|
153
152
|
this.firstConnection = true;
|
|
154
153
|
this.connectionTransitionTimes = [];
|
|
155
|
-
this.messageCountAfterDisconnection = 0;
|
|
156
154
|
this.attachStarted = false;
|
|
157
155
|
this._dirtyContainer = false;
|
|
158
156
|
this.savedOps = [];
|
|
@@ -175,8 +173,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
175
173
|
};
|
|
176
174
|
const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
|
|
177
175
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
178
|
-
const pendingLocalState = loadProps
|
|
179
|
-
this._canReconnect = canReconnect
|
|
176
|
+
const pendingLocalState = loadProps?.pendingLocalState;
|
|
177
|
+
this._canReconnect = canReconnect ?? true;
|
|
180
178
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
181
179
|
this.urlResolver = urlResolver;
|
|
182
180
|
this.serviceFactory = documentServiceFactory;
|
|
@@ -184,14 +182,17 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
184
182
|
// Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
|
|
185
183
|
// all clients that were loaded from the same loader (including summarizer clients).
|
|
186
184
|
// Tracking alternative ways to handle this in AB#4129.
|
|
187
|
-
this.options =
|
|
185
|
+
this.options = { ...options };
|
|
188
186
|
this.scope = scope;
|
|
189
187
|
this.detachedBlobStorage = detachedBlobStorage;
|
|
190
188
|
this.protocolHandlerBuilder =
|
|
191
|
-
protocolHandlerBuilder
|
|
189
|
+
protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
192
190
|
// Note that we capture the createProps here so we can replicate the creation call when we want to clone.
|
|
193
191
|
this.clone = async (_loadProps, createParamOverrides) => {
|
|
194
|
-
return Container.load(_loadProps,
|
|
192
|
+
return Container.load(_loadProps, {
|
|
193
|
+
...createProps,
|
|
194
|
+
...createParamOverrides,
|
|
195
|
+
});
|
|
195
196
|
};
|
|
196
197
|
// Create logger for data stores to use
|
|
197
198
|
const type = this.client.details.type;
|
|
@@ -199,42 +200,42 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
199
200
|
const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
|
|
200
201
|
// Need to use the property getter for docId because for detached flow we don't have the docId initially.
|
|
201
202
|
// We assign the id later so property getter is used.
|
|
202
|
-
this.subLogger =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
203
|
+
this.subLogger = createChildLogger({
|
|
204
|
+
logger: subLogger,
|
|
205
|
+
properties: {
|
|
206
|
+
all: {
|
|
207
|
+
clientType,
|
|
208
|
+
containerId: uuid(),
|
|
209
|
+
docId: () => this.resolvedUrl?.id,
|
|
210
|
+
containerAttachState: () => this._attachState,
|
|
211
|
+
containerLifecycleState: () => this._lifecycleState,
|
|
212
|
+
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
213
|
+
serializedContainer: pendingLocalState !== undefined,
|
|
214
|
+
},
|
|
215
|
+
// we need to be judicious with our logging here to avoid generating too much data
|
|
216
|
+
// all data logged here should be broadly applicable, and not specific to a
|
|
217
|
+
// specific error or class of errors
|
|
218
|
+
error: {
|
|
219
|
+
// load information to associate errors with the specific load point
|
|
220
|
+
dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
|
|
221
|
+
dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
|
|
222
|
+
dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
|
|
223
|
+
containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
|
|
224
|
+
containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
|
|
225
|
+
// message information to associate errors with the specific execution state
|
|
226
|
+
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
227
|
+
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
228
|
+
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
229
|
+
dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
|
|
229
230
|
? "null"
|
|
230
|
-
:
|
|
231
|
+
: this.deltaManager?.lastMessage?.clientId,
|
|
232
|
+
dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
|
|
233
|
+
connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
231
234
|
},
|
|
232
|
-
dmLastMsgClientSeq: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientSequenceNumber; },
|
|
233
|
-
connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
234
235
|
},
|
|
235
236
|
});
|
|
236
237
|
// Prefix all events in this file with container-loader
|
|
237
|
-
this.mc =
|
|
238
|
+
this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
|
|
238
239
|
this._deltaManager = this.createDeltaManager();
|
|
239
240
|
this.connectionStateHandler = createConnectionStateHandler({
|
|
240
241
|
logger: this.mc.logger,
|
|
@@ -258,9 +259,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
258
259
|
// Report issues only if we already loaded container - op processing is paused while container is loading,
|
|
259
260
|
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
260
261
|
// It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
|
|
261
|
-
this._deltaManager.logConnectionIssue(
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
this._deltaManager.logConnectionIssue({
|
|
263
|
+
eventName,
|
|
264
|
+
mode,
|
|
265
|
+
category: this._lifecycleState === "loading" ? "generic" : category,
|
|
266
|
+
duration: performance.now() -
|
|
267
|
+
this.connectionTransitionTimes[ConnectionState.CatchingUp],
|
|
268
|
+
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
269
|
+
});
|
|
264
270
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
265
271
|
// to very slow op fetches and we will eventually get there.
|
|
266
272
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -277,7 +283,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
277
283
|
clientShouldHaveLeft: (clientId) => {
|
|
278
284
|
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
279
285
|
},
|
|
280
|
-
}, this.deltaManager, pendingLocalState
|
|
286
|
+
}, this.deltaManager, pendingLocalState?.clientId);
|
|
281
287
|
this.on(savedContainerEvent, () => {
|
|
282
288
|
this.connectionStateHandler.containerSaved();
|
|
283
289
|
});
|
|
@@ -289,14 +295,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
289
295
|
: combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
|
|
290
296
|
// Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
|
|
291
297
|
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
292
|
-
const forceEnableSummarizeProtocolTree =
|
|
293
|
-
|
|
298
|
+
const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
|
|
299
|
+
options.summarizeProtocolTree;
|
|
300
|
+
this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
294
301
|
const isDomAvailable = typeof document === "object" &&
|
|
295
302
|
document !== null &&
|
|
296
303
|
typeof document.addEventListener === "function" &&
|
|
297
304
|
document.addEventListener !== null;
|
|
298
|
-
// keep track of last time page was visible for telemetry
|
|
299
|
-
if (isDomAvailable) {
|
|
305
|
+
// keep track of last time page was visible for telemetry (on interactive clients only)
|
|
306
|
+
if (isDomAvailable && interactive) {
|
|
300
307
|
this.lastVisible = document.hidden ? performance.now() : undefined;
|
|
301
308
|
this.visibilityEventHandler = () => {
|
|
302
309
|
if (document.hidden) {
|
|
@@ -317,7 +324,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
317
324
|
* @internal
|
|
318
325
|
*/
|
|
319
326
|
static async load(loadProps, createProps) {
|
|
320
|
-
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
327
|
+
const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } = loadProps;
|
|
321
328
|
const container = new Container(createProps, loadProps);
|
|
322
329
|
const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
|
|
323
330
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
@@ -325,19 +332,20 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
325
332
|
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
326
333
|
// to return container, so ignore this value and use undefined for opsBeforeReturn
|
|
327
334
|
const mode = pendingLocalState
|
|
328
|
-
?
|
|
335
|
+
? { ...(loadMode ?? defaultMode), opsBeforeReturn: undefined }
|
|
336
|
+
: loadMode ?? defaultMode;
|
|
329
337
|
const onClosed = (err) => {
|
|
330
338
|
// pre-0.58 error message: containerClosedWithoutErrorDuringLoad
|
|
331
|
-
reject(err
|
|
339
|
+
reject(err ?? new GenericError("Container closed without error during load"));
|
|
332
340
|
};
|
|
333
341
|
container.on("closed", onClosed);
|
|
334
342
|
container
|
|
335
|
-
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
343
|
+
.load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
|
|
336
344
|
.finally(() => {
|
|
337
345
|
container.removeListener("closed", onClosed);
|
|
338
346
|
})
|
|
339
347
|
.then((props) => {
|
|
340
|
-
event.end(
|
|
348
|
+
event.end({ ...props, ...loadMode });
|
|
341
349
|
resolve(container);
|
|
342
350
|
}, (error) => {
|
|
343
351
|
const err = normalizeError(error);
|
|
@@ -381,10 +389,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
391
|
get closed() {
|
|
384
|
-
return (this._lifecycleState === "closing" ||
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
392
|
+
return (this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed);
|
|
393
|
+
}
|
|
394
|
+
get disposed() {
|
|
395
|
+
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
388
396
|
}
|
|
389
397
|
get runtime() {
|
|
390
398
|
if (this._runtime === undefined) {
|
|
@@ -405,7 +413,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
405
413
|
return this;
|
|
406
414
|
}
|
|
407
415
|
get resolvedUrl() {
|
|
408
|
-
var _a;
|
|
409
416
|
/**
|
|
410
417
|
* All attached containers will have a document service,
|
|
411
418
|
* this is required, as attached containers are attached to
|
|
@@ -417,7 +424,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
417
424
|
* is always the same as the containers, as we had to
|
|
418
425
|
* obtain the resolved url, and then create the service from it.
|
|
419
426
|
*/
|
|
420
|
-
return
|
|
427
|
+
return this.service?.resolvedUrl;
|
|
421
428
|
}
|
|
422
429
|
get readOnlyInfo() {
|
|
423
430
|
return this._deltaManager.readOnlyInfo;
|
|
@@ -445,8 +452,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
445
452
|
return this._clientId;
|
|
446
453
|
}
|
|
447
454
|
get offlineLoadEnabled() {
|
|
448
|
-
|
|
449
|
-
|
|
455
|
+
const enabled = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
|
|
456
|
+
this.options?.enableOfflineLoad === true;
|
|
450
457
|
// summarizer will not have any pending state we want to save
|
|
451
458
|
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
452
459
|
}
|
|
@@ -483,18 +490,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
483
490
|
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
484
491
|
*/
|
|
485
492
|
async getEntryPoint() {
|
|
486
|
-
var _a, _b;
|
|
487
493
|
if (this._disposed) {
|
|
488
494
|
throw new UsageError("The context is already disposed");
|
|
489
495
|
}
|
|
490
496
|
if (this._runtime !== undefined) {
|
|
491
|
-
return
|
|
497
|
+
return this._runtime.getEntryPoint?.();
|
|
492
498
|
}
|
|
493
499
|
return new Promise((resolve, reject) => {
|
|
494
500
|
const runtimeInstantiatedHandler = () => {
|
|
495
|
-
var _a, _b;
|
|
496
501
|
assert(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
|
|
497
|
-
resolve(
|
|
502
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
498
503
|
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
499
504
|
};
|
|
500
505
|
const disposedHandler = () => {
|
|
@@ -528,7 +533,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
528
533
|
assert(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
|
|
529
534
|
}
|
|
530
535
|
closeCore(error) {
|
|
531
|
-
var _a;
|
|
532
536
|
assert(!this.closed, 0x315 /* re-entrancy */);
|
|
533
537
|
try {
|
|
534
538
|
// Ensure that we raise all key events even if one of these throws
|
|
@@ -544,7 +548,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
544
548
|
: "generic",
|
|
545
549
|
}, error);
|
|
546
550
|
this._lifecycleState = "closing";
|
|
547
|
-
|
|
551
|
+
this._protocolHandler?.close();
|
|
548
552
|
this.connectionStateHandler.dispose();
|
|
549
553
|
}
|
|
550
554
|
catch (exception) {
|
|
@@ -564,7 +568,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
564
568
|
}
|
|
565
569
|
}
|
|
566
570
|
disposeCore(error) {
|
|
567
|
-
var _a, _b, _c;
|
|
568
571
|
assert(!this._disposed, 0x54c /* Container already disposed */);
|
|
569
572
|
this._disposed = true;
|
|
570
573
|
try {
|
|
@@ -581,15 +584,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
581
584
|
if (this._lifecycleState !== "closed") {
|
|
582
585
|
this._lifecycleState = "disposing";
|
|
583
586
|
}
|
|
584
|
-
|
|
587
|
+
this._protocolHandler?.close();
|
|
585
588
|
this.connectionStateHandler.dispose();
|
|
586
589
|
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
587
|
-
|
|
590
|
+
this._runtime?.dispose(maybeError);
|
|
588
591
|
this.storageAdapter.dispose();
|
|
589
592
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
590
593
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
591
594
|
// Driver need to ensure all caches are cleared on critical errors
|
|
592
|
-
|
|
595
|
+
this.service?.dispose(error);
|
|
593
596
|
}
|
|
594
597
|
catch (exception) {
|
|
595
598
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
|
|
@@ -605,15 +608,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
605
608
|
this._lifecycleEvents.emit("disposed");
|
|
606
609
|
}
|
|
607
610
|
}
|
|
608
|
-
closeAndGetPendingLocalState() {
|
|
611
|
+
async closeAndGetPendingLocalState() {
|
|
609
612
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
610
613
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
611
614
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
612
|
-
|
|
615
|
+
this.disconnect(); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
|
|
616
|
+
const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
|
|
613
617
|
this.close();
|
|
614
618
|
return pendingState;
|
|
615
619
|
}
|
|
616
|
-
getPendingLocalState() {
|
|
620
|
+
async getPendingLocalState() {
|
|
621
|
+
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
622
|
+
}
|
|
623
|
+
async getPendingLocalStateCore(props) {
|
|
617
624
|
if (!this.offlineLoadEnabled) {
|
|
618
625
|
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
619
626
|
}
|
|
@@ -624,8 +631,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
624
631
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
625
632
|
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
626
633
|
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
634
|
+
const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
|
|
627
635
|
const pendingState = {
|
|
628
|
-
pendingRuntimeState
|
|
636
|
+
pendingRuntimeState,
|
|
629
637
|
baseSnapshot: this.baseSnapshot,
|
|
630
638
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
631
639
|
savedOps: this.savedOps,
|
|
@@ -654,7 +662,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
654
662
|
}
|
|
655
663
|
async attach(request) {
|
|
656
664
|
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
657
|
-
var _a;
|
|
658
665
|
if (this._lifecycleState !== "loaded") {
|
|
659
666
|
// pre-0.58 error message: containerNotValidForAttach
|
|
660
667
|
throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
@@ -746,7 +753,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
746
753
|
catch (error) {
|
|
747
754
|
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
748
755
|
const newError = normalizeError(error);
|
|
749
|
-
newError.addTelemetryProperties({ resolvedUrl:
|
|
756
|
+
newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
|
|
750
757
|
this.close(newError);
|
|
751
758
|
throw newError;
|
|
752
759
|
}
|
|
@@ -853,7 +860,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
853
860
|
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
854
861
|
*/
|
|
855
862
|
async satisfies(constraintCodeDetails) {
|
|
856
|
-
var _a, _b;
|
|
857
863
|
// If we have no module, it can't satisfy anything.
|
|
858
864
|
if (this._loadedModule === undefined) {
|
|
859
865
|
return false;
|
|
@@ -863,8 +869,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
863
869
|
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
864
870
|
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
865
871
|
}
|
|
866
|
-
const maybeCompareExport =
|
|
867
|
-
if (
|
|
872
|
+
const maybeCompareExport = this._loadedModule?.module.fluidExport;
|
|
873
|
+
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
|
|
868
874
|
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
869
875
|
}
|
|
870
876
|
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
@@ -874,7 +880,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
874
880
|
return false;
|
|
875
881
|
}
|
|
876
882
|
for (const comparer of comparers) {
|
|
877
|
-
const satisfies = await comparer.satisfies(
|
|
883
|
+
const satisfies = await comparer.satisfies(this._loadedModule?.details, constraintCodeDetails);
|
|
878
884
|
if (satisfies === false) {
|
|
879
885
|
return false;
|
|
880
886
|
}
|
|
@@ -897,8 +903,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
897
903
|
*
|
|
898
904
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
899
905
|
*/
|
|
900
|
-
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
|
|
901
|
-
var _a, _b, _c;
|
|
906
|
+
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
|
|
902
907
|
this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
|
|
903
908
|
// Ideally we always connect as "read" by default.
|
|
904
909
|
// Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
|
|
@@ -947,9 +952,51 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
947
952
|
}
|
|
948
953
|
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
|
|
949
954
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
950
|
-
const sequenceNumber =
|
|
951
|
-
const dmAttributes = sequenceNumber !== undefined ?
|
|
955
|
+
const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
|
|
956
|
+
const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
|
|
952
957
|
let opsBeforeReturnP;
|
|
958
|
+
if (loadMode.pauseAfterLoad === true) {
|
|
959
|
+
// If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
|
|
960
|
+
if (loadMode.opsBeforeReturn === "sequenceNumber") {
|
|
961
|
+
assert(loadToSequenceNumber !== undefined, 0x727 /* sequenceNumber should be defined */);
|
|
962
|
+
// Note: It is possible that we think the latest snapshot is newer than the specified sequence number
|
|
963
|
+
// due to saved ops that may be replayed after the snapshot.
|
|
964
|
+
// https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
|
|
965
|
+
if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
|
|
966
|
+
throw new Error("Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.");
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
// Force readonly mode - this will ensure we don't receive an error for the lack of join op
|
|
970
|
+
this.forceReadonly(true);
|
|
971
|
+
// We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
|
|
972
|
+
const opHandler = () => {
|
|
973
|
+
if (loadToSequenceNumber === undefined) {
|
|
974
|
+
// If there is no specified sequence number, pause after the inbound queue is empty.
|
|
975
|
+
if (this.deltaManager.inbound.length !== 0) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
else {
|
|
980
|
+
// If there is a specified sequence number, keep processing until we reach it.
|
|
981
|
+
if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
// Pause op processing once we have processed the desired number of ops.
|
|
986
|
+
void this.deltaManager.inbound.pause();
|
|
987
|
+
void this.deltaManager.outbound.pause();
|
|
988
|
+
this.off("op", opHandler);
|
|
989
|
+
};
|
|
990
|
+
if ((loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
|
|
991
|
+
this.deltaManager.lastSequenceNumber === loadToSequenceNumber) {
|
|
992
|
+
// If we have already reached the desired sequence number, call opHandler() to pause immediately.
|
|
993
|
+
opHandler();
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
// If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
|
|
997
|
+
this.on("op", opHandler);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
953
1000
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
954
1001
|
// Kick off any ops fetching if required.
|
|
955
1002
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -958,6 +1005,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
958
1005
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
959
1006
|
this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
|
|
960
1007
|
break;
|
|
1008
|
+
case "sequenceNumber":
|
|
1009
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "sequenceNumber");
|
|
1010
|
+
break;
|
|
961
1011
|
case "cached":
|
|
962
1012
|
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
963
1013
|
break;
|
|
@@ -971,19 +1021,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
971
1021
|
// Initialize the protocol handler
|
|
972
1022
|
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
973
1023
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
974
|
-
await this.
|
|
975
|
-
codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
|
|
1024
|
+
await this.instantiateRuntime(codeDetails, snapshot, pendingLocalState?.pendingRuntimeState);
|
|
976
1025
|
// replay saved ops
|
|
977
1026
|
if (pendingLocalState) {
|
|
978
1027
|
for (const message of pendingLocalState.savedOps) {
|
|
979
1028
|
this.processRemoteMessage(message);
|
|
980
1029
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
981
|
-
await
|
|
1030
|
+
await this.runtime.notifyOpReplay?.(message);
|
|
982
1031
|
}
|
|
983
1032
|
pendingLocalState.savedOps = [];
|
|
984
1033
|
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
985
1034
|
assert(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
|
|
986
|
-
this._clientId = pendingLocalState
|
|
1035
|
+
this._clientId = pendingLocalState?.clientId;
|
|
987
1036
|
}
|
|
988
1037
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
989
1038
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
@@ -1014,6 +1063,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1014
1063
|
unreachableCase(loadMode.deltaConnection);
|
|
1015
1064
|
}
|
|
1016
1065
|
}
|
|
1066
|
+
// If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
|
|
1067
|
+
if (loadToSequenceNumber !== undefined &&
|
|
1068
|
+
this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1069
|
+
await new Promise((resolve, reject) => {
|
|
1070
|
+
const opHandler = (message) => {
|
|
1071
|
+
if (message.sequenceNumber >= loadToSequenceNumber) {
|
|
1072
|
+
resolve();
|
|
1073
|
+
this.off("op", opHandler);
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
this.on("op", opHandler);
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1017
1079
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
1018
1080
|
// But if that did not happen for some reason, fail load for sure.
|
|
1019
1081
|
// Otherwise we can get into situations where container is closed and does not try to connect to ordering
|
|
@@ -1031,7 +1093,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1031
1093
|
dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
|
|
1032
1094
|
};
|
|
1033
1095
|
}
|
|
1034
|
-
async createDetached(
|
|
1096
|
+
async createDetached(codeDetails) {
|
|
1035
1097
|
const attributes = {
|
|
1036
1098
|
sequenceNumber: detachedContainerRefSeqNumber,
|
|
1037
1099
|
term: OnlyValidTermValue,
|
|
@@ -1039,14 +1101,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1039
1101
|
};
|
|
1040
1102
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1041
1103
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1042
|
-
const qValues = initQuorumValuesFromCodeDetails(
|
|
1104
|
+
const qValues = initQuorumValuesFromCodeDetails(codeDetails);
|
|
1043
1105
|
this.initializeProtocolState(attributes, {
|
|
1044
1106
|
members: [],
|
|
1045
1107
|
proposals: [],
|
|
1046
1108
|
values: qValues,
|
|
1047
1109
|
});
|
|
1048
|
-
|
|
1049
|
-
await this.instantiateContextDetached(false);
|
|
1110
|
+
await this.instantiateRuntime(codeDetails, undefined);
|
|
1050
1111
|
this.setLoaded();
|
|
1051
1112
|
}
|
|
1052
1113
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
@@ -1061,14 +1122,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1061
1122
|
// Initialize the protocol handler
|
|
1062
1123
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1063
1124
|
const qValues = await readAndParse(this.storageAdapter, baseTree.blobs.quorumValues);
|
|
1064
|
-
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1065
1125
|
this.initializeProtocolState(attributes, {
|
|
1066
1126
|
members: [],
|
|
1067
1127
|
proposals: [],
|
|
1068
|
-
values:
|
|
1128
|
+
values: qValues,
|
|
1069
1129
|
});
|
|
1070
|
-
|
|
1071
|
-
snapshotTree);
|
|
1130
|
+
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1131
|
+
await this.instantiateRuntime(codeDetails, snapshotTree);
|
|
1072
1132
|
this.setLoaded();
|
|
1073
1133
|
}
|
|
1074
1134
|
async getDocumentAttributes(storage, tree) {
|
|
@@ -1105,7 +1165,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1105
1165
|
}
|
|
1106
1166
|
initializeProtocolState(attributes, quorumSnapshot) {
|
|
1107
1167
|
const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })));
|
|
1108
|
-
const protocolLogger =
|
|
1168
|
+
const protocolLogger = createChildLogger({
|
|
1169
|
+
logger: this.subLogger,
|
|
1170
|
+
namespace: "ProtocolHandler",
|
|
1171
|
+
});
|
|
1109
1172
|
protocol.quorum.on("error", (error) => {
|
|
1110
1173
|
protocolLogger.sendErrorEvent(error);
|
|
1111
1174
|
});
|
|
@@ -1167,8 +1230,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1167
1230
|
return pkg;
|
|
1168
1231
|
}
|
|
1169
1232
|
get client() {
|
|
1170
|
-
|
|
1171
|
-
const client = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.client) !== undefined
|
|
1233
|
+
const client = this.options?.client !== undefined
|
|
1172
1234
|
? this.options.client
|
|
1173
1235
|
: {
|
|
1174
1236
|
details: {
|
|
@@ -1199,7 +1261,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1199
1261
|
}
|
|
1200
1262
|
createDeltaManager() {
|
|
1201
1263
|
const serviceProvider = () => this.service;
|
|
1202
|
-
const deltaManager = new DeltaManager(serviceProvider,
|
|
1264
|
+
const deltaManager = new DeltaManager(serviceProvider, createChildLogger({ logger: this.subLogger, namespace: "DeltaManager" }), () => this.activeConnection(), (props) => new ConnectionManager(serviceProvider, () => this.isDirty, this.client, this._canReconnect, createChildLogger({ logger: this.subLogger, namespace: "ConnectionManager" }), props));
|
|
1203
1265
|
// Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
|
|
1204
1266
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1205
1267
|
deltaManager.inbound.pause();
|
|
@@ -1216,8 +1278,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1216
1278
|
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1217
1279
|
});
|
|
1218
1280
|
deltaManager.on("disconnect", (reason, error) => {
|
|
1219
|
-
|
|
1220
|
-
(_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
|
|
1281
|
+
this.noopHeuristic?.notifyDisconnect();
|
|
1221
1282
|
if (!this.closed) {
|
|
1222
1283
|
this.connectionStateHandler.receivedDisconnectEvent(reason, error);
|
|
1223
1284
|
}
|
|
@@ -1252,7 +1313,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1252
1313
|
}, prefetchType);
|
|
1253
1314
|
}
|
|
1254
1315
|
logConnectionStateChangeTelemetry(value, oldState, reason, error) {
|
|
1255
|
-
var _a;
|
|
1256
1316
|
// Log actual event
|
|
1257
1317
|
const time = performance.now();
|
|
1258
1318
|
this.connectionTransitionTimes[value] = time;
|
|
@@ -1269,7 +1329,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1269
1329
|
if (value === ConnectionState.Connected) {
|
|
1270
1330
|
durationFromDisconnected =
|
|
1271
1331
|
time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
1272
|
-
durationFromDisconnected =
|
|
1332
|
+
durationFromDisconnected = formatTick(durationFromDisconnected);
|
|
1273
1333
|
}
|
|
1274
1334
|
else if (value === ConnectionState.CatchingUp) {
|
|
1275
1335
|
// This info is of most interesting while Catching Up.
|
|
@@ -1282,19 +1342,31 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1282
1342
|
}
|
|
1283
1343
|
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1284
1344
|
}
|
|
1285
|
-
this.mc.logger.sendPerformanceEvent(
|
|
1345
|
+
this.mc.logger.sendPerformanceEvent({
|
|
1346
|
+
eventName: `ConnectionStateChange_${ConnectionState[value]}`,
|
|
1347
|
+
from: ConnectionState[oldState],
|
|
1348
|
+
duration,
|
|
1286
1349
|
durationFromDisconnected,
|
|
1287
1350
|
reason,
|
|
1288
|
-
connectionInitiationReason,
|
|
1289
|
-
|
|
1351
|
+
connectionInitiationReason,
|
|
1352
|
+
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
1353
|
+
clientId: this.clientId,
|
|
1354
|
+
autoReconnect,
|
|
1355
|
+
opsBehind,
|
|
1356
|
+
online: OnlineStatus[isOnline()],
|
|
1357
|
+
lastVisible: this.lastVisible !== undefined
|
|
1290
1358
|
? performance.now() - this.lastVisible
|
|
1291
|
-
: undefined,
|
|
1359
|
+
: undefined,
|
|
1360
|
+
checkpointSequenceNumber,
|
|
1361
|
+
quorumSize: this._protocolHandler?.quorum.getMembers().size,
|
|
1362
|
+
isDirty: this.isDirty,
|
|
1363
|
+
...this._deltaManager.connectionProps,
|
|
1364
|
+
}, error);
|
|
1292
1365
|
if (value === ConnectionState.Connected) {
|
|
1293
1366
|
this.firstConnection = false;
|
|
1294
1367
|
}
|
|
1295
1368
|
}
|
|
1296
1369
|
propagateConnectionState(initialTransition, disconnectedReason) {
|
|
1297
|
-
var _a;
|
|
1298
1370
|
// When container loaded, we want to propagate initial connection state.
|
|
1299
1371
|
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1300
1372
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
@@ -1304,22 +1376,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1304
1376
|
return;
|
|
1305
1377
|
}
|
|
1306
1378
|
const state = this.connectionState === ConnectionState.Connected;
|
|
1307
|
-
const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
|
|
1308
|
-
!this.firstConnection &&
|
|
1309
|
-
this.connectionMode === "write";
|
|
1310
|
-
if (logOpsOnReconnect) {
|
|
1311
|
-
this.messageCountAfterDisconnection = 0;
|
|
1312
|
-
}
|
|
1313
1379
|
// Both protocol and context should not be undefined if we got so far.
|
|
1314
|
-
this.setContextConnectedState(state,
|
|
1380
|
+
this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
|
|
1315
1381
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1316
1382
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1317
|
-
if (logOpsOnReconnect) {
|
|
1318
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1319
|
-
eventName: "OpsSentOnReconnect",
|
|
1320
|
-
count: this.messageCountAfterDisconnection,
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
1383
|
}
|
|
1324
1384
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1325
1385
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
@@ -1357,13 +1417,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1357
1417
|
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
|
|
1358
1418
|
}
|
|
1359
1419
|
submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
|
|
1360
|
-
var _a;
|
|
1361
1420
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1362
1421
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
1363
1422
|
return -1;
|
|
1364
1423
|
}
|
|
1365
|
-
this.
|
|
1366
|
-
(_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
|
|
1424
|
+
this.noopHeuristic?.notifyMessageSent();
|
|
1367
1425
|
return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
|
|
1368
1426
|
}
|
|
1369
1427
|
processRemoteMessage(message) {
|
|
@@ -1438,8 +1496,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1438
1496
|
* @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
|
|
1439
1497
|
*/
|
|
1440
1498
|
async fetchSnapshotTree(specifiedVersion) {
|
|
1441
|
-
|
|
1442
|
-
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1499
|
+
const version = await this.getVersion(specifiedVersion ?? null);
|
|
1443
1500
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1444
1501
|
// We should have a defined version to load from if specified version requested
|
|
1445
1502
|
this.mc.logger.sendErrorEvent({
|
|
@@ -1448,22 +1505,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1448
1505
|
});
|
|
1449
1506
|
}
|
|
1450
1507
|
this._loadedFromVersion = version;
|
|
1451
|
-
const snapshot = (
|
|
1508
|
+
const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
|
|
1452
1509
|
if (snapshot === undefined && version !== undefined) {
|
|
1453
1510
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1454
1511
|
}
|
|
1455
|
-
return { snapshot, versionId: version
|
|
1456
|
-
}
|
|
1457
|
-
async instantiateContextDetached(existing, snapshot) {
|
|
1458
|
-
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1459
|
-
if (codeDetails === undefined) {
|
|
1460
|
-
throw new Error("pkg should be provided in create flow!!");
|
|
1461
|
-
}
|
|
1462
|
-
await this.instantiateContext(existing, codeDetails, snapshot);
|
|
1512
|
+
return { snapshot, versionId: version?.id };
|
|
1463
1513
|
}
|
|
1464
|
-
async
|
|
1465
|
-
|
|
1466
|
-
assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1514
|
+
async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
|
|
1515
|
+
assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1467
1516
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1468
1517
|
// are set. Global requests will still go directly to the loader
|
|
1469
1518
|
const maybeLoader = this.scope;
|
|
@@ -1474,25 +1523,20 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1474
1523
|
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
1475
1524
|
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
1476
1525
|
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
1477
|
-
details:
|
|
1526
|
+
details: loadCodeResult.details ?? codeDetails,
|
|
1478
1527
|
};
|
|
1479
1528
|
const fluidExport = this._loadedModule.module.fluidExport;
|
|
1480
|
-
const runtimeFactory = fluidExport
|
|
1529
|
+
const runtimeFactory = fluidExport?.IRuntimeFactory;
|
|
1481
1530
|
if (runtimeFactory === undefined) {
|
|
1482
1531
|
throw new Error(packageNotFactoryError);
|
|
1483
1532
|
}
|
|
1484
|
-
const getSpecifiedCodeDetails = () =>
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
const context = new ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, this.protocolHandler.audience, loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; }, () => this.clientId, () => this._deltaManager.serviceConfiguration, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
|
|
1489
|
-
this._lifecycleEvents.once("disposed", () => {
|
|
1490
|
-
context.dispose();
|
|
1491
|
-
});
|
|
1533
|
+
const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
|
|
1534
|
+
this.protocolHandler.quorum.get("code2"));
|
|
1535
|
+
const existing = snapshot !== undefined;
|
|
1536
|
+
const context = new ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, this.protocolHandler.audience, loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => this.clientId, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
|
|
1492
1537
|
this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
|
|
1493
1538
|
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
1494
1539
|
this._loadedCodeDetails = codeDetails;
|
|
1495
|
-
this.emit("contextChanged", codeDetails);
|
|
1496
1540
|
}
|
|
1497
1541
|
/**
|
|
1498
1542
|
* Set the connected state of the ContainerContext
|
|
@@ -1501,8 +1545,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1501
1545
|
* @param readonly - Is the container in readonly mode?
|
|
1502
1546
|
*/
|
|
1503
1547
|
setContextConnectedState(state, readonly) {
|
|
1504
|
-
|
|
1505
|
-
if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
|
|
1548
|
+
if (this._runtime?.disposed === false) {
|
|
1506
1549
|
/**
|
|
1507
1550
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1508
1551
|
* ops getting through to the DeltaManager.
|