@fluidframework/container-loader 0.47.0-36699 → 0.48.0-38142

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/lib/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { parse } from \"url\";\nimport { v4 as uuid } from \"uuid\";\nimport {\n assert,\n bufferToString,\n stringToBuffer,\n Uint8ArrayToArrayBuffer,\n unreachableCase,\n} from \"@fluidframework/common-utils\";\nimport { ISummaryTree, ISnapshotTree, SummaryType } from \"@fluidframework/protocol-definitions\";\n\n// This is used when we rehydrate a container from the snapshot. Here we put the blob contents\n// in separate property: blobContents.\nexport interface ISnapshotTreeWithBlobContents extends ISnapshotTree {\n blobsContents: {[path: string]: ArrayBufferLike},\n trees: {[path: string]: ISnapshotTreeWithBlobContents},\n}\n\nexport interface IParsedUrl {\n id: string;\n path: string;\n query: string;\n /**\n * Null means do not use snapshots, undefined means load latest snapshot\n * otherwise it's version ID passed to IDocumentStorageService.getVersions() to figure out what snapshot to use.\n * If needed, can add undefined which is treated by Container.load() as load latest snapshot.\n */\n version: string | null | undefined;\n}\n\nexport function parseUrl(url: string): IParsedUrl | undefined {\n const parsed = parse(url, true);\n if (typeof parsed.pathname !== \"string\") {\n throw new Error(\"Failed to parse pathname\");\n }\n const query = parsed.search ?? \"\";\n const regex = /^\\/([^/]*\\/[^/]*)(\\/?.*)$/;\n const match = regex.exec(parsed.pathname);\n return (match?.length === 3)\n ? { id: match[1], path: match[2], query, version: parsed.query.version as string }\n : undefined;\n}\n\n/**\n * Converts summary tree (for upload) to snapshot tree (for download).\n * Summary tree blobs contain contents, but snapshot tree blobs normally\n * contain IDs pointing to storage. This will create 2 blob entries in the\n * snapshot tree for each blob in the summary tree. One will be the regular\n * path pointing to a uniquely generated ID. Then there will be another\n * entry with the path as that uniquely generated ID, and value as the\n * blob contents as a base-64 string.\n * @param summary - summary to convert\n */\nfunction convertSummaryToSnapshotWithEmbeddedBlobContents(\n summary: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n const treeNode: ISnapshotTreeWithBlobContents = {\n blobs: {},\n blobsContents: {},\n trees: {},\n commits: {},\n id: uuid(),\n unreferenced: summary.unreferenced,\n };\n const keys = Object.keys(summary.tree);\n for (const key of keys) {\n const summaryObject = summary.tree[key];\n\n switch (summaryObject.type) {\n case SummaryType.Tree: {\n treeNode.trees[key] = convertSummaryToSnapshotWithEmbeddedBlobContents(summaryObject);\n break;\n }\n case SummaryType.Attachment:\n treeNode.blobs[key] = summaryObject.id;\n break;\n case SummaryType.Blob: {\n const blobId = uuid();\n treeNode.blobs[key] = blobId;\n const contentBuffer = typeof summaryObject.content === \"string\" ?\n stringToBuffer(summaryObject.content, \"utf8\") : Uint8ArrayToArrayBuffer(summaryObject.content);\n treeNode.blobsContents[blobId] = contentBuffer;\n // 0.43 back-compat old runtime will still expect content in the blobs only.\n // So need to put in blobs for now.\n treeNode.blobs[blobId] = bufferToString(contentBuffer, \"base64\");\n break;\n }\n case SummaryType.Handle:\n throw new Error(\"No handles should be there in summary in detached container!!\");\n break;\n default: {\n unreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);\n }\n }\n }\n return treeNode;\n}\n\n/**\n * Combine and convert protocol and app summary tree to format which is readable by container while rehydrating.\n * @param protocolSummaryTree - Protocol Summary Tree\n * @param appSummaryTree - App Summary Tree\n */\nexport function convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree: ISummaryTree,\n appSummaryTree: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n // Shallow copy is fine, since we are doing a deep clone below.\n const combinedSummary: ISummaryTree = {\n type: SummaryType.Tree,\n tree: { ...appSummaryTree.tree },\n };\n\n combinedSummary.tree[\".protocol\"] = protocolSummaryTree;\n const snapshotTreeWithBlobContents =\n convertSummaryToSnapshotWithEmbeddedBlobContents(combinedSummary);\n return snapshotTreeWithBlobContents;\n}\n\n// This function converts the snapshot taken in detached container(by serialize api) to snapshotTree with which\n// a detached container can be rehydrated.\nexport const getSnapshotTreeFromSerializedContainer = (detachedContainerSnapshot: ISummaryTree) => {\n const protocolSummaryTree = detachedContainerSnapshot.tree[\".protocol\"] as ISummaryTree;\n const appSummaryTree = detachedContainerSnapshot.tree[\".app\"] as ISummaryTree;\n assert(protocolSummaryTree !== undefined && appSummaryTree !== undefined,\n 0x1e0 /* \"Protocol and App summary trees should be present\" */);\n const snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree,\n appSummaryTree,\n );\n return snapshotTreeWithBlobContents;\n};\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { parse } from \"url\";\nimport { v4 as uuid } from \"uuid\";\nimport {\n assert,\n stringToBuffer,\n Uint8ArrayToArrayBuffer,\n unreachableCase,\n} from \"@fluidframework/common-utils\";\nimport { ISummaryTree, ISnapshotTree, SummaryType } from \"@fluidframework/protocol-definitions\";\n\n// This is used when we rehydrate a container from the snapshot. Here we put the blob contents\n// in separate property: blobContents.\nexport interface ISnapshotTreeWithBlobContents extends ISnapshotTree {\n blobsContents: {[path: string]: ArrayBufferLike},\n trees: {[path: string]: ISnapshotTreeWithBlobContents},\n}\n\nexport interface IParsedUrl {\n id: string;\n path: string;\n query: string;\n /**\n * Null means do not use snapshots, undefined means load latest snapshot\n * otherwise it's version ID passed to IDocumentStorageService.getVersions() to figure out what snapshot to use.\n * If needed, can add undefined which is treated by Container.load() as load latest snapshot.\n */\n version: string | null | undefined;\n}\n\nexport function parseUrl(url: string): IParsedUrl | undefined {\n const parsed = parse(url, true);\n if (typeof parsed.pathname !== \"string\") {\n throw new Error(\"Failed to parse pathname\");\n }\n const query = parsed.search ?? \"\";\n const regex = /^\\/([^/]*\\/[^/]*)(\\/?.*)$/;\n const match = regex.exec(parsed.pathname);\n return (match?.length === 3)\n ? { id: match[1], path: match[2], query, version: parsed.query.version as string }\n : undefined;\n}\n\n/**\n * Converts summary tree (for upload) to snapshot tree (for download).\n * Summary tree blobs contain contents, but snapshot tree blobs normally\n * contain IDs pointing to storage. This will create 2 blob entries in the\n * snapshot tree for each blob in the summary tree. One will be the regular\n * path pointing to a uniquely generated ID. Then there will be another\n * entry with the path as that uniquely generated ID, and value as the\n * blob contents as a base-64 string.\n * @param summary - summary to convert\n */\nfunction convertSummaryToSnapshotWithEmbeddedBlobContents(\n summary: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n const treeNode: ISnapshotTreeWithBlobContents = {\n blobs: {},\n blobsContents: {},\n trees: {},\n commits: {},\n id: uuid(),\n unreferenced: summary.unreferenced,\n };\n const keys = Object.keys(summary.tree);\n for (const key of keys) {\n const summaryObject = summary.tree[key];\n\n switch (summaryObject.type) {\n case SummaryType.Tree: {\n treeNode.trees[key] = convertSummaryToSnapshotWithEmbeddedBlobContents(summaryObject);\n break;\n }\n case SummaryType.Attachment:\n treeNode.blobs[key] = summaryObject.id;\n break;\n case SummaryType.Blob: {\n const blobId = uuid();\n treeNode.blobs[key] = blobId;\n const contentBuffer = typeof summaryObject.content === \"string\" ?\n stringToBuffer(summaryObject.content, \"utf8\") : Uint8ArrayToArrayBuffer(summaryObject.content);\n treeNode.blobsContents[blobId] = contentBuffer;\n break;\n }\n case SummaryType.Handle:\n throw new Error(\"No handles should be there in summary in detached container!!\");\n break;\n default: {\n unreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);\n }\n }\n }\n return treeNode;\n}\n\n/**\n * Combine and convert protocol and app summary tree to format which is readable by container while rehydrating.\n * @param protocolSummaryTree - Protocol Summary Tree\n * @param appSummaryTree - App Summary Tree\n */\nexport function convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree: ISummaryTree,\n appSummaryTree: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n // Shallow copy is fine, since we are doing a deep clone below.\n const combinedSummary: ISummaryTree = {\n type: SummaryType.Tree,\n tree: { ...appSummaryTree.tree },\n };\n\n combinedSummary.tree[\".protocol\"] = protocolSummaryTree;\n const snapshotTreeWithBlobContents =\n convertSummaryToSnapshotWithEmbeddedBlobContents(combinedSummary);\n return snapshotTreeWithBlobContents;\n}\n\n// This function converts the snapshot taken in detached container(by serialize api) to snapshotTree with which\n// a detached container can be rehydrated.\nexport const getSnapshotTreeFromSerializedContainer = (detachedContainerSnapshot: ISummaryTree) => {\n const protocolSummaryTree = detachedContainerSnapshot.tree[\".protocol\"] as ISummaryTree;\n const appSummaryTree = detachedContainerSnapshot.tree[\".app\"] as ISummaryTree;\n assert(protocolSummaryTree !== undefined && appSummaryTree !== undefined,\n 0x1e0 /* \"Protocol and App summary trees should be present\" */);\n const snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree,\n appSummaryTree,\n );\n return snapshotTreeWithBlobContents;\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "0.47.0-36699",
3
+ "version": "0.48.0-38142",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": "https://github.com/microsoft/FluidFramework",
@@ -59,13 +59,13 @@
59
59
  "@fluidframework/common-definitions": "^0.20.1",
