@fluidframework/container-loader 0.52.0-44610 → 0.53.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/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 +0 -21
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +96 -123
- 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/deltaManager.d.ts +0 -7
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +31 -33
- package/dist/deltaManager.js.map +1 -1
- package/dist/loader.d.ts +5 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +6 -1
- 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/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 +0 -21
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +97 -124
- 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/deltaManager.d.ts +0 -7
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +31 -33
- package/lib/deltaManager.js.map +1 -1
- package/lib/loader.d.ts +5 -0
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +6 -1
- 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/package.json +11 -11
- package/src/connectionStateHandler.ts +8 -0
- package/src/container.ts +126 -148
- package/src/containerContext.ts +4 -0
- package/src/deltaManager.ts +35 -35
- package/src/loader.ts +29 -19
- package/src/packageVersion.ts +1 -1
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,
|
|
@@ -279,7 +279,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
279
279
|
onClosed(err);
|
|
280
280
|
});
|
|
281
281
|
}),
|
|
282
|
-
{ start: true, end: true, cancel: "
|
|
282
|
+
{ start: true, end: true, cancel: "generic" },
|
|
283
283
|
);
|
|
284
284
|
}
|
|
285
285
|
|
|
@@ -293,9 +293,16 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
293
293
|
const container = new Container(
|
|
294
294
|
loader,
|
|
295
295
|
{});
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
|
|
297
|
+
return PerformanceEvent.timedExecAsync(
|
|
298
|
+
container.logger,
|
|
299
|
+
{ eventName: "CreateDetached" },
|
|
300
|
+
async (_event) => {
|
|
301
|
+
container._lifecycleState = "loading";
|
|
302
|
+
await container.createDetached(codeDetails);
|
|
303
|
+
return container;
|
|
304
|
+
},
|
|
305
|
+
{ start: true, end: true, cancel: "generic" });
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
/**
|
|
@@ -309,10 +316,16 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
309
316
|
const container = new Container(
|
|
310
317
|
loader,
|
|
311
318
|
{});
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
return PerformanceEvent.timedExecAsync(
|
|
320
|
+
container.logger,
|
|
321
|
+
{ eventName: "RehydrateDetachedFromSnapshot" },
|
|
322
|
+
async (_event) => {
|
|
323
|
+
const deserializedSummary = JSON.parse(snapshot) as ISummaryTree;
|
|
324
|
+
container._lifecycleState = "loading";
|
|
325
|
+
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
326
|
+
return container;
|
|
327
|
+
},
|
|
328
|
+
{ start: true, end: true, cancel: "generic" });
|
|
316
329
|
}
|
|
317
330
|
|
|
318
331
|
public subLogger: TelemetryLogger;
|
|
@@ -412,33 +425,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
412
425
|
return this._loadedFromVersion;
|
|
413
426
|
}
|
|
414
427
|
|
|
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
428
|
public get readOnlyInfo(): ReadOnlyInfo {
|
|
443
429
|
return this._deltaManager.readOnlyInfo;
|
|
444
430
|
}
|
|
@@ -576,7 +562,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
576
562
|
{
|
|
577
563
|
all: {
|
|
578
564
|
clientType, // Differentiating summarizer container from main container
|
|
579
|
-
loaderVersion: pkgVersion,
|
|
580
565
|
containerId: uuid(),
|
|
581
566
|
docId: () => this.id,
|
|
582
567
|
containerAttachState: () => this._attachState,
|
|
@@ -672,22 +657,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
672
657
|
switch (event) {
|
|
673
658
|
case dirtyContainerEvent:
|
|
674
659
|
if (this._dirtyContainer) {
|
|
675
|
-
listener(
|
|
660
|
+
listener();
|
|
676
661
|
}
|
|
677
662
|
break;
|
|
678
663
|
case savedContainerEvent:
|
|
679
664
|
if (!this._dirtyContainer) {
|
|
680
|
-
listener(
|
|
665
|
+
listener();
|
|
681
666
|
}
|
|
682
667
|
break;
|
|
683
668
|
case connectedEventName:
|
|
684
669
|
if (this.connected) {
|
|
685
|
-
listener(
|
|
670
|
+
listener(this.clientId);
|
|
686
671
|
}
|
|
687
672
|
break;
|
|
688
673
|
case disconnectedEventName:
|
|
689
674
|
if (!this.connected) {
|
|
690
|
-
listener(
|
|
675
|
+
listener();
|
|
691
676
|
}
|
|
692
677
|
break;
|
|
693
678
|
default:
|
|
@@ -719,6 +704,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
719
704
|
|
|
720
705
|
this._protocolHandler?.close();
|
|
721
706
|
|
|
707
|
+
this.connectionStateHandler.dispose();
|
|
708
|
+
|
|
722
709
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
723
710
|
|
|
724
711
|
assert(this.connectionState === ConnectionState.Disconnected,
|
|
@@ -786,113 +773,113 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
786
773
|
}
|
|
787
774
|
|
|
788
775
|
public async attach(request: IRequest): Promise<void> {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
776
|
+
await PerformanceEvent.timedExecAsync(this.logger, { eventName: "Attach" }, async () => {
|
|
777
|
+
if (this._lifecycleState !== "loaded") {
|
|
778
|
+
throw new UsageError(`containerNotValidForAttach [${this._lifecycleState}]`);
|
|
779
|
+
}
|
|
792
780
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
781
|
+
// If container is already attached or attach is in progress, throw an error.
|
|
782
|
+
assert(this._attachState === AttachState.Detached && !this.attachStarted,
|
|
783
|
+
0x205 /* "attach() called more than once" */);
|
|
784
|
+
this.attachStarted = true;
|
|
797
785
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
786
|
+
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
787
|
+
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
788
|
+
&& this.loader.services.detachedBlobStorage.size > 0;
|
|
801
789
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
790
|
+
try {
|
|
791
|
+
assert(this.deltaManager.inbound.length === 0,
|
|
792
|
+
0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
793
|
+
|
|
794
|
+
let summary: ISummaryTree;
|
|
795
|
+
if (!hasAttachmentBlobs) {
|
|
796
|
+
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
797
|
+
// semantics around what the attach means as far as async code goes.
|
|
798
|
+
const appSummary: ISummaryTree = this.context.createSummary();
|
|
799
|
+
const protocolSummary = this.captureProtocolSummary();
|
|
800
|
+
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
801
|
+
|
|
802
|
+
// Set the state as attaching as we are starting the process of attaching container.
|
|
803
|
+
// This should be fired after taking the summary because it is the place where we are
|
|
804
|
+
// starting to attach the container to storage.
|
|
805
|
+
// Also, this should only be fired in detached container.
|
|
806
|
+
this._attachState = AttachState.Attaching;
|
|
807
|
+
this.context.notifyAttaching();
|
|
808
|
+
}
|
|
820
809
|
|
|
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
|
-
}
|
|
810
|
+
// Actually go and create the resolved document
|
|
811
|
+
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
812
|
+
ensureFluidResolvedUrl(createNewResolvedUrl);
|
|
813
|
+
if (this.service === undefined) {
|
|
814
|
+
this.service = await runWithRetry(
|
|
815
|
+
async () => this.serviceFactory.createContainer(
|
|
816
|
+
summary,
|
|
817
|
+
createNewResolvedUrl,
|
|
818
|
+
this.subLogger,
|
|
819
|
+
),
|
|
820
|
+
"containerAttach",
|
|
821
|
+
this.logger,
|
|
822
|
+
{}, // progress
|
|
823
|
+
);
|
|
857
824
|
}
|
|
825
|
+
const resolvedUrl = this.service.resolvedUrl;
|
|
826
|
+
ensureFluidResolvedUrl(resolvedUrl);
|
|
827
|
+
this._resolvedUrl = resolvedUrl;
|
|
828
|
+
await this.connectStorageService();
|
|
829
|
+
|
|
830
|
+
if (hasAttachmentBlobs) {
|
|
831
|
+
// upload blobs to storage
|
|
832
|
+
assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
833
|
+
|
|
834
|
+
// build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
|
|
835
|
+
// support blob handles that only know about the local IDs
|
|
836
|
+
const redirectTable = new Map<string, string>();
|
|
837
|
+
// if new blobs are added while uploading, upload them too
|
|
838
|
+
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
839
|
+
const newIds = this.loader.services.detachedBlobStorage.getBlobIds().filter(
|
|
840
|
+
(id) => !redirectTable.has(id));
|
|
841
|
+
for (const id of newIds) {
|
|
842
|
+
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
843
|
+
const response = await this.storageService.createBlob(blob);
|
|
844
|
+
redirectTable.set(id, response.id);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
858
847
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
848
|
+
// take summary and upload
|
|
849
|
+
const appSummary: ISummaryTree = this.context.createSummary(redirectTable);
|
|
850
|
+
const protocolSummary = this.captureProtocolSummary();
|
|
851
|
+
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
863
852
|
|
|
864
|
-
|
|
865
|
-
|
|
853
|
+
this._attachState = AttachState.Attaching;
|
|
854
|
+
this.context.notifyAttaching();
|
|
866
855
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
856
|
+
await this.storageService.uploadSummaryWithContext(summary, {
|
|
857
|
+
referenceSequenceNumber: 0,
|
|
858
|
+
ackHandle: undefined,
|
|
859
|
+
proposalHandle: undefined,
|
|
860
|
+
});
|
|
861
|
+
}
|
|
873
862
|
|
|
874
|
-
|
|
875
|
-
|
|
863
|
+
this._attachState = AttachState.Attached;
|
|
864
|
+
this.emit("attached");
|
|
876
865
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
866
|
+
// Propagate current connection state through the system.
|
|
867
|
+
this.propagateConnectionState();
|
|
868
|
+
if (!this.closed) {
|
|
869
|
+
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
870
|
+
}
|
|
871
|
+
} catch(error) {
|
|
872
|
+
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
873
|
+
const newError = normalizeError(error);
|
|
874
|
+
const resolvedUrl = this.resolvedUrl;
|
|
875
|
+
if (isFluidResolvedUrl(resolvedUrl)) {
|
|
876
|
+
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
877
|
+
}
|
|
878
|
+
this.close(newError);
|
|
879
|
+
throw newError;
|
|
892
880
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
881
|
+
},
|
|
882
|
+
{ start: true, end: true, cancel: "generic" });
|
|
896
883
|
}
|
|
897
884
|
|
|
898
885
|
public async request(path: IRequest): Promise<IResponse> {
|
|
@@ -1539,7 +1526,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1539
1526
|
if (this.clientDetailsOverride !== undefined) {
|
|
1540
1527
|
merge(client.details, this.clientDetailsOverride);
|
|
1541
1528
|
}
|
|
1542
|
-
|
|
1529
|
+
client.details.environment = [client.details.environment, ` loaderVersion:${pkgVersion}`].join(";");
|
|
1543
1530
|
return client;
|
|
1544
1531
|
}
|
|
1545
1532
|
|
|
@@ -1550,17 +1537,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1550
1537
|
* If it's not true, runtime is not in position to send ops.
|
|
1551
1538
|
*/
|
|
1552
1539
|
private activeConnection() {
|
|
1553
|
-
|
|
1540
|
+
return this.connectionState === ConnectionState.Connected &&
|
|
1554
1541
|
this._deltaManager.connectionMode === "write";
|
|
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;
|
|
1564
1542
|
}
|
|
1565
1543
|
|
|
1566
1544
|
private createDeltaManager() {
|
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;
|
package/src/deltaManager.ts
CHANGED
|
@@ -93,6 +93,8 @@ const createReconnectError = (fluidErrorCode: string, err: any) =>
|
|
|
93
93
|
(errorMessage: string) => new GenericNetworkError(fluidErrorCode, errorMessage, true /* canRetry */),
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
+
const fatalConnectErrorProp = { fatalConnectError: true };
|
|
97
|
+
|
|
96
98
|
export interface IConnectionArgs {
|
|
97
99
|
mode?: ConnectionMode;
|
|
98
100
|
fetchOpsFromStorage?: boolean;
|
|
@@ -331,7 +333,7 @@ export class DeltaManager
|
|
|
331
333
|
public get connectionMode(): ConnectionMode {
|
|
332
334
|
assert(!this.downgradedConnection || this.connection?.mode === "write",
|
|
333
335
|
0x277 /* "Did we forget to reset downgradedConnection on new connection?" */);
|
|
334
|
-
if (this.connection === undefined
|
|
336
|
+
if (this.connection === undefined) {
|
|
335
337
|
return "read";
|
|
336
338
|
}
|
|
337
339
|
return this.connection.mode;
|
|
@@ -347,23 +349,13 @@ export class DeltaManager
|
|
|
347
349
|
* and do not know if user has write access to a file.
|
|
348
350
|
* @deprecated - use readOnlyInfo
|
|
349
351
|
*/
|
|
350
|
-
|
|
352
|
+
public get readonly() {
|
|
351
353
|
if (this._forceReadonly) {
|
|
352
354
|
return true;
|
|
353
355
|
}
|
|
354
356
|
return this._readonlyPermissions;
|
|
355
357
|
}
|
|
356
358
|
|
|
357
|
-
/**
|
|
358
|
-
* Tells if user has no write permissions for file in storage
|
|
359
|
-
* It is undefined if we have not yet established websocket connection
|
|
360
|
-
* and do not know if user has write access to a file.
|
|
361
|
-
* @deprecated - use readOnlyInfo
|
|
362
|
-
*/
|
|
363
|
-
public get readonlyPermissions() {
|
|
364
|
-
return this._readonlyPermissions;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
359
|
public get readOnlyInfo(): ReadOnlyInfo {
|
|
368
360
|
const storageOnly = this.connection !== undefined && this.connection instanceof NoDeltaStream;
|
|
369
361
|
if (storageOnly || this._forceReadonly || this._readonlyPermissions === true) {
|
|
@@ -402,6 +394,7 @@ export class DeltaManager
|
|
|
402
394
|
return {
|
|
403
395
|
...common,
|
|
404
396
|
connectionMode: this.connectionMode,
|
|
397
|
+
relayServiceAgent: this.connection.relayServiceAgent,
|
|
405
398
|
};
|
|
406
399
|
} else {
|
|
407
400
|
return {
|
|
@@ -452,15 +445,15 @@ export class DeltaManager
|
|
|
452
445
|
value: readonly,
|
|
453
446
|
});
|
|
454
447
|
}
|
|
455
|
-
const oldValue = this.readonly;
|
|
448
|
+
const oldValue = this.readOnlyInfo.readonly;
|
|
456
449
|
this._forceReadonly = readonly;
|
|
457
450
|
|
|
458
|
-
if (oldValue !== this.readonly) {
|
|
451
|
+
if (oldValue !== this.readOnlyInfo.readonly) {
|
|
459
452
|
assert(this._reconnectMode !== ReconnectMode.Never,
|
|
460
453
|
0x279 /* "API is not supported for non-connecting or closed container" */);
|
|
461
454
|
|
|
462
455
|
let reconnect = false;
|
|
463
|
-
if (this.readonly === true) {
|
|
456
|
+
if (this.readOnlyInfo.readonly === true) {
|
|
464
457
|
// If we switch to readonly while connected, we should disconnect first
|
|
465
458
|
// See comment in the "readonly" event handler to deltaManager set up by
|
|
466
459
|
// the ContainerRuntime constructor
|
|
@@ -473,7 +466,7 @@ export class DeltaManager
|
|
|
473
466
|
|
|
474
467
|
reconnect = this.disconnectFromDeltaStream("Force readonly");
|
|
475
468
|
}
|
|
476
|
-
safeRaiseEvent(this, this.logger, "readonly", this.readonly);
|
|
469
|
+
safeRaiseEvent(this, this.logger, "readonly", this.readOnlyInfo.readonly);
|
|
477
470
|
if (reconnect) {
|
|
478
471
|
// reconnect if we disconnected from before.
|
|
479
472
|
this.triggerConnect({ reason: "forceReadonly", mode: "read", fetchOpsFromStorage: false });
|
|
@@ -511,10 +504,10 @@ export class DeltaManager
|
|
|
511
504
|
}
|
|
512
505
|
|
|
513
506
|
private set_readonlyPermissions(readonly: boolean) {
|
|
514
|
-
const oldValue = this.readonly;
|
|
507
|
+
const oldValue = this.readOnlyInfo.readonly;
|
|
515
508
|
this._readonlyPermissions = readonly;
|
|
516
|
-
if (oldValue !== this.readonly) {
|
|
517
|
-
safeRaiseEvent(this, this.logger, "readonly", this.readonly);
|
|
509
|
+
if (oldValue !== this.readOnlyInfo.readonly) {
|
|
510
|
+
safeRaiseEvent(this, this.logger, "readonly", this.readOnlyInfo.readonly);
|
|
518
511
|
}
|
|
519
512
|
}
|
|
520
513
|
|
|
@@ -640,7 +633,6 @@ export class DeltaManager
|
|
|
640
633
|
existing: connection.existing,
|
|
641
634
|
checkpointSequenceNumber: connection.checkpointSequenceNumber,
|
|
642
635
|
get initialClients() { return connection.initialClients; },
|
|
643
|
-
maxMessageSize: connection.serviceConfiguration.maxMessageSize,
|
|
644
636
|
mode: connection.mode,
|
|
645
637
|
serviceConfiguration: connection.serviceConfiguration,
|
|
646
638
|
version: connection.version,
|
|
@@ -713,9 +705,7 @@ export class DeltaManager
|
|
|
713
705
|
}
|
|
714
706
|
|
|
715
707
|
const docService = this.serviceProvider();
|
|
716
|
-
|
|
717
|
-
throw new Error("Container is not attached");
|
|
718
|
-
}
|
|
708
|
+
assert(docService !== undefined, 0x2a7 /* "Container is not attached" */);
|
|
719
709
|
|
|
720
710
|
if (docService.policies?.storageOnly === true) {
|
|
721
711
|
const connection = new NoDeltaStream();
|
|
@@ -758,7 +748,7 @@ export class DeltaManager
|
|
|
758
748
|
|
|
759
749
|
// Socket.io error when we connect to wrong socket, or hit some multiplexing bug
|
|
760
750
|
if (!canRetryOnError(origError)) {
|
|
761
|
-
const error = normalizeError(origError);
|
|
751
|
+
const error = normalizeError(origError, { props: fatalConnectErrorProp });
|
|
762
752
|
this.close(error);
|
|
763
753
|
throw error;
|
|
764
754
|
}
|
|
@@ -817,9 +807,11 @@ export class DeltaManager
|
|
|
817
807
|
const cleanupAndReject = (error) => {
|
|
818
808
|
this.connectionP = undefined;
|
|
819
809
|
this.removeListener("closed", cleanupAndReject);
|
|
810
|
+
|
|
820
811
|
// This error came from some logic error in this file. Fail-fast to learn and fix the issue faster
|
|
821
|
-
|
|
822
|
-
|
|
812
|
+
const normalizedError = normalizeError(error, { props: fatalConnectErrorProp });
|
|
813
|
+
this.close(normalizedError);
|
|
814
|
+
deferred.reject(normalizedError);
|
|
823
815
|
};
|
|
824
816
|
this.on("closed", cleanupAndReject);
|
|
825
817
|
|
|
@@ -857,7 +849,7 @@ export class DeltaManager
|
|
|
857
849
|
// const serializedContent = JSON.stringify(this.messageBuffer);
|
|
858
850
|
// const maxOpSize = this.context.deltaManager.maxMessageSize;
|
|
859
851
|
|
|
860
|
-
if (this.readonly === true) {
|
|
852
|
+
if (this.readOnlyInfo.readonly === true) {
|
|
861
853
|
assert(this.readOnlyInfo.readonly === true, 0x1f0 /* "Unexpected mismatch in readonly" */);
|
|
862
854
|
const error = new GenericError("deltaManagerReadonlySubmit", undefined /* error */, {
|
|
863
855
|
readonly: this.readOnlyInfo.readonly,
|
|
@@ -885,7 +877,7 @@ export class DeltaManager
|
|
|
885
877
|
// Note that we also want nacks to be rare and be treated as catastrophic failures.
|
|
886
878
|
// Be careful with reentrancy though - disconnected event should not be be raised in the
|
|
887
879
|
// middle of the current workflow, but rather on clean stack!
|
|
888
|
-
if (this.connectionMode === "read") {
|
|
880
|
+
if (this.connectionMode === "read" || this.downgradedConnection) {
|
|
889
881
|
if (!this.pendingReconnect) {
|
|
890
882
|
this.pendingReconnect = true;
|
|
891
883
|
Promise.resolve().then(async () => {
|
|
@@ -1024,7 +1016,8 @@ export class DeltaManager
|
|
|
1024
1016
|
from, // inclusive
|
|
1025
1017
|
to, // exclusive
|
|
1026
1018
|
controller.signal,
|
|
1027
|
-
cacheOnly
|
|
1019
|
+
cacheOnly,
|
|
1020
|
+
this.fetchReason);
|
|
1028
1021
|
|
|
1029
1022
|
// eslint-disable-next-line no-constant-condition
|
|
1030
1023
|
while (true) {
|
|
@@ -1059,8 +1052,12 @@ export class DeltaManager
|
|
|
1059
1052
|
|
|
1060
1053
|
this.closeAbortController.abort();
|
|
1061
1054
|
|
|
1055
|
+
const disconnectReason = error !== undefined
|
|
1056
|
+
? `Closing DeltaManager (${error.message})`
|
|
1057
|
+
: "Closing DeltaManager";
|
|
1058
|
+
|
|
1062
1059
|
// This raises "disconnect" event if we have active connection.
|
|
1063
|
-
this.disconnectFromDeltaStream(
|
|
1060
|
+
this.disconnectFromDeltaStream(disconnectReason);
|
|
1064
1061
|
|
|
1065
1062
|
this._inbound.clear();
|
|
1066
1063
|
this._outbound.clear();
|
|
@@ -1201,7 +1198,7 @@ export class DeltaManager
|
|
|
1201
1198
|
|
|
1202
1199
|
if (this.closed) {
|
|
1203
1200
|
// Raise proper events, Log telemetry event and close connection.
|
|
1204
|
-
this.disconnectFromDeltaStream(
|
|
1201
|
+
this.disconnectFromDeltaStream("DeltaManager already closed");
|
|
1205
1202
|
return;
|
|
1206
1203
|
}
|
|
1207
1204
|
|
|
@@ -1233,6 +1230,9 @@ export class DeltaManager
|
|
|
1233
1230
|
clientId: connection.clientId,
|
|
1234
1231
|
mode: connection.mode,
|
|
1235
1232
|
};
|
|
1233
|
+
if (connection.relayServiceAgent !== undefined) {
|
|
1234
|
+
this.connectionStateProps.relayServiceAgent = connection.relayServiceAgent;
|
|
1235
|
+
}
|
|
1236
1236
|
this._hasCheckpointSequenceNumber = false;
|
|
1237
1237
|
|
|
1238
1238
|
// Some storages may provide checkpointSequenceNumber to identify how far client is behind.
|
|
@@ -1368,11 +1368,13 @@ export class DeltaManager
|
|
|
1368
1368
|
const canRetry = error !== undefined ? canRetryOnError(error) : true;
|
|
1369
1369
|
|
|
1370
1370
|
// If reconnection is not an option, close the DeltaManager
|
|
1371
|
-
if (
|
|
1371
|
+
if (!canRetry) {
|
|
1372
|
+
this.close(normalizeError(error, { props: fatalConnectErrorProp }));
|
|
1373
|
+
} else if (this.reconnectMode === ReconnectMode.Never) {
|
|
1372
1374
|
// Do not raise container error if we are closing just because we lost connection.
|
|
1373
1375
|
// Those errors (like IdleDisconnect) would show up in telemetry dashboards and
|
|
1374
1376
|
// are very misleading, as first initial reaction - some logic is broken.
|
|
1375
|
-
this.close(
|
|
1377
|
+
this.close();
|
|
1376
1378
|
}
|
|
1377
1379
|
|
|
1378
1380
|
// If closed then we can't reconnect
|
|
@@ -1580,8 +1582,6 @@ export class DeltaManager
|
|
|
1580
1582
|
// We have been kicked out from quorum
|
|
1581
1583
|
this.logger.sendPerformanceEvent({ eventName: "ReadConnectionTransition" });
|
|
1582
1584
|
this.downgradedConnection = true;
|
|
1583
|
-
assert(this.connectionMode === "read",
|
|
1584
|
-
0x27c /* "effective connectionMode should be 'read' after downgrade" */);
|
|
1585
1585
|
}
|
|
1586
1586
|
}
|
|
1587
1587
|
|