@fluidframework/container-loader 2.0.0-internal.5.4.0 → 2.0.0-internal.6.1.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/dist/connectionManager.d.ts +4 -4
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +57 -49
- 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 +26 -28
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +10 -5
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +183 -134
- 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 +20 -7
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -3
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.js +2 -3
- package/dist/debugLogger.js.map +1 -1
- package/dist/deltaManager.d.ts +19 -6
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +67 -28
- 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 +57 -42
- 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 +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/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/connectionManager.d.ts +4 -4
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +58 -50
- 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 +26 -28
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +10 -5
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +182 -133
- 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 +20 -7
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +3 -3
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.js +2 -3
- package/lib/debugLogger.js.map +1 -1
- package/lib/deltaManager.d.ts +19 -6
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +67 -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 +57 -42
- 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 +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/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 +15 -19
- package/src/connectionManager.ts +53 -34
- package/src/connectionStateHandler.ts +30 -37
- package/src/container.ts +156 -76
- package/src/containerContext.ts +0 -24
- package/src/contracts.ts +27 -10
- package/src/deltaManager.ts +41 -18
- package/src/loader.ts +37 -23
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +33 -2
- package/src/utils.ts +29 -0
package/lib/container.js
CHANGED
|
@@ -8,18 +8,18 @@ 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, } from "@fluidframework/driver-utils";
|
|
12
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
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";
|
|
22
|
+
import { combineAppAndProtocolSummary, getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer, } from "./utils";
|
|
23
23
|
import { initQuorumValuesFromCodeDetails } from "./quorum";
|
|
24
24
|
import { NoopHeuristic } from "./noopHeuristic";
|
|
25
25
|
import { ConnectionManager } from "./connectionManager";
|
|
@@ -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",
|
|
@@ -174,8 +173,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
174
173
|
};
|
|
175
174
|
const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
|
|
176
175
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
177
|
-
const pendingLocalState = loadProps
|
|
178
|
-
this._canReconnect = canReconnect
|
|
176
|
+
const pendingLocalState = loadProps?.pendingLocalState;
|
|
177
|
+
this._canReconnect = canReconnect ?? true;
|
|
179
178
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
180
179
|
this.urlResolver = urlResolver;
|
|
181
180
|
this.serviceFactory = documentServiceFactory;
|
|
@@ -183,14 +182,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
183
182
|
// Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
|
|
184
183
|
// all clients that were loaded from the same loader (including summarizer clients).
|
|
185
184
|
// Tracking alternative ways to handle this in AB#4129.
|
|
186
|
-
this.options =
|
|
185
|
+
this.options = { ...options };
|
|
187
186
|
this.scope = scope;
|
|
188
187
|
this.detachedBlobStorage = detachedBlobStorage;
|
|
189
188
|
this.protocolHandlerBuilder =
|
|
190
|
-
protocolHandlerBuilder
|
|
189
|
+
protocolHandlerBuilder ??
|
|
190
|
+
((attributes, quorumSnapshot, sendProposal) => new ProtocolHandler(attributes, quorumSnapshot, sendProposal, new Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
|
|
191
191
|
// Note that we capture the createProps here so we can replicate the creation call when we want to clone.
|
|
192
192
|
this.clone = async (_loadProps, createParamOverrides) => {
|
|
193
|
-
return Container.load(_loadProps,
|
|
193
|
+
return Container.load(_loadProps, {
|
|
194
|
+
...createProps,
|
|
195
|
+
...createParamOverrides,
|
|
196
|
+
});
|
|
194
197
|
};
|
|
195
198
|
// Create logger for data stores to use
|
|
196
199
|
const type = this.client.details.type;
|
|
@@ -204,7 +207,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
204
207
|
all: {
|
|
205
208
|
clientType,
|
|
206
209
|
containerId: uuid(),
|
|
207
|
-
docId: () =>
|
|
210
|
+
docId: () => this.resolvedUrl?.id,
|
|
208
211
|
containerAttachState: () => this._attachState,
|
|
209
212
|
containerLifecycleState: () => this._lifecycleState,
|
|
210
213
|
containerConnectionState: () => ConnectionState[this.connectionState],
|
|
@@ -215,22 +218,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
215
218
|
// specific error or class of errors
|
|
216
219
|
error: {
|
|
217
220
|
// load information to associate errors with the specific load point
|
|
218
|
-
dmInitialSeqNumber: () =>
|
|
219
|
-
dmLastProcessedSeqNumber: () =>
|
|
220
|
-
dmLastKnownSeqNumber: () =>
|
|
221
|
-
containerLoadedFromVersionId: () =>
|
|
222
|
-
containerLoadedFromVersionDate: () =>
|
|
221
|
+
dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
|
|
222
|
+
dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
|
|
223
|
+
dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
|
|
224
|
+
containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
|
|
225
|
+
containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
|
|
223
226
|
// message information to associate errors with the specific execution state
|
|
224
227
|
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
225
|
-
dmLastMsqSeqNumber: () =>
|
|
226
|
-
dmLastMsqSeqTimestamp: () =>
|
|
227
|
-
dmLastMsqSeqClientId: () =>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
: (_d = (_c = this.deltaManager) === null || _c === void 0 ? void 0 : _c.lastMessage) === null || _d === void 0 ? void 0 : _d.clientId;
|
|
232
|
-
},
|
|
233
|
-
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; },
|
|
228
|
+
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
229
|
+
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
230
|
+
dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
|
|
231
|
+
? "null"
|
|
232
|
+
: this.deltaManager?.lastMessage?.clientId,
|
|
233
|
+
dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
|
|
234
234
|
connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
235
235
|
},
|
|
236
236
|
},
|
|
@@ -240,11 +240,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
240
240
|
this._deltaManager = this.createDeltaManager();
|
|
241
241
|
this.connectionStateHandler = createConnectionStateHandler({
|
|
242
242
|
logger: this.mc.logger,
|
|
243
|
-
connectionStateChanged: (value, oldState, reason
|
|
243
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
244
244
|
if (value === ConnectionState.Connected) {
|
|
245
245
|
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
246
246
|
}
|
|
247
|
-
this.logConnectionStateChangeTelemetry(value, oldState, reason
|
|
247
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
248
248
|
if (this._lifecycleState === "loaded") {
|
|
249
249
|
this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
|
|
250
250
|
? reason
|
|
@@ -260,9 +260,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
260
260
|
// Report issues only if we already loaded container - op processing is paused while container is loading,
|
|
261
261
|
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
262
262
|
// It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
|
|
263
|
-
this._deltaManager.logConnectionIssue(
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
this._deltaManager.logConnectionIssue({
|
|
264
|
+
eventName,
|
|
265
|
+
mode,
|
|
266
|
+
category: this._lifecycleState === "loading" ? "generic" : category,
|
|
267
|
+
duration: performance.now() -
|
|
268
|
+
this.connectionTransitionTimes[ConnectionState.CatchingUp],
|
|
269
|
+
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
270
|
+
});
|
|
266
271
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
267
272
|
// to very slow op fetches and we will eventually get there.
|
|
268
273
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -272,14 +277,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
272
277
|
// Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
|
|
273
278
|
// to call this.applyForConnectedState("addMemberEvent") for "read" connections)
|
|
274
279
|
if (mode === "read") {
|
|
275
|
-
|
|
276
|
-
this.
|
|
280
|
+
const reason = { text: "NoJoinSignal" };
|
|
281
|
+
this.disconnectInternal(reason);
|
|
282
|
+
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
277
283
|
}
|
|
278
284
|
},
|
|
279
285
|
clientShouldHaveLeft: (clientId) => {
|
|
280
286
|
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
281
287
|
},
|
|
282
|
-
}, this.deltaManager, pendingLocalState
|
|
288
|
+
}, this.deltaManager, pendingLocalState?.clientId);
|
|
283
289
|
this.on(savedContainerEvent, () => {
|
|
284
290
|
this.connectionStateHandler.containerSaved();
|
|
285
291
|
});
|
|
@@ -291,8 +297,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
291
297
|
: combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
|
|
292
298
|
// Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
|
|
293
299
|
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
294
|
-
const forceEnableSummarizeProtocolTree =
|
|
295
|
-
|
|
300
|
+
const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
|
|
301
|
+
options.summarizeProtocolTree;
|
|
302
|
+
this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
296
303
|
const isDomAvailable = typeof document === "object" &&
|
|
297
304
|
document !== null &&
|
|
298
305
|
typeof document.addEventListener === "function" &&
|
|
@@ -319,7 +326,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
319
326
|
* @internal
|
|
320
327
|
*/
|
|
321
328
|
static async load(loadProps, createProps) {
|
|
322
|
-
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
329
|
+
const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } = loadProps;
|
|
323
330
|
const container = new Container(createProps, loadProps);
|
|
324
331
|
const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
|
|
325
332
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
@@ -327,19 +334,20 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
327
334
|
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
328
335
|
// to return container, so ignore this value and use undefined for opsBeforeReturn
|
|
329
336
|
const mode = pendingLocalState
|
|
330
|
-
?
|
|
337
|
+
? { ...(loadMode ?? defaultMode), opsBeforeReturn: undefined }
|
|
338
|
+
: loadMode ?? defaultMode;
|
|
331
339
|
const onClosed = (err) => {
|
|
332
340
|
// pre-0.58 error message: containerClosedWithoutErrorDuringLoad
|
|
333
|
-
reject(err
|
|
341
|
+
reject(err ?? new GenericError("Container closed without error during load"));
|
|
334
342
|
};
|
|
335
343
|
container.on("closed", onClosed);
|
|
336
344
|
container
|
|
337
|
-
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
345
|
+
.load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
|
|
338
346
|
.finally(() => {
|
|
339
347
|
container.removeListener("closed", onClosed);
|
|
340
348
|
})
|
|
341
349
|
.then((props) => {
|
|
342
|
-
event.end(
|
|
350
|
+
event.end({ ...props, ...loadMode });
|
|
343
351
|
resolve(container);
|
|
344
352
|
}, (error) => {
|
|
345
353
|
const err = normalizeError(error);
|
|
@@ -407,7 +415,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
407
415
|
return this;
|
|
408
416
|
}
|
|
409
417
|
get resolvedUrl() {
|
|
410
|
-
var _a;
|
|
411
418
|
/**
|
|
412
419
|
* All attached containers will have a document service,
|
|
413
420
|
* this is required, as attached containers are attached to
|
|
@@ -419,7 +426,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
419
426
|
* is always the same as the containers, as we had to
|
|
420
427
|
* obtain the resolved url, and then create the service from it.
|
|
421
428
|
*/
|
|
422
|
-
return
|
|
429
|
+
return this.service?.resolvedUrl;
|
|
423
430
|
}
|
|
424
431
|
get readOnlyInfo() {
|
|
425
432
|
return this._deltaManager.readOnlyInfo;
|
|
@@ -447,8 +454,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
447
454
|
return this._clientId;
|
|
448
455
|
}
|
|
449
456
|
get offlineLoadEnabled() {
|
|
450
|
-
|
|
451
|
-
|
|
457
|
+
const enabled = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
|
|
458
|
+
this.options?.enableOfflineLoad === true;
|
|
452
459
|
// summarizer will not have any pending state we want to save
|
|
453
460
|
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
454
461
|
}
|
|
@@ -485,18 +492,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
485
492
|
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
486
493
|
*/
|
|
487
494
|
async getEntryPoint() {
|
|
488
|
-
var _a, _b;
|
|
489
495
|
if (this._disposed) {
|
|
490
496
|
throw new UsageError("The context is already disposed");
|
|
491
497
|
}
|
|
492
498
|
if (this._runtime !== undefined) {
|
|
493
|
-
return
|
|
499
|
+
return this._runtime.getEntryPoint?.();
|
|
494
500
|
}
|
|
495
501
|
return new Promise((resolve, reject) => {
|
|
496
502
|
const runtimeInstantiatedHandler = () => {
|
|
497
|
-
var _a, _b;
|
|
498
503
|
assert(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
|
|
499
|
-
resolve(
|
|
504
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
500
505
|
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
501
506
|
};
|
|
502
507
|
const disposedHandler = () => {
|
|
@@ -530,7 +535,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
530
535
|
assert(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
|
|
531
536
|
}
|
|
532
537
|
closeCore(error) {
|
|
533
|
-
var _a;
|
|
534
538
|
assert(!this.closed, 0x315 /* re-entrancy */);
|
|
535
539
|
try {
|
|
536
540
|
// Ensure that we raise all key events even if one of these throws
|
|
@@ -546,7 +550,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
546
550
|
: "generic",
|
|
547
551
|
}, error);
|
|
548
552
|
this._lifecycleState = "closing";
|
|
549
|
-
|
|
553
|
+
this._protocolHandler?.close();
|
|
550
554
|
this.connectionStateHandler.dispose();
|
|
551
555
|
}
|
|
552
556
|
catch (exception) {
|
|
@@ -566,7 +570,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
566
570
|
}
|
|
567
571
|
}
|
|
568
572
|
disposeCore(error) {
|
|
569
|
-
var _a, _b, _c;
|
|
570
573
|
assert(!this._disposed, 0x54c /* Container already disposed */);
|
|
571
574
|
this._disposed = true;
|
|
572
575
|
try {
|
|
@@ -583,15 +586,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
583
586
|
if (this._lifecycleState !== "closed") {
|
|
584
587
|
this._lifecycleState = "disposing";
|
|
585
588
|
}
|
|
586
|
-
|
|
589
|
+
this._protocolHandler?.close();
|
|
587
590
|
this.connectionStateHandler.dispose();
|
|
588
591
|
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
589
|
-
|
|
592
|
+
this._runtime?.dispose(maybeError);
|
|
590
593
|
this.storageAdapter.dispose();
|
|
591
594
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
592
595
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
593
596
|
// Driver need to ensure all caches are cleared on critical errors
|
|
594
|
-
|
|
597
|
+
this.service?.dispose(error);
|
|
595
598
|
}
|
|
596
599
|
catch (exception) {
|
|
597
600
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
|
|
@@ -607,15 +610,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
607
610
|
this._lifecycleEvents.emit("disposed");
|
|
608
611
|
}
|
|
609
612
|
}
|
|
610
|
-
closeAndGetPendingLocalState() {
|
|
613
|
+
async closeAndGetPendingLocalState() {
|
|
611
614
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
612
615
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
613
616
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
614
|
-
|
|
617
|
+
this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
|
|
618
|
+
const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
|
|
615
619
|
this.close();
|
|
616
620
|
return pendingState;
|
|
617
621
|
}
|
|
618
|
-
getPendingLocalState() {
|
|
622
|
+
async getPendingLocalState() {
|
|
623
|
+
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
624
|
+
}
|
|
625
|
+
async getPendingLocalStateCore(props) {
|
|
619
626
|
if (!this.offlineLoadEnabled) {
|
|
620
627
|
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
621
628
|
}
|
|
@@ -626,8 +633,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
626
633
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
627
634
|
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
628
635
|
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
636
|
+
const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
|
|
629
637
|
const pendingState = {
|
|
630
|
-
pendingRuntimeState
|
|
638
|
+
pendingRuntimeState,
|
|
631
639
|
baseSnapshot: this.baseSnapshot,
|
|
632
640
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
633
641
|
savedOps: this.savedOps,
|
|
@@ -656,7 +664,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
656
664
|
}
|
|
657
665
|
async attach(request) {
|
|
658
666
|
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
659
|
-
var _a;
|
|
660
667
|
if (this._lifecycleState !== "loaded") {
|
|
661
668
|
// pre-0.58 error message: containerNotValidForAttach
|
|
662
669
|
throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
@@ -741,14 +748,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
741
748
|
if (!this.closed) {
|
|
742
749
|
this.resumeInternal({
|
|
743
750
|
fetchOpsFromStorage: false,
|
|
744
|
-
reason: "createDetached",
|
|
751
|
+
reason: { text: "createDetached" },
|
|
745
752
|
});
|
|
746
753
|
}
|
|
747
754
|
}
|
|
748
755
|
catch (error) {
|
|
749
756
|
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
750
757
|
const newError = normalizeError(error);
|
|
751
|
-
newError.addTelemetryProperties({ resolvedUrl:
|
|
758
|
+
newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
|
|
752
759
|
this.close(newError);
|
|
753
760
|
throw newError;
|
|
754
761
|
}
|
|
@@ -757,7 +764,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
757
764
|
async request(path) {
|
|
758
765
|
return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
|
|
759
766
|
}
|
|
760
|
-
setAutoReconnectInternal(mode) {
|
|
767
|
+
setAutoReconnectInternal(mode, reason) {
|
|
761
768
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
762
769
|
if (currentMode === mode) {
|
|
763
770
|
return;
|
|
@@ -771,7 +778,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
771
778
|
connectionState: ConnectionState[this.connectionState],
|
|
772
779
|
duration,
|
|
773
780
|
});
|
|
774
|
-
this._deltaManager.connectionManager.setAutoReconnect(mode);
|
|
781
|
+
this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
|
|
775
782
|
}
|
|
776
783
|
connect() {
|
|
777
784
|
if (this.closed) {
|
|
@@ -784,7 +791,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
784
791
|
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
785
792
|
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
786
793
|
// assuming that connect() is called quickly after initial container boot.
|
|
787
|
-
this.connectInternal({
|
|
794
|
+
this.connectInternal({
|
|
795
|
+
reason: { text: "DocumentConnect" },
|
|
796
|
+
fetchOpsFromStorage: false,
|
|
797
|
+
});
|
|
788
798
|
}
|
|
789
799
|
}
|
|
790
800
|
connectInternal(args) {
|
|
@@ -794,21 +804,21 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
794
804
|
this.resumeInternal(args);
|
|
795
805
|
// Set Auto Reconnect Mode
|
|
796
806
|
const mode = ReconnectMode.Enabled;
|
|
797
|
-
this.setAutoReconnectInternal(mode);
|
|
807
|
+
this.setAutoReconnectInternal(mode, args.reason);
|
|
798
808
|
}
|
|
799
809
|
disconnect() {
|
|
800
810
|
if (this.closed) {
|
|
801
811
|
throw new UsageError(`The Container is closed and cannot be disconnected`);
|
|
802
812
|
}
|
|
803
813
|
else {
|
|
804
|
-
this.disconnectInternal();
|
|
814
|
+
this.disconnectInternal({ text: "DocumentDisconnect" });
|
|
805
815
|
}
|
|
806
816
|
}
|
|
807
|
-
disconnectInternal() {
|
|
817
|
+
disconnectInternal(reason) {
|
|
808
818
|
assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
|
|
809
819
|
// Set Auto Reconnect Mode
|
|
810
820
|
const mode = ReconnectMode.Disabled;
|
|
811
|
-
this.setAutoReconnectInternal(mode);
|
|
821
|
+
this.setAutoReconnectInternal(mode, reason);
|
|
812
822
|
}
|
|
813
823
|
resumeInternal(args) {
|
|
814
824
|
assert(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
|
|
@@ -855,7 +865,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
855
865
|
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
856
866
|
*/
|
|
857
867
|
async satisfies(constraintCodeDetails) {
|
|
858
|
-
var _a, _b;
|
|
859
868
|
// If we have no module, it can't satisfy anything.
|
|
860
869
|
if (this._loadedModule === undefined) {
|
|
861
870
|
return false;
|
|
@@ -865,8 +874,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
865
874
|
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
866
875
|
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
867
876
|
}
|
|
868
|
-
const maybeCompareExport =
|
|
869
|
-
if (
|
|
877
|
+
const maybeCompareExport = this._loadedModule?.module.fluidExport;
|
|
878
|
+
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
|
|
870
879
|
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
871
880
|
}
|
|
872
881
|
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
@@ -876,7 +885,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
876
885
|
return false;
|
|
877
886
|
}
|
|
878
887
|
for (const comparer of comparers) {
|
|
879
|
-
const satisfies = await comparer.satisfies(
|
|
888
|
+
const satisfies = await comparer.satisfies(this._loadedModule?.details, constraintCodeDetails);
|
|
880
889
|
if (satisfies === false) {
|
|
881
890
|
return false;
|
|
882
891
|
}
|
|
@@ -899,8 +908,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
899
908
|
*
|
|
900
909
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
901
910
|
*/
|
|
902
|
-
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
|
|
903
|
-
var _a, _b, _c;
|
|
911
|
+
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
|
|
904
912
|
this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
|
|
905
913
|
// Ideally we always connect as "read" by default.
|
|
906
914
|
// Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
|
|
@@ -912,7 +920,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
912
920
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
913
921
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
914
922
|
const connectionArgs = {
|
|
915
|
-
reason: "DocumentOpen",
|
|
923
|
+
reason: { text: "DocumentOpen" },
|
|
916
924
|
mode: "write",
|
|
917
925
|
fetchOpsFromStorage: false,
|
|
918
926
|
};
|
|
@@ -949,9 +957,51 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
949
957
|
}
|
|
950
958
|
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
|
|
951
959
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
952
|
-
const sequenceNumber =
|
|
953
|
-
const dmAttributes = sequenceNumber !== undefined ?
|
|
960
|
+
const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
|
|
961
|
+
const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
|
|
954
962
|
let opsBeforeReturnP;
|
|
963
|
+
if (loadMode.pauseAfterLoad === true) {
|
|
964
|
+
// If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
|
|
965
|
+
if (loadMode.opsBeforeReturn === "sequenceNumber") {
|
|
966
|
+
assert(loadToSequenceNumber !== undefined, 0x727 /* sequenceNumber should be defined */);
|
|
967
|
+
// Note: It is possible that we think the latest snapshot is newer than the specified sequence number
|
|
968
|
+
// due to saved ops that may be replayed after the snapshot.
|
|
969
|
+
// https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
|
|
970
|
+
if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
|
|
971
|
+
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.");
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// Force readonly mode - this will ensure we don't receive an error for the lack of join op
|
|
975
|
+
this.forceReadonly(true);
|
|
976
|
+
// We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
|
|
977
|
+
const opHandler = () => {
|
|
978
|
+
if (loadToSequenceNumber === undefined) {
|
|
979
|
+
// If there is no specified sequence number, pause after the inbound queue is empty.
|
|
980
|
+
if (this.deltaManager.inbound.length !== 0) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
// If there is a specified sequence number, keep processing until we reach it.
|
|
986
|
+
if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
// Pause op processing once we have processed the desired number of ops.
|
|
991
|
+
void this.deltaManager.inbound.pause();
|
|
992
|
+
void this.deltaManager.outbound.pause();
|
|
993
|
+
this.off("op", opHandler);
|
|
994
|
+
};
|
|
995
|
+
if ((loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
|
|
996
|
+
this.deltaManager.lastSequenceNumber === loadToSequenceNumber) {
|
|
997
|
+
// If we have already reached the desired sequence number, call opHandler() to pause immediately.
|
|
998
|
+
opHandler();
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
// If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
|
|
1002
|
+
this.on("op", opHandler);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
955
1005
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
956
1006
|
// Kick off any ops fetching if required.
|
|
957
1007
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -960,6 +1010,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
960
1010
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
961
1011
|
this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
|
|
962
1012
|
break;
|
|
1013
|
+
case "sequenceNumber":
|
|
1014
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "sequenceNumber");
|
|
1015
|
+
break;
|
|
963
1016
|
case "cached":
|
|
964
1017
|
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
965
1018
|
break;
|
|
@@ -973,18 +1026,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
973
1026
|
// Initialize the protocol handler
|
|
974
1027
|
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
975
1028
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
976
|
-
await this.instantiateRuntime(codeDetails, snapshot, pendingLocalState
|
|
1029
|
+
await this.instantiateRuntime(codeDetails, snapshot, pendingLocalState?.pendingRuntimeState);
|
|
977
1030
|
// replay saved ops
|
|
978
1031
|
if (pendingLocalState) {
|
|
979
1032
|
for (const message of pendingLocalState.savedOps) {
|
|
980
1033
|
this.processRemoteMessage(message);
|
|
981
1034
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
982
|
-
await
|
|
1035
|
+
await this.runtime.notifyOpReplay?.(message);
|
|
983
1036
|
}
|
|
984
1037
|
pendingLocalState.savedOps = [];
|
|
985
1038
|
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
986
1039
|
assert(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
|
|
987
|
-
this._clientId = pendingLocalState
|
|
1040
|
+
this._clientId = pendingLocalState?.clientId;
|
|
988
1041
|
}
|
|
989
1042
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
990
1043
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
@@ -1015,6 +1068,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1015
1068
|
unreachableCase(loadMode.deltaConnection);
|
|
1016
1069
|
}
|
|
1017
1070
|
}
|
|
1071
|
+
// If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
|
|
1072
|
+
if (loadToSequenceNumber !== undefined &&
|
|
1073
|
+
this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1074
|
+
await new Promise((resolve, reject) => {
|
|
1075
|
+
const opHandler = (message) => {
|
|
1076
|
+
if (message.sequenceNumber >= loadToSequenceNumber) {
|
|
1077
|
+
resolve();
|
|
1078
|
+
this.off("op", opHandler);
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
this.on("op", opHandler);
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1018
1084
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
1019
1085
|
// But if that did not happen for some reason, fail load for sure.
|
|
1020
1086
|
// Otherwise we can get into situations where container is closed and does not try to connect to ordering
|
|
@@ -1169,8 +1235,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1169
1235
|
return pkg;
|
|
1170
1236
|
}
|
|
1171
1237
|
get client() {
|
|
1172
|
-
|
|
1173
|
-
const client = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.client) !== undefined
|
|
1238
|
+
const client = this.options?.client !== undefined
|
|
1174
1239
|
? this.options.client
|
|
1175
1240
|
: {
|
|
1176
1241
|
details: {
|
|
@@ -1217,11 +1282,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1217
1282
|
deltaManager.on("cancelEstablishingConnection", (reason) => {
|
|
1218
1283
|
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1219
1284
|
});
|
|
1220
|
-
deltaManager.on("disconnect", (reason
|
|
1221
|
-
|
|
1222
|
-
(_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
|
|
1285
|
+
deltaManager.on("disconnect", (reason) => {
|
|
1286
|
+
this.noopHeuristic?.notifyDisconnect();
|
|
1223
1287
|
if (!this.closed) {
|
|
1224
|
-
this.connectionStateHandler.receivedDisconnectEvent(reason
|
|
1288
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1225
1289
|
}
|
|
1226
1290
|
});
|
|
1227
1291
|
deltaManager.on("throttled", (warning) => {
|
|
@@ -1253,8 +1317,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1253
1317
|
},
|
|
1254
1318
|
}, prefetchType);
|
|
1255
1319
|
}
|
|
1256
|
-
logConnectionStateChangeTelemetry(value, oldState, reason
|
|
1257
|
-
var _a;
|
|
1320
|
+
logConnectionStateChangeTelemetry(value, oldState, reason) {
|
|
1258
1321
|
// Log actual event
|
|
1259
1322
|
const time = performance.now();
|
|
1260
1323
|
this.connectionTransitionTimes[value] = time;
|
|
@@ -1284,19 +1347,31 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1284
1347
|
}
|
|
1285
1348
|
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1286
1349
|
}
|
|
1287
|
-
this.mc.logger.sendPerformanceEvent(
|
|
1350
|
+
this.mc.logger.sendPerformanceEvent({
|
|
1351
|
+
eventName: `ConnectionStateChange_${ConnectionState[value]}`,
|
|
1352
|
+
from: ConnectionState[oldState],
|
|
1353
|
+
duration,
|
|
1288
1354
|
durationFromDisconnected,
|
|
1289
|
-
reason,
|
|
1290
|
-
connectionInitiationReason,
|
|
1291
|
-
|
|
1355
|
+
reason: reason?.text,
|
|
1356
|
+
connectionInitiationReason,
|
|
1357
|
+
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
1358
|
+
clientId: this.clientId,
|
|
1359
|
+
autoReconnect,
|
|
1360
|
+
opsBehind,
|
|
1361
|
+
online: OnlineStatus[isOnline()],
|
|
1362
|
+
lastVisible: this.lastVisible !== undefined
|
|
1292
1363
|
? performance.now() - this.lastVisible
|
|
1293
|
-
: undefined,
|
|
1364
|
+
: undefined,
|
|
1365
|
+
checkpointSequenceNumber,
|
|
1366
|
+
quorumSize: this._protocolHandler?.quorum.getMembers().size,
|
|
1367
|
+
isDirty: this.isDirty,
|
|
1368
|
+
...this._deltaManager.connectionProps,
|
|
1369
|
+
}, reason?.error);
|
|
1294
1370
|
if (value === ConnectionState.Connected) {
|
|
1295
1371
|
this.firstConnection = false;
|
|
1296
1372
|
}
|
|
1297
1373
|
}
|
|
1298
1374
|
propagateConnectionState(initialTransition, disconnectedReason) {
|
|
1299
|
-
var _a;
|
|
1300
1375
|
// When container loaded, we want to propagate initial connection state.
|
|
1301
1376
|
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1302
1377
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
@@ -1307,9 +1382,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1307
1382
|
}
|
|
1308
1383
|
const state = this.connectionState === ConnectionState.Connected;
|
|
1309
1384
|
// Both protocol and context should not be undefined if we got so far.
|
|
1310
|
-
this.setContextConnectedState(state,
|
|
1385
|
+
this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
|
|
1311
1386
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1312
|
-
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1387
|
+
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
|
|
1313
1388
|
}
|
|
1314
1389
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1315
1390
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
@@ -1347,12 +1422,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1347
1422
|
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
|
|
1348
1423
|
}
|
|
1349
1424
|
submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
|
|
1350
|
-
var _a;
|
|
1351
1425
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1352
1426
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
1353
1427
|
return -1;
|
|
1354
1428
|
}
|
|
1355
|
-
|
|
1429
|
+
this.noopHeuristic?.notifyMessageSent();
|
|
1356
1430
|
return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
|
|
1357
1431
|
}
|
|
1358
1432
|
processRemoteMessage(message) {
|
|
@@ -1360,23 +1434,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1360
1434
|
this.savedOps.push(message);
|
|
1361
1435
|
}
|
|
1362
1436
|
const local = this.clientId === message.clientId;
|
|
1363
|
-
// Check and report if we're getting messages from a clientId that we previously
|
|
1364
|
-
// flagged should have left, or from a client that's not in the quorum but should be
|
|
1365
|
-
if (message.clientId != null) {
|
|
1366
|
-
const client = this.protocolHandler.quorum.getMember(message.clientId);
|
|
1367
|
-
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
1368
|
-
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
1369
|
-
throw new Error("Remote message's clientId is missing from the quorum");
|
|
1370
|
-
}
|
|
1371
|
-
// Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
|
|
1372
|
-
// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
|
|
1373
|
-
// document we don't need to blow up aggressively.
|
|
1374
|
-
if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
|
|
1375
|
-
!canBeCoalescedByService(message)) {
|
|
1376
|
-
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
1377
|
-
throw new Error("Remote message's clientId already should have left");
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
1437
|
// Allow the protocol handler to process the message
|
|
1381
1438
|
const result = this.protocolHandler.processMessage(message, local);
|
|
1382
1439
|
// Forward messages to the loaded runtime for processing
|
|
@@ -1427,8 +1484,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1427
1484
|
* @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
|
|
1428
1485
|
*/
|
|
1429
1486
|
async fetchSnapshotTree(specifiedVersion) {
|
|
1430
|
-
|
|
1431
|
-
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1487
|
+
const version = await this.getVersion(specifiedVersion ?? null);
|
|
1432
1488
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1433
1489
|
// We should have a defined version to load from if specified version requested
|
|
1434
1490
|
this.mc.logger.sendErrorEvent({
|
|
@@ -1437,15 +1493,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1437
1493
|
});
|
|
1438
1494
|
}
|
|
1439
1495
|
this._loadedFromVersion = version;
|
|
1440
|
-
const snapshot = (
|
|
1496
|
+
const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
|
|
1441
1497
|
if (snapshot === undefined && version !== undefined) {
|
|
1442
1498
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1443
1499
|
}
|
|
1444
|
-
return { snapshot, versionId: version
|
|
1500
|
+
return { snapshot, versionId: version?.id };
|
|
1445
1501
|
}
|
|
1446
1502
|
async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
|
|
1447
|
-
|
|
1448
|
-
assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1503
|
+
assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1449
1504
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1450
1505
|
// are set. Global requests will still go directly to the loader
|
|
1451
1506
|
const maybeLoader = this.scope;
|
|
@@ -1456,22 +1511,17 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1456
1511
|
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
1457
1512
|
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
1458
1513
|
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
1459
|
-
details:
|
|
1514
|
+
details: loadCodeResult.details ?? codeDetails,
|
|
1460
1515
|
};
|
|
1461
1516
|
const fluidExport = this._loadedModule.module.fluidExport;
|
|
1462
|
-
const runtimeFactory = fluidExport
|
|
1517
|
+
const runtimeFactory = fluidExport?.IRuntimeFactory;
|
|
1463
1518
|
if (runtimeFactory === undefined) {
|
|
1464
1519
|
throw new Error(packageNotFactoryError);
|
|
1465
1520
|
}
|
|
1466
|
-
const getSpecifiedCodeDetails = () =>
|
|
1467
|
-
|
|
1468
|
-
return ((_a = this.protocolHandler.quorum.get("code")) !== null && _a !== void 0 ? _a : this.protocolHandler.quorum.get("code2"));
|
|
1469
|
-
};
|
|
1521
|
+
const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
|
|
1522
|
+
this.protocolHandler.quorum.get("code2"));
|
|
1470
1523
|
const existing = snapshot !== undefined;
|
|
1471
|
-
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, () =>
|
|
1472
|
-
this._lifecycleEvents.once("disposed", () => {
|
|
1473
|
-
context.dispose();
|
|
1474
|
-
});
|
|
1524
|
+
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);
|
|
1475
1525
|
this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
|
|
1476
1526
|
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
1477
1527
|
this._loadedCodeDetails = codeDetails;
|
|
@@ -1483,8 +1533,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1483
1533
|
* @param readonly - Is the container in readonly mode?
|
|
1484
1534
|
*/
|
|
1485
1535
|
setContextConnectedState(state, readonly) {
|
|
1486
|
-
|
|
1487
|
-
if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
|
|
1536
|
+
if (this._runtime?.disposed === false) {
|
|
1488
1537
|
/**
|
|
1489
1538
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1490
1539
|
* ops getting through to the DeltaManager.
|