@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.
Files changed (58) hide show
  1. package/dist/catchUpMonitor.d.ts +6 -17
  2. package/dist/catchUpMonitor.d.ts.map +1 -1
  3. package/dist/catchUpMonitor.js +5 -36
  4. package/dist/catchUpMonitor.js.map +1 -1
  5. package/dist/connectionManager.d.ts.map +1 -1
  6. package/dist/connectionManager.js +3 -6
  7. package/dist/connectionManager.js.map +1 -1
  8. package/dist/connectionStateHandler.d.ts +80 -26
  9. package/dist/connectionStateHandler.d.ts.map +1 -1
  10. package/dist/connectionStateHandler.js +170 -89
  11. package/dist/connectionStateHandler.js.map +1 -1
  12. package/dist/container.d.ts +12 -11
  13. package/dist/container.d.ts.map +1 -1
  14. package/dist/container.js +76 -100
  15. package/dist/container.js.map +1 -1
  16. package/dist/containerStorageAdapter.d.ts +10 -24
  17. package/dist/containerStorageAdapter.d.ts.map +1 -1
  18. package/dist/containerStorageAdapter.js +50 -16
  19. package/dist/containerStorageAdapter.js.map +1 -1
  20. package/dist/deltaManager.js +4 -4
  21. package/dist/deltaManager.js.map +1 -1
  22. package/dist/packageVersion.d.ts +1 -1
  23. package/dist/packageVersion.d.ts.map +1 -1
  24. package/dist/packageVersion.js +1 -1
  25. package/dist/packageVersion.js.map +1 -1
  26. package/lib/catchUpMonitor.d.ts +6 -17
  27. package/lib/catchUpMonitor.d.ts.map +1 -1
  28. package/lib/catchUpMonitor.js +5 -35
  29. package/lib/catchUpMonitor.js.map +1 -1
  30. package/lib/connectionManager.d.ts.map +1 -1
  31. package/lib/connectionManager.js +3 -6
  32. package/lib/connectionManager.js.map +1 -1
  33. package/lib/connectionStateHandler.d.ts +80 -26
  34. package/lib/connectionStateHandler.d.ts.map +1 -1
  35. package/lib/connectionStateHandler.js +170 -90
  36. package/lib/connectionStateHandler.js.map +1 -1
  37. package/lib/container.d.ts +12 -11
  38. package/lib/container.d.ts.map +1 -1
  39. package/lib/container.js +77 -101
  40. package/lib/container.js.map +1 -1
  41. package/lib/containerStorageAdapter.d.ts +10 -24
  42. package/lib/containerStorageAdapter.d.ts.map +1 -1
  43. package/lib/containerStorageAdapter.js +50 -15
  44. package/lib/containerStorageAdapter.js.map +1 -1
  45. package/lib/deltaManager.js +4 -4
  46. package/lib/deltaManager.js.map +1 -1
  47. package/lib/packageVersion.d.ts +1 -1
  48. package/lib/packageVersion.d.ts.map +1 -1
  49. package/lib/packageVersion.js +1 -1
  50. package/lib/packageVersion.js.map +1 -1
  51. package/package.json +11 -11
  52. package/src/catchUpMonitor.ts +7 -47
  53. package/src/connectionManager.ts +3 -5
  54. package/src/connectionStateHandler.ts +231 -106
  55. package/src/container.ts +89 -118
  56. package/src/containerStorageAdapter.ts +64 -15
  57. package/src/deltaManager.ts +4 -4
  58. 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
