@fluidframework/container-runtime 2.1.0-274160 → 2.1.0-276985

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 (201) hide show
  1. package/README.md +14 -5
  2. package/container-runtime.test-files.tar +0 -0
  3. package/{lib → dist/blobManager}/blobManager.d.ts +9 -28
  4. package/dist/blobManager/blobManager.d.ts.map +1 -0
  5. package/dist/{blobManager.js → blobManager/blobManager.js} +23 -83
  6. package/dist/blobManager/blobManager.js.map +1 -0
  7. package/dist/blobManager/blobManagerSnapSum.d.ts +30 -0
  8. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -0
  9. package/dist/blobManager/blobManagerSnapSum.js +82 -0
  10. package/dist/blobManager/blobManagerSnapSum.js.map +1 -0
  11. package/dist/blobManager/index.d.ts +7 -0
  12. package/dist/blobManager/index.d.ts.map +1 -0
  13. package/dist/blobManager/index.js +16 -0
  14. package/dist/blobManager/index.js.map +1 -0
  15. package/dist/channelCollection.d.ts +1 -0
  16. package/dist/channelCollection.d.ts.map +1 -1
  17. package/dist/channelCollection.js +23 -13
  18. package/dist/channelCollection.js.map +1 -1
  19. package/dist/containerRuntime.d.ts +20 -7
  20. package/dist/containerRuntime.d.ts.map +1 -1
  21. package/dist/containerRuntime.js +82 -77
  22. package/dist/containerRuntime.js.map +1 -1
  23. package/dist/dataStoreContext.d.ts +1 -0
  24. package/dist/dataStoreContext.d.ts.map +1 -1
  25. package/dist/dataStoreContext.js +7 -2
  26. package/dist/dataStoreContext.js.map +1 -1
  27. package/dist/gc/garbageCollection.js +2 -2
  28. package/dist/gc/garbageCollection.js.map +1 -1
  29. package/dist/gc/gcDefinitions.d.ts +9 -0
  30. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  31. package/dist/gc/gcDefinitions.js +1 -0
  32. package/dist/gc/gcDefinitions.js.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/legacy.d.ts +1 -1
  37. package/dist/messageTypes.d.ts +1 -0
  38. package/dist/messageTypes.d.ts.map +1 -1
  39. package/dist/messageTypes.js +1 -0
  40. package/dist/messageTypes.js.map +1 -1
  41. package/dist/opLifecycle/batchManager.d.ts +4 -0
  42. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  43. package/dist/opLifecycle/batchManager.js.map +1 -1
  44. package/dist/opLifecycle/outbox.d.ts +8 -4
  45. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  46. package/dist/opLifecycle/outbox.js +18 -16
  47. package/dist/opLifecycle/outbox.js.map +1 -1
  48. package/dist/opLifecycle/remoteMessageProcessor.d.ts +17 -1
  49. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  50. package/dist/opLifecycle/remoteMessageProcessor.js +43 -5
  51. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  52. package/dist/packageVersion.d.ts +1 -1
  53. package/dist/packageVersion.js +1 -1
  54. package/dist/packageVersion.js.map +1 -1
  55. package/dist/pendingStateManager.d.ts +11 -7
  56. package/dist/pendingStateManager.d.ts.map +1 -1
  57. package/dist/pendingStateManager.js +34 -15
  58. package/dist/pendingStateManager.js.map +1 -1
  59. package/dist/summary/documentSchema.d.ts +8 -0
  60. package/dist/summary/documentSchema.d.ts.map +1 -1
  61. package/dist/summary/documentSchema.js +2 -0
  62. package/dist/summary/documentSchema.js.map +1 -1
  63. package/dist/summary/index.d.ts +1 -1
  64. package/dist/summary/index.d.ts.map +1 -1
  65. package/dist/summary/index.js +1 -2
  66. package/dist/summary/index.js.map +1 -1
  67. package/dist/summary/orderedClientElection.d.ts +1 -0
  68. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  69. package/dist/summary/orderedClientElection.js.map +1 -1
  70. package/dist/summary/runWhileConnectedCoordinator.d.ts +1 -0
  71. package/dist/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
  72. package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
  73. package/dist/summary/summarizer.d.ts +1 -0
  74. package/dist/summary/summarizer.d.ts.map +1 -1
  75. package/dist/summary/summarizer.js +1 -0
  76. package/dist/summary/summarizer.js.map +1 -1
  77. package/dist/summary/summarizerTypes.d.ts +29 -0
  78. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  79. package/dist/summary/summarizerTypes.js.map +1 -1
  80. package/dist/summary/summaryCollection.d.ts +10 -0
  81. package/dist/summary/summaryCollection.d.ts.map +1 -1
  82. package/dist/summary/summaryCollection.js +1 -0
  83. package/dist/summary/summaryCollection.js.map +1 -1
  84. package/dist/summary/summaryFormat.d.ts +8 -1
  85. package/dist/summary/summaryFormat.d.ts.map +1 -1
  86. package/dist/summary/summaryFormat.js +3 -3
  87. package/dist/summary/summaryFormat.js.map +1 -1
  88. package/{dist → lib/blobManager}/blobManager.d.ts +9 -28
  89. package/lib/blobManager/blobManager.d.ts.map +1 -0
  90. package/lib/{blobManager.js → blobManager/blobManager.js} +21 -83
  91. package/lib/blobManager/blobManager.js.map +1 -0
  92. package/lib/blobManager/blobManagerSnapSum.d.ts +30 -0
  93. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -0
  94. package/lib/blobManager/blobManagerSnapSum.js +75 -0
  95. package/lib/blobManager/blobManagerSnapSum.js.map +1 -0
  96. package/lib/blobManager/index.d.ts +7 -0
  97. package/lib/blobManager/index.d.ts.map +1 -0
  98. package/lib/blobManager/index.js +7 -0
  99. package/lib/blobManager/index.js.map +1 -0
  100. package/lib/channelCollection.d.ts +1 -0
  101. package/lib/channelCollection.d.ts.map +1 -1
  102. package/lib/channelCollection.js +23 -13
  103. package/lib/channelCollection.js.map +1 -1
  104. package/lib/containerRuntime.d.ts +20 -7
  105. package/lib/containerRuntime.d.ts.map +1 -1
  106. package/lib/containerRuntime.js +31 -26
  107. package/lib/containerRuntime.js.map +1 -1
  108. package/lib/dataStoreContext.d.ts +1 -0
  109. package/lib/dataStoreContext.d.ts.map +1 -1
  110. package/lib/dataStoreContext.js +7 -2
  111. package/lib/dataStoreContext.js.map +1 -1
  112. package/lib/gc/garbageCollection.js +2 -2
  113. package/lib/gc/garbageCollection.js.map +1 -1
  114. package/lib/gc/gcDefinitions.d.ts +9 -0
  115. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  116. package/lib/gc/gcDefinitions.js +1 -0
  117. package/lib/gc/gcDefinitions.js.map +1 -1
  118. package/lib/index.d.ts +1 -1
  119. package/lib/index.d.ts.map +1 -1
  120. package/lib/index.js.map +1 -1
  121. package/lib/legacy.d.ts +1 -1
  122. package/lib/messageTypes.d.ts +1 -0
  123. package/lib/messageTypes.d.ts.map +1 -1
  124. package/lib/messageTypes.js +1 -0
  125. package/lib/messageTypes.js.map +1 -1
  126. package/lib/opLifecycle/batchManager.d.ts +4 -0
  127. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  128. package/lib/opLifecycle/batchManager.js.map +1 -1
  129. package/lib/opLifecycle/outbox.d.ts +8 -4
  130. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  131. package/lib/opLifecycle/outbox.js +18 -16
  132. package/lib/opLifecycle/outbox.js.map +1 -1
  133. package/lib/opLifecycle/remoteMessageProcessor.d.ts +17 -1
  134. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  135. package/lib/opLifecycle/remoteMessageProcessor.js +41 -3
  136. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  137. package/lib/packageVersion.d.ts +1 -1
  138. package/lib/packageVersion.js +1 -1
  139. package/lib/packageVersion.js.map +1 -1
  140. package/lib/pendingStateManager.d.ts +11 -7
  141. package/lib/pendingStateManager.d.ts.map +1 -1
  142. package/lib/pendingStateManager.js +35 -16
  143. package/lib/pendingStateManager.js.map +1 -1
  144. package/lib/summary/documentSchema.d.ts +8 -0
  145. package/lib/summary/documentSchema.d.ts.map +1 -1
  146. package/lib/summary/documentSchema.js +2 -0
  147. package/lib/summary/documentSchema.js.map +1 -1
  148. package/lib/summary/index.d.ts +1 -1
  149. package/lib/summary/index.d.ts.map +1 -1
  150. package/lib/summary/index.js +1 -1
  151. package/lib/summary/index.js.map +1 -1
  152. package/lib/summary/orderedClientElection.d.ts +1 -0
  153. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  154. package/lib/summary/orderedClientElection.js.map +1 -1
  155. package/lib/summary/runWhileConnectedCoordinator.d.ts +1 -0
  156. package/lib/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
  157. package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
  158. package/lib/summary/summarizer.d.ts +1 -0
  159. package/lib/summary/summarizer.d.ts.map +1 -1
  160. package/lib/summary/summarizer.js +1 -0
  161. package/lib/summary/summarizer.js.map +1 -1
  162. package/lib/summary/summarizerTypes.d.ts +29 -0
  163. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  164. package/lib/summary/summarizerTypes.js.map +1 -1
  165. package/lib/summary/summaryCollection.d.ts +10 -0
  166. package/lib/summary/summaryCollection.d.ts.map +1 -1
  167. package/lib/summary/summaryCollection.js +1 -0
  168. package/lib/summary/summaryCollection.js.map +1 -1
  169. package/lib/summary/summaryFormat.d.ts +8 -1
  170. package/lib/summary/summaryFormat.d.ts.map +1 -1
  171. package/lib/summary/summaryFormat.js +1 -1
  172. package/lib/summary/summaryFormat.js.map +1 -1
  173. package/package.json +23 -23
  174. package/src/{blobManager.ts → blobManager/blobManager.ts} +38 -122
  175. package/src/blobManager/blobManagerSnapSum.ts +133 -0
  176. package/src/blobManager/index.ts +19 -0
  177. package/src/channelCollection.ts +23 -13
  178. package/src/containerRuntime.ts +57 -39
  179. package/src/dataStoreContext.ts +8 -2
  180. package/src/gc/garbageCollection.ts +2 -2
  181. package/src/gc/gcDefinitions.ts +9 -0
  182. package/src/index.ts +1 -1
  183. package/src/messageTypes.ts +1 -0
  184. package/src/opLifecycle/batchManager.ts +4 -0
  185. package/src/opLifecycle/outbox.ts +19 -21
  186. package/src/opLifecycle/remoteMessageProcessor.ts +63 -6
  187. package/src/packageVersion.ts +1 -1
  188. package/src/pendingStateManager.ts +43 -20
  189. package/src/summary/documentSchema.ts +8 -0
  190. package/src/summary/index.ts +0 -1
  191. package/src/summary/orderedClientElection.ts +1 -0
  192. package/src/summary/runWhileConnectedCoordinator.ts +1 -0
  193. package/src/summary/summarizer.ts +1 -0
  194. package/src/summary/summarizerTypes.ts +29 -0
  195. package/src/summary/summaryCollection.ts +10 -0
  196. package/src/summary/summaryFormat.ts +9 -1
  197. package/dist/blobManager.d.ts.map +0 -1
  198. package/dist/blobManager.js.map +0 -1
  199. package/lib/blobManager.d.ts.map +0 -1
  200. package/lib/blobManager.js.map +0 -1
  201. /package/api-report/{container-runtime.alpha.api.md → container-runtime.legacy.alpha.api.md} +0 -0
