@fluidframework/container-loader 0.52.1 → 0.53.0-46105
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 -32
- 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 -32
- 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 +8 -8
- package/src/connectionStateHandler.ts +8 -0
- package/src/container.ts +126 -148
- package/src/containerContext.ts +4 -0
- package/src/deltaManager.ts +35 -34
- 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
|
|
|
@@ -713,9 +706,7 @@ export class DeltaManager
|
|
|
713
706
|
}
|
|
714
707
|
|
|
715
708
|
const docService = this.serviceProvider();
|
|
716
|
-
|
|
717
|
-
throw new Error("Container is not attached");
|
|
718
|
-
}
|
|
709
|
+
assert(docService !== undefined, 0x2a7 /* "Container is not attached" */);
|
|
719
710
|
|
|
720
711
|
if (docService.policies?.storageOnly === true) {
|
|
721
712
|
const connection = new NoDeltaStream();
|
|
@@ -758,7 +749,7 @@ export class DeltaManager
|
|
|
758
749
|
|
|
759
750
|
// Socket.io error when we connect to wrong socket, or hit some multiplexing bug
|
|
760
751
|
if (!canRetryOnError(origError)) {
|
|
761
|
-
const error = normalizeError(origError);
|
|
752
|
+
const error = normalizeError(origError, { props: fatalConnectErrorProp });
|
|
762
753
|
this.close(error);
|
|
763
754
|
throw error;
|
|
764
755
|
}
|
|
@@ -817,9 +808,11 @@ export class DeltaManager
|
|
|
817
808
|
const cleanupAndReject = (error) => {
|
|
818
809
|
this.connectionP = undefined;
|
|
819
810
|
this.removeListener("closed", cleanupAndReject);
|
|
811
|
+
|
|
820
812
|
// This error came from some logic error in this file. Fail-fast to learn and fix the issue faster
|
|
821
|
-
|
|
822
|
-
|
|
813
|
+
const normalizedError = normalizeError(error, { props: fatalConnectErrorProp });
|
|
814
|
+
this.close(normalizedError);
|
|
815
|
+
deferred.reject(normalizedError);
|
|
823
816
|
};
|
|
824
817
|
this.on("closed", cleanupAndReject);
|
|
825
818
|
|
|
@@ -857,7 +850,7 @@ export class DeltaManager
|
|
|
857
850
|
// const serializedContent = JSON.stringify(this.messageBuffer);
|
|
858
851
|
// const maxOpSize = this.context.deltaManager.maxMessageSize;
|
|
859
852
|
|
|
860
|
-
if (this.readonly === true) {
|
|
853
|
+
if (this.readOnlyInfo.readonly === true) {
|
|
861
854
|
assert(this.readOnlyInfo.readonly === true, 0x1f0 /* "Unexpected mismatch in readonly" */);
|
|
862
855
|
const error = new GenericError("deltaManagerReadonlySubmit", undefined /* error */, {
|
|
863
856
|
readonly: this.readOnlyInfo.readonly,
|
|
@@ -885,7 +878,7 @@ export class DeltaManager
|
|
|
885
878
|
// Note that we also want nacks to be rare and be treated as catastrophic failures.
|
|
886
879
|
// Be careful with reentrancy though - disconnected event should not be be raised in the
|
|
887
880
|
// middle of the current workflow, but rather on clean stack!
|
|
888
|
-
if (this.connectionMode === "read") {
|
|
881
|
+
if (this.connectionMode === "read" || this.downgradedConnection) {
|
|
889
882
|
if (!this.pendingReconnect) {
|
|
890
883
|
this.pendingReconnect = true;
|
|
891
884
|
Promise.resolve().then(async () => {
|
|
@@ -1024,7 +1017,8 @@ export class DeltaManager
|
|
|
1024
1017
|
from, // inclusive
|
|
1025
1018
|
to, // exclusive
|
|
1026
1019
|
controller.signal,
|
|
1027
|
-
cacheOnly
|
|
1020
|
+
cacheOnly,
|
|
1021
|
+
this.fetchReason);
|
|
1028
1022
|
|
|
1029
1023
|
// eslint-disable-next-line no-constant-condition
|
|
1030
1024
|
while (true) {
|
|
@@ -1059,8 +1053,12 @@ export class DeltaManager
|
|
|
1059
1053
|
|
|
1060
1054
|
this.closeAbortController.abort();
|
|
1061
1055
|
|
|
1056
|
+
const disconnectReason = error !== undefined
|
|
1057
|
+
? `Closing DeltaManager (${error.message})`
|
|
1058
|
+
: "Closing DeltaManager";
|
|
1059
|
+
|
|
1062
1060
|
// This raises "disconnect" event if we have active connection.
|
|
1063
|
-
this.disconnectFromDeltaStream(
|
|
1061
|
+
this.disconnectFromDeltaStream(disconnectReason);
|
|
1064
1062
|
|
|
1065
1063
|
this._inbound.clear();
|
|
1066
1064
|
this._outbound.clear();
|
|
@@ -1201,7 +1199,7 @@ export class DeltaManager
|
|
|
1201
1199
|
|
|
1202
1200
|
if (this.closed) {
|
|
1203
1201
|
// Raise proper events, Log telemetry event and close connection.
|
|
1204
|
-
this.disconnectFromDeltaStream(
|
|
1202
|
+
this.disconnectFromDeltaStream("DeltaManager already closed");
|
|
1205
1203
|
return;
|
|
1206
1204
|
}
|
|
1207
1205
|
|
|
@@ -1233,6 +1231,9 @@ export class DeltaManager
|
|
|
1233
1231
|
clientId: connection.clientId,
|
|
1234
1232
|
mode: connection.mode,
|
|
1235
1233
|
};
|
|
1234
|
+
if (connection.relayServiceAgent !== undefined) {
|
|
1235
|
+
this.connectionStateProps.relayServiceAgent = connection.relayServiceAgent;
|
|
1236
|
+
}
|
|
1236
1237
|
this._hasCheckpointSequenceNumber = false;
|
|
1237
1238
|
|
|
1238
1239
|
// Some storages may provide checkpointSequenceNumber to identify how far client is behind.
|
|
@@ -1368,11 +1369,13 @@ export class DeltaManager
|
|
|
1368
1369
|
const canRetry = error !== undefined ? canRetryOnError(error) : true;
|
|
1369
1370
|
|
|
1370
1371
|
// If reconnection is not an option, close the DeltaManager
|
|
1371
|
-
if (
|
|
1372
|
+
if (!canRetry) {
|
|
1373
|
+
this.close(normalizeError(error, { props: fatalConnectErrorProp }));
|
|
1374
|
+
} else if (this.reconnectMode === ReconnectMode.Never) {
|
|
1372
1375
|
// Do not raise container error if we are closing just because we lost connection.
|
|
1373
1376
|
// Those errors (like IdleDisconnect) would show up in telemetry dashboards and
|
|
1374
1377
|
// are very misleading, as first initial reaction - some logic is broken.
|
|
1375
|
-
this.close(
|
|
1378
|
+
this.close();
|
|
1376
1379
|
}
|
|
1377
1380
|
|
|
1378
1381
|
// If closed then we can't reconnect
|
|
@@ -1580,8 +1583,6 @@ export class DeltaManager
|
|
|
1580
1583
|
// We have been kicked out from quorum
|
|
1581
1584
|
this.logger.sendPerformanceEvent({ eventName: "ReadConnectionTransition" });
|
|
1582
1585
|
this.downgradedConnection = true;
|
|
1583
|
-
assert(this.connectionMode === "read",
|
|
1584
|
-
0x27c /* "effective connectionMode should be 'read' after downgrade" */);
|
|
1585
1586
|
}
|
|
1586
1587
|
}
|
|
1587
1588
|
|