- IDisposable, ITelemetryLogger, ITelemetryProperties,
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 { ConnectionStateHandler } from "./connectionStateHandler";
97
- import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
98
- import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
99
- import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdapter";
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
- * @returns true: container is up to date, it processed all the ops that were know at the time of first connection
156
- * false: storage does not provide indication of how far the client is. Container processed
157
- * all the ops known to it, but it maybe still behind.
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 _storage: ContainerStorageAdapter;
407
+ private readonly storageService: ContainerStorageAdapter;
402
408
  public get storage(): IDocumentStorageService {
403
- return this._storage;
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: 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.connected;
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.connectionStateHandler.clientId;
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.connectionStateHandler = new ConnectionStateHandler(
630
+ this._deltaManager = this.createDeltaManager();
631
+
632
+ this._clientId = config.serializedContainerState?.clientId;
633
+ this.connectionStateHandler = createConnectionStateHandler(
631
634
  {
632
- quorumClients: () => this._protocolHandler?.quorum,
633
- logConnectionStateChangeTelemetry: (value, oldState, reason) =>
634
- this.logConnectionStateChangeTelemetry(value, oldState, reason),
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.mc.logger,
654
- config.serializedContainerState?.clientId,
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._deltaManager = this.createDeltaManager();
662
- this._storage = new ContainerStorageAdapter(
663
- () => {
664
- if (this.attachState !== AttachState.Attached) {
665
- if (this.loader.services.detachedBlobStorage !== undefined) {
666
- return new BlobOnlyStorage(this.loader.services.detachedBlobStorage, this.mc.logger);
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._storageService?.dispose();
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.connectStorageService();
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 - one of the following
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.connectStorageService();
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.connectStorageService().catch((error) => this.close(error));
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
- this._protocolHandler = pendingLocalState === undefined
1192
- ? await this.initializeProtocolStateFromSnapshot(
1183
+ if (pendingLocalState === undefined) {
1184
+ await this.initializeProtocolStateFromSnapshot(
1193
1185
  attributes,
1194
1186
  this.storageService,
1195
- snapshot,
1196
- ) : await this.initializeProtocolState(
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._protocolHandler = await this.initializeProtocolState(
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._storage.loadSnapshotForRehydratingContainer(snapshotTree);
1304
- const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
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._storage,
1305
+ this.storageService,
1312
1306
  baseTree.blobs.quorumValues,
1313
1307
  );
1314
1308
  const codeDetails = getCodeDetailsFromQuorumValues(qValues);
1315
- this._protocolHandler =
1316
- await this.initializeProtocolState(
1317
- attributes,
1318
- {
1319
- members: [],
1320
- proposals: [],
1321
- values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
1322
- }, // IQuorumSnapShot
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<IProtocolHandler> {
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
- const protocolHandler = await this.initializeProtocolState(attributes, quorumSnapshot);
1403
- return protocolHandler;
1373
+ this.initializeProtocolState(attributes, quorumSnapshot);
1404
1374
  }
1405
1375
 
1406
- private async initializeProtocolState(
1376
+ private initializeProtocolState(
1407
1377
  attributes: IDocumentAttributes,
1408
1378
  quorumSnapshot: IQuorumSnapshot,
1409
- ): Promise<IProtocolHandler> {
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
- return protocol;
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
- if (this.firstConnection) {
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
- constructor(private readonly storageGetter: () => IDocumentStorageService) {}
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.storageGetter().policies;
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.storageGetter().repositoryUrl;
95
+ return this._storageService.repositoryUrl;
55
96
  }
56
97
 
57
98
  public async getSnapshotTree(version?: IVersion, scenarioName?: string): Promise<ISnapshotTree | null> {
58
- return this.storageGetter().getSnapshotTree(version, scenarioName);
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.storageGetter().readBlob(id);
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.storageGetter().getVersions(versionId, count, scenarioName, fetchSource);
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.storageGetter().uploadSummaryWithContext(summary, context);
120
+ return this._storageService.uploadSummaryWithContext(summary, context);
80
121
  }
81
122
 
82
123
  public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {
83
- return this.storageGetter().downloadSummary(handle);
124
+ return this._storageService.downloadSummary(handle);
84
125
  }
85
126
 
86
127
  public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {
87
- return this.storageGetter().createBlob(file);
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
- export class BlobOnlyStorage implements IDocumentStorageService {
136
+ class BlobOnlyStorage implements IDocumentStorageService {
96
137
  constructor(
97
- private readonly blobStorage: IDetachedBlobStorage,
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.blobStorage.createBlob(content);
143
+ return this.verifyStorage().createBlob(content);
103
144
  }
104
145
 
105
146
  public async readBlob(blobId: string): Promise<ArrayBufferLike> {
106
- return this.blobStorage.readBlob(blobId);
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");
@@ -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, "no batch markup on single message");
249
+ assert(batch[0].metadata?.batch === undefined, 0x3c9 /* no batch markup on single message */);
250
250
  } else {
251
- assert(batch[0].metadata?.batch === true, "no start batch markup");
252
- assert(batch[batch.length - 1].metadata?.batch === false, "no end batch markup");
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, "reentrancy");
257
+ assert(this.messageBuffer.length === 0, 0x3cc /* reentrancy */);
258
258
  }
259
259
 
260
260
  public get connectionProps(): ITelemetryProperties {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-internal.1.2.0.93071";
9
+ export const pkgVersion = "2.0.0-internal.1.2.1";