@@ -21,7 +21,6 @@ import { assert, Deferred } from "@fluidframework/core-utils/internal";
21
21
  import {
22
22
  IDocumentStorageService,
23
23
  ICreateBlobResponse,
24
- ISnapshotTree,
25
24
  ISequencedDocumentMessage,
26
25
  } from "@fluidframework/driver-definitions/internal";
27
26
  import { canRetryOnError, runWithRetry } from "@fluidframework/driver-utils/internal";
@@ -32,7 +31,6 @@ import {
32
31
  } from "@fluidframework/runtime-definitions/internal";
33
32
  import {
34
33
  FluidHandleBase,
35
- SummaryTreeBuilder,
36
34
  createResponseError,
37
35
  generateHandleContextPath,
38
36
  responseToException,
@@ -47,7 +45,14 @@ import {
47
45
  } from "@fluidframework/telemetry-utils/internal";
48
46
  import { v4 as uuid } from "uuid";
49
47
 
50
- import { IBlobMetadata } from "./metadata.js";
48
+ import { IBlobMetadata } from "../metadata.js";
49
+
50
+ import {
51
+ getStorageIds,
52
+ summarizeBlobManagerState,
53
+ toRedirectTable,
54
+ type IBlobManagerLoadInfo,
55
+ } from "./blobManagerSnapSum.js";
51
56
 
52
57
  /**
53
58
  * This class represents blob (long string)
@@ -87,15 +92,6 @@ export class BlobHandle extends FluidHandleBase<ArrayBufferLike> {
87
92
  }
88
93
  }
89
94
 
90
- /**
91
- * Information from a snapshot needed to load BlobManager
92
- * @alpha
93
- */
94
- export interface IBlobManagerLoadInfo {
95
- ids?: string[];
96
- redirectTable?: [string, string][];
97
- }
98
-
99
95
  // Restrict the IContainerRuntime interface to the subset required by BlobManager. This helps to make
100
96
  // the contract explicit and reduces the amount of mocking required for tests.
101
97
  export type IBlobManagerRuntime = Pick<
@@ -145,9 +141,9 @@ const stashedPendingBlobOverrides: Pick<
145
141
  uploadTime: undefined,
146
142
  } as const;