60
60
  "@fluidframework/common-utils": "^0.32.1",
61
61
  "@fluidframework/container-definitions": "^0.39.8",
62
- "@fluidframework/container-utils": "0.47.0-36699",
62
+ "@fluidframework/container-utils": "0.48.0-38142",
63
63
  "@fluidframework/core-interfaces": "^0.39.7",
64
64
  "@fluidframework/driver-definitions": "^0.39.6",
65
- "@fluidframework/driver-utils": "0.47.0-36699",
66
- "@fluidframework/protocol-base": "^0.1030.0-0",
65
+ "@fluidframework/driver-utils": "0.48.0-38142",
66
+ "@fluidframework/protocol-base": "^0.1031.0-37526",
67
67
  "@fluidframework/protocol-definitions": "^0.1024.0",
68
- "@fluidframework/telemetry-utils": "0.47.0-36699",
68
+ "@fluidframework/telemetry-utils": "0.48.0-38142",
69
69
  "abort-controller": "^3.0.0",
70
70
  "double-ended-queue": "^2.1.0-0",
71
71
  "lodash": "^4.17.21",
@@ -74,8 +74,8 @@
74
74
  "devDependencies": {
75
75
  "@fluidframework/build-common": "^0.23.0-0",
76
76
  "@fluidframework/eslint-config-fluid": "^0.23.0",
77
- "@fluidframework/mocha-test-setup": "0.47.0-36699",
78
- "@fluidframework/test-loader-utils": "0.47.0-36699",
77
+ "@fluidframework/mocha-test-setup": "0.48.0-38142",
78
+ "@fluidframework/test-loader-utils": "0.48.0-38142",
79
79
  "@microsoft/api-extractor": "^7.16.1",
80
80
  "@types/double-ended-queue": "^2.1.0",
81
81
  "@types/lodash": "^4.14.118",
package/src/container.ts CHANGED
@@ -185,11 +185,13 @@ export async function waitContainerToCatchUp(container: Container) {
185
185
  const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
186
186
 
187
187
  const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
188
+ assert(deltaManager.lastSequenceNumber <= connectionOpSeqNumber,
189
+ 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
188
190
  if (deltaManager.lastSequenceNumber === connectionOpSeqNumber) {
189
191
  accept(hasCheckpointSequenceNumber);
190
192
  return;
191
193
  }
192
- const callbackOps = (message) => {
194
+ const callbackOps = (message: ISequencedDocumentMessage) => {
193
195
  if (connectionOpSeqNumber <= message.sequenceNumber) {
194
196
  accept(hasCheckpointSequenceNumber);
195
197
  deltaManager.off("op", callbackOps);
@@ -643,6 +645,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
643
645
  dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
644
646
  dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
645
647
  dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId,
648
+ connectionState: () => ConnectionState[this.connectionState],
649
+ connectionStateDuration:
650
+ () => performance.now() - this.connectionTransitionTimes[this.connectionState],
646
651
  },
647
652
  });
648
653
 
@@ -970,8 +975,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
970
975
  }
971
976
 
972
977
  public setAutoReconnect(reconnect: boolean) {
973
- if (reconnect && this.closed) {
974
- throw new Error("Attempting to setAutoReconnect() a closed DeltaManager");
978
+ if (this.closed) {
979
+ throw new Error("Attempting to setAutoReconnect() a closed Container");
975
980
  }
976
981
 
977
982
  this._deltaManager.setAutomaticReconnect(reconnect);
@@ -1234,42 +1239,30 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1234
1239
 
1235
1240
  const attributes = await this.getDocumentAttributes(this.storageService, snapshot);
1236
1241
 
1237
- // Attach op handlers to start processing ops
1238
- this.attachDeltaManagerOpHandler(attributes);
1239
-
1240
- // ...load in the existing quorum
1241
- // Initialize the protocol handler
1242
- const protocolHandlerP =
1243
- this.loadAndInitializeProtocolState(attributes, this.storageService, snapshot);
1244
-
1245
1242
  let opsBeforeReturnP: Promise<void> | undefined;
1246
1243
 
1247
- // Initialize document details - if loading a snapshot use that - otherwise we need to wait on
1248
- // the initial details
1244
+ // Attach op handlers to finish initialization and be able to start processing ops
1245
+ // Kick off any ops fetching if required.
1249
1246
  switch (loadMode.opsBeforeReturn) {
1250
1247
  case undefined:
1251
- if (loadMode.deltaConnection !== "none") {
1252
- // Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
1253
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1254
- this._deltaManager.preFetchOps(false);
1255
- }
1248
+ // Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
1249
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1250
+ this.attachDeltaManagerOpHandler(attributes, loadMode.deltaConnection !== "none" ? "all" : "none");
1256
1251
  break;
1257
1252
  case "cached":
1258
- opsBeforeReturnP = this._deltaManager.preFetchOps(true);
1259
- // Keep going with fetching ops from storage once we have all cached ops in.
1260
- // Ops processing will start once cached ops are in and and will stop when queue is empty
1261
- // (which in most cases will happen when we are done processing cached ops)
1262
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1263
- opsBeforeReturnP.then(async () => this._deltaManager.preFetchOps(false));
1253
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "cached");
1264
1254
  break;
1265
1255
  case "all":
1266
- opsBeforeReturnP = this._deltaManager.preFetchOps(false);
1256
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "all");
1267
1257
  break;
1268
1258
  default:
1269
1259
  unreachableCase(loadMode.opsBeforeReturn);
1270
1260
  }
1271
1261
 
1272
- this._protocolHandler = await protocolHandlerP;
1262
+ // ...load in the existing quorum
1263
+ // Initialize the protocol handler
1264
+ this._protocolHandler =
1265
+ await this.loadAndInitializeProtocolState(attributes, this.storageService, snapshot);
1273
1266
 
1274
1267
  const codeDetails = this.getCodeDetailsFromQuorum();
1275
1268
  await this.instantiateContext(
@@ -1350,7 +1343,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1350
1343
  const proposals: [number, ISequencedProposal, string[]][] = [];
1351
1344
  const values: [string, ICommittedProposal][] = [["code", committedCodeProposal]];
1352
1345
 
1353
- this.attachDeltaManagerOpHandler(attributes);
1346
+ await this.attachDeltaManagerOpHandler(attributes);
1354
1347
 
1355
1348
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1356
1349
  this._protocolHandler = await this.initializeProtocolState(
@@ -1380,7 +1373,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1380
1373
  this._storage.loadSnapshotForRehydratingContainer(snapshotTree);
1381
1374
  const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
1382
1375
  assert(attributes.sequenceNumber === 0, 0x0db /* "Seq number in detached container should be 0!!" */);
1383
- this.attachDeltaManagerOpHandler(attributes);
1376
+ await this.attachDeltaManagerOpHandler(attributes);
1384
1377
 
1385
1378
  // ...load in the existing quorum
1386
1379
  // Initialize the protocol handler
@@ -1654,8 +1647,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1654
1647
  return deltaManager;
1655
1648
  }
1656
1649
 
1657
- private attachDeltaManagerOpHandler(attributes: IDocumentAttributes): void {
1658
- this._deltaManager.attachOpHandler(
1650
+ private async attachDeltaManagerOpHandler(
1651
+ attributes: IDocumentAttributes,
1652
+ prefetchType?: "cached" | "all" | "none")
1653
+ {
1654
+ return this._deltaManager.attachOpHandler(
1659
1655
  attributes.minimumSequenceNumber,
1660
1656
  attributes.sequenceNumber,
1661
1657
  attributes.term ?? 1,
@@ -1664,7 +1660,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1664
1660
  processSignal: (message) => {
1665
1661
  this.processSignal(message);
1666
1662
  },
1667
- });
1663
+ },
1664
+ prefetchType);
1668
1665
  }
1669
1666
 
1670
1667
  private logConnectionStateChangeTelemetry(
@@ -380,13 +380,17 @@ export class DeltaManager
380
380
  * about current or last connection (if there is no connection at the moment)
381
381
  */
382
382
  public connectionProps(): ITelemetryProperties {
383
+ const common = {
384
+ sequenceNumber: this.lastSequenceNumber,
385
+ };
383
386
  if (this.connection !== undefined) {
384
387
  return {
385
- sequenceNumber: this.lastSequenceNumber,
388
+ ...common,
386
389
  connectionMode: this.connectionMode,
387
390
  };
388
391
  } else {
389
392
  return {
393
+ ...common,
390
394
  // Report how many ops this client sent in last disconnected session
391
395
  sentOps: this.clientSequenceNumber,
392
396
  };
@@ -552,11 +556,12 @@ export class DeltaManager
552
556
  /**
553
557
  * Sets the sequence number from which inbound messages should be returned
554
558
  */
555
- public attachOpHandler(
559
+ public async attachOpHandler(
556
560
  minSequenceNumber: number,
557
561
  sequenceNumber: number,
558
562
  term: number,
559
563
  handler: IDeltaHandlerStrategy,
564
+ prefetchType: "cached" | "all" | "none" = "none",
560
565
  ) {
561
566
  this.initSequenceNumber = sequenceNumber;
562
567
  this.lastProcessedSequenceNumber = sequenceNumber;
@@ -571,24 +576,36 @@ export class DeltaManager
571
576
  // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
572
577
  assert(!!(this.handler as any), 0x0e3 /* "Newly set op handler is null/undefined!" */);
573
578
 
579
+ // There should be no pending fetch!
580
+ // This API is called right after attachOpHandler by Container.load().
581
+ // We might have connection already and it might have called fetchMissingDeltas() from
582
+ // setupNewSuccessfulConnection. But it should do nothing, because there is no way to fetch ops before
583
+ // we know snapshot sequence number that is set in attachOpHandler. So all such calls should be noop.
584
+ assert(this.fetchReason === undefined, "There can't be pending fetch that early in boot sequence!");
585
+
586
+ if (this.closed) {
587
+ return;
588
+ }
589
+
574
590
  this._inbound.resume();
575
591
  this._inboundSignal.resume();
576
592
 
577
- // We could have connected to delta stream before getting here
578
- // If so, it's time to process any accumulated ops, as there might be no other event that
579
- // will force these pending ops to be processed.
580
- // Or request OPs from snapshot / or point zero (if we have no ops at all)
581
- if (this.pending.length > 0) {
582
- this.processPendingOps("DocumentOpen");
593
+ if (prefetchType !== "none") {
594
+ const cacheOnly = prefetchType === "cached";
595
+ await this.fetchMissingDeltasCore("DocumentOpen", cacheOnly, this.lastQueuedSequenceNumber);
596
+
597
+ // Keep going with fetching ops from storage once we have all cached ops in.
598
+ // But do not block load and make this request async / not blocking this api.
599
+ // Ops processing will start once cached ops are in and and will stop when queue is empty
600
+ // (which in most cases will happen when we are done processing cached ops)
601
+ if (cacheOnly) {
602
+ // fire and forget
603
+ this.fetchMissingDeltas("DocumentOpen", this.lastQueuedSequenceNumber);
604
+ }
583
605
  }
584
- }
585
606
 
586
- public async preFetchOps(cacheOnly: boolean) {
587
- // Note that might already got connected to delta stream by now.
588
- // If we did, then we proactively fetch ops at the end of setupNewSuccessfulConnection to ensure
589
- if (this.connection === undefined) {
590
- return this.fetchMissingDeltasCore("DocumentOpen", cacheOnly, this.lastQueuedSequenceNumber, undefined);
591
- }
607
+ // Ensure there is no need to call this.processPendingOps() at the end of boot sequence
608
+ assert(this.fetchReason !== undefined || this.pending.length === 0, "pending ops are not dropped");
592
609
  }
593
610
 
594
611
  private static detailsFromConnection(connection: IDocumentDeltaConnection): IConnectionDetails {
@@ -630,6 +647,8 @@ export class DeltaManager
630
647
  }
631
648
 
632
649
  private async connectCore(args: IConnectionArgs): Promise<IDocumentDeltaConnection> {
650
+ assert(!this.closed, "not closed");
651
+
633
652
  if (this.connection !== undefined) {
634
653
  return this.connection;
635
654
  }
@@ -664,7 +683,7 @@ export class DeltaManager
664
683
  this.handler !== undefined || !fetchOpsFromStorage,
665
684
  this.logger,
666
685
  "CantFetchWithoutBaseline"); // can't fetch if no baseline
667
- if (fetchOpsFromStorage && this.handler !== undefined) {
686
+ if (fetchOpsFromStorage) {
668
687
  this.fetchMissingDeltas(args.reason, this.lastQueuedSequenceNumber);
669
688
  }
670
689
 
@@ -1037,15 +1056,7 @@ export class DeltaManager
1037
1056
  // check message.content for Back-compat with old service.
1038
1057
  const reconnectInfo = message.content !== undefined
1039
1058
  ? getNackReconnectInfo(message.content) :
1040
- createGenericNetworkError(`Nack: unknown reason`, true);
1041
-
1042
- if (this.reconnectMode !== ReconnectMode.Enabled) {
1043
- this.logger.sendErrorEvent({
1044
- eventName: "NackWithNoReconnect",
1045
- reason: reconnectInfo.message,
1046
- mode: this.connectionMode,
1047
- });
1048
- }
1059
+ createGenericNetworkError("nack:UnknownReason", true);
1049
1060
 
1050
1061
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1051
1062
  this.reconnectOnError(
@@ -1417,7 +1428,7 @@ export class DeltaManager
1417
1428
  const message2 = this.comparableMessagePayload(message);
1418
1429
  if (message1 !== message2) {
1419
1430
  const error = new NonRetryableError(
1420
- "Two messages with same seq# and different payload!",
1431
+ "twoMessagesWithSameSeqNumAndDifferentPayload",
1421
1432
  DriverErrorType.fileOverwrittenInStorage,
1422
1433
  {
1423
1434
  clientId: this.connection?.clientId,
@@ -1513,6 +1524,10 @@ export class DeltaManager
1513
1524
  }
1514
1525
  this.lastProcessedSequenceNumber = message.sequenceNumber;
1515
1526
 
1527
+ // a bunch of code assumes that this is true
1528
+ assert(this.lastProcessedSequenceNumber <= this.lastObservedSeqNumber,
1529
+ 0x267 /* "lastObservedSeqNumber should be updated first" */);
1530
+
1516
1531
  // Back-compat for older server with no term
1517
1532
  if (message.term === undefined) {
1518
1533
  message.term = 1;
@@ -1525,6 +1540,9 @@ export class DeltaManager
1525
1540
  this.handler.process(message);
1526
1541
 
1527
1542
  const endTime = Date.now();
1543
+
1544
+ // Should be last, after changing this.lastProcessedSequenceNumber above, as many callers
1545
+ // test this.lastProcessedSequenceNumber instead of using op.sequenceNumber itself.
1528
1546
  this.emit("op", message, endTime - startTime);
1529
1547
  }
1530
1548
 
@@ -1532,8 +1550,9 @@ export class DeltaManager
1532
1550
  * Retrieves the missing deltas between the given sequence numbers
1533
1551
  */
1534
1552
  private fetchMissingDeltas(reasonArg: string, lastKnowOp: number, to?: number) {
1535
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1536
- this.fetchMissingDeltasCore(reasonArg, false /* cacheOnly */, lastKnowOp, to);
1553
+ this.fetchMissingDeltasCore(reasonArg, false /* cacheOnly */, lastKnowOp, to).catch((error) => {
1554
+ this.logger.sendErrorEvent({ eventName: "fetchMissingDeltasException" }, error);
1555
+ });
1537
1556
  }
1538
1557
 
1539
1558
  /**
@@ -1555,6 +1574,12 @@ export class DeltaManager
1555
1574
  return;
1556
1575
  }
1557
1576
 
1577
+ if (this.handler === undefined) {
1578
+ // We do not poses yet any information
1579
+ assert(lastKnowOp === 0, "initial state");
1580
+ return;
1581
+ }
1582
+
1558
1583
  try {
1559
1584
  assert(lastKnowOp === this.lastQueuedSequenceNumber, 0x0f1 /* "from arg" */);
1560
1585
  let from = lastKnowOp + 1;
@@ -1596,12 +1621,40 @@ export class DeltaManager
1596
1621
  * Sorts pending ops and attempts to apply them
1597
1622
  */
1598
1623
  private processPendingOps(reason?: string): void {
1599
- if (this.handler !== undefined) {
1600
- const pendingSorted = this.pending.sort((a, b) => a.sequenceNumber - b.sequenceNumber);
1601
- this.pending = [];
1602
- // Given that we do not track where these ops came from any more, it's not very
1603
- // actionably to report gaps in this range.
1604
- this.enqueueMessages(pendingSorted, `${reason}_pending`, true /* allowGaps */);
1624
+ if (this.closed) {
1625
+ return;
1626
+ }
1627
+
1628
+ assert(this.handler !== undefined, "handler should be installed");
1629
+
1630
+ const pendingSorted = this.pending.sort((a, b) => a.sequenceNumber - b.sequenceNumber);
1631
+ this.pending = [];
1632
+ // Given that we do not track where these ops came from any more, it's not very
1633
+ // actionably to report gaps in this range.
1634
+ this.enqueueMessages(pendingSorted, `${reason}_pending`, true /* allowGaps */);
1635
+
1636
+ // Re-entrancy is ignored by fetchMissingDeltas, execution will come here when it's over
1637
+ if (this.fetchReason === undefined) {
1638
+ // See issue #7312 for more details
1639
+ // We observe cases where client gets into situation where it is not aware of missing ops
1640
+ // (i.e. client being behind), and as such, does not attempt to fetch them.
1641
+ // In some cases client may not have enough signal (example - "read" connection that is silent -
1642
+ // there is no easy way for client to realize it's behind, see a bit of commentary / logic at the
1643
+ // end of setupNewSuccessfulConnection). In other cases it should be able to learn that info ("write"
1644
+ // connection, learn by receiving its own join op), but data suggest it does not happen.
1645
+ // In 50% of these cases we do know we are behind through checkpointSequenceNumber on connection object
1646
+ // and thus can leverage that to trigger recovery. But this is not going to solve all the problems
1647
+ // (the other 50%), and thus these errors below should be looked at even if code below results in
1648
+ // recovery.
1649
+ if (this.lastQueuedSequenceNumber < this.lastObservedSeqNumber) {
1650
+ // connectionMode === "read" case is too noisy, so not log it.
1651
+ // It happens because fetch in setupNewSuccessfulConnection get cancelled due to other fetch, and we
1652
+ // never retry (other than here)
1653
+ if (this.connectionMode === "write") {
1654
+ this.logConnectionIssue({ eventName: "OpsBehind" });
1655
+ }
1656
+ this.fetchMissingDeltas("OpsBehind", this.lastQueuedSequenceNumber);
1657
+ }
1605
1658
  }
1606
1659
  }
1607
1660
 
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "0.47.0-36699";
9
+ export const pkgVersion = "0.48.0-38142";
package/src/utils.ts CHANGED
@@ -7,7 +7,6 @@ import { parse } from "url";
7
7
  import { v4 as uuid } from "uuid";
8
8
  import {
9
9
  assert,
10
- bufferToString,
11
10
  stringToBuffer,
12
11
  Uint8ArrayToArrayBuffer,
13
12
  unreachableCase,
@@ -85,9 +84,6 @@ function convertSummaryToSnapshotWithEmbeddedBlobContents(
85
84
  const contentBuffer = typeof summaryObject.content === "string" ?
86
85
  stringToBuffer(summaryObject.content, "utf8") : Uint8ArrayToArrayBuffer(summaryObject.content);
87
86
  treeNode.blobsContents[blobId] = contentBuffer;
88
- // 0.43 back-compat old runtime will still expect content in the blobs only.
89
- // So need to put in blobs for now.
90
- treeNode.blobs[blobId] = bufferToString(contentBuffer, "base64");
91
87
  break;
92
88
  }
93
89
  case SummaryType.Handle: