@fluidframework/container-loader 0.52.0 → 0.54.0-47413
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.d.ts.map +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.d.ts.map +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
|
@@ -60,6 +60,8 @@ export class ConnectionStateHandler {
|
|
|
60
60
|
// Default is 90 sec for which we are going to wait for its own "leave" message.
|
|
61
61
|
this.handler.maxClientLeaveWaitTime ?? 90000,
|
|
62
62
|
() => {
|
|
63
|
+
assert(!this.connected,
|
|
64
|
+
0x2ac /* "Connected when timeout waiting for leave from previous session fired!" */);
|
|
63
65
|
this.applyForConnectedState("timeout");
|
|
64
66
|
},
|
|
65
67
|
);
|
|
@@ -89,6 +91,11 @@ export class ConnectionStateHandler {
|
|
|
89
91
|
this.joinOpTimer.clear();
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
public dispose() {
|
|
95
|
+
assert(!this.joinOpTimer.hasTimer, 0x2a5 /* "join timer" */);
|
|
96
|
+
this.prevClientLeftTimer.clear();
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
public receivedAddMemberEvent(clientId: string) {
|
|
93
100
|
// This is the only one that requires the pending client ID
|
|
94
101
|
if (clientId === this.pendingClientId) {
|
|
@@ -181,6 +188,7 @@ export class ConnectionStateHandler {
|
|
|
181
188
|
if ((protocolHandler !== undefined && protocolHandler.quorum.getMember(details.clientId) !== undefined)
|
|
182
189
|
|| connectionMode === "read"
|
|
183
190
|
) {
|
|
191
|
+
assert(!this.prevClientLeftTimer.hasTimer, 0x2a6 /* "there should be no timer for 'read' connections" */);
|
|
184
192
|
this.setConnectionState(ConnectionState.Connected);
|
|
185
193
|
} else if (connectionMode === "write") {
|
|
186
194
|
this.startJoinOpTimer();
|
package/src/container.ts
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
ensureFluidResolvedUrl,
|
|
52
52
|
combineAppAndProtocolSummary,
|
|
53
53
|
runWithRetry,
|
|
54
|
-
|
|
54
|
+
isFluidResolvedUrl,
|
|
55
55
|
} from "@fluidframework/driver-utils";
|
|
56
56
|
import {
|
|
57
57
|
isSystemMessage,
|
|
@@ -96,7 +96,8 @@ import {
|
|
|
96
96
|
} from "@fluidframework/telemetry-utils";
|
|
97
97
|
import { Audience } from "./audience";
|
|
98
98
|
import { ContainerContext } from "./containerContext";
|
|
99
|
-
import {
|
|
99
|
+
import { ReconnectMode, IConnectionManagerFactoryArgs } from "./contracts";
|
|
100
|
+
import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
100
101
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
101
102
|
import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
|
|
102
103
|
import { pkgVersion } from "./packageVersion";
|
|
@@ -107,6 +108,7 @@ import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdap
|
|
|
107
108
|
import { getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
108
109
|
import { QuorumProxy } from "./quorum";
|
|
109
110
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
111
|
+
import { ConnectionManager } from "./connectionManager";
|
|
110
112
|
|
|
111
113
|
const detachedContainerRefSeqNumber = 0;
|
|
112
114
|
|
|
@@ -169,7 +171,7 @@ export enum ConnectionState {
|
|
|
169
171
|
* false: storage does not provide indication of how far the client is. Container processed
|
|
170
172
|
* all the ops known to it, but it maybe still behind.
|
|
171
173
|
*/
|
|
172
|
-
export async function waitContainerToCatchUp(container:
|
|
174
|
+
export async function waitContainerToCatchUp(container: IContainer) {
|
|
173
175
|
// Make sure we stop waiting if container is closed.
|
|
174
176
|
if (container.closed) {
|
|
175
177
|
throw new Error("Container is closed");
|
|
@@ -216,7 +218,9 @@ export async function waitContainerToCatchUp(container: Container) {
|
|
|
216
218
|
};
|
|
217
219
|
container.on(connectedEventName, callback);
|
|
218
220
|
|
|
219
|
-
|
|
221
|
+
// TODO: Remove null check after next release #8523
|
|
222
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
223
|
+
container.resume!();
|
|
220
224
|
});
|
|
221
225
|
}
|
|
222
226
|
|
|
@@ -279,7 +283,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
279
283
|
onClosed(err);
|
|
280
284
|
});
|
|
281
285
|
}),
|
|
282
|
-
{ start: true, end: true, cancel: "
|
|
286
|
+
{ start: true, end: true, cancel: "generic" },
|
|
283
287
|
);
|
|
284
288
|
}
|
|
285
289
|
|
|
@@ -293,9 +297,16 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
293
297
|
const container = new Container(
|
|
294
298
|
loader,
|
|
295
299
|
{});
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
|
|
301
|
+
return PerformanceEvent.timedExecAsync(
|
|
302
|
+
container.logger,
|
|
303
|
+
{ eventName: "CreateDetached" },
|
|
304
|
+
async (_event) => {
|
|
305
|
+
container._lifecycleState = "loading";
|
|
306
|
+
await container.createDetached(codeDetails);
|
|
307
|
+
return container;
|
|
308
|
+
},
|
|
309
|
+
{ start: true, end: true, cancel: "generic" });
|
|
299
310
|
}
|
|
300
311
|
|
|
301
312
|
/**
|
|
@@ -309,10 +320,16 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
309
320
|
const container = new Container(
|
|
310
321
|
loader,
|
|
311
322
|
{});
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
323
|
+
return PerformanceEvent.timedExecAsync(
|
|
324
|
+
container.logger,
|
|
325
|
+
{ eventName: "RehydrateDetachedFromSnapshot" },
|
|
326
|
+
async (_event) => {
|
|
327
|
+
const deserializedSummary = JSON.parse(snapshot) as ISummaryTree;
|
|
328
|
+
container._lifecycleState = "loading";
|
|
329
|
+
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
330
|
+
return container;
|
|
331
|
+
},
|
|
332
|
+
{ start: true, end: true, cancel: "generic" });
|
|
316
333
|
}
|
|
317
334
|
|
|
318
335
|
public subLogger: TelemetryLogger;
|
|
@@ -361,7 +378,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
361
378
|
}
|
|
362
379
|
|
|
363
380
|
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
364
|
-
private readonly _deltaManager: DeltaManager
|
|
381
|
+
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
365
382
|
private service: IDocumentService | undefined;
|
|
366
383
|
private readonly _audience: Audience;
|
|
367
384
|
|
|
@@ -402,6 +419,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
402
419
|
this.loader.services.options?.noopCountFrequency,
|
|
403
420
|
);
|
|
404
421
|
|
|
422
|
+
private get connectionMode() { return this._deltaManager.connectionManager.connectionMode; }
|
|
423
|
+
|
|
405
424
|
public get IFluidRouter(): IFluidRouter { return this; }
|
|
406
425
|
|
|
407
426
|
public get resolvedUrl(): IResolvedUrl | undefined {
|
|
@@ -412,33 +431,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
412
431
|
return this._loadedFromVersion;
|
|
413
432
|
}
|
|
414
433
|
|
|
415
|
-
/**
|
|
416
|
-
* Tells if container is in read-only mode.
|
|
417
|
-
* Data stores should listen for "readonly" notifications and disallow user making changes to data stores.
|
|
418
|
-
* Readonly state can be because of no storage write permission,
|
|
419
|
-
* or due to host forcing readonly mode for container.
|
|
420
|
-
*
|
|
421
|
-
* We do not differentiate here between no write access to storage vs. host disallowing changes to container -
|
|
422
|
-
* in all cases container runtime and data stores should respect readonly state and not allow local changes.
|
|
423
|
-
*
|
|
424
|
-
* It is undefined if we have not yet established websocket connection
|
|
425
|
-
* and do not know if user has write access to a file.
|
|
426
|
-
* @deprecated - use readOnlyInfo
|
|
427
|
-
*/
|
|
428
|
-
public get readonly() {
|
|
429
|
-
return this._deltaManager.readonly;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Tells if user has no write permissions for file in storage
|
|
434
|
-
* It is undefined if we have not yet established websocket connection
|
|
435
|
-
* and do not know if user has write access to a file.
|
|
436
|
-
* @deprecated - use readOnlyInfo
|
|
437
|
-
*/
|
|
438
|
-
public get readonlyPermissions() {
|
|
439
|
-
return this._deltaManager.readonlyPermissions;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
434
|
public get readOnlyInfo(): ReadOnlyInfo {
|
|
443
435
|
return this._deltaManager.readOnlyInfo;
|
|
444
436
|
}
|
|
@@ -447,7 +439,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
447
439
|
* Tracks host requiring read-only mode.
|
|
448
440
|
*/
|
|
449
441
|
public forceReadonly(readonly: boolean) {
|
|
450
|
-
this._deltaManager.forceReadonly(readonly);
|
|
442
|
+
this._deltaManager.connectionManager.forceReadonly(readonly);
|
|
451
443
|
}
|
|
452
444
|
|
|
453
445
|
public get id(): string {
|
|
@@ -487,7 +479,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
487
479
|
* Set once this.connected is true, otherwise undefined
|
|
488
480
|
*/
|
|
489
481
|
public get scopes(): string[] | undefined {
|
|
490
|
-
return this._deltaManager.scopes;
|
|
482
|
+
return this._deltaManager.connectionManager.scopes;
|
|
491
483
|
}
|
|
492
484
|
|
|
493
485
|
public get clientDetails(): IClientDetails {
|
|
@@ -576,7 +568,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
576
568
|
{
|
|
577
569
|
all: {
|
|
578
570
|
clientType, // Differentiating summarizer container from main container
|
|
579
|
-
loaderVersion: pkgVersion,
|
|
580
571
|
containerId: uuid(),
|
|
581
572
|
docId: () => this.id,
|
|
582
573
|
containerAttachState: () => this._attachState,
|
|
@@ -610,7 +601,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
610
601
|
protocolHandler: () => this._protocolHandler,
|
|
611
602
|
logConnectionStateChangeTelemetry: (value, oldState, reason) =>
|
|
612
603
|
this.logConnectionStateChangeTelemetry(value, oldState, reason),
|
|
613
|
-
shouldClientJoinWrite: () => this._deltaManager.shouldJoinWrite(),
|
|
604
|
+
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
614
605
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
615
606
|
logConnectionIssue: (eventName: string) => {
|
|
616
607
|
// We get here when socket does not receive any ops on "write" connection, including
|
|
@@ -672,22 +663,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
672
663
|
switch (event) {
|
|
673
664
|
case dirtyContainerEvent:
|
|
674
665
|
if (this._dirtyContainer) {
|
|
675
|
-
listener(
|
|
666
|
+
listener();
|
|
676
667
|
}
|
|
677
668
|
break;
|
|
678
669
|
case savedContainerEvent:
|
|
679
670
|
if (!this._dirtyContainer) {
|
|
680
|
-
listener(
|
|
671
|
+
listener();
|
|
681
672
|
}
|
|
682
673
|
break;
|
|
683
674
|
case connectedEventName:
|
|
684
675
|
if (this.connected) {
|
|
685
|
-
listener(
|
|
676
|
+
listener(this.clientId);
|
|
686
677
|
}
|
|
687
678
|
break;
|
|
688
679
|
case disconnectedEventName:
|
|
689
680
|
if (!this.connected) {
|
|
690
|
-
listener(
|
|
681
|
+
listener();
|
|
691
682
|
}
|
|
692
683
|
break;
|
|
693
684
|
default:
|
|
@@ -719,6 +710,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
719
710
|
|
|
720
711
|
this._protocolHandler?.close();
|
|
721
712
|
|
|
713
|
+
this.connectionStateHandler.dispose();
|
|
714
|
+
|
|
722
715
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
723
716
|
|
|
724
717
|
assert(this.connectionState === ConnectionState.Disconnected,
|
|
@@ -786,113 +779,113 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
786
779
|
}
|
|
787
780
|
|
|
788
781
|
public async attach(request: IRequest): Promise<void> {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
782
|
+
await PerformanceEvent.timedExecAsync(this.logger, { eventName: "Attach" }, async () => {
|
|
783
|
+
if (this._lifecycleState !== "loaded") {
|
|
784
|
+
throw new UsageError(`containerNotValidForAttach [${this._lifecycleState}]`);
|
|
785
|
+
}
|
|
792
786
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
787
|
+
// If container is already attached or attach is in progress, throw an error.
|
|
788
|
+
assert(this._attachState === AttachState.Detached && !this.attachStarted,
|
|
789
|
+
0x205 /* "attach() called more than once" */);
|
|
790
|
+
this.attachStarted = true;
|
|
797
791
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
792
|
+
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
793
|
+
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
794
|
+
&& this.loader.services.detachedBlobStorage.size > 0;
|
|
801
795
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
796
|
+
try {
|
|
797
|
+
assert(this.deltaManager.inbound.length === 0,
|
|
798
|
+
0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
799
|
+
|
|
800
|
+
let summary: ISummaryTree;
|
|
801
|
+
if (!hasAttachmentBlobs) {
|
|
802
|
+
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
803
|
+
// semantics around what the attach means as far as async code goes.
|
|
804
|
+
const appSummary: ISummaryTree = this.context.createSummary();
|
|
805
|
+
const protocolSummary = this.captureProtocolSummary();
|
|
806
|
+
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
807
|
+
|
|
808
|
+
// Set the state as attaching as we are starting the process of attaching container.
|
|
809
|
+
// This should be fired after taking the summary because it is the place where we are
|
|
810
|
+
// starting to attach the container to storage.
|
|
811
|
+
// Also, this should only be fired in detached container.
|
|
812
|
+
this._attachState = AttachState.Attaching;
|
|
813
|
+
this.context.notifyAttaching();
|
|
814
|
+
}
|
|
820
815
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
const resolvedUrl = this.service.resolvedUrl;
|
|
837
|
-
ensureFluidResolvedUrl(resolvedUrl);
|
|
838
|
-
this._resolvedUrl = resolvedUrl;
|
|
839
|
-
await this.connectStorageService();
|
|
840
|
-
|
|
841
|
-
if (hasAttachmentBlobs) {
|
|
842
|
-
// upload blobs to storage
|
|
843
|
-
assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
844
|
-
|
|
845
|
-
// build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
|
|
846
|
-
// support blob handles that only know about the local IDs
|
|
847
|
-
const redirectTable = new Map<string, string>();
|
|
848
|
-
// if new blobs are added while uploading, upload them too
|
|
849
|
-
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
850
|
-
const newIds = this.loader.services.detachedBlobStorage.getBlobIds().filter(
|
|
851
|
-
(id) => !redirectTable.has(id));
|
|
852
|
-
for (const id of newIds) {
|
|
853
|
-
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
854
|
-
const response = await this.storageService.createBlob(blob);
|
|
855
|
-
redirectTable.set(id, response.id);
|
|
856
|
-
}
|
|
816
|
+
// Actually go and create the resolved document
|
|
817
|
+
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
818
|
+
ensureFluidResolvedUrl(createNewResolvedUrl);
|
|
819
|
+
if (this.service === undefined) {
|
|
820
|
+
this.service = await runWithRetry(
|
|
821
|
+
async () => this.serviceFactory.createContainer(
|
|
822
|
+
summary,
|
|
823
|
+
createNewResolvedUrl,
|
|
824
|
+
this.subLogger,
|
|
825
|
+
),
|
|
826
|
+
"containerAttach",
|
|
827
|
+
this.logger,
|
|
828
|
+
{}, // progress
|
|
829
|
+
);
|
|
857
830
|
}
|
|
831
|
+
const resolvedUrl = this.service.resolvedUrl;
|
|
832
|
+
ensureFluidResolvedUrl(resolvedUrl);
|
|
833
|
+
this._resolvedUrl = resolvedUrl;
|
|
834
|
+
await this.connectStorageService();
|
|
835
|
+
|
|
836
|
+
if (hasAttachmentBlobs) {
|
|
837
|
+
// upload blobs to storage
|
|
838
|
+
assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
839
|
+
|
|
840
|
+
// build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
|
|
841
|
+
// support blob handles that only know about the local IDs
|
|
842
|
+
const redirectTable = new Map<string, string>();
|
|
843
|
+
// if new blobs are added while uploading, upload them too
|
|
844
|
+
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
845
|
+
const newIds = this.loader.services.detachedBlobStorage.getBlobIds().filter(
|
|
846
|
+
(id) => !redirectTable.has(id));
|
|
847
|
+
for (const id of newIds) {
|
|
848
|
+
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
849
|
+
const response = await this.storageService.createBlob(blob);
|
|
850
|
+
redirectTable.set(id, response.id);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
858
853
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
854
|
+
// take summary and upload
|
|
855
|
+
const appSummary: ISummaryTree = this.context.createSummary(redirectTable);
|
|
856
|
+
const protocolSummary = this.captureProtocolSummary();
|
|
857
|
+
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
863
858
|
|
|
864
|
-
|
|
865
|
-
|
|
859
|
+
this._attachState = AttachState.Attaching;
|
|
860
|
+
this.context.notifyAttaching();
|
|
866
861
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
862
|
+
await this.storageService.uploadSummaryWithContext(summary, {
|
|
863
|
+
referenceSequenceNumber: 0,
|
|
864
|
+
ackHandle: undefined,
|
|
865
|
+
proposalHandle: undefined,
|
|
866
|
+
});
|
|
867
|
+
}
|
|
873
868
|
|
|
874
|
-
|
|
875
|
-
|
|
869
|
+
this._attachState = AttachState.Attached;
|
|
870
|
+
this.emit("attached");
|
|
876
871
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
872
|
+
// Propagate current connection state through the system.
|
|
873
|
+
this.propagateConnectionState();
|
|
874
|
+
if (!this.closed) {
|
|
875
|
+
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
876
|
+
}
|
|
877
|
+
} catch(error) {
|
|
878
|
+
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
879
|
+
const newError = normalizeError(error);
|
|
880
|
+
const resolvedUrl = this.resolvedUrl;
|
|
881
|
+
if (isFluidResolvedUrl(resolvedUrl)) {
|
|
882
|
+
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
883
|
+
}
|
|
884
|
+
this.close(newError);
|
|
885
|
+
throw newError;
|
|
892
886
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
887
|
+
},
|
|
888
|
+
{ start: true, end: true, cancel: "generic" });
|
|
896
889
|
}
|
|
897
890
|
|
|
898
891
|
public async request(path: IRequest): Promise<IResponse> {
|
|
@@ -928,7 +921,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
928
921
|
throw new Error("Attempting to setAutoReconnect() a closed Container");
|
|
929
922
|
}
|
|
930
923
|
const mode = reconnect ? ReconnectMode.Enabled : ReconnectMode.Disabled;
|
|
931
|
-
const currentMode = this._deltaManager.reconnectMode;
|
|
924
|
+
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
932
925
|
|
|
933
926
|
if (currentMode === mode) {
|
|
934
927
|
return;
|
|
@@ -940,12 +933,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
940
933
|
|
|
941
934
|
this.logger.sendTelemetryEvent({
|
|
942
935
|
eventName: reconnect ? "AutoReconnectEnabled" : "AutoReconnectDisabled",
|
|
943
|
-
connectionMode: this.
|
|
936
|
+
connectionMode: this.connectionMode,
|
|
944
937
|
connectionState: ConnectionState[this.connectionState],
|
|
945
938
|
duration,
|
|
946
939
|
});
|
|
947
940
|
|
|
948
|
-
this._deltaManager.setAutoReconnect(mode);
|
|
941
|
+
this._deltaManager.connectionManager.setAutoReconnect(mode);
|
|
949
942
|
|
|
950
943
|
// If container state is not attached and resumed, then don't connect to delta stream. Also don't set the
|
|
951
944
|
// manual reconnection flag to true as we haven't made the initial connection yet.
|
|
@@ -956,13 +949,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
956
949
|
}
|
|
957
950
|
|
|
958
951
|
// Ensure connection to web socket
|
|
959
|
-
this.connectToDeltaStream({ reason: "autoReconnect" })
|
|
960
|
-
// All errors are reported through events ("error" / "disconnected") and telemetry in DeltaManager
|
|
961
|
-
// So there shouldn't be a need to record error here.
|
|
962
|
-
// But we have number of cases where reconnects do not happen, and no errors are recorded, so
|
|
963
|
-
// adding this log point for easier diagnostics
|
|
964
|
-
this.logger.sendTelemetryEvent({ eventName: "setAutoReconnectError" }, error);
|
|
965
|
-
});
|
|
952
|
+
this.connectToDeltaStream({ reason: "autoReconnect" });
|
|
966
953
|
}
|
|
967
954
|
}
|
|
968
955
|
|
|
@@ -986,8 +973,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
986
973
|
}
|
|
987
974
|
|
|
988
975
|
// Ensure connection to web socket
|
|
989
|
-
|
|
990
|
-
this.connectToDeltaStream(args).catch(() => { });
|
|
976
|
+
this.connectToDeltaStream(args);
|
|
991
977
|
}
|
|
992
978
|
|
|
993
979
|
/**
|
|
@@ -1142,7 +1128,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1142
1128
|
}
|
|
1143
1129
|
}
|
|
1144
1130
|
|
|
1145
|
-
private
|
|
1131
|
+
private connectToDeltaStream(args: IConnectionArgs) {
|
|
1146
1132
|
this.recordConnectStartTime();
|
|
1147
1133
|
|
|
1148
1134
|
// All agents need "write" access, including summarizer.
|
|
@@ -1150,7 +1136,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1150
1136
|
args.mode = "write";
|
|
1151
1137
|
}
|
|
1152
1138
|
|
|
1153
|
-
|
|
1139
|
+
this._deltaManager.connect(args);
|
|
1154
1140
|
}
|
|
1155
1141
|
|
|
1156
1142
|
/**
|
|
@@ -1170,8 +1156,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1170
1156
|
}
|
|
1171
1157
|
this.service = await this.serviceFactory.createDocumentService(this._resolvedUrl, this.subLogger);
|
|
1172
1158
|
|
|
1173
|
-
let startConnectionP: Promise<IConnectionDetails> | undefined;
|
|
1174
|
-
|
|
1175
1159
|
// Ideally we always connect as "read" by default.
|
|
1176
1160
|
// Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
|
|
1177
1161
|
// We should not rely on it by (one of them will address the issue, but we need to address both)
|
|
@@ -1186,8 +1170,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1186
1170
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
1187
1171
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
1188
1172
|
if (loadMode.deltaConnection === undefined) {
|
|
1189
|
-
|
|
1190
|
-
startConnectionP.catch((error) => { });
|
|
1173
|
+
this.connectToDeltaStream(connectionArgs);
|
|
1191
1174
|
}
|
|
1192
1175
|
|
|
1193
1176
|
await this.connectStorageService();
|
|
@@ -1539,7 +1522,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1539
1522
|
if (this.clientDetailsOverride !== undefined) {
|
|
1540
1523
|
merge(client.details, this.clientDetailsOverride);
|
|
1541
1524
|
}
|
|
1542
|
-
|
|
1525
|
+
client.details.environment = [client.details.environment, ` loaderVersion:${pkgVersion}`].join(";");
|
|
1543
1526
|
return client;
|
|
1544
1527
|
}
|
|
1545
1528
|
|
|
@@ -1550,26 +1533,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1550
1533
|
* If it's not true, runtime is not in position to send ops.
|
|
1551
1534
|
*/
|
|
1552
1535
|
private activeConnection() {
|
|
1553
|
-
|
|
1554
|
-
this.
|
|
1555
|
-
|
|
1556
|
-
// Check for presence of current client in quorum for "write" connections - inactive clients
|
|
1557
|
-
// would get leave op after some long timeout (5 min) and that should automatically transition
|
|
1558
|
-
// state to "read" mode.
|
|
1559
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1560
|
-
assert(!active || this.getQuorum().getMember(this.clientId!) !== undefined,
|
|
1561
|
-
0x276 /* "active connection not present in quorum" */);
|
|
1562
|
-
|
|
1563
|
-
return active;
|
|
1536
|
+
return this.connectionState === ConnectionState.Connected &&
|
|
1537
|
+
this.connectionMode === "write";
|
|
1564
1538
|
}
|
|
1565
1539
|
|
|
1566
1540
|
private createDeltaManager() {
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
|
|
1541
|
+
const serviceProvider = () => this.service;
|
|
1542
|
+
const deltaManager = new DeltaManager<ConnectionManager>(
|
|
1543
|
+
serviceProvider,
|
|
1570
1544
|
ChildLogger.create(this.subLogger, "DeltaManager"),
|
|
1571
|
-
this._canReconnect,
|
|
1572
1545
|
() => this.activeConnection(),
|
|
1546
|
+
(props: IConnectionManagerFactoryArgs) => new ConnectionManager(
|
|
1547
|
+
serviceProvider,
|
|
1548
|
+
this.client,
|
|
1549
|
+
this._canReconnect,
|
|
1550
|
+
ChildLogger.create(this.subLogger, "ConnectionManager"),
|
|
1551
|
+
props),
|
|
1573
1552
|
);
|
|
1574
1553
|
|
|
1575
1554
|
// Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
|
|
@@ -1579,17 +1558,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1579
1558
|
deltaManager.inboundSignal.pause();
|
|
1580
1559
|
|
|
1581
1560
|
deltaManager.on("connect", (details: IConnectionDetails, opsBehind?: number) => {
|
|
1582
|
-
this.connectionStateHandler.receivedConnectEvent(
|
|
1583
|
-
this._deltaManager.connectionMode,
|
|
1584
|
-
details,
|
|
1585
|
-
);
|
|
1586
|
-
|
|
1587
1561
|
// Back-compat for new client and old server.
|
|
1588
1562
|
this._audience.clear();
|
|
1589
1563
|
|
|
1590
1564
|
for (const priorClient of details.initialClients ?? []) {
|
|
1591
1565
|
this._audience.addMember(priorClient.clientId, priorClient.client);
|
|
1592
1566
|
}
|
|
1567
|
+
|
|
1568
|
+
this.connectionStateHandler.receivedConnectEvent(
|
|
1569
|
+
this.connectionMode,
|
|
1570
|
+
details,
|
|
1571
|
+
);
|
|
1593
1572
|
});
|
|
1594
1573
|
|
|
1595
1574
|
deltaManager.on("disconnect", (reason: string) => {
|
|
@@ -1646,7 +1625,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1646
1625
|
let checkpointSequenceNumber: number | undefined;
|
|
1647
1626
|
let opsBehind: number | undefined;
|
|
1648
1627
|
if (value === ConnectionState.Disconnected) {
|
|
1649
|
-
autoReconnect = this._deltaManager.reconnectMode;
|
|
1628
|
+
autoReconnect = this._deltaManager.connectionManager.reconnectMode;
|
|
1650
1629
|
} else {
|
|
1651
1630
|
if (value === ConnectionState.Connected) {
|
|
1652
1631
|
durationFromDisconnected = time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
@@ -1674,7 +1653,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1674
1653
|
durationFromDisconnected,
|
|
1675
1654
|
reason,
|
|
1676
1655
|
connectionInitiationReason,
|
|
1677
|
-
socketDocumentId: this._deltaManager.socketDocumentId,
|
|
1678
1656
|
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
1679
1657
|
clientId: this.clientId,
|
|
1680
1658
|
autoReconnect,
|
|
@@ -1682,7 +1660,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1682
1660
|
online: OnlineStatus[isOnline()],
|
|
1683
1661
|
lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined,
|
|
1684
1662
|
checkpointSequenceNumber,
|
|
1685
|
-
...this._deltaManager.connectionProps
|
|
1663
|
+
...this._deltaManager.connectionProps,
|
|
1686
1664
|
});
|
|
1687
1665
|
|
|
1688
1666
|
if (value === ConnectionState.Connected) {
|
|
@@ -1695,7 +1673,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1695
1673
|
const logOpsOnReconnect: boolean =
|
|
1696
1674
|
this.connectionState === ConnectionState.Connected &&
|
|
1697
1675
|
!this.firstConnection &&
|
|
1698
|
-
this.
|
|
1676
|
+
this.connectionMode === "write";
|
|
1699
1677
|
if (logOpsOnReconnect) {
|
|
1700
1678
|
this.messageCountAfterDisconnection = 0;
|
|
1701
1679
|
}
|
package/src/containerContext.ts
CHANGED
|
@@ -191,6 +191,10 @@ export class ContainerContext implements IContainerContext {
|
|
|
191
191
|
this.attachListener();
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
|
|
195
|
+
return (this.quorum.get("code") ?? this.quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
194
198
|
public dispose(error?: Error): void {
|
|
195
199
|
if (this._disposed) {
|
|
196
200
|
return;
|