147
143
 
144
+ export const blobManagerBasePath = "_blobs" as const;
145
+
148
146
  export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
149
- public static readonly basePath = "_blobs";
150
- private static readonly redirectTableBlobName = ".redirectTable";
151
147
  private readonly mc: MonitoringContext;
152
148
 
153
149
  /**
@@ -177,10 +173,10 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
177
173
  private readonly routeContext: IFluidHandleContext;
178
174
  private readonly getStorage: () => IDocumentStorageService;
179
175
  // Called when a blob node is requested. blobPath is the path of the blob's node in GC's graph.
180
- // blobPath's format - `/<BlobManager.basePath>/<blobId>`.
176
+ // blobPath's format - `/<basePath>/<blobId>`.
181
177
  private readonly blobRequested: (blobPath: string) => void;
182
178
  // Called to check if a blob has been deleted by GC.
183
- // blobPath's format - `/<BlobManager.basePath>/<blobId>`.
179
+ // blobPath's format - `/<basePath>/<blobId>`.
184
180
  private readonly isBlobDeleted: (blobPath: string) => boolean;
185
181
  private readonly runtime: IBlobManagerRuntime;
186
182
  private readonly closeContainer: (error?: ICriticalContainerError) => void;
@@ -201,10 +197,10 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
201
197
  */
