@fluidframework/container-loader 0.52.1 → 0.54.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/dist/connectionManager.d.ts +153 -0
- package/dist/connectionManager.d.ts.map +1 -0
- package/dist/connectionManager.js +664 -0
- package/dist/connectionManager.js.map +1 -0
- package/dist/connectionStateHandler.d.ts +1 -0
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +6 -0
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +2 -22
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +121 -151
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +1 -0
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +4 -0
- package/dist/containerContext.js.map +1 -1
- package/dist/contracts.d.ts +112 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +14 -0
- package/dist/contracts.js.map +1 -0
- package/dist/deltaManager.d.ts +26 -142
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +143 -770
- package/dist/deltaManager.js.map +1 -1
- package/dist/loader.d.ts +14 -4
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +10 -4
- 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/protocolTreeDocumentStorageService.d.ts +2 -2
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/connectionManager.d.ts +153 -0
- package/lib/connectionManager.d.ts.map +1 -0
- package/lib/connectionManager.js +660 -0
- package/lib/connectionManager.js.map +1 -0
- package/lib/connectionStateHandler.d.ts +1 -0
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +6 -0
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +2 -22
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +122 -152
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +1 -0
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +4 -0
- package/lib/containerContext.js.map +1 -1
- package/lib/contracts.d.ts +112 -0
- package/lib/contracts.d.ts.map +1 -0
- package/lib/contracts.js +11 -0
- package/lib/contracts.js.map +1 -0
- package/lib/deltaManager.d.ts +26 -142
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +147 -774
- package/lib/deltaManager.js.map +1 -1
- package/lib/loader.d.ts +14 -4
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +11 -5
- 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/protocolTreeDocumentStorageService.d.ts +2 -2
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/connectionManager.ts +892 -0
- package/src/connectionStateHandler.ts +8 -0
- package/src/container.ts +165 -187
- package/src/containerContext.ts +4 -0
- package/src/contracts.ts +156 -0
- package/src/deltaManager.ts +181 -978
- package/src/loader.ts +59 -27
- package/src/packageVersion.ts +1 -1
package/lib/container.js
CHANGED
|
@@ -9,13 +9,14 @@ import { assert, performance, unreachableCase } from "@fluidframework/common-uti
|
|
|
9
9
|
import { isFluidCodeDetails, } from "@fluidframework/core-interfaces";
|
|
10
10
|
import { AttachState, } from "@fluidframework/container-definitions";
|
|
11
11
|
import { DataCorruptionError, extractSafePropertiesFromMessage, GenericError, UsageError, } from "@fluidframework/container-utils";
|
|
12
|
-
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry,
|
|
12
|
+
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
|
|
13
13
|
import { isSystemMessage, ProtocolOpHandler, } from "@fluidframework/protocol-base";
|
|
14
14
|
import { FileMode, MessageType, TreeEntry, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
15
15
|
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, } from "@fluidframework/telemetry-utils";
|
|
16
16
|
import { Audience } from "./audience";
|
|
17
17
|
import { ContainerContext } from "./containerContext";
|
|
18
|
-
import {
|
|
18
|
+
import { ReconnectMode } from "./contracts";
|
|
19
|
+
import { DeltaManager } from "./deltaManager";
|
|
19
20
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
20
21
|
import { RelativeLoader } from "./loader";
|
|
21
22
|
import { pkgVersion } from "./packageVersion";
|
|
@@ -26,6 +27,7 @@ import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdap
|
|
|
26
27
|
import { getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
27
28
|
import { QuorumProxy } from "./quorum";
|
|
28
29
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
30
|
+
import { ConnectionManager } from "./connectionManager";
|
|
29
31
|
const detachedContainerRefSeqNumber = 0;
|
|
30
32
|
const dirtyContainerEvent = "dirty";
|
|
31
33
|
const savedContainerEvent = "saved";
|
|
@@ -92,6 +94,8 @@ export async function waitContainerToCatchUp(container) {
|
|
|
92
94
|
waitForOps();
|
|
93
95
|
};
|
|
94
96
|
container.on(connectedEventName, callback);
|
|
97
|
+
// TODO: Remove null check after next release #8523
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
95
99
|
container.resume();
|
|
96
100
|
});
|
|
97
101
|
}
|
|
@@ -137,7 +141,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
137
141
|
this.subLogger = ChildLogger.create(loader.services.subLogger, undefined, {
|
|
138
142
|
all: {
|
|
139
143
|
clientType,
|
|
140
|
-
loaderVersion: pkgVersion,
|
|
141
144
|
containerId: uuid(),
|
|
142
145
|
docId: () => this.id,
|
|
143
146
|
containerAttachState: () => this._attachState,
|
|
@@ -166,7 +169,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
166
169
|
this.connectionStateHandler = new ConnectionStateHandler({
|
|
167
170
|
protocolHandler: () => this._protocolHandler,
|
|
168
171
|
logConnectionStateChangeTelemetry: (value, oldState, reason) => this.logConnectionStateChangeTelemetry(value, oldState, reason),
|
|
169
|
-
shouldClientJoinWrite: () => this._deltaManager.shouldJoinWrite(),
|
|
172
|
+
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
170
173
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
171
174
|
logConnectionIssue: (eventName) => {
|
|
172
175
|
// We get here when socket does not receive any ops on "write" connection, including
|
|
@@ -222,22 +225,22 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
222
225
|
switch (event) {
|
|
223
226
|
case dirtyContainerEvent:
|
|
224
227
|
if (this._dirtyContainer) {
|
|
225
|
-
listener(
|
|
228
|
+
listener();
|
|
226
229
|
}
|
|
227
230
|
break;
|
|
228
231
|
case savedContainerEvent:
|
|
229
232
|
if (!this._dirtyContainer) {
|
|
230
|
-
listener(
|
|
233
|
+
listener();
|
|
231
234
|
}
|
|
232
235
|
break;
|
|
233
236
|
case connectedEventName:
|
|
234
237
|
if (this.connected) {
|
|
235
|
-
listener(
|
|
238
|
+
listener(this.clientId);
|
|
236
239
|
}
|
|
237
240
|
break;
|
|
238
241
|
case disconnectedEventName:
|
|
239
242
|
if (!this.connected) {
|
|
240
|
-
listener(
|
|
243
|
+
listener();
|
|
241
244
|
}
|
|
242
245
|
break;
|
|
243
246
|
default:
|
|
@@ -284,16 +287,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
284
287
|
container.close(err);
|
|
285
288
|
onClosed(err);
|
|
286
289
|
});
|
|
287
|
-
}), { start: true, end: true, cancel: "
|
|
290
|
+
}), { start: true, end: true, cancel: "generic" });
|
|
288
291
|
}
|
|
289
292
|
/**
|
|
290
293
|
* Create a new container in a detached state.
|
|
291
294
|
*/
|
|
292
295
|
static async createDetached(loader, codeDetails) {
|
|
293
296
|
const container = new Container(loader, {});
|
|
294
|
-
container.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
+
return PerformanceEvent.timedExecAsync(container.logger, { eventName: "CreateDetached" }, async (_event) => {
|
|
298
|
+
container._lifecycleState = "loading";
|
|
299
|
+
await container.createDetached(codeDetails);
|
|
300
|
+
return container;
|
|
301
|
+
}, { start: true, end: true, cancel: "generic" });
|
|
297
302
|
}
|
|
298
303
|
/**
|
|
299
304
|
* Create a new container in a detached state that is initialized with a
|
|
@@ -301,10 +306,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
301
306
|
*/
|
|
302
307
|
static async rehydrateDetachedFromSnapshot(loader, snapshot) {
|
|
303
308
|
const container = new Container(loader, {});
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
309
|
+
return PerformanceEvent.timedExecAsync(container.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
|
|
310
|
+
const deserializedSummary = JSON.parse(snapshot);
|
|
311
|
+
container._lifecycleState = "loading";
|
|
312
|
+
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
313
|
+
return container;
|
|
314
|
+
}, { start: true, end: true, cancel: "generic" });
|
|
308
315
|
}
|
|
309
316
|
get loaded() {
|
|
310
317
|
return (this._lifecycleState !== "created" && this._lifecycleState !== "loading");
|
|
@@ -342,6 +349,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
342
349
|
}
|
|
343
350
|
return this._protocolHandler;
|
|
344
351
|
}
|
|
352
|
+
get connectionMode() { return this._deltaManager.connectionManager.connectionMode; }
|
|
345
353
|
get IFluidRouter() { return this; }
|
|
346
354
|
get resolvedUrl() {
|
|
347
355
|
return this._resolvedUrl;
|
|
@@ -349,31 +357,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
349
357
|
get loadedFromVersion() {
|
|
350
358
|
return this._loadedFromVersion;
|
|
351
359
|
}
|
|
352
|
-
/**
|
|
353
|
-
* Tells if container is in read-only mode.
|
|
354
|
-
* Data stores should listen for "readonly" notifications and disallow user making changes to data stores.
|
|
355
|
-
* Readonly state can be because of no storage write permission,
|
|
356
|
-
* or due to host forcing readonly mode for container.
|
|
357
|
-
*
|
|
358
|
-
* We do not differentiate here between no write access to storage vs. host disallowing changes to container -
|
|
359
|
-
* in all cases container runtime and data stores should respect readonly state and not allow local changes.
|
|
360
|
-
*
|
|
361
|
-
* It is undefined if we have not yet established websocket connection
|
|
362
|
-
* and do not know if user has write access to a file.
|
|
363
|
-
* @deprecated - use readOnlyInfo
|
|
364
|
-
*/
|
|
365
|
-
get readonly() {
|
|
366
|
-
return this._deltaManager.readonly;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Tells if user has no write permissions for file in storage
|
|
370
|
-
* It is undefined if we have not yet established websocket connection
|
|
371
|
-
* and do not know if user has write access to a file.
|
|
372
|
-
* @deprecated - use readOnlyInfo
|
|
373
|
-
*/
|
|
374
|
-
get readonlyPermissions() {
|
|
375
|
-
return this._deltaManager.readonlyPermissions;
|
|
376
|
-
}
|
|
377
360
|
get readOnlyInfo() {
|
|
378
361
|
return this._deltaManager.readOnlyInfo;
|
|
379
362
|
}
|
|
@@ -381,7 +364,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
381
364
|
* Tracks host requiring read-only mode.
|
|
382
365
|
*/
|
|
383
366
|
forceReadonly(readonly) {
|
|
384
|
-
this._deltaManager.forceReadonly(readonly);
|
|
367
|
+
this._deltaManager.connectionManager.forceReadonly(readonly);
|
|
385
368
|
}
|
|
386
369
|
get id() {
|
|
387
370
|
var _a, _b;
|
|
@@ -415,7 +398,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
415
398
|
* Set once this.connected is true, otherwise undefined
|
|
416
399
|
*/
|
|
417
400
|
get scopes() {
|
|
418
|
-
return this._deltaManager.scopes;
|
|
401
|
+
return this._deltaManager.connectionManager.scopes;
|
|
419
402
|
}
|
|
420
403
|
get clientDetails() {
|
|
421
404
|
return this._deltaManager.clientDetails;
|
|
@@ -482,6 +465,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
482
465
|
try {
|
|
483
466
|
this._deltaManager.close(error);
|
|
484
467
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
468
|
+
this.connectionStateHandler.dispose();
|
|
485
469
|
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
486
470
|
assert(this.connectionState === ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
|
|
487
471
|
(_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
|
|
@@ -531,89 +515,88 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
531
515
|
return JSON.stringify(combinedSummary);
|
|
532
516
|
}
|
|
533
517
|
async attach(request) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
// If container is already attached or attach is in progress, throw an error.
|
|
538
|
-
assert(this._attachState === AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
539
|
-
this.attachStarted = true;
|
|
540
|
-
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
541
|
-
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
542
|
-
&& this.loader.services.detachedBlobStorage.size > 0;
|
|
543
|
-
try {
|
|
544
|
-
assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
545
|
-
let summary;
|
|
546
|
-
if (!hasAttachmentBlobs) {
|
|
547
|
-
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
548
|
-
// semantics around what the attach means as far as async code goes.
|
|
549
|
-
const appSummary = this.context.createSummary();
|
|
550
|
-
const protocolSummary = this.captureProtocolSummary();
|
|
551
|
-
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
552
|
-
// Set the state as attaching as we are starting the process of attaching container.
|
|
553
|
-
// This should be fired after taking the summary because it is the place where we are
|
|
554
|
-
// starting to attach the container to storage.
|
|
555
|
-
// Also, this should only be fired in detached container.
|
|
556
|
-
this._attachState = AttachState.Attaching;
|
|
557
|
-
this.context.notifyAttaching();
|
|
558
|
-
}
|
|
559
|
-
// Actually go and create the resolved document
|
|
560
|
-
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
561
|
-
ensureFluidResolvedUrl(createNewResolvedUrl);
|
|
562
|
-
if (this.service === undefined) {
|
|
563
|
-
this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger), "containerAttach", this.logger, {});
|
|
518
|
+
await PerformanceEvent.timedExecAsync(this.logger, { eventName: "Attach" }, async () => {
|
|
519
|
+
if (this._lifecycleState !== "loaded") {
|
|
520
|
+
throw new UsageError(`containerNotValidForAttach [${this._lifecycleState}]`);
|
|
564
521
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
this.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
522
|
+
// If container is already attached or attach is in progress, throw an error.
|
|
523
|
+
assert(this._attachState === AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
524
|
+
this.attachStarted = true;
|
|
525
|
+
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
526
|
+
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
527
|
+
&& this.loader.services.detachedBlobStorage.size > 0;
|
|
528
|
+
try {
|
|
529
|
+
assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
530
|
+
let summary;
|
|
531
|
+
if (!hasAttachmentBlobs) {
|
|
532
|
+
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
533
|
+
// semantics around what the attach means as far as async code goes.
|
|
534
|
+
const appSummary = this.context.createSummary();
|
|
535
|
+
const protocolSummary = this.captureProtocolSummary();
|
|
536
|
+
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
537
|
+
// Set the state as attaching as we are starting the process of attaching container.
|
|
538
|
+
// This should be fired after taking the summary because it is the place where we are
|
|
539
|
+
// starting to attach the container to storage.
|
|
540
|
+
// Also, this should only be fired in detached container.
|
|
541
|
+
this._attachState = AttachState.Attaching;
|
|
542
|
+
this.context.notifyAttaching();
|
|
543
|
+
}
|
|
544
|
+
// Actually go and create the resolved document
|
|
545
|
+
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
546
|
+
ensureFluidResolvedUrl(createNewResolvedUrl);
|
|
547
|
+
if (this.service === undefined) {
|
|
548
|
+
this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger), "containerAttach", this.logger, {});
|
|
549
|
+
}
|
|
550
|
+
const resolvedUrl = this.service.resolvedUrl;
|
|
551
|
+
ensureFluidResolvedUrl(resolvedUrl);
|
|
552
|
+
this._resolvedUrl = resolvedUrl;
|
|
553
|
+
await this.connectStorageService();
|
|
554
|
+
if (hasAttachmentBlobs) {
|
|
555
|
+
// upload blobs to storage
|
|
556
|
+
assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
557
|
+
// build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
|
|
558
|
+
// support blob handles that only know about the local IDs
|
|
559
|
+
const redirectTable = new Map();
|
|
560
|
+
// if new blobs are added while uploading, upload them too
|
|
561
|
+
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
562
|
+
const newIds = this.loader.services.detachedBlobStorage.getBlobIds().filter((id) => !redirectTable.has(id));
|
|
563
|
+
for (const id of newIds) {
|
|
564
|
+
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
565
|
+
const response = await this.storageService.createBlob(blob);
|
|
566
|
+
redirectTable.set(id, response.id);
|
|
567
|
+
}
|
|
582
568
|
}
|
|
569
|
+
// take summary and upload
|
|
570
|
+
const appSummary = this.context.createSummary(redirectTable);
|
|
571
|
+
const protocolSummary = this.captureProtocolSummary();
|
|
572
|
+
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
573
|
+
this._attachState = AttachState.Attaching;
|
|
574
|
+
this.context.notifyAttaching();
|
|
575
|
+
await this.storageService.uploadSummaryWithContext(summary, {
|
|
576
|
+
referenceSequenceNumber: 0,
|
|
577
|
+
ackHandle: undefined,
|
|
578
|
+
proposalHandle: undefined,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
this._attachState = AttachState.Attached;
|
|
582
|
+
this.emit("attached");
|
|
583
|
+
// Propagate current connection state through the system.
|
|
584
|
+
this.propagateConnectionState();
|
|
585
|
+
if (!this.closed) {
|
|
586
|
+
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
583
587
|
}
|
|
584
|
-
// take summary and upload
|
|
585
|
-
const appSummary = this.context.createSummary(redirectTable);
|
|
586
|
-
const protocolSummary = this.captureProtocolSummary();
|
|
587
|
-
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
588
|
-
this._attachState = AttachState.Attaching;
|
|
589
|
-
this.context.notifyAttaching();
|
|
590
|
-
await this.storageService.uploadSummaryWithContext(summary, {
|
|
591
|
-
referenceSequenceNumber: 0,
|
|
592
|
-
ackHandle: undefined,
|
|
593
|
-
proposalHandle: undefined,
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
this._attachState = AttachState.Attached;
|
|
597
|
-
this.emit("attached");
|
|
598
|
-
// Propagate current connection state through the system.
|
|
599
|
-
this.propagateConnectionState();
|
|
600
|
-
if (!this.closed) {
|
|
601
|
-
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
602
588
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
589
|
+
catch (error) {
|
|
590
|
+
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
591
|
+
const newError = normalizeError(error);
|
|
592
|
+
const resolvedUrl = this.resolvedUrl;
|
|
593
|
+
if (isFluidResolvedUrl(resolvedUrl)) {
|
|
594
|
+
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
595
|
+
}
|
|
596
|
+
this.close(newError);
|
|
597
|
+
throw newError;
|
|
613
598
|
}
|
|
614
|
-
|
|
615
|
-
throw newError;
|
|
616
|
-
}
|
|
599
|
+
}, { start: true, end: true, cancel: "generic" });
|
|
617
600
|
}
|
|
618
601
|
async request(path) {
|
|
619
602
|
return PerformanceEvent.timedExecAsync(this.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
|
|
@@ -642,7 +625,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
642
625
|
throw new Error("Attempting to setAutoReconnect() a closed Container");
|
|
643
626
|
}
|
|
644
627
|
const mode = reconnect ? ReconnectMode.Enabled : ReconnectMode.Disabled;
|
|
645
|
-
const currentMode = this._deltaManager.reconnectMode;
|
|
628
|
+
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
646
629
|
if (currentMode === mode) {
|
|
647
630
|
return;
|
|
648
631
|
}
|
|
@@ -651,11 +634,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
651
634
|
this.setAutoReconnectTime = now;
|
|
652
635
|
this.logger.sendTelemetryEvent({
|
|
653
636
|
eventName: reconnect ? "AutoReconnectEnabled" : "AutoReconnectDisabled",
|
|
654
|
-
connectionMode: this.
|
|
637
|
+
connectionMode: this.connectionMode,
|
|
655
638
|
connectionState: ConnectionState[this.connectionState],
|
|
656
639
|
duration,
|
|
657
640
|
});
|
|
658
|
-
this._deltaManager.setAutoReconnect(mode);
|
|
641
|
+
this._deltaManager.connectionManager.setAutoReconnect(mode);
|
|
659
642
|
// If container state is not attached and resumed, then don't connect to delta stream. Also don't set the
|
|
660
643
|
// manual reconnection flag to true as we haven't made the initial connection yet.
|
|
661
644
|
if (reconnect && this._attachState === AttachState.Attached && this.resumedOpProcessingAfterLoad) {
|
|
@@ -664,13 +647,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
664
647
|
this.manualReconnectInProgress = true;
|
|
665
648
|
}
|
|
666
649
|
// Ensure connection to web socket
|
|
667
|
-
this.connectToDeltaStream({ reason: "autoReconnect" })
|
|
668
|
-
// All errors are reported through events ("error" / "disconnected") and telemetry in DeltaManager
|
|
669
|
-
// So there shouldn't be a need to record error here.
|
|
670
|
-
// But we have number of cases where reconnects do not happen, and no errors are recorded, so
|
|
671
|
-
// adding this log point for easier diagnostics
|
|
672
|
-
this.logger.sendTelemetryEvent({ eventName: "setAutoReconnectError" }, error);
|
|
673
|
-
});
|
|
650
|
+
this.connectToDeltaStream({ reason: "autoReconnect" });
|
|
674
651
|
}
|
|
675
652
|
}
|
|
676
653
|
resume() {
|
|
@@ -690,8 +667,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
690
667
|
this._deltaManager.inboundSignal.resume();
|
|
691
668
|
}
|
|
692
669
|
// Ensure connection to web socket
|
|
693
|
-
|
|
694
|
-
this.connectToDeltaStream(args).catch(() => { });
|
|
670
|
+
this.connectToDeltaStream(args);
|
|
695
671
|
}
|
|
696
672
|
/**
|
|
697
673
|
* Raise non-critical error to host. Calling this API will not close container.
|
|
@@ -818,13 +794,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
818
794
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
819
795
|
}
|
|
820
796
|
}
|
|
821
|
-
|
|
797
|
+
connectToDeltaStream(args) {
|
|
822
798
|
this.recordConnectStartTime();
|
|
823
799
|
// All agents need "write" access, including summarizer.
|
|
824
800
|
if (!this._canReconnect || !this.client.details.capabilities.interactive) {
|
|
825
801
|
args.mode = "write";
|
|
826
802
|
}
|
|
827
|
-
|
|
803
|
+
this._deltaManager.connect(args);
|
|
828
804
|
}
|
|
829
805
|
/**
|
|
830
806
|
* Load container.
|
|
@@ -838,7 +814,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
838
814
|
throw new Error("Attempting to load without a resolved url");
|
|
839
815
|
}
|
|
840
816
|
this.service = await this.serviceFactory.createDocumentService(this._resolvedUrl, this.subLogger);
|
|
841
|
-
let startConnectionP;
|
|
842
817
|
// Ideally we always connect as "read" by default.
|
|
843
818
|
// Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
|
|
844
819
|
// We should not rely on it by (one of them will address the issue, but we need to address both)
|
|
@@ -852,8 +827,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
852
827
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
853
828
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
854
829
|
if (loadMode.deltaConnection === undefined) {
|
|
855
|
-
|
|
856
|
-
startConnectionP.catch((error) => { });
|
|
830
|
+
this.connectToDeltaStream(connectionArgs);
|
|
857
831
|
}
|
|
858
832
|
await this.connectStorageService();
|
|
859
833
|
this._attachState = AttachState.Attached;
|
|
@@ -1107,6 +1081,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1107
1081
|
if (this.clientDetailsOverride !== undefined) {
|
|
1108
1082
|
merge(client.details, this.clientDetailsOverride);
|
|
1109
1083
|
}
|
|
1084
|
+
client.details.environment = [client.details.environment, ` loaderVersion:${pkgVersion}`].join(";");
|
|
1110
1085
|
return client;
|
|
1111
1086
|
}
|
|
1112
1087
|
/**
|
|
@@ -1116,17 +1091,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1116
1091
|
* If it's not true, runtime is not in position to send ops.
|
|
1117
1092
|
*/
|
|
1118
1093
|
activeConnection() {
|
|
1119
|
-
|
|
1120
|
-
this.
|
|
1121
|
-
// Check for presence of current client in quorum for "write" connections - inactive clients
|
|
1122
|
-
// would get leave op after some long timeout (5 min) and that should automatically transition
|
|
1123
|
-
// state to "read" mode.
|
|
1124
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1125
|
-
assert(!active || this.getQuorum().getMember(this.clientId) !== undefined, 0x276 /* "active connection not present in quorum" */);
|
|
1126
|
-
return active;
|
|
1094
|
+
return this.connectionState === ConnectionState.Connected &&
|
|
1095
|
+
this.connectionMode === "write";
|
|
1127
1096
|
}
|
|
1128
1097
|
createDeltaManager() {
|
|
1129
|
-
const
|
|
1098
|
+
const serviceProvider = () => this.service;
|
|
1099
|
+
const deltaManager = new DeltaManager(serviceProvider, ChildLogger.create(this.subLogger, "DeltaManager"), () => this.activeConnection(), (props) => new ConnectionManager(serviceProvider, this.client, this._canReconnect, ChildLogger.create(this.subLogger, "ConnectionManager"), props));
|
|
1130
1100
|
// Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
|
|
1131
1101
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1132
1102
|
deltaManager.inbound.pause();
|
|
@@ -1134,12 +1104,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1134
1104
|
deltaManager.inboundSignal.pause();
|
|
1135
1105
|
deltaManager.on("connect", (details, opsBehind) => {
|
|
1136
1106
|
var _a;
|
|
1137
|
-
this.connectionStateHandler.receivedConnectEvent(this._deltaManager.connectionMode, details);
|
|
1138
1107
|
// Back-compat for new client and old server.
|
|
1139
1108
|
this._audience.clear();
|
|
1140
1109
|
for (const priorClient of (_a = details.initialClients) !== null && _a !== void 0 ? _a : []) {
|
|
1141
1110
|
this._audience.addMember(priorClient.clientId, priorClient.client);
|
|
1142
1111
|
}
|
|
1112
|
+
this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details);
|
|
1143
1113
|
});
|
|
1144
1114
|
deltaManager.on("disconnect", (reason) => {
|
|
1145
1115
|
this.manualReconnectInProgress = false;
|
|
@@ -1177,7 +1147,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1177
1147
|
let checkpointSequenceNumber;
|
|
1178
1148
|
let opsBehind;
|
|
1179
1149
|
if (value === ConnectionState.Disconnected) {
|
|
1180
|
-
autoReconnect = this._deltaManager.reconnectMode;
|
|
1150
|
+
autoReconnect = this._deltaManager.connectionManager.reconnectMode;
|
|
1181
1151
|
}
|
|
1182
1152
|
else {
|
|
1183
1153
|
if (value === ConnectionState.Connected) {
|
|
@@ -1204,8 +1174,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1204
1174
|
this.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${ConnectionState[value]}`, from: ConnectionState[oldState], duration,
|
|
1205
1175
|
durationFromDisconnected,
|
|
1206
1176
|
reason,
|
|
1207
|
-
connectionInitiationReason,
|
|
1208
|
-
opsBehind, online: OnlineStatus[isOnline()], lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined, checkpointSequenceNumber }, this._deltaManager.connectionProps
|
|
1177
|
+
connectionInitiationReason, pendingClientId: this.connectionStateHandler.pendingClientId, clientId: this.clientId, autoReconnect,
|
|
1178
|
+
opsBehind, online: OnlineStatus[isOnline()], lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined, checkpointSequenceNumber }, this._deltaManager.connectionProps));
|
|
1209
1179
|
if (value === ConnectionState.Connected) {
|
|
1210
1180
|
this.firstConnection = false;
|
|
1211
1181
|
this.manualReconnectInProgress = false;
|
|
@@ -1214,7 +1184,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1214
1184
|
propagateConnectionState() {
|
|
1215
1185
|
const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
|
|
1216
1186
|
!this.firstConnection &&
|
|
1217
|
-
this.
|
|
1187
|
+
this.connectionMode === "write";
|
|
1218
1188
|
if (logOpsOnReconnect) {
|
|
1219
1189
|
this.messageCountAfterDisconnection = 0;
|
|
1220
1190
|
}
|