@fluidframework/container-loader 2.0.0-dev.5.3.2.178189 → 2.0.0-dev.6.4.0.191258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -0
- package/README.md +10 -6
- package/dist/audience.d.ts +1 -0
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +5 -3
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.js +2 -2
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts +5 -5
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +97 -93
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +15 -14
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +50 -52
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +20 -9
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +327 -277
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +2 -7
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +2 -14
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +12 -13
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +21 -8
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -3
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.d.ts +30 -0
- package/dist/debugLogger.d.ts.map +1 -0
- package/dist/debugLogger.js +95 -0
- package/dist/debugLogger.js.map +1 -0
- package/dist/deltaManager.d.ts +21 -10
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +114 -66
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.d.ts +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +10 -10
- package/dist/deltaQueue.js.map +1 -1
- package/dist/disposal.d.ts +2 -2
- package/dist/disposal.d.ts.map +1 -1
- package/dist/disposal.js +1 -1
- package/dist/disposal.js.map +1 -1
- package/dist/error.d.ts +23 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +32 -0
- package/dist/error.js.map +1 -0
- package/dist/loader.d.ts +22 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +82 -51
- package/dist/loader.js.map +1 -1
- package/dist/noopHeuristic.d.ts +2 -2
- package/dist/noopHeuristic.d.ts.map +1 -1
- package/dist/noopHeuristic.js +6 -5
- package/dist/noopHeuristic.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +4 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +25 -4
- package/dist/protocol.js.map +1 -1
- package/dist/quorum.d.ts +4 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +1 -13
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +4 -4
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts +8 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +30 -11
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts +1 -0
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +4 -2
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.js +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts +5 -5
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +74 -67
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +15 -14
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +27 -29
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +20 -9
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +288 -238
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +2 -7
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +2 -14
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +5 -6
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +21 -8
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +3 -3
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.d.ts +30 -0
- package/lib/debugLogger.d.ts.map +1 -0
- package/lib/debugLogger.js +91 -0
- package/lib/debugLogger.js.map +1 -0
- package/lib/deltaManager.d.ts +21 -10
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +88 -37
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.d.ts +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +3 -3
- package/lib/deltaQueue.js.map +1 -1
- package/lib/disposal.d.ts +2 -2
- package/lib/disposal.d.ts.map +1 -1
- package/lib/disposal.js +1 -1
- package/lib/disposal.js.map +1 -1
- package/lib/error.d.ts +23 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +28 -0
- package/lib/error.js.map +1 -0
- package/lib/loader.d.ts +22 -3
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +82 -51
- package/lib/loader.js.map +1 -1
- package/lib/noopHeuristic.d.ts +2 -2
- package/lib/noopHeuristic.d.ts.map +1 -1
- package/lib/noopHeuristic.js +2 -1
- package/lib/noopHeuristic.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +4 -2
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +25 -4
- package/lib/protocol.js.map +1 -1
- package/lib/quorum.d.ts +4 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +0 -11
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +2 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts +8 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +25 -7
- package/lib/utils.js.map +1 -1
- package/package.json +26 -32
- package/src/audience.ts +7 -1
- package/src/catchUpMonitor.ts +1 -1
- package/src/connectionManager.ts +75 -51
- package/src/connectionStateHandler.ts +31 -38
- package/src/container.ts +335 -240
- package/src/containerContext.ts +0 -16
- package/src/containerStorageAdapter.ts +2 -1
- package/src/contracts.ts +27 -11
- package/src/debugLogger.ts +113 -0
- package/src/deltaManager.ts +84 -34
- package/src/deltaQueue.ts +2 -1
- package/src/disposal.ts +2 -2
- package/src/error.ts +44 -0
- package/src/loader.ts +83 -35
- package/src/noopHeuristic.ts +3 -2
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +33 -2
- package/src/quorum.ts +0 -10
- package/src/retriableDocumentStorageService.ts +2 -4
- package/src/utils.ts +33 -8
package/lib/container.js
CHANGED
|
@@ -5,22 +5,23 @@
|
|
|
5
5
|
// eslint-disable-next-line import/no-internal-modules
|
|
6
6
|
import merge from "lodash/merge";
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
|
-
import {
|
|
8
|
+
import { assert, unreachableCase } from "@fluidframework/core-utils";
|
|
9
|
+
import { TypedEventEmitter, performance } from "@fluid-internal/client-utils";
|
|
10
|
+
import { LogLevel, } from "@fluidframework/core-interfaces";
|
|
9
11
|
import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
|
|
10
|
-
import {
|
|
11
|
-
import { readAndParse, OnlineStatus, isOnline, combineAppAndProtocolSummary, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, canBeCoalescedByService, } from "@fluidframework/driver-utils";
|
|
12
|
+
import { readAndParse, OnlineStatus, isOnline, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, } from "@fluidframework/driver-utils";
|
|
12
13
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
|
-
import {
|
|
14
|
+
import { createChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, connectedEventName, normalizeError, createChildMonitoringContext, wrapError, formatTick, GenericError, UsageError, } from "@fluidframework/telemetry-utils";
|
|
14
15
|
import { Audience } from "./audience";
|
|
15
16
|
import { ContainerContext } from "./containerContext";
|
|
16
|
-
import { ReconnectMode, getPackageName } from "./contracts";
|
|
17
|
+
import { ReconnectMode, getPackageName, } from "./contracts";
|
|
17
18
|
import { DeltaManager } from "./deltaManager";
|
|
18
19
|
import { RelativeLoader } from "./loader";
|
|
19
20
|
import { pkgVersion } from "./packageVersion";
|
|
20
21
|
import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTreeWithBlobContents, } from "./containerStorageAdapter";
|
|
21
22
|
import { createConnectionStateHandler } from "./connectionStateHandler";
|
|
22
|
-
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
23
|
-
import { initQuorumValuesFromCodeDetails
|
|
23
|
+
import { combineAppAndProtocolSummary, getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer, } from "./utils";
|
|
24
|
+
import { initQuorumValuesFromCodeDetails } from "./quorum";
|
|
24
25
|
import { NoopHeuristic } from "./noopHeuristic";
|
|
25
26
|
import { ConnectionManager } from "./connectionManager";
|
|
26
27
|
import { ConnectionState } from "./connectionState";
|
|
@@ -104,7 +105,7 @@ export async function waitContainerToCatchUp(container) {
|
|
|
104
105
|
}
|
|
105
106
|
const getCodeProposal =
|
|
106
107
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
107
|
-
(quorum) =>
|
|
108
|
+
(quorum) => quorum.get("code") ?? quorum.get("code2");
|
|
108
109
|
/**
|
|
109
110
|
* Helper function to report to telemetry cases where operation takes longer than expected (200ms)
|
|
110
111
|
* @param logger - logger to use
|
|
@@ -124,7 +125,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
124
125
|
* @internal
|
|
125
126
|
*/
|
|
126
127
|
constructor(createProps, loadProps) {
|
|
127
|
-
var _a;
|
|
128
128
|
super((name, error) => {
|
|
129
129
|
this.mc.logger.sendErrorEvent({
|
|
130
130
|
eventName: "ContainerEventHandlerException",
|
|
@@ -152,7 +152,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
152
152
|
this.inboundQueuePausedFromInit = true;
|
|
153
153
|
this.firstConnection = true;
|
|
154
154
|
this.connectionTransitionTimes = [];
|
|
155
|
-
this.messageCountAfterDisconnection = 0;
|
|
156
155
|
this.attachStarted = false;
|
|
157
156
|
this._dirtyContainer = false;
|
|
158
157
|
this.savedOps = [];
|
|
@@ -175,8 +174,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
175
174
|
};
|
|
176
175
|
const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
|
|
177
176
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
178
|
-
const pendingLocalState = loadProps
|
|
179
|
-
this.
|
|
177
|
+
const pendingLocalState = loadProps?.pendingLocalState;
|
|
178
|
+
this._clientId = pendingLocalState?.clientId;
|
|
179
|
+
this._canReconnect = canReconnect ?? true;
|
|
180
180
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
181
181
|
this.urlResolver = urlResolver;
|
|
182
182
|
this.serviceFactory = documentServiceFactory;
|
|
@@ -184,14 +184,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
184
184
|
// Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
|
|
185
185
|
// all clients that were loaded from the same loader (including summarizer clients).
|
|
186
186
|
// Tracking alternative ways to handle this in AB#4129.
|
|
187
|
-
this.options =
|
|
187
|
+
this.options = { ...options };
|
|
188
188
|
this.scope = scope;
|
|
189
189
|
this.detachedBlobStorage = detachedBlobStorage;
|
|
190
190
|
this.protocolHandlerBuilder =
|
|
191
|
-
protocolHandlerBuilder
|
|
191
|
+
protocolHandlerBuilder ??
|
|
192
|
+
((attributes, quorumSnapshot, sendProposal) => new ProtocolHandler(attributes, quorumSnapshot, sendProposal, new Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
|
|
192
193
|
// Note that we capture the createProps here so we can replicate the creation call when we want to clone.
|
|
193
194
|
this.clone = async (_loadProps, createParamOverrides) => {
|
|
194
|
-
return Container.load(_loadProps,
|
|
195
|
+
return Container.load(_loadProps, {
|
|
196
|
+
...createProps,
|
|
197
|
+
...createParamOverrides,
|
|
198
|
+
});
|
|
195
199
|
};
|
|
196
200
|
// Create logger for data stores to use
|
|
197
201
|
const type = this.client.details.type;
|
|
@@ -199,50 +203,50 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
199
203
|
const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
|
|
200
204
|
// Need to use the property getter for docId because for detached flow we don't have the docId initially.
|
|
201
205
|
// 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
|
-
|
|
206
|
+
this.subLogger = createChildLogger({
|
|
207
|
+
logger: subLogger,
|
|
208
|
+
properties: {
|
|
209
|
+
all: {
|
|
210
|
+
clientType,
|
|
211
|
+
containerId: uuid(),
|
|
212
|
+
docId: () => this.resolvedUrl?.id,
|
|
213
|
+
containerAttachState: () => this._attachState,
|
|
214
|
+
containerLifecycleState: () => this._lifecycleState,
|
|
215
|
+
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
216
|
+
serializedContainer: pendingLocalState !== undefined,
|
|
217
|
+
},
|
|
218
|
+
// we need to be judicious with our logging here to avoid generating too much data
|
|
219
|
+
// all data logged here should be broadly applicable, and not specific to a
|
|
220
|
+
// specific error or class of errors
|
|
221
|
+
error: {
|
|
222
|
+
// load information to associate errors with the specific load point
|
|
223
|
+
dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
|
|
224
|
+
dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
|
|
225
|
+
dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
|
|
226
|
+
containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
|
|
227
|
+
containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
|
|
228
|
+
// message information to associate errors with the specific execution state
|
|
229
|
+
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
230
|
+
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
231
|
+
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
232
|
+
dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
|
|
229
233
|
? "null"
|
|
230
|
-
:
|
|
234
|
+
: this.deltaManager?.lastMessage?.clientId,
|
|
235
|
+
dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
|
|
236
|
+
connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
231
237
|
},
|
|
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
238
|
},
|
|
235
239
|
});
|
|
236
240
|
// Prefix all events in this file with container-loader
|
|
237
|
-
this.mc =
|
|
241
|
+
this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
|
|
238
242
|
this._deltaManager = this.createDeltaManager();
|
|
239
243
|
this.connectionStateHandler = createConnectionStateHandler({
|
|
240
244
|
logger: this.mc.logger,
|
|
241
|
-
connectionStateChanged: (value, oldState, reason
|
|
245
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
242
246
|
if (value === ConnectionState.Connected) {
|
|
243
247
|
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
244
248
|
}
|
|
245
|
-
this.logConnectionStateChangeTelemetry(value, oldState, reason
|
|
249
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
246
250
|
if (this._lifecycleState === "loaded") {
|
|
247
251
|
this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
|
|
248
252
|
? reason
|
|
@@ -258,9 +262,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
258
262
|
// Report issues only if we already loaded container - op processing is paused while container is loading,
|
|
259
263
|
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
260
264
|
// 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
|
-
|
|
265
|
+
this._deltaManager.logConnectionIssue({
|
|
266
|
+
eventName,
|
|
267
|
+
mode,
|
|
268
|
+
category: this._lifecycleState === "loading" ? "generic" : category,
|
|
269
|
+
duration: performance.now() -
|
|
270
|
+
this.connectionTransitionTimes[ConnectionState.CatchingUp],
|
|
271
|
+
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
272
|
+
});
|
|
264
273
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
265
274
|
// to very slow op fetches and we will eventually get there.
|
|
266
275
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -270,14 +279,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
270
279
|
// Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
|
|
271
280
|
// to call this.applyForConnectedState("addMemberEvent") for "read" connections)
|
|
272
281
|
if (mode === "read") {
|
|
273
|
-
|
|
274
|
-
this.
|
|
282
|
+
const reason = { text: "NoJoinSignal" };
|
|
283
|
+
this.disconnectInternal(reason);
|
|
284
|
+
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
275
285
|
}
|
|
276
286
|
},
|
|
277
287
|
clientShouldHaveLeft: (clientId) => {
|
|
278
288
|
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
279
289
|
},
|
|
280
|
-
}, this.deltaManager, pendingLocalState
|
|
290
|
+
}, this.deltaManager, pendingLocalState?.clientId);
|
|
281
291
|
this.on(savedContainerEvent, () => {
|
|
282
292
|
this.connectionStateHandler.containerSaved();
|
|
283
293
|
});
|
|
@@ -289,14 +299,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
289
299
|
: combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
|
|
290
300
|
// Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
|
|
291
301
|
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
292
|
-
const forceEnableSummarizeProtocolTree =
|
|
293
|
-
|
|
302
|
+
const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
|
|
303
|
+
options.summarizeProtocolTree;
|
|
304
|
+
this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
294
305
|
const isDomAvailable = typeof document === "object" &&
|
|
295
306
|
document !== null &&
|
|
296
307
|
typeof document.addEventListener === "function" &&
|
|
297
308
|
document.addEventListener !== null;
|
|
298
|
-
// keep track of last time page was visible for telemetry
|
|
299
|
-
if (isDomAvailable) {
|
|
309
|
+
// keep track of last time page was visible for telemetry (on interactive clients only)
|
|
310
|
+
if (isDomAvailable && interactive) {
|
|
300
311
|
this.lastVisible = document.hidden ? performance.now() : undefined;
|
|
301
312
|
this.visibilityEventHandler = () => {
|
|
302
313
|
if (document.hidden) {
|
|
@@ -317,7 +328,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
317
328
|
* @internal
|
|
318
329
|
*/
|
|
319
330
|
static async load(loadProps, createProps) {
|
|
320
|
-
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
331
|
+
const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } = loadProps;
|
|
321
332
|
const container = new Container(createProps, loadProps);
|
|
322
333
|
const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
|
|
323
334
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
@@ -325,26 +336,31 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
325
336
|
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
326
337
|
// to return container, so ignore this value and use undefined for opsBeforeReturn
|
|
327
338
|
const mode = pendingLocalState
|
|
328
|
-
?
|
|
339
|
+
? { ...(loadMode ?? defaultMode), opsBeforeReturn: undefined }
|
|
340
|
+
: loadMode ?? defaultMode;
|
|
329
341
|
const onClosed = (err) => {
|
|
330
342
|
// pre-0.58 error message: containerClosedWithoutErrorDuringLoad
|
|
331
|
-
reject(err
|
|
343
|
+
reject(err ?? new GenericError("Container closed without error during load"));
|
|
332
344
|
};
|
|
333
345
|
container.on("closed", onClosed);
|
|
334
346
|
container
|
|
335
|
-
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
347
|
+
.load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
|
|
336
348
|
.finally(() => {
|
|
337
349
|
container.removeListener("closed", onClosed);
|
|
338
350
|
})
|
|
339
351
|
.then((props) => {
|
|
340
|
-
event.end(
|
|
352
|
+
event.end({ ...props, ...loadMode });
|
|
341
353
|
resolve(container);
|
|
342
354
|
}, (error) => {
|
|
343
355
|
const err = normalizeError(error);
|
|
344
356
|
// Depending where error happens, we can be attempting to connect to web socket
|
|
345
357
|
// and continuously retrying (consider offline mode)
|
|
346
358
|
// Host has no container to close, so it's prudent to do it here
|
|
359
|
+
// Note: We could only dispose the container instead of just close but that would
|
|
360
|
+
// the telemetry where users sometimes search for ContainerClose event to look
|
|
361
|
+
// for load failures. So not removing this at this time.
|
|
347
362
|
container.close(err);
|
|
363
|
+
container.dispose(err);
|
|
348
364
|
onClosed(err);
|
|
349
365
|
});
|
|
350
366
|
}), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
|
|
@@ -381,10 +397,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
381
397
|
}
|
|
382
398
|
}
|
|
383
399
|
get closed() {
|
|
384
|
-
return (this._lifecycleState === "closing" ||
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
400
|
+
return (this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed);
|
|
401
|
+
}
|
|
402
|
+
get disposed() {
|
|
403
|
+
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
388
404
|
}
|
|
389
405
|
get runtime() {
|
|
390
406
|
if (this._runtime === undefined) {
|
|
@@ -405,7 +421,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
405
421
|
return this;
|
|
406
422
|
}
|
|
407
423
|
get resolvedUrl() {
|
|
408
|
-
var _a;
|
|
409
424
|
/**
|
|
410
425
|
* All attached containers will have a document service,
|
|
411
426
|
* this is required, as attached containers are attached to
|
|
@@ -417,7 +432,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
417
432
|
* is always the same as the containers, as we had to
|
|
418
433
|
* obtain the resolved url, and then create the service from it.
|
|
419
434
|
*/
|
|
420
|
-
return
|
|
435
|
+
return this.service?.resolvedUrl;
|
|
421
436
|
}
|
|
422
437
|
get readOnlyInfo() {
|
|
423
438
|
return this._deltaManager.readOnlyInfo;
|
|
@@ -445,8 +460,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
445
460
|
return this._clientId;
|
|
446
461
|
}
|
|
447
462
|
get offlineLoadEnabled() {
|
|
448
|
-
|
|
449
|
-
|
|
463
|
+
const enabled = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
|
|
464
|
+
this.options?.enableOfflineLoad === true;
|
|
450
465
|
// summarizer will not have any pending state we want to save
|
|
451
466
|
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
452
467
|
}
|
|
@@ -483,18 +498,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
483
498
|
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
484
499
|
*/
|
|
485
500
|
async getEntryPoint() {
|
|
486
|
-
var _a, _b;
|
|
487
501
|
if (this._disposed) {
|
|
488
502
|
throw new UsageError("The context is already disposed");
|
|
489
503
|
}
|
|
490
504
|
if (this._runtime !== undefined) {
|
|
491
|
-
return
|
|
505
|
+
return this._runtime.getEntryPoint?.();
|
|
492
506
|
}
|
|
493
507
|
return new Promise((resolve, reject) => {
|
|
494
508
|
const runtimeInstantiatedHandler = () => {
|
|
495
|
-
var _a, _b;
|
|
496
509
|
assert(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
|
|
497
|
-
resolve(
|
|
510
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
498
511
|
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
499
512
|
};
|
|
500
513
|
const disposedHandler = () => {
|
|
@@ -528,7 +541,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
528
541
|
assert(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
|
|
529
542
|
}
|
|
530
543
|
closeCore(error) {
|
|
531
|
-
var _a;
|
|
532
544
|
assert(!this.closed, 0x315 /* re-entrancy */);
|
|
533
545
|
try {
|
|
534
546
|
// Ensure that we raise all key events even if one of these throws
|
|
@@ -544,7 +556,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
544
556
|
: "generic",
|
|
545
557
|
}, error);
|
|
546
558
|
this._lifecycleState = "closing";
|
|
547
|
-
|
|
559
|
+
this._protocolHandler?.close();
|
|
548
560
|
this.connectionStateHandler.dispose();
|
|
549
561
|
}
|
|
550
562
|
catch (exception) {
|
|
@@ -564,7 +576,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
564
576
|
}
|
|
565
577
|
}
|
|
566
578
|
disposeCore(error) {
|
|
567
|
-
var _a, _b, _c;
|
|
568
579
|
assert(!this._disposed, 0x54c /* Container already disposed */);
|
|
569
580
|
this._disposed = true;
|
|
570
581
|
try {
|
|
@@ -581,15 +592,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
581
592
|
if (this._lifecycleState !== "closed") {
|
|
582
593
|
this._lifecycleState = "disposing";
|
|
583
594
|
}
|
|
584
|
-
|
|
595
|
+
this._protocolHandler?.close();
|
|
585
596
|
this.connectionStateHandler.dispose();
|
|
586
597
|
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
587
|
-
|
|
598
|
+
this._runtime?.dispose(maybeError);
|
|
588
599
|
this.storageAdapter.dispose();
|
|
589
600
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
590
601
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
591
602
|
// Driver need to ensure all caches are cleared on critical errors
|
|
592
|
-
|
|
603
|
+
this.service?.dispose(error);
|
|
593
604
|
}
|
|
594
605
|
catch (exception) {
|
|
595
606
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
|
|
@@ -605,36 +616,48 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
605
616
|
this._lifecycleEvents.emit("disposed");
|
|
606
617
|
}
|
|
607
618
|
}
|
|
608
|
-
closeAndGetPendingLocalState() {
|
|
619
|
+
async closeAndGetPendingLocalState() {
|
|
609
620
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
610
621
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
611
622
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
612
|
-
|
|
623
|
+
this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
|
|
624
|
+
const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
|
|
613
625
|
this.close();
|
|
614
626
|
return pendingState;
|
|
615
627
|
}
|
|
616
|
-
getPendingLocalState() {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
625
|
-
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
626
|
-
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
627
|
-
const pendingState = {
|
|
628
|
-
pendingRuntimeState: this.runtime.getPendingLocalState(),
|
|
629
|
-
baseSnapshot: this.baseSnapshot,
|
|
630
|
-
snapshotBlobs: this.baseSnapshotBlobs,
|
|
631
|
-
savedOps: this.savedOps,
|
|
632
|
-
url: this.resolvedUrl.url,
|
|
633
|
-
term: OnlyValidTermValue,
|
|
628
|
+
async getPendingLocalState() {
|
|
629
|
+
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
630
|
+
}
|
|
631
|
+
async getPendingLocalStateCore(props) {
|
|
632
|
+
return PerformanceEvent.timedExecAsync(this.mc.logger, {
|
|
633
|
+
eventName: "getPendingLocalState",
|
|
634
|
+
notifyImminentClosure: props.notifyImminentClosure,
|
|
635
|
+
savedOpsSize: this.savedOps.length,
|
|
634
636
|
clientId: this.clientId,
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
|
|
637
|
+
}, async () => {
|
|
638
|
+
if (!this.offlineLoadEnabled) {
|
|
639
|
+
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
640
|
+
}
|
|
641
|
+
if (this.closed || this._disposed) {
|
|
642
|
+
throw new UsageError("Pending state cannot be retried if the container is closed or disposed");
|
|
643
|
+
}
|
|
644
|
+
assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
645
|
+
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
646
|
+
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
647
|
+
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
648
|
+
const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
|
|
649
|
+
const pendingState = {
|
|
650
|
+
pendingRuntimeState,
|
|
651
|
+
baseSnapshot: this.baseSnapshot,
|
|
652
|
+
snapshotBlobs: this.baseSnapshotBlobs,
|
|
653
|
+
savedOps: this.savedOps,
|
|
654
|
+
url: this.resolvedUrl.url,
|
|
655
|
+
term: OnlyValidTermValue,
|
|
656
|
+
// no need to save this if there is no pending runtime state
|
|
657
|
+
clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
|
|
658
|
+
};
|
|
659
|
+
return JSON.stringify(pendingState);
|
|
660
|
+
});
|
|
638
661
|
}
|
|
639
662
|
get attachState() {
|
|
640
663
|
return this._attachState;
|
|
@@ -652,9 +675,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
652
675
|
}
|
|
653
676
|
return JSON.stringify(combinedSummary);
|
|
654
677
|
}
|
|
655
|
-
async attach(request) {
|
|
678
|
+
async attach(request, attachProps) {
|
|
656
679
|
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
657
|
-
var _a;
|
|
658
680
|
if (this._lifecycleState !== "loaded") {
|
|
659
681
|
// pre-0.58 error message: containerNotValidForAttach
|
|
660
682
|
throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
@@ -737,16 +759,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
737
759
|
this.runtime.setAttachState(AttachState.Attached);
|
|
738
760
|
this.emit("attached");
|
|
739
761
|
if (!this.closed) {
|
|
740
|
-
this.
|
|
762
|
+
this.handleDeltaConnectionArg({
|
|
741
763
|
fetchOpsFromStorage: false,
|
|
742
|
-
reason: "createDetached",
|
|
743
|
-
});
|
|
764
|
+
reason: { text: "createDetached" },
|
|
765
|
+
}, attachProps?.deltaConnection);
|
|
744
766
|
}
|
|
745
767
|
}
|
|
746
768
|
catch (error) {
|
|
747
769
|
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
748
770
|
const newError = normalizeError(error);
|
|
749
|
-
newError.addTelemetryProperties({ resolvedUrl:
|
|
771
|
+
newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
|
|
750
772
|
this.close(newError);
|
|
751
773
|
throw newError;
|
|
752
774
|
}
|
|
@@ -755,7 +777,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
755
777
|
async request(path) {
|
|
756
778
|
return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
|
|
757
779
|
}
|
|
758
|
-
setAutoReconnectInternal(mode) {
|
|
780
|
+
setAutoReconnectInternal(mode, reason) {
|
|
759
781
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
760
782
|
if (currentMode === mode) {
|
|
761
783
|
return;
|
|
@@ -769,7 +791,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
769
791
|
connectionState: ConnectionState[this.connectionState],
|
|
770
792
|
duration,
|
|
771
793
|
});
|
|
772
|
-
this._deltaManager.connectionManager.setAutoReconnect(mode);
|
|
794
|
+
this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
|
|
773
795
|
}
|
|
774
796
|
connect() {
|
|
775
797
|
if (this.closed) {
|
|
@@ -782,7 +804,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
782
804
|
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
783
805
|
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
784
806
|
// assuming that connect() is called quickly after initial container boot.
|
|
785
|
-
this.connectInternal({
|
|
807
|
+
this.connectInternal({
|
|
808
|
+
reason: { text: "DocumentConnect" },
|
|
809
|
+
fetchOpsFromStorage: false,
|
|
810
|
+
});
|
|
786
811
|
}
|
|
787
812
|
}
|
|
788
813
|
connectInternal(args) {
|
|
@@ -792,21 +817,21 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
792
817
|
this.resumeInternal(args);
|
|
793
818
|
// Set Auto Reconnect Mode
|
|
794
819
|
const mode = ReconnectMode.Enabled;
|
|
795
|
-
this.setAutoReconnectInternal(mode);
|
|
820
|
+
this.setAutoReconnectInternal(mode, args.reason);
|
|
796
821
|
}
|
|
797
822
|
disconnect() {
|
|
798
823
|
if (this.closed) {
|
|
799
824
|
throw new UsageError(`The Container is closed and cannot be disconnected`);
|
|
800
825
|
}
|
|
801
826
|
else {
|
|
802
|
-
this.disconnectInternal();
|
|
827
|
+
this.disconnectInternal({ text: "DocumentDisconnect" });
|
|
803
828
|
}
|
|
804
829
|
}
|
|
805
|
-
disconnectInternal() {
|
|
830
|
+
disconnectInternal(reason) {
|
|
806
831
|
assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
|
|
807
832
|
// Set Auto Reconnect Mode
|
|
808
833
|
const mode = ReconnectMode.Disabled;
|
|
809
|
-
this.setAutoReconnectInternal(mode);
|
|
834
|
+
this.setAutoReconnectInternal(mode, reason);
|
|
810
835
|
}
|
|
811
836
|
resumeInternal(args) {
|
|
812
837
|
assert(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
|
|
@@ -853,7 +878,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
853
878
|
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
854
879
|
*/
|
|
855
880
|
async satisfies(constraintCodeDetails) {
|
|
856
|
-
var _a, _b;
|
|
857
881
|
// If we have no module, it can't satisfy anything.
|
|
858
882
|
if (this._loadedModule === undefined) {
|
|
859
883
|
return false;
|
|
@@ -863,8 +887,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
863
887
|
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
864
888
|
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
865
889
|
}
|
|
866
|
-
const maybeCompareExport =
|
|
867
|
-
if (
|
|
890
|
+
const maybeCompareExport = this._loadedModule?.module.fluidExport;
|
|
891
|
+
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
|
|
868
892
|
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
869
893
|
}
|
|
870
894
|
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
@@ -874,7 +898,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
874
898
|
return false;
|
|
875
899
|
}
|
|
876
900
|
for (const comparer of comparers) {
|
|
877
|
-
const satisfies = await comparer.satisfies(
|
|
901
|
+
const satisfies = await comparer.satisfies(this._loadedModule?.details, constraintCodeDetails);
|
|
878
902
|
if (satisfies === false) {
|
|
879
903
|
return false;
|
|
880
904
|
}
|
|
@@ -897,8 +921,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
897
921
|
*
|
|
898
922
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
899
923
|
*/
|
|
900
|
-
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
|
|
901
|
-
|
|
924
|
+
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
|
|
925
|
+
const timings = { phase1: performance.now() };
|
|
902
926
|
this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
|
|
903
927
|
// Ideally we always connect as "read" by default.
|
|
904
928
|
// Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
|
|
@@ -910,7 +934,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
910
934
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
911
935
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
912
936
|
const connectionArgs = {
|
|
913
|
-
reason: "DocumentOpen",
|
|
937
|
+
reason: { text: "DocumentOpen" },
|
|
914
938
|
mode: "write",
|
|
915
939
|
fetchOpsFromStorage: false,
|
|
916
940
|
};
|
|
@@ -929,6 +953,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
929
953
|
});
|
|
930
954
|
}
|
|
931
955
|
this._attachState = AttachState.Attached;
|
|
956
|
+
timings.phase2 = performance.now();
|
|
932
957
|
// Fetch specified snapshot.
|
|
933
958
|
const { snapshot, versionId } = pendingLocalState === undefined
|
|
934
959
|
? await this.fetchSnapshotTree(specifiedVersion)
|
|
@@ -947,9 +972,51 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
947
972
|
}
|
|
948
973
|
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
|
|
949
974
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
950
|
-
const sequenceNumber =
|
|
951
|
-
const dmAttributes = sequenceNumber !== undefined ?
|
|
975
|
+
const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
|
|
976
|
+
const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
|
|
952
977
|
let opsBeforeReturnP;
|
|
978
|
+
if (loadMode.pauseAfterLoad === true) {
|
|
979
|
+
// If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
|
|
980
|
+
if (loadMode.opsBeforeReturn === "sequenceNumber") {
|
|
981
|
+
assert(loadToSequenceNumber !== undefined, 0x727 /* sequenceNumber should be defined */);
|
|
982
|
+
// Note: It is possible that we think the latest snapshot is newer than the specified sequence number
|
|
983
|
+
// due to saved ops that may be replayed after the snapshot.
|
|
984
|
+
// https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
|
|
985
|
+
if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
|
|
986
|
+
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.");
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
// Force readonly mode - this will ensure we don't receive an error for the lack of join op
|
|
990
|
+
this.forceReadonly(true);
|
|
991
|
+
// We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
|
|
992
|
+
const opHandler = () => {
|
|
993
|
+
if (loadToSequenceNumber === undefined) {
|
|
994
|
+
// If there is no specified sequence number, pause after the inbound queue is empty.
|
|
995
|
+
if (this.deltaManager.inbound.length !== 0) {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
// If there is a specified sequence number, keep processing until we reach it.
|
|
1001
|
+
if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
// Pause op processing once we have processed the desired number of ops.
|
|
1006
|
+
void this.deltaManager.inbound.pause();
|
|
1007
|
+
void this.deltaManager.outbound.pause();
|
|
1008
|
+
this.off("op", opHandler);
|
|
1009
|
+
};
|
|
1010
|
+
if ((loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
|
|
1011
|
+
this.deltaManager.lastSequenceNumber === loadToSequenceNumber) {
|
|
1012
|
+
// If we have already reached the desired sequence number, call opHandler() to pause immediately.
|
|
1013
|
+
opHandler();
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
// If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
|
|
1017
|
+
this.on("op", opHandler);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
953
1020
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
954
1021
|
// Kick off any ops fetching if required.
|
|
955
1022
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -958,11 +1025,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
958
1025
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
959
1026
|
this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
|
|
960
1027
|
break;
|
|
1028
|
+
case "sequenceNumber":
|
|
961
1029
|
case "cached":
|
|
962
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
963
|
-
break;
|
|
964
1030
|
case "all":
|
|
965
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes,
|
|
1031
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, loadMode.opsBeforeReturn);
|
|
966
1032
|
break;
|
|
967
1033
|
default:
|
|
968
1034
|
unreachableCase(loadMode.opsBeforeReturn);
|
|
@@ -970,20 +1036,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
970
1036
|
// ...load in the existing quorum
|
|
971
1037
|
// Initialize the protocol handler
|
|
972
1038
|
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
1039
|
+
timings.phase3 = performance.now();
|
|
973
1040
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
974
|
-
await this.
|
|
975
|
-
|
|
1041
|
+
await this.instantiateRuntime(codeDetails, snapshot,
|
|
1042
|
+
// give runtime a dummy value so it knows we're loading from a stash blob
|
|
1043
|
+
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined);
|
|
976
1044
|
// replay saved ops
|
|
977
1045
|
if (pendingLocalState) {
|
|
978
1046
|
for (const message of pendingLocalState.savedOps) {
|
|
979
1047
|
this.processRemoteMessage(message);
|
|
980
1048
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
981
|
-
await
|
|
1049
|
+
await this.runtime.notifyOpReplay?.(message);
|
|
982
1050
|
}
|
|
983
1051
|
pendingLocalState.savedOps = [];
|
|
984
|
-
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
985
|
-
assert(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
|
|
986
|
-
this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
|
|
987
1052
|
}
|
|
988
1053
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
989
1054
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
@@ -995,24 +1060,20 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
995
1060
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
996
1061
|
this._deltaManager.inbound.pause();
|
|
997
1062
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1063
|
+
this.handleDeltaConnectionArg(connectionArgs, loadMode.deltaConnection, pendingLocalState !== undefined);
|
|
1064
|
+
}
|
|
1065
|
+
// If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
|
|
1066
|
+
if (loadToSequenceNumber !== undefined &&
|
|
1067
|
+
this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1068
|
+
await new Promise((resolve, reject) => {
|
|
1069
|
+
const opHandler = (message) => {
|
|
1070
|
+
if (message.sequenceNumber >= loadToSequenceNumber) {
|
|
1071
|
+
resolve();
|
|
1072
|
+
this.off("op", opHandler);
|
|
1003
1073
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
this.inboundQueuePausedFromInit = false;
|
|
1008
|
-
this._deltaManager.inbound.resume();
|
|
1009
|
-
this._deltaManager.inboundSignal.resume();
|
|
1010
|
-
break;
|
|
1011
|
-
case "none":
|
|
1012
|
-
break;
|
|
1013
|
-
default:
|
|
1014
|
-
unreachableCase(loadMode.deltaConnection);
|
|
1015
|
-
}
|
|
1074
|
+
};
|
|
1075
|
+
this.on("op", opHandler);
|
|
1076
|
+
});
|
|
1016
1077
|
}
|
|
1017
1078
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
1018
1079
|
// But if that did not happen for some reason, fail load for sure.
|
|
@@ -1024,6 +1085,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1024
1085
|
}
|
|
1025
1086
|
// Internal context is fully loaded at this point
|
|
1026
1087
|
this.setLoaded();
|
|
1088
|
+
timings.end = performance.now();
|
|
1089
|
+
this.subLogger.sendTelemetryEvent({
|
|
1090
|
+
eventName: "LoadStagesTimings",
|
|
1091
|
+
details: JSON.stringify(timings),
|
|
1092
|
+
}, undefined, LogLevel.verbose);
|
|
1027
1093
|
return {
|
|
1028
1094
|
sequenceNumber: attributes.sequenceNumber,
|
|
1029
1095
|
version: versionId,
|
|
@@ -1031,7 +1097,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1031
1097
|
dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
|
|
1032
1098
|
};
|
|
1033
1099
|
}
|
|
1034
|
-
async createDetached(
|
|
1100
|
+
async createDetached(codeDetails) {
|
|
1035
1101
|
const attributes = {
|
|
1036
1102
|
sequenceNumber: detachedContainerRefSeqNumber,
|
|
1037
1103
|
term: OnlyValidTermValue,
|
|
@@ -1039,14 +1105,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1039
1105
|
};
|
|
1040
1106
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1041
1107
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1042
|
-
const qValues = initQuorumValuesFromCodeDetails(
|
|
1108
|
+
const qValues = initQuorumValuesFromCodeDetails(codeDetails);
|
|
1043
1109
|
this.initializeProtocolState(attributes, {
|
|
1044
1110
|
members: [],
|
|
1045
1111
|
proposals: [],
|
|
1046
1112
|
values: qValues,
|
|
1047
1113
|
});
|
|
1048
|
-
|
|
1049
|
-
await this.instantiateContextDetached(false);
|
|
1114
|
+
await this.instantiateRuntime(codeDetails, undefined);
|
|
1050
1115
|
this.setLoaded();
|
|
1051
1116
|
}
|
|
1052
1117
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
@@ -1061,14 +1126,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1061
1126
|
// Initialize the protocol handler
|
|
1062
1127
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1063
1128
|
const qValues = await readAndParse(this.storageAdapter, baseTree.blobs.quorumValues);
|
|
1064
|
-
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1065
1129
|
this.initializeProtocolState(attributes, {
|
|
1066
1130
|
members: [],
|
|
1067
1131
|
proposals: [],
|
|
1068
|
-
values:
|
|
1132
|
+
values: qValues,
|
|
1069
1133
|
});
|
|
1070
|
-
|
|
1071
|
-
snapshotTree);
|
|
1134
|
+
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1135
|
+
await this.instantiateRuntime(codeDetails, snapshotTree);
|
|
1072
1136
|
this.setLoaded();
|
|
1073
1137
|
}
|
|
1074
1138
|
async getDocumentAttributes(storage, tree) {
|
|
@@ -1105,7 +1169,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1105
1169
|
}
|
|
1106
1170
|
initializeProtocolState(attributes, quorumSnapshot) {
|
|
1107
1171
|
const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })));
|
|
1108
|
-
const protocolLogger =
|
|
1172
|
+
const protocolLogger = createChildLogger({
|
|
1173
|
+
logger: this.subLogger,
|
|
1174
|
+
namespace: "ProtocolHandler",
|
|
1175
|
+
});
|
|
1109
1176
|
protocol.quorum.on("error", (error) => {
|
|
1110
1177
|
protocolLogger.sendErrorEvent(error);
|
|
1111
1178
|
});
|
|
@@ -1167,8 +1234,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1167
1234
|
return pkg;
|
|
1168
1235
|
}
|
|
1169
1236
|
get client() {
|
|
1170
|
-
|
|
1171
|
-
const client = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.client) !== undefined
|
|
1237
|
+
const client = this.options?.client !== undefined
|
|
1172
1238
|
? this.options.client
|
|
1173
1239
|
: {
|
|
1174
1240
|
details: {
|
|
@@ -1199,7 +1265,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1199
1265
|
}
|
|
1200
1266
|
createDeltaManager() {
|
|
1201
1267
|
const serviceProvider = () => this.service;
|
|
1202
|
-
const deltaManager = new DeltaManager(serviceProvider,
|
|
1268
|
+
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
1269
|
// Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
|
|
1204
1270
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1205
1271
|
deltaManager.inbound.pause();
|
|
@@ -1215,11 +1281,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1215
1281
|
deltaManager.on("cancelEstablishingConnection", (reason) => {
|
|
1216
1282
|
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1217
1283
|
});
|
|
1218
|
-
deltaManager.on("disconnect", (reason
|
|
1219
|
-
|
|
1220
|
-
(_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
|
|
1284
|
+
deltaManager.on("disconnect", (reason) => {
|
|
1285
|
+
this.noopHeuristic?.notifyDisconnect();
|
|
1221
1286
|
if (!this.closed) {
|
|
1222
|
-
this.connectionStateHandler.receivedDisconnectEvent(reason
|
|
1287
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1223
1288
|
}
|
|
1224
1289
|
});
|
|
1225
1290
|
deltaManager.on("throttled", (warning) => {
|
|
@@ -1251,8 +1316,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1251
1316
|
},
|
|
1252
1317
|
}, prefetchType);
|
|
1253
1318
|
}
|
|
1254
|
-
logConnectionStateChangeTelemetry(value, oldState, reason
|
|
1255
|
-
var _a;
|
|
1319
|
+
logConnectionStateChangeTelemetry(value, oldState, reason) {
|
|
1256
1320
|
// Log actual event
|
|
1257
1321
|
const time = performance.now();
|
|
1258
1322
|
this.connectionTransitionTimes[value] = time;
|
|
@@ -1269,7 +1333,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1269
1333
|
if (value === ConnectionState.Connected) {
|
|
1270
1334
|
durationFromDisconnected =
|
|
1271
1335
|
time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
1272
|
-
durationFromDisconnected =
|
|
1336
|
+
durationFromDisconnected = formatTick(durationFromDisconnected);
|
|
1273
1337
|
}
|
|
1274
1338
|
else if (value === ConnectionState.CatchingUp) {
|
|
1275
1339
|
// This info is of most interesting while Catching Up.
|
|
@@ -1282,19 +1346,31 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1282
1346
|
}
|
|
1283
1347
|
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1284
1348
|
}
|
|
1285
|
-
this.mc.logger.sendPerformanceEvent(
|
|
1349
|
+
this.mc.logger.sendPerformanceEvent({
|
|
1350
|
+
eventName: `ConnectionStateChange_${ConnectionState[value]}`,
|
|
1351
|
+
from: ConnectionState[oldState],
|
|
1352
|
+
duration,
|
|
1286
1353
|
durationFromDisconnected,
|
|
1287
|
-
reason,
|
|
1288
|
-
connectionInitiationReason,
|
|
1289
|
-
|
|
1354
|
+
reason: reason?.text,
|
|
1355
|
+
connectionInitiationReason,
|
|
1356
|
+
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
1357
|
+
clientId: this.clientId,
|
|
1358
|
+
autoReconnect,
|
|
1359
|
+
opsBehind,
|
|
1360
|
+
online: OnlineStatus[isOnline()],
|
|
1361
|
+
lastVisible: this.lastVisible !== undefined
|
|
1290
1362
|
? performance.now() - this.lastVisible
|
|
1291
|
-
: undefined,
|
|
1363
|
+
: undefined,
|
|
1364
|
+
checkpointSequenceNumber,
|
|
1365
|
+
quorumSize: this._protocolHandler?.quorum.getMembers().size,
|
|
1366
|
+
isDirty: this.isDirty,
|
|
1367
|
+
...this._deltaManager.connectionProps,
|
|
1368
|
+
}, reason?.error);
|
|
1292
1369
|
if (value === ConnectionState.Connected) {
|
|
1293
1370
|
this.firstConnection = false;
|
|
1294
1371
|
}
|
|
1295
1372
|
}
|
|
1296
1373
|
propagateConnectionState(initialTransition, disconnectedReason) {
|
|
1297
|
-
var _a;
|
|
1298
1374
|
// When container loaded, we want to propagate initial connection state.
|
|
1299
1375
|
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1300
1376
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
@@ -1304,22 +1380,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1304
1380
|
return;
|
|
1305
1381
|
}
|
|
1306
1382
|
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
1383
|
// Both protocol and context should not be undefined if we got so far.
|
|
1314
|
-
this.setContextConnectedState(state,
|
|
1384
|
+
this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
|
|
1315
1385
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1316
|
-
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
|
-
}
|
|
1386
|
+
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
|
|
1323
1387
|
}
|
|
1324
1388
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1325
1389
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
@@ -1357,13 +1421,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1357
1421
|
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
|
|
1358
1422
|
}
|
|
1359
1423
|
submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
|
|
1360
|
-
var _a;
|
|
1361
1424
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1362
1425
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
1363
1426
|
return -1;
|
|
1364
1427
|
}
|
|
1365
|
-
this.
|
|
1366
|
-
(_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
|
|
1428
|
+
this.noopHeuristic?.notifyMessageSent();
|
|
1367
1429
|
return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
|
|
1368
1430
|
}
|
|
1369
1431
|
processRemoteMessage(message) {
|
|
@@ -1371,23 +1433,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1371
1433
|
this.savedOps.push(message);
|
|
1372
1434
|
}
|
|
1373
1435
|
const local = this.clientId === message.clientId;
|
|
1374
|
-
// Check and report if we're getting messages from a clientId that we previously
|
|
1375
|
-
// flagged should have left, or from a client that's not in the quorum but should be
|
|
1376
|
-
if (message.clientId != null) {
|
|
1377
|
-
const client = this.protocolHandler.quorum.getMember(message.clientId);
|
|
1378
|
-
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
1379
|
-
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
1380
|
-
throw new Error("Remote message's clientId is missing from the quorum");
|
|
1381
|
-
}
|
|
1382
|
-
// Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
|
|
1383
|
-
// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
|
|
1384
|
-
// document we don't need to blow up aggressively.
|
|
1385
|
-
if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
|
|
1386
|
-
!canBeCoalescedByService(message)) {
|
|
1387
|
-
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
1388
|
-
throw new Error("Remote message's clientId already should have left");
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
1436
|
// Allow the protocol handler to process the message
|
|
1392
1437
|
const result = this.protocolHandler.processMessage(message, local);
|
|
1393
1438
|
// Forward messages to the loaded runtime for processing
|
|
@@ -1438,8 +1483,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1438
1483
|
* @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
|
|
1439
1484
|
*/
|
|
1440
1485
|
async fetchSnapshotTree(specifiedVersion) {
|
|
1441
|
-
|
|
1442
|
-
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1486
|
+
const version = await this.getVersion(specifiedVersion ?? null);
|
|
1443
1487
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1444
1488
|
// We should have a defined version to load from if specified version requested
|
|
1445
1489
|
this.mc.logger.sendErrorEvent({
|
|
@@ -1448,22 +1492,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1448
1492
|
});
|
|
1449
1493
|
}
|
|
1450
1494
|
this._loadedFromVersion = version;
|
|
1451
|
-
const snapshot = (
|
|
1495
|
+
const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
|
|
1452
1496
|
if (snapshot === undefined && version !== undefined) {
|
|
1453
1497
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1454
1498
|
}
|
|
1455
|
-
return { snapshot, versionId: version
|
|
1499
|
+
return { snapshot, versionId: version?.id };
|
|
1456
1500
|
}
|
|
1457
|
-
async
|
|
1458
|
-
|
|
1459
|
-
if (codeDetails === undefined) {
|
|
1460
|
-
throw new Error("pkg should be provided in create flow!!");
|
|
1461
|
-
}
|
|
1462
|
-
await this.instantiateContext(existing, codeDetails, snapshot);
|
|
1463
|
-
}
|
|
1464
|
-
async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
|
|
1465
|
-
var _a, _b;
|
|
1466
|
-
assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1501
|
+
async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
|
|
1502
|
+
assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1467
1503
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1468
1504
|
// are set. Global requests will still go directly to the loader
|
|
1469
1505
|
const maybeLoader = this.scope;
|
|
@@ -1474,25 +1510,20 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1474
1510
|
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
1475
1511
|
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
1476
1512
|
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
1477
|
-
details:
|
|
1513
|
+
details: loadCodeResult.details ?? codeDetails,
|
|
1478
1514
|
};
|
|
1479
1515
|
const fluidExport = this._loadedModule.module.fluidExport;
|
|
1480
|
-
const runtimeFactory = fluidExport
|
|
1516
|
+
const runtimeFactory = fluidExport?.IRuntimeFactory;
|
|
1481
1517
|
if (runtimeFactory === undefined) {
|
|
1482
1518
|
throw new Error(packageNotFactoryError);
|
|
1483
1519
|
}
|
|
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
|
-
});
|
|
1520
|
+
const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
|
|
1521
|
+
this.protocolHandler.quorum.get("code2"));
|
|
1522
|
+
const existing = snapshot !== undefined;
|
|
1523
|
+
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.resolvedUrl?.id, () => this.clientId, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
|
|
1492
1524
|
this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
|
|
1493
1525
|
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
1494
1526
|
this._loadedCodeDetails = codeDetails;
|
|
1495
|
-
this.emit("contextChanged", codeDetails);
|
|
1496
1527
|
}
|
|
1497
1528
|
/**
|
|
1498
1529
|
* Set the connected state of the ContainerContext
|
|
@@ -1501,8 +1532,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1501
1532
|
* @param readonly - Is the container in readonly mode?
|
|
1502
1533
|
*/
|
|
1503
1534
|
setContextConnectedState(state, readonly) {
|
|
1504
|
-
|
|
1505
|
-
if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
|
|
1535
|
+
if (this._runtime?.disposed === false) {
|
|
1506
1536
|
/**
|
|
1507
1537
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1508
1538
|
* ops getting through to the DeltaManager.
|
|
@@ -1512,5 +1542,25 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1512
1542
|
this.runtime.setConnectionState(state && !readonly, this.clientId);
|
|
1513
1543
|
}
|
|
1514
1544
|
}
|
|
1545
|
+
handleDeltaConnectionArg(connectionArgs, deltaConnectionArg, canConnect = true) {
|
|
1546
|
+
switch (deltaConnectionArg) {
|
|
1547
|
+
case undefined:
|
|
1548
|
+
if (canConnect) {
|
|
1549
|
+
// connect to delta stream now since we did not before
|
|
1550
|
+
this.connectToDeltaStream(connectionArgs);
|
|
1551
|
+
}
|
|
1552
|
+
// intentional fallthrough
|
|
1553
|
+
case "delayed":
|
|
1554
|
+
assert(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
|
|
1555
|
+
this.inboundQueuePausedFromInit = false;
|
|
1556
|
+
this._deltaManager.inbound.resume();
|
|
1557
|
+
this._deltaManager.inboundSignal.resume();
|
|
1558
|
+
break;
|
|
1559
|
+
case "none":
|
|
1560
|
+
break;
|
|
1561
|
+
default:
|
|
1562
|
+
unreachableCase(deltaConnectionArg);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1515
1565
|
}
|
|
1516
1566
|
//# sourceMappingURL=container.js.map
|