202
198
  sendBlobAttachOp: (localId: string, storageId?: string) => void;
203
199
  // Called when a blob node is requested. blobPath is the path of the blob's node in GC's graph.
204
- // blobPath's format - `/<BlobManager.basePath>/<blobId>`.
200
+ // blobPath's format - `/<basePath>/<blobId>`.
205
201
  readonly blobRequested: (blobPath: string) => void;
206
202
  // Called to check if a blob has been deleted by GC.
207
- // blobPath's format - `/<BlobManager.basePath>/<blobId>`.
203
+ // blobPath's format - `/<basePath>/<blobId>`.
208
204
  readonly isBlobDeleted: (blobPath: string) => boolean;
209
205
  readonly runtime: IBlobManagerRuntime;
210
206
  stashedBlobs: IPendingBlobs | undefined;
@@ -234,7 +230,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
234
230
  namespace: "BlobManager",
235
231
  });
236
232
 
237
- this.redirectTable = this.load(snapshot);
233
+ this.redirectTable = toRedirectTable(snapshot, this.mc.logger, this.runtime.attachState);
238
234
 
239
235
  // Begin uploading stashed blobs from previous container instance
240
236
  Object.entries(stashedBlobs ?? {}).forEach(([localId, entry]) => {
@@ -346,27 +342,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
346
342
  );
347
343
  }
348
344
 
