@fluidframework/container-loader 2.0.0-internal.1.2.0.93071 → 2.0.0-internal.1.2.1
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/catchUpMonitor.d.ts +6 -17
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js +5 -36
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +3 -6
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +80 -26
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +170 -89
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +12 -11
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +76 -100
- package/dist/container.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +10 -24
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +50 -16
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/deltaManager.js +4 -4
- package/dist/deltaManager.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/catchUpMonitor.d.ts +6 -17
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js +5 -35
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +3 -6
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +80 -26
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +170 -90
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +12 -11
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +77 -101
- package/lib/container.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +10 -24
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +50 -15
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/deltaManager.js +4 -4
- package/lib/deltaManager.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/catchUpMonitor.ts +7 -47
- package/src/connectionManager.ts +3 -5
- package/src/connectionStateHandler.ts +231 -106
- package/src/container.ts +89 -118
- package/src/containerStorageAdapter.ts +64 -15
- package/src/deltaManager.ts +4 -4
- package/src/packageVersion.ts +1 -1
package/src/container.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import merge from "lodash/merge";
|
|
8
8
|
import { v4 as uuid } from "uuid";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
ITelemetryLogger, ITelemetryProperties,
|
|
11
11
|
} from "@fluidframework/common-definitions";
|
|
12
12
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
13
13
|
import {
|
|
@@ -93,10 +93,11 @@ import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
|
93
93
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
94
94
|
import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
|
|
95
95
|
import { pkgVersion } from "./packageVersion";
|
|
96
|
-
import {
|
|
97
|
-
import {
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
97
|
+
import {
|
|
98
|
+
IConnectionStateHandler,
|
|
99
|
+
createConnectionStateHandler,
|
|
100
|
+
} from "./connectionStateHandler";
|
|
100
101
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
101
102
|
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
102
103
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
@@ -147,14 +148,19 @@ export interface IContainerConfig {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
/**
|
|
150
|
-
* Waits until container connects to delta storage and gets up-to-date
|
|
151
|
+
* Waits until container connects to delta storage and gets up-to-date.
|
|
152
|
+
*
|
|
151
153
|
* Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
|
|
152
154
|
* up to date. Host may chose to wait in such case and retry resolving URI.
|
|
155
|
+
*
|
|
153
156
|
* Warning: Will wait infinitely for connection to establish if there is no connection.
|
|
154
157
|
* May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
+
*
|
|
159
|
+
* @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
|
|
160
|
+
*
|
|
161
|
+
* `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
|
|
162
|
+
* but it maybe still behind.
|
|
163
|
+
*
|
|
158
164
|
* @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
|
|
159
165
|
*/
|
|
160
166
|
export async function waitContainerToCatchUp(container: IContainer) {
|
|
@@ -387,7 +393,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
387
393
|
// Only transition states if currently loading
|
|
388
394
|
if (this._lifecycleState === "loading") {
|
|
389
395
|
// Propagate current connection state through the system.
|
|
390
|
-
this.propagateConnectionState();
|
|
396
|
+
this.propagateConnectionState(true /* initial transition */);
|
|
391
397
|
this._lifecycleState = "loaded";
|
|
392
398
|
}
|
|
393
399
|
}
|
|
@@ -398,17 +404,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
398
404
|
|
|
399
405
|
private _attachState = AttachState.Detached;
|
|
400
406
|
|
|
401
|
-
private readonly
|
|
407
|
+
private readonly storageService: ContainerStorageAdapter;
|
|
402
408
|
public get storage(): IDocumentStorageService {
|
|
403
|
-
return this.
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
private _storageService: IDocumentStorageService & IDisposable | undefined;
|
|
407
|
-
private get storageService(): IDocumentStorageService {
|
|
408
|
-
if (this._storageService === undefined) {
|
|
409
|
-
throw new Error("Attempted to access storageService before it was defined");
|
|
410
|
-
}
|
|
411
|
-
return this._storageService;
|
|
409
|
+
return this.storageService;
|
|
412
410
|
}
|
|
413
411
|
|
|
414
412
|
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
@@ -443,7 +441,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
443
441
|
|
|
444
442
|
private lastVisible: number | undefined;
|
|
445
443
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
446
|
-
private readonly connectionStateHandler:
|
|
444
|
+
private readonly connectionStateHandler: IConnectionStateHandler;
|
|
447
445
|
|
|
448
446
|
private setAutoReconnectTime = performance.now();
|
|
449
447
|
|
|
@@ -485,7 +483,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
485
483
|
}
|
|
486
484
|
|
|
487
485
|
public get connected(): boolean {
|
|
488
|
-
return this.connectionStateHandler.
|
|
486
|
+
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
489
487
|
}
|
|
490
488
|
|
|
491
489
|
/**
|
|
@@ -496,12 +494,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
496
494
|
return this._deltaManager.serviceConfiguration;
|
|
497
495
|
}
|
|
498
496
|
|
|
497
|
+
private _clientId: string | undefined;
|
|
498
|
+
|
|
499
499
|
/**
|
|
500
500
|
* The server provided id of the client.
|
|
501
501
|
* Set once this.connected is true, otherwise undefined
|
|
502
502
|
*/
|
|
503
503
|
public get clientId(): string | undefined {
|
|
504
|
-
return this.
|
|
504
|
+
return this._clientId;
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
/**
|
|
@@ -627,11 +627,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
627
627
|
summarizeProtocolTree,
|
|
628
628
|
};
|
|
629
629
|
|
|
630
|
-
this.
|
|
630
|
+
this._deltaManager = this.createDeltaManager();
|
|
631
|
+
|
|
632
|
+
this._clientId = config.serializedContainerState?.clientId;
|
|
633
|
+
this.connectionStateHandler = createConnectionStateHandler(
|
|
631
634
|
{
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
+
logger: this.mc.logger,
|
|
636
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
637
|
+
if (value === ConnectionState.Connected) {
|
|
638
|
+
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
639
|
+
}
|
|
640
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
641
|
+
if (this._lifecycleState === "loaded") {
|
|
642
|
+
this.propagateConnectionState(false /* initial transition */);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
635
645
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
636
646
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
637
647
|
logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => {
|
|
@@ -643,35 +653,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
643
653
|
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
644
654
|
});
|
|
645
655
|
},
|
|
646
|
-
connectionStateChanged: () => {
|
|
647
|
-
// Fire events only if container is fully loaded and not closed
|
|
648
|
-
if (this._lifecycleState === "loaded") {
|
|
649
|
-
this.propagateConnectionState();
|
|
650
|
-
}
|
|
651
|
-
},
|
|
652
656
|
},
|
|
653
|
-
this.
|
|
654
|
-
|
|
657
|
+
this.deltaManager,
|
|
658
|
+
this._clientId,
|
|
655
659
|
);
|
|
656
660
|
|
|
657
661
|
this.on(savedContainerEvent, () => {
|
|
658
662
|
this.connectionStateHandler.containerSaved();
|
|
659
663
|
});
|
|
660
664
|
|
|
661
|
-
this.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
this.mc.logger.sendErrorEvent({
|
|
669
|
-
eventName: "NoRealStorageInDetachedContainer",
|
|
670
|
-
});
|
|
671
|
-
throw new Error("Real storage calls not allowed in Unattached container");
|
|
672
|
-
}
|
|
673
|
-
return this.storageService;
|
|
674
|
-
},
|
|
665
|
+
this.storageService = new ContainerStorageAdapter(
|
|
666
|
+
this.loader.services.detachedBlobStorage,
|
|
667
|
+
this.mc.logger,
|
|
668
|
+
this.options.summarizeProtocolTree === true
|
|
669
|
+
? () => this.captureProtocolSummary()
|
|
670
|
+
: undefined,
|
|
675
671
|
);
|
|
676
672
|
|
|
677
673
|
const isDomAvailable = typeof document === "object" &&
|
|
@@ -771,7 +767,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
771
767
|
|
|
772
768
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
773
769
|
|
|
774
|
-
this.
|
|
770
|
+
this.storageService.dispose();
|
|
775
771
|
|
|
776
772
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
777
773
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
@@ -893,7 +889,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
893
889
|
const resolvedUrl = this.service.resolvedUrl;
|
|
894
890
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
895
891
|
this._resolvedUrl = resolvedUrl;
|
|
896
|
-
await this.
|
|
892
|
+
await this.storageService.connectToService(this.service);
|
|
897
893
|
|
|
898
894
|
if (hasAttachmentBlobs) {
|
|
899
895
|
// upload blobs to storage
|
|
@@ -931,8 +927,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
931
927
|
this._attachState = AttachState.Attached;
|
|
932
928
|
this.emit("attached");
|
|
933
929
|
|
|
934
|
-
// Propagate current connection state through the system.
|
|
935
|
-
this.propagateConnectionState();
|
|
936
930
|
if (!this.closed) {
|
|
937
931
|
this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
|
|
938
932
|
}
|
|
@@ -1108,9 +1102,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1108
1102
|
/**
|
|
1109
1103
|
* Load container.
|
|
1110
1104
|
*
|
|
1111
|
-
* @param specifiedVersion -
|
|
1112
|
-
* - undefined - fetch latest snapshot
|
|
1113
|
-
* - otherwise, version sha to load snapshot
|
|
1105
|
+
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
1114
1106
|
*/
|
|
1115
1107
|
private async load(
|
|
1116
1108
|
specifiedVersion: string | undefined,
|
|
@@ -1144,10 +1136,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1144
1136
|
}
|
|
1145
1137
|
|
|
1146
1138
|
if (!pendingLocalState) {
|
|
1147
|
-
await this.
|
|
1139
|
+
await this.storageService.connectToService(this.service);
|
|
1148
1140
|
} else {
|
|
1149
1141
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
1150
|
-
this.
|
|
1142
|
+
this.storageService.connectToService(this.service).catch((error) => this.close(error));
|
|
1151
1143
|
}
|
|
1152
1144
|
|
|
1153
1145
|
this._attachState = AttachState.Attached;
|
|
@@ -1188,12 +1180,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1188
1180
|
|
|
1189
1181
|
// ...load in the existing quorum
|
|
1190
1182
|
// Initialize the protocol handler
|
|
1191
|
-
|
|
1192
|
-
|
|
1183
|
+
if (pendingLocalState === undefined) {
|
|
1184
|
+
await this.initializeProtocolStateFromSnapshot(
|
|
1193
1185
|
attributes,
|
|
1194
1186
|
this.storageService,
|
|
1195
|
-
snapshot
|
|
1196
|
-
|
|
1187
|
+
snapshot);
|
|
1188
|
+
} else {
|
|
1189
|
+
this.initializeProtocolState(
|
|
1197
1190
|
attributes,
|
|
1198
1191
|
{
|
|
1199
1192
|
members: pendingLocalState.protocol.members,
|
|
@@ -1201,6 +1194,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1201
1194
|
values: pendingLocalState.protocol.values,
|
|
1202
1195
|
}, // pending IQuorumSnapshot
|
|
1203
1196
|
);
|
|
1197
|
+
}
|
|
1204
1198
|
|
|
1205
1199
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1206
1200
|
await this.instantiateContext(
|
|
@@ -1275,7 +1269,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1275
1269
|
|
|
1276
1270
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1277
1271
|
const qValues = initQuorumValuesFromCodeDetails(source);
|
|
1278
|
-
this.
|
|
1272
|
+
this.initializeProtocolState(
|
|
1279
1273
|
attributes,
|
|
1280
1274
|
{
|
|
1281
1275
|
members: [],
|
|
@@ -1300,27 +1294,26 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1300
1294
|
}
|
|
1301
1295
|
|
|
1302
1296
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
1303
|
-
this.
|
|
1304
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1297
|
+
this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1298
|
+
const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
|
|
1305
1299
|
|
|
1306
1300
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1307
1301
|
|
|
1308
1302
|
// Initialize the protocol handler
|
|
1309
1303
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1310
1304
|
const qValues = await readAndParse<[string, ICommittedProposal][]>(
|
|
1311
|
-
this.
|
|
1305
|
+
this.storageService,
|
|
1312
1306
|
baseTree.blobs.quorumValues,
|
|
1313
1307
|
);
|
|
1314
1308
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1315
|
-
this.
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
);
|
|
1309
|
+
this.initializeProtocolState(
|
|
1310
|
+
attributes,
|
|
1311
|
+
{
|
|
1312
|
+
members: [],
|
|
1313
|
+
proposals: [],
|
|
1314
|
+
values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
|
|
1315
|
+
}, // IQuorumSnapShot
|
|
1316
|
+
);
|
|
1324
1317
|
|
|
1325
1318
|
await this.instantiateContextDetached(
|
|
1326
1319
|
true, // existing
|
|
@@ -1330,28 +1323,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1330
1323
|
this.setLoaded();
|
|
1331
1324
|
}
|
|
1332
1325
|
|
|
1333
|
-
private async connectStorageService(): Promise<void> {
|
|
1334
|
-
if (this._storageService !== undefined) {
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
|
|
1339
|
-
const storageService = await this.service.connectToStorage();
|
|
1340
|
-
|
|
1341
|
-
this._storageService =
|
|
1342
|
-
new RetriableDocumentStorageService(storageService, this.mc.logger);
|
|
1343
|
-
|
|
1344
|
-
if (this.options.summarizeProtocolTree === true) {
|
|
1345
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
1346
|
-
this._storageService =
|
|
1347
|
-
new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
// ensure we did not lose that policy in the process of wrapping
|
|
1351
|
-
assert(storageService.policies?.minBlobSize === this.storageService.policies?.minBlobSize,
|
|
1352
|
-
0x0e0 /* "lost minBlobSize policy" */);
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
1326
|
private async getDocumentAttributes(
|
|
1356
1327
|
storage: IDocumentStorageService,
|
|
1357
1328
|
tree: ISnapshotTree | undefined,
|
|
@@ -1383,7 +1354,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1383
1354
|
attributes: IDocumentAttributes,
|
|
1384
1355
|
storage: IDocumentStorageService,
|
|
1385
1356
|
snapshot: ISnapshotTree | undefined,
|
|
1386
|
-
): Promise<
|
|
1357
|
+
): Promise<void> {
|
|
1387
1358
|
const quorumSnapshot: IQuorumSnapshot = {
|
|
1388
1359
|
members: [],
|
|
1389
1360
|
proposals: [],
|
|
@@ -1399,14 +1370,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1399
1370
|
]);
|
|
1400
1371
|
}
|
|
1401
1372
|
|
|
1402
|
-
|
|
1403
|
-
return protocolHandler;
|
|
1373
|
+
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
1404
1374
|
}
|
|
1405
1375
|
|
|
1406
|
-
private
|
|
1376
|
+
private initializeProtocolState(
|
|
1407
1377
|
attributes: IDocumentAttributes,
|
|
1408
1378
|
quorumSnapshot: IQuorumSnapshot,
|
|
1409
|
-
):
|
|
1379
|
+
): void {
|
|
1410
1380
|
const protocolHandlerBuilder =
|
|
1411
1381
|
this.protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
|
|
1412
1382
|
const protocol = protocolHandlerBuilder(
|
|
@@ -1448,8 +1418,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1448
1418
|
});
|
|
1449
1419
|
}
|
|
1450
1420
|
});
|
|
1451
|
-
|
|
1452
|
-
|
|
1421
|
+
// we need to make sure this member get set in a synchronous context,
|
|
1422
|
+
// or other things can happen after the object that will be set is created, but not yet set
|
|
1423
|
+
// this was breaking this._initialClients handling
|
|
1424
|
+
//
|
|
1425
|
+
this._protocolHandler = protocol;
|
|
1453
1426
|
}
|
|
1454
1427
|
|
|
1455
1428
|
private captureProtocolSummary(): ISummaryTree {
|
|
@@ -1553,15 +1526,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1553
1526
|
}
|
|
1554
1527
|
}
|
|
1555
1528
|
|
|
1556
|
-
const deltaManagerForCatchingUp =
|
|
1557
|
-
this.mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true ?
|
|
1558
|
-
this.deltaManager
|
|
1559
|
-
: undefined;
|
|
1560
|
-
|
|
1561
1529
|
this.connectionStateHandler.receivedConnectEvent(
|
|
1562
1530
|
this.connectionMode,
|
|
1563
1531
|
details,
|
|
1564
|
-
deltaManagerForCatchingUp,
|
|
1565
1532
|
);
|
|
1566
1533
|
});
|
|
1567
1534
|
|
|
@@ -1636,11 +1603,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1636
1603
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1637
1604
|
}
|
|
1638
1605
|
}
|
|
1639
|
-
|
|
1640
|
-
connectionInitiationReason = "InitialConnect";
|
|
1641
|
-
} else {
|
|
1642
|
-
connectionInitiationReason = "AutoReconnect";
|
|
1643
|
-
}
|
|
1606
|
+
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1644
1607
|
}
|
|
1645
1608
|
|
|
1646
1609
|
this.mc.logger.sendPerformanceEvent({
|
|
@@ -1666,7 +1629,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1666
1629
|
}
|
|
1667
1630
|
}
|
|
1668
1631
|
|
|
1669
|
-
private propagateConnectionState() {
|
|
1632
|
+
private propagateConnectionState(initialTransition: boolean) {
|
|
1633
|
+
// When container loaded, we want to propagate initial connection state.
|
|
1634
|
+
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1635
|
+
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
1636
|
+
if (!initialTransition &&
|
|
1637
|
+
this.connectionState !== ConnectionState.Connected &&
|
|
1638
|
+
this.connectionState !== ConnectionState.Disconnected) {
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
const state = this.connectionState === ConnectionState.Connected;
|
|
1642
|
+
|
|
1670
1643
|
const logOpsOnReconnect: boolean =
|
|
1671
1644
|
this.connectionState === ConnectionState.Connected &&
|
|
1672
1645
|
!this.firstConnection &&
|
|
@@ -1675,8 +1648,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1675
1648
|
this.messageCountAfterDisconnection = 0;
|
|
1676
1649
|
}
|
|
1677
1650
|
|
|
1678
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
1679
|
-
|
|
1680
1651
|
// Both protocol and context should not be undefined if we got so far.
|
|
1681
1652
|
|
|
1682
1653
|
this.setContextConnectedState(state, this._deltaManager.connectionManager.readOnlyInfo.readonly ?? false);
|
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
6
|
+
import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
+
import { assert } from "@fluidframework/common-utils";
|
|
7
8
|
import { ISnapshotTreeWithBlobContents } from "@fluidframework/container-definitions";
|
|
8
9
|
import {
|
|
9
10
|
FetchSource,
|
|
11
|
+
IDocumentService,
|
|
10
12
|
IDocumentStorageService,
|
|
11
13
|
IDocumentStorageServicePolicies,
|
|
12
14
|
ISummaryContext,
|
|
13
15
|
} from "@fluidframework/driver-definitions";
|
|
16
|
+
import { UsageError } from "@fluidframework/driver-utils";
|
|
14
17
|
import {
|
|
15
18
|
ICreateBlobResponse,
|
|
16
19
|
ISnapshotTree,
|
|
@@ -19,14 +22,52 @@ import {
|
|
|
19
22
|
IVersion,
|
|
20
23
|
} from "@fluidframework/protocol-definitions";
|
|
21
24
|
import { IDetachedBlobStorage } from "./loader";
|
|
25
|
+
import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
|
|
26
|
+
import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* This class wraps the actual storage and make sure no wrong apis are called according to
|
|
25
30
|
* container attach state.
|
|
26
31
|
*/
|
|
27
|
-
export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
32
|
+
export class ContainerStorageAdapter implements IDocumentStorageService, IDisposable {
|
|
28
33
|
private readonly blobContents: { [id: string]: ArrayBufferLike; } = {};
|
|
29
|
-
|
|
34
|
+
private _storageService: IDocumentStorageService & Partial<IDisposable>;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
detachedBlobStorage: IDetachedBlobStorage | undefined,
|
|
38
|
+
private readonly logger: ITelemetryLogger,
|
|
39
|
+
private readonly captureProtocolSummary?: () => ISummaryTree,
|
|
40
|
+
) {
|
|
41
|
+
this._storageService = new BlobOnlyStorage(detachedBlobStorage, logger);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
disposed: boolean = false;
|
|
45
|
+
dispose(error?: Error): void {
|
|
46
|
+
this._storageService?.dispose?.(error);
|
|
47
|
+
this.disposed = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async connectToService(service: IDocumentService): Promise<void> {
|
|
51
|
+
if (!(this._storageService instanceof BlobOnlyStorage)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const storageService = await service.connectToStorage();
|
|
56
|
+
const retriableStorage = this._storageService =
|
|
57
|
+
new RetriableDocumentStorageService(
|
|
58
|
+
storageService,
|
|
59
|
+
this.logger);
|
|
60
|
+
|
|
61
|
+
if (this.captureProtocolSummary !== undefined) {
|
|
62
|
+
this.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
63
|
+
this._storageService =
|
|
64
|
+
new ProtocolTreeStorageService(retriableStorage, this.captureProtocolSummary);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ensure we did not lose that policy in the process of wrapping
|
|
68
|
+
assert(storageService.policies?.minBlobSize === this._storageService.policies?.minBlobSize,
|
|
69
|
+
0x0e0 /* "lost minBlobSize policy" */);
|
|
70
|
+
}
|
|
30
71
|
|
|
31
72
|
public loadSnapshotForRehydratingContainer(snapshotTree: ISnapshotTreeWithBlobContents) {
|
|
32
73
|
this.getBlobContents(snapshotTree);
|
|
@@ -45,17 +86,17 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
45
86
|
// back-compat 0.40 containerRuntime requests policies even in detached container if storage is present
|
|
46
87
|
// and storage is always present in >=0.41.
|
|
47
88
|
try {
|
|
48
|
-
return this.
|
|
89
|
+
return this._storageService.policies;
|
|
49
90
|
} catch (e) {}
|
|
50
91
|
return undefined;
|
|
51
92
|
}
|
|
52
93
|
|
|
53
94
|
public get repositoryUrl(): string {
|
|
54
|
-
return this.
|
|
95
|
+
return this._storageService.repositoryUrl;
|
|
55
96
|
}
|
|
56
97
|
|
|
57
98
|
public async getSnapshotTree(version?: IVersion, scenarioName?: string): Promise<ISnapshotTree | null> {
|
|
58
|
-
return this.
|
|
99
|
+
return this._storageService.getSnapshotTree(version, scenarioName);
|
|
59
100
|
}
|
|
60
101
|
|
|
61
102
|
public async readBlob(id: string): Promise<ArrayBufferLike> {
|
|
@@ -63,7 +104,7 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
63
104
|
if (blob !== undefined) {
|
|
64
105
|
return blob;
|
|
65
106
|
}
|
|
66
|
-
return this.
|
|
107
|
+
return this._storageService.readBlob(id);
|
|
67
108
|
}
|
|
68
109
|
|
|
69
110
|
public async getVersions(
|
|
@@ -72,19 +113,19 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
72
113
|
scenarioName?: string,
|
|
73
114
|
fetchSource?: FetchSource,
|
|
74
115
|
): Promise<IVersion[]> {
|
|
75
|
-
return this.
|
|
116
|
+
return this._storageService.getVersions(versionId, count, scenarioName, fetchSource);
|
|
76
117
|
}
|
|
77
118
|
|
|
78
119
|
public async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {
|
|
79
|
-
return this.
|
|
120
|
+
return this._storageService.uploadSummaryWithContext(summary, context);
|
|
80
121
|
}
|
|
81
122
|
|
|
82
123
|
public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {
|
|
83
|
-
return this.
|
|
124
|
+
return this._storageService.downloadSummary(handle);
|
|
84
125
|
}
|
|
85
126
|
|
|
86
127
|
public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
87
|
-
return this.
|
|
128
|
+
return this._storageService.createBlob(file);
|
|
88
129
|
}
|
|
89
130
|
}
|
|
90
131
|
|
|
@@ -92,18 +133,25 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
92
133
|
* Storage which only supports createBlob() and readBlob(). This is used with IDetachedBlobStorage to support
|
|
93
134
|
* blobs in detached containers.
|
|
94
135
|
*/
|
|
95
|
-
|
|
136
|
+
class BlobOnlyStorage implements IDocumentStorageService {
|
|
96
137
|
constructor(
|
|
97
|
-
private readonly
|
|
138
|
+
private readonly detachedStorage: IDetachedBlobStorage | undefined,
|
|
98
139
|
private readonly logger: ITelemetryLogger,
|
|
99
140
|
) { }
|
|
100
141
|
|
|
101
142
|
public async createBlob(content: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
102
|
-
return this.
|
|
143
|
+
return this.verifyStorage().createBlob(content);
|
|
103
144
|
}
|
|
104
145
|
|
|
105
146
|
public async readBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
106
|
-
return this.
|
|
147
|
+
return this.verifyStorage().readBlob(blobId);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private verifyStorage(): IDetachedBlobStorage {
|
|
151
|
+
if (this.detachedStorage === undefined) {
|
|
152
|
+
throw new UsageError("Real storage calls not allowed in Unattached container");
|
|
153
|
+
}
|
|
154
|
+
return this.detachedStorage;
|
|
107
155
|
}
|
|
108
156
|
|
|
109
157
|
public get policies(): IDocumentStorageServicePolicies | undefined {
|
|
@@ -123,6 +171,7 @@ export class BlobOnlyStorage implements IDocumentStorageService {
|
|
|
123
171
|
/* eslint-enable @typescript-eslint/unbound-method */
|
|
124
172
|
|
|
125
173
|
private notCalled(): never {
|
|
174
|
+
this.verifyStorage();
|
|
126
175
|
try {
|
|
127
176
|
// some browsers may not populate stack unless exception is thrown
|
|
128
177
|
throw new Error("BlobOnlyStorage not implemented method used");
|
package/src/deltaManager.ts
CHANGED
|
@@ -246,15 +246,15 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
246
246
|
this.emit("prepareSend", batch);
|
|
247
247
|
|
|
248
248
|
if (batch.length === 1) {
|
|
249
|
-
assert(batch[0].metadata?.batch === undefined,
|
|
249
|
+
assert(batch[0].metadata?.batch === undefined, 0x3c9 /* no batch markup on single message */);
|
|
250
250
|
} else {
|
|
251
|
-
assert(batch[0].metadata?.batch === true,
|
|
252
|
-
assert(batch[batch.length - 1].metadata?.batch === false,
|
|
251
|
+
assert(batch[0].metadata?.batch === true, 0x3ca /* no start batch markup */);
|
|
252
|
+
assert(batch[batch.length - 1].metadata?.batch === false, 0x3cb /* no end batch markup */);
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
this.connectionManager.sendMessages(batch);
|
|
256
256
|
|
|
257
|
-
assert(this.messageBuffer.length === 0,
|
|
257
|
+
assert(this.messageBuffer.length === 0, 0x3cc /* reentrancy */);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
public get connectionProps(): ITelemetryProperties {
|
package/src/packageVersion.ts
CHANGED