349
- /**
350
- * Set of actual storage IDs (i.e., IDs that can be requested from storage). This will be empty if the container is
351
- * detached or there are no (non-pending) attachment blobs in the document
352
- */
353
- private get storageIds(): Set<string> {
354
- const ids = new Set<string | undefined>(this.redirectTable.values());
355
-
356
- // If we are detached, we will not have storage IDs, only undefined
357
- const undefinedValueInTable = ids.delete(undefined);
358
-
359
- // For a detached container, entries are inserted into the redirect table with an undefined storage ID.
360
- // For an attached container, entries are inserted w/storage ID after the BlobAttach op round-trips.
361
- assert(
362
- !undefinedValueInTable ||
363
- (this.runtime.attachState === AttachState.Detached && ids.size === 0),
364
- 0x382 /* 'redirectTable' must contain only undefined while detached / defined values while attached */,
365
- );
366
-
367
- return ids as Set<string>;
368
- }
369
-
370
345
  public async getBlob(blobId: string): Promise<ArrayBufferLike> {
371
346
  // Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
372
347
  // an error, failing the call.
@@ -418,7 +393,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
418
393
  }
419
394
  : undefined;
420
395
  return new BlobHandle(
421
- `${BlobManager.basePath}/${id}`,
396
+ getGCNodePathFromBlobId(id),
422
397
  this.routeContext,
423
398
  async () => this.getBlob(id),
424
399
  callback,
@@ -568,7 +543,8 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
568
543
  if (!entry.opsent) {
569
544
  this.sendBlobAttachOp(localId, response.id);
570
545
  }
571
- if (this.storageIds.has(response.id)) {
546
+ const storageIds = getStorageIds(this.redirectTable, this.runtime.attachState);
547
+ if (storageIds.has(response.id)) {
572
548
  // The blob is de-duped. Set up a local ID to storage ID mapping and return the blob. Since this is
573
549
  // an existing blob, we don't have to wait for the op to be ack'd since this step has already
574
550
  // happened before and so, the server won't delete it.
@@ -661,74 +637,8 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
661
637
  }
662
638
  }
663
639
 
664
- /**
665
- * Reads blobs needed to load BlobManager from storage.
666
- * @param blobsTree - Tree containing IDs of previously attached blobs. We
667
- * look for the IDs in the blob entries of the tree since the both the r11s
668
- * and SPO drivers replace the attachment types returned in snapshot() with blobs.
669
- */
670
- public static async load(
671
- blobsTree: ISnapshotTree | undefined,
672
- tryFetchBlob: (id: string) => Promise<[string, string][]>,
673
- ): Promise<IBlobManagerLoadInfo> {
674
- if (!blobsTree) {
675
- return {};
676
- }
677
- let redirectTable;
678
- const tableId = blobsTree.blobs[this.redirectTableBlobName];
679
- if (tableId) {
680
- redirectTable = await tryFetchBlob(tableId);
681
- }
682
- const ids = Object.entries(blobsTree.blobs)
683
- .filter(([k, _]) => k !== this.redirectTableBlobName)
684
- .map(([_, v]) => v);
685
- return { ids, redirectTable };
686
- }
687
-
688
- /**
689
- * Load a set of previously attached blob IDs and redirect table from a previous snapshot.
690
- */
691
- private load(snapshot: IBlobManagerLoadInfo): Map<string, string | undefined> {
692
- this.mc.logger.sendTelemetryEvent({
693
- eventName: "AttachmentBlobsLoaded",
694
- count: snapshot.ids?.length ?? 0,
695
- redirectTable: snapshot.redirectTable?.length,
696
- });
697
- const table = new Map<string, string | undefined>(snapshot.redirectTable);
698
- if (snapshot.ids) {
699
- const detached = this.runtime.attachState === AttachState.Detached;
700
- // If we are detached, we don't have storage IDs yet, so set to undefined
701
- // Otherwise, set identity (id -> id) entries
702
- snapshot.ids.forEach((entry) => table.set(entry, detached ? undefined : entry));
703
- }
704
- return table;
705
- }
706
-
707
640
  public summarize(telemetryContext?: ITelemetryContext): ISummaryTreeWithStats {
708
- // if storageIds is empty, it means we are detached and have only local IDs, or that there are no blobs attached
709
- const blobIds =
710
- this.storageIds.size > 0
711
- ? Array.from(this.storageIds)
712
- : Array.from(this.redirectTable.keys());
713
- const builder = new SummaryTreeBuilder();
714
- blobIds.forEach((blobId) => {
715
- builder.addAttachment(blobId);
716
- });
717
-
718
- // Any non-identity entries in the table need to be saved in the summary
719
- if (this.redirectTable.size > blobIds.length) {
720
- builder.addBlob(
721
- BlobManager.redirectTableBlobName,
722
- // filter out identity entries
723
- JSON.stringify(
724
- Array.from(this.redirectTable.entries()).filter(
725
- ([localId, storageId]) => localId !== storageId,
726
- ),
727
- ),
728
- );
729
- }
730
-
731
- return builder.getSummaryTree();
641
+ return summarizeBlobManagerState(this.redirectTable, this.runtime.attachState);
732
642
  }
733
643
 
734
644
  /**
@@ -765,7 +675,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
765
675
 
766
676
  /**
767
677
  * Delete blobs with the given routes from the redirect table.
768
- * The routes are GC nodes paths of format -`/<BlobManager.basePath>/<blobId>`. The blob ids are all local ids.
678
+ * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<blobId>`. The blob ids are all local ids.
769
679
  * Deleting the blobs involves 2 steps:
770
680
  * 1. The redirect table entry for the local ids are deleted.
771
681
  * 2. If the storage ids corresponding to the deleted local ids are not in-use anymore, the redirect table entries
@@ -838,7 +748,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
838
748
  this.mc.logger.sendErrorEvent(
839
749
  {
840
750
  eventName: "GC_Deleted_Blob_Requested",
841
- pkg: BlobManager.basePath,
751
+ pkg: blobManagerBasePath,
842
752
  },
843
753
  error,
844
754
  );
@@ -935,22 +845,28 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
935
845
  }
936
846
 
937
847
  /**
938
- * For a blobId, returns its path in GC's graph. The node path is of the format `/<BlobManager.basePath>/<blobId>`.
848
+ * For a blobId, returns its path in GC's graph. The node path is of the format `/<blobManagerBasePath>/<blobId>`.
939
849
  * This path must match the path of the blob handle returned by the createBlob API because blobs are marked
940
850
  * referenced by storing these handles in a referenced DDS.
941
851
  */
942
- function getGCNodePathFromBlobId(blobId: string) {
943
- return `/${BlobManager.basePath}/${blobId}`;
944
- }
852
+ const getGCNodePathFromBlobId = (blobId: string) => `/${blobManagerBasePath}/${blobId}`;
945
853
 
946
854
  /**
947
- * For a given GC node path, return the blobId. The node path is of the format `/<BlobManager.basePath>/<blobId>`.
855
+ * For a given GC node path, return the blobId. The node path is of the format `/<basePath>/<blobId>`.
948
856
  */
949
- function getBlobIdFromGCNodePath(nodePath: string) {
857
+ const getBlobIdFromGCNodePath = (nodePath: string) => {
950
858
  const pathParts = nodePath.split("/");
951
- assert(
952
- pathParts.length === 3 && pathParts[1] === BlobManager.basePath,
953
- 0x5bd /* Invalid blob node path */,
954
- );
859
+ assert(areBlobPathParts(pathParts), 0x5bd /* Invalid blob node path */);
955
860
  return pathParts[2];
956
- }
861
+ };
862
+
863
+ /**
864
+ * Returns whether a given path is for attachment blobs that are in the format - "/blobManagerBasePath/...".
865
+ */
866
+ export const isBlobPath = (path: string): path is `/${typeof blobManagerBasePath}/${string}` =>
867
+ areBlobPathParts(path.split("/"));
868
+
869
+ export const areBlobPathParts = (
870
+ pathParts: string[],
871
+ ): pathParts is ["", typeof blobManagerBasePath, string] =>
872
+ pathParts.length === 3 && pathParts[1] === blobManagerBasePath;
@@ -0,0 +1,133 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ AttachState,
8
+ type IContainerContext,
9
+ } from "@fluidframework/container-definitions/internal";
10
+ import { assert } from "@fluidframework/core-utils/internal";
11
+ import { readAndParse } from "@fluidframework/driver-utils/internal";
12
+ import type { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions/internal";
13
+ import { SummaryTreeBuilder } from "@fluidframework/runtime-utils/internal";
14
+ import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
15
+
16
+ /**
17
+ * Information from a snapshot needed to load BlobManager
18
+ * @legacy
19
+ * @alpha
20
+ */
21
+ export interface IBlobManagerLoadInfo {
22
+ ids?: string[];
23
+ redirectTable?: [string, string][];
24
+ }
25
+
26
+ export const redirectTableBlobName = ".redirectTable";
27
+
28
+ /**
29
+ * @internal
30
+ */
31
+ export const blobsTreeName = ".blobs";
32
+
33
+ /**
34
+ * Reads blobs needed to load BlobManager from storage.
35
+ *
36
+ */
37
+ export const loadBlobManagerLoadInfo = async (
38
+ context: Pick<IContainerContext, "baseSnapshot" | "storage" | "attachState">,
39
+ ): Promise<IBlobManagerLoadInfo> => loadV1(context);
40
+
41
+ const loadV1 = async (
42
+ context: Pick<IContainerContext, "baseSnapshot" | "storage" | "attachState">,
43
+ ): Promise<IBlobManagerLoadInfo> => {
44
+ const blobsTree = context.baseSnapshot?.trees[blobsTreeName];
45
+
46
+ if (!blobsTree) {
47
+ return {};
48
+ }
49
+ let redirectTableEntries: [string, string][] = [];
50
+ const tableId = blobsTree.blobs[redirectTableBlobName];
51
+ if (tableId) {
52
+ redirectTableEntries = await readAndParse(context.storage, tableId);
53
+ }
54
+ const ids = Object.entries(blobsTree.blobs)
55
+ .filter(([k, _]) => k !== redirectTableBlobName)
56
+ .map(([_, v]) => v);
57
+
58
+ return { ids, redirectTable: redirectTableEntries };
59
+ };
60
+
61
+ export const toRedirectTable = (
62
+ snapshot: IBlobManagerLoadInfo,
63
+ logger: ITelemetryLoggerExt,
64
+ attachState: AttachState,
65
+ ): Map<string, string | undefined> => {
66
+ logger.sendTelemetryEvent({
67
+ eventName: "AttachmentBlobsLoaded",
68
+ count: snapshot.ids?.length ?? 0,
69
+ redirectTable: snapshot.redirectTable?.length,
70
+ });
71
+ const redirectTable = new Map<string, string | undefined>(snapshot.redirectTable);
72
+ const detached = attachState !== AttachState.Attached;
73
+ if (snapshot.ids) {
74
+ // If we are detached, we don't have storage IDs yet, so set to undefined
75
+ // Otherwise, set identity (id -> id) entries.
76
+ snapshot.ids.forEach((entry) => redirectTable.set(entry, detached ? undefined : entry));
77
+ }
78
+ return redirectTable;
79
+ };
80
+
81
+ export const summarizeBlobManagerState = (
82
+ redirectTable: Map<string, string | undefined>,
83
+ attachState: AttachState,
84
+ ): ISummaryTreeWithStats => summarizeV1(redirectTable, attachState);
85
+
86
+ const summarizeV1 = (
87
+ redirectTable: Map<string, string | undefined>,
88
+ attachState: AttachState,
89
+ ): ISummaryTreeWithStats => {
90
+ const storageIds = getStorageIds(redirectTable, attachState);
91
+
92
+ // if storageIds is empty, it means we are detached and have only local IDs, or that there are no blobs attached
93
+ const blobIds =
94
+ storageIds.size > 0 ? Array.from(storageIds) : Array.from(redirectTable.keys());
95
+ const builder = new SummaryTreeBuilder();
96
+ blobIds.forEach((blobId) => {
97
+ builder.addAttachment(blobId);
98
+ });
99
+
100
+ // Any non-identity entries in the table need to be saved in the summary
101
+ if (redirectTable.size > blobIds.length) {
102
+ builder.addBlob(
103
+ redirectTableBlobName,
104
+ // filter out identity entries
105
+ JSON.stringify(
106
+ Array.from(redirectTable.entries()).filter(
107
+ ([localId, storageId]) => localId !== storageId,
108
+ ),
109
+ ),
110
+ );
111
+ }
112
+
113
+ return builder.getSummaryTree();
114
+ };
115
+
116
+ export const getStorageIds = (
117
+ redirectTable: Map<string, string | undefined>,
118
+ attachState: AttachState,
119
+ ) => {
120
+ const ids = new Set<string | undefined>(redirectTable.values());
121
+
122
+ // If we are detached, we will not have storage IDs, only undefined
123
+ const undefinedValueInTable = ids.delete(undefined);
124
+
125
+ // For a detached container, entries are inserted into the redirect table with an undefined storage ID.
126
+ // For an attached container, entries are inserted w/storage ID after the BlobAttach op round-trips.
127
+ assert(
128
+ !undefinedValueInTable || (attachState === AttachState.Detached && ids.size === 0),
129
+ 0x382 /* 'redirectTable' must contain only undefined while detached / defined values while attached */,
130
+ );
131
+
132
+ return ids as Set<string>;
133
+ };
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export {
7
+ BlobManager,
8
+ IPendingBlobs,
9
+ IBlobManagerRuntime,
10
+ IBlobManagerEvents,
11
+ blobManagerBasePath,
12
+ isBlobPath,
13
+ } from "./blobManager.js";
14
+ export {
15
+ loadBlobManagerLoadInfo,
16
+ IBlobManagerLoadInfo,
17
+ blobsTreeName,
18
+ redirectTableBlobName,
19
+ } from "./blobManagerSnapSum.js";
@@ -70,6 +70,7 @@ import {
70
70
  extractSafePropertiesFromMessage,
71
71
  tagCodeArtifacts,
72
72
  } from "@fluidframework/telemetry-utils/internal";
73
+ import { v4 as uuid } from "uuid";
73
74
 
74
75
  import {
75
76
  DeletedResponseHeaderKey,
@@ -113,6 +114,7 @@ export enum RuntimeHeaders {
113
114
  }
114
115
 
115
116
  /** True if a tombstoned object should be returned without erroring
117
+ * @legacy
116
118
  * @alpha
117
119
  */
118
120
  export const AllowTombstoneRequestHeaderKey = "allowTombstone"; // Belongs in the enum above, but avoiding the breaking change
@@ -610,20 +612,28 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
610
612
  * Please note that above mentioned functions have the implementation they have (allowing #2) due to #1.
611
613
  */
612
614
  protected createDataStoreId(): string {
613
- // We use three non-overlapping namespaces:
614
- // - detached state: even numbers
615
- // - attached state: odd numbers
616
- // - uuids
617
- // In first two cases we will encode result as strings in more compact form.
618
- if (this.parentContext.attachState === AttachState.Detached) {
619
- // container is detached, only one client observes content, no way to hit collisions with other clients.
620
- return encodeCompactIdToString(2 * this.contexts.size);
621
- }
622
- const id = this.parentContext.containerRuntime.generateDocumentUniqueId();
623
- if (typeof id === "number") {
624
- return encodeCompactIdToString(2 * id + 1);
615
+ /**
616
+ * There is currently a bug where certain data store ids such as "[" are getting converted to ASCII characters
617
+ * in the snapshot.
618
+ * So, return short ids only if explicitly enabled via feature flags. Else, return uuid();
619
+ */
620
+ if (this.mc.config.getBoolean("Fluid.Runtime.UseShortIds") === true) {
621
+ // We use three non-overlapping namespaces:
622
+ // - detached state: even numbers
623
+ // - attached state: odd numbers
624
+ // - uuids
625
+ // In first two cases we will encode result as strings in more compact form.
626
+ if (this.parentContext.attachState === AttachState.Detached) {
627
+ // container is detached, only one client observes content, no way to hit collisions with other clients.
628
+ return encodeCompactIdToString(2 * this.contexts.size);
629
+ }
630
+ const id = this.parentContext.containerRuntime.generateDocumentUniqueId();
631
+ if (typeof id === "number") {
632
+ return encodeCompactIdToString(2 * id + 1);
633
+ }
634
+ return id;
625
635
  }
626
- return id;
636
+ return uuid();
627
637
  }
628
638
 
629
639
  public createDetachedDataStore(