@fluidframework/container-runtime 2.60.0 → 2.61.0-355516

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 (70) hide show
  1. package/.mocharc.cjs +1 -2
  2. package/api-report/container-runtime.legacy.beta.api.md +2 -1
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +33 -16
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +126 -106
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/blobManager/blobManagerSnapSum.d.ts +4 -4
  9. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  10. package/dist/blobManager/blobManagerSnapSum.js +30 -33
  11. package/dist/blobManager/blobManagerSnapSum.js.map +1 -1
  12. package/dist/channelCollection.d.ts +6 -2
  13. package/dist/channelCollection.d.ts.map +1 -1
  14. package/dist/channelCollection.js +1 -0
  15. package/dist/channelCollection.js.map +1 -1
  16. package/dist/containerCompatibility.d.ts +18 -0
  17. package/dist/containerCompatibility.d.ts.map +1 -1
  18. package/dist/containerCompatibility.js +23 -1
  19. package/dist/containerCompatibility.js.map +1 -1
  20. package/dist/containerRuntime.d.ts +15 -3
  21. package/dist/containerRuntime.d.ts.map +1 -1
  22. package/dist/containerRuntime.js +75 -52
  23. package/dist/containerRuntime.js.map +1 -1
  24. package/dist/dataStoreContext.d.ts +5 -1
  25. package/dist/dataStoreContext.d.ts.map +1 -1
  26. package/dist/dataStoreContext.js +1 -0
  27. package/dist/dataStoreContext.js.map +1 -1
  28. package/dist/legacy.d.ts +2 -1
  29. package/dist/packageVersion.d.ts +1 -1
  30. package/dist/packageVersion.d.ts.map +1 -1
  31. package/dist/packageVersion.js +1 -1
  32. package/dist/packageVersion.js.map +1 -1
  33. package/lib/blobManager/blobManager.d.ts +33 -16
  34. package/lib/blobManager/blobManager.d.ts.map +1 -1
  35. package/lib/blobManager/blobManager.js +126 -106
  36. package/lib/blobManager/blobManager.js.map +1 -1
  37. package/lib/blobManager/blobManagerSnapSum.d.ts +4 -4
  38. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  39. package/lib/blobManager/blobManagerSnapSum.js +26 -29
  40. package/lib/blobManager/blobManagerSnapSum.js.map +1 -1
  41. package/lib/channelCollection.d.ts +6 -2
  42. package/lib/channelCollection.d.ts.map +1 -1
  43. package/lib/channelCollection.js +1 -0
  44. package/lib/channelCollection.js.map +1 -1
  45. package/lib/containerCompatibility.d.ts +18 -0
  46. package/lib/containerCompatibility.d.ts.map +1 -1
  47. package/lib/containerCompatibility.js +22 -0
  48. package/lib/containerCompatibility.js.map +1 -1
  49. package/lib/containerRuntime.d.ts +15 -3
  50. package/lib/containerRuntime.d.ts.map +1 -1
  51. package/lib/containerRuntime.js +26 -3
  52. package/lib/containerRuntime.js.map +1 -1
  53. package/lib/dataStoreContext.d.ts +5 -1
  54. package/lib/dataStoreContext.d.ts.map +1 -1
  55. package/lib/dataStoreContext.js +1 -0
  56. package/lib/dataStoreContext.js.map +1 -1
  57. package/lib/legacy.d.ts +2 -1
  58. package/lib/packageVersion.d.ts +1 -1
  59. package/lib/packageVersion.d.ts.map +1 -1
  60. package/lib/packageVersion.js +1 -1
  61. package/lib/packageVersion.js.map +1 -1
  62. package/lib/tsdoc-metadata.json +1 -1
  63. package/package.json +27 -27
  64. package/src/blobManager/blobManager.ts +138 -123
  65. package/src/blobManager/blobManagerSnapSum.ts +31 -53
  66. package/src/channelCollection.ts +9 -1
  67. package/src/containerCompatibility.ts +56 -0
  68. package/src/containerRuntime.ts +35 -4
  69. package/src/dataStoreContext.ts +7 -0
  70. package/src/packageVersion.ts +1 -1
@@ -39,7 +39,7 @@ export declare class BlobHandle extends FluidHandleBase<ArrayBufferLike> impleme
39
39
  readonly notifyFailed: (error: unknown) => void;
40
40
  attachGraph(): void;
41
41
  }
42
- export type IBlobManagerRuntime = Pick<IContainerRuntime, "attachState" | "connected" | "baseLogger" | "clientDetails" | "disposed"> & IEventProvider<IContainerRuntimeEvents>;
42
+ export type IBlobManagerRuntime = Pick<IContainerRuntime, "attachState" | "baseLogger" | "disposed"> & IEventProvider<IContainerRuntimeEvents>;
43
43
  export interface IPendingBlobs {
44
44
  [localId: string]: {
45
45
  blob: string;
@@ -59,11 +59,10 @@ export declare class BlobManager {
59
59
  get events(): Listenable<IBlobManagerEvents>;
60
60
  private readonly internalEvents;
61
61
  /**
62
- * Map of local IDs to storage IDs. Contains identity entries (storageId storageId) for storage IDs. All requested IDs should
63
- * be a key in this map. Blobs created while the container is detached are stored in IDetachedBlobStorage which
64
- * gives local IDs; the storage IDs are filled in at attach time.
65
- * Note: It contains mappings from all clients, i.e., from remote clients as well. local ID comes from the client
66
- * that uploaded the blob but its mapping to storage ID is needed in all clients in order to retrieve the blob.
62
+ * Map of local IDs to storage IDs. Also includes identity mappings of storage ID to storage ID for all known
63
+ * storage IDs. All requested IDs must be a key in this map. Blobs created while the container is detached are
64
+ * stored in IDetachedBlobStorage which gives pseudo storage IDs; the real storage IDs are filled in at attach
65
+ * time via setRedirectTable().
67
66
  */
68
67
  private readonly redirectTable;
69
68
  /**
@@ -82,7 +81,7 @@ export declare class BlobManager {
82
81
  private readonly blobRequested;
83
82
  private readonly isBlobDeleted;
84
83
  private readonly runtime;
85
- private readonly localBlobIdGenerator;
84
+ private readonly localIdGenerator;
86
85
  private readonly createBlobPayloadPending;
87
86
  constructor(props: {
88
87
  readonly routeContext: IFluidHandleContext;
@@ -103,20 +102,31 @@ export declare class BlobManager {
103
102
  readonly isBlobDeleted: (blobPath: string) => boolean;
104
103
  readonly runtime: IBlobManagerRuntime;
105
104
  stashedBlobs: IPendingBlobs | undefined;
106
- readonly localBlobIdGenerator?: (() => string) | undefined;
105
+ readonly localIdGenerator?: (() => string) | undefined;
107
106
  readonly createBlobPayloadPending: boolean;
108
107
  });
109
108
  get allBlobsAttached(): boolean;
110
109
  get hasPendingBlobs(): boolean;
111
110
  private createAbortError;
112
- hasBlob(blobId: string): boolean;
111
+ hasBlob(localId: string): boolean;
112
+ /**
113
+ * Lookup the blob storage ID for a given local blob id.
114
+ * @param localId - The local blob id. Likely coming from a handle.
115
+ * @returns The storage ID if found and the blob is not pending, undefined otherwise.
116
+ * @remarks
117
+ * For blobs with pending payloads (localId exists but upload hasn't finished), this is expected to return undefined.
118
+ * Consumers should use the observability APIs on the handle (handle.payloadState, payloadShared event)
119
+ * to understand/wait for storage ID availability.
120
+ * Similarly, when the runtime is detached, this will return undefined as no blobs have been uploaded to storage.
121
+ */
122
+ lookupTemporaryBlobStorageId(localId: string): string | undefined;
113
123
  /**
114
124
  * Retrieve the blob with the given local blob id.
115
- * @param blobId - The local blob id. Likely coming from a handle.
125
+ * @param localId - The local blob id. Likely coming from a handle.
116
126
  * @param payloadPending - Whether we suspect the payload may be pending and not available yet.
117
127
  * @returns A promise which resolves to the blob contents
118
128
  */
119
- getBlob(blobId: string, payloadPending: boolean): Promise<ArrayBufferLike>;
129
+ getBlob(localId: string, payloadPending: boolean): Promise<ArrayBufferLike>;
120
130
  private getBlobHandle;
121
131
  private createBlobDetached;
122
132
  createBlob(blob: ArrayBufferLike, signal?: AbortSignal): Promise<IFluidHandleInternalPayloadPending<ArrayBufferLike>>;
@@ -163,16 +173,17 @@ export declare class BlobManager {
163
173
  * Delete blobs with the given routes from the redirect table.
164
174
  *
165
175
  * @remarks
166
- * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<blobId>`. The blob ids are all local ids.
176
+ * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<localId>`.
167
177
  * Deleting the blobs involves 2 steps:
168
178
  *
169
179
  * 1. The redirect table entry for the local ids are deleted.
170
180
  *
171
- * 2. If the storage ids corresponding to the deleted local ids are not in-use anymore, the redirect table entries
172
- * for the storage ids are deleted as well.
181
+ * 2. If the storage ids corresponding to the deleted local ids are not referenced by any further local ids, the
182
+ * identity mappings in the redirect table are deleted as well.
173
183
  *
174
184
  * Note that this does not delete the blobs from storage service immediately. Deleting the blobs from redirect table
175
- * will remove them the next summary. The service would them delete them some time in the future.
185
+ * will ensure we don't create an attachment blob for them at the next summary. The service would then delete them
186
+ * some time in the future.
176
187
  */
177
188
  private deleteBlobsFromRedirectTable;
178
189
  /**
@@ -180,7 +191,13 @@ export declare class BlobManager {
180
191
  * log an error and throw if necessary.
181
192
  */
182
193
  private verifyBlobNotDeleted;
183
- setRedirectTable(table: Map<string, string>): void;
194
+ /**
195
+ * Called in detached state just prior to attaching, this will update the redirect table by
196
+ * converting the pseudo storage IDs into real storage IDs using the provided detachedStorageTable.
197
+ * The provided table must have exactly the same set of pseudo storage IDs as are found in the redirect table.
198
+ * @param detachedStorageTable - A map of pseudo storage IDs to real storage IDs.
199
+ */
200
+ readonly patchRedirectTable: (detachedStorageTable: Map<string, string>) => void;
184
201
  /**
185
202
  * To be used in getPendingLocalState flow. Get a serializable record of the blobs that are
186
203
  * pending upload and/or their BlobAttach op, which can be given to a new BlobManager to
@@ -1 +1 @@
1
- {"version":3,"file":"blobManager.d.ts","sourceRoot":"","sources":["../../src/blobManager/blobManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"blobManager.d.ts","sourceRoot":"","sources":["../../src/blobManager/blobManager.ts"],"names":[],"mappings":"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"}
@@ -74,20 +74,44 @@ export class BlobManager {
74
74
  * because we know that the server will not delete the blob corresponding to that storage ID.
75
75
  */
76
76
  this.opsInFlight = new Map();
77
- const { routeContext, blobManagerLoadInfo, storage, sendBlobAttachOp, blobRequested, isBlobDeleted, runtime, localBlobIdGenerator, createBlobPayloadPending, } = props;
77
+ /**
78
+ * Called in detached state just prior to attaching, this will update the redirect table by
79
+ * converting the pseudo storage IDs into real storage IDs using the provided detachedStorageTable.
80
+ * The provided table must have exactly the same set of pseudo storage IDs as are found in the redirect table.
81
+ * @param detachedStorageTable - A map of pseudo storage IDs to real storage IDs.
82
+ */
83
+ this.patchRedirectTable = (detachedStorageTable) => {
84
+ assert(this.runtime.attachState === AttachState.Detached, 0x252 /* "redirect table can only be set in detached container" */);
85
+ // The values of the redirect table are the pseudo storage IDs, which are the keys of the
86
+ // detachedStorageTable. We expect to have a many:1 mapping from local IDs to pseudo
87
+ // storage IDs (many in the case that the storage dedupes the blob).
88
+ assert(new Set(this.redirectTable.values()).size === detachedStorageTable.size, 0x391 /* Redirect table size must match BlobManager's local ID count */);
89
+ // Taking a snapshot of the redirect table entries before iterating, because
90
+ // we will be adding identity mappings to the the redirect table as we iterate
91
+ // and we don't want to include those in the iteration.
92
+ const redirectTableEntries = [...this.redirectTable.entries()];
93
+ for (const [localId, detachedStorageId] of redirectTableEntries) {
94
+ const newStorageId = detachedStorageTable.get(detachedStorageId);
95
+ assert(newStorageId !== undefined, "Couldn't find a matching storage ID");
96
+ this.setRedirection(localId, newStorageId);
97
+ // set identity (id -> id) entry
98
+ this.setRedirection(newStorageId, newStorageId);
99
+ }
100
+ };
101
+ const { routeContext, blobManagerLoadInfo, storage, sendBlobAttachOp, blobRequested, isBlobDeleted, runtime, localIdGenerator, createBlobPayloadPending, } = props;
78
102
  this.routeContext = routeContext;
79
103
  this.storage = storage;
80
104
  this.blobRequested = blobRequested;
81
105
  this.isBlobDeleted = isBlobDeleted;
82
106
  this.runtime = runtime;
83
- this.localBlobIdGenerator = localBlobIdGenerator ?? uuid;
107
+ this.localIdGenerator = localIdGenerator ?? uuid;
84
108
  this.createBlobPayloadPending = createBlobPayloadPending;
85
109
  this.mc = createChildMonitoringContext({
86
110
  logger: this.runtime.baseLogger,
87
111
  namespace: "BlobManager",
88
112
  });
89
- this.redirectTable = toRedirectTable(blobManagerLoadInfo, this.mc.logger, this.runtime.attachState);
90
- this.sendBlobAttachOp = (localId, blobId) => {
113
+ this.redirectTable = toRedirectTable(blobManagerLoadInfo, this.mc.logger);
114
+ this.sendBlobAttachOp = (localId, storageId) => {
91
115
  const pendingEntry = this.pendingBlobs.get(localId);
92
116
  assert(pendingEntry !== undefined, 0x725 /* Must have pending blob entry for upcoming op */);
93
117
  if (pendingEntry?.uploadTime && pendingEntry?.minTTLInSeconds) {
@@ -113,7 +137,7 @@ export class BlobManager {
113
137
  }
114
138
  }
115
139
  pendingEntry.opsent = true;
116
- sendBlobAttachOp(localId, blobId);
140
+ sendBlobAttachOp(localId, storageId);
117
141
  };
118
142
  }
119
143
  get allBlobsAttached() {
@@ -134,56 +158,63 @@ export class BlobManager {
134
158
  uploadTime: pending?.uploadTime,
135
159
  });
136
160
  }
137
- hasBlob(blobId) {
138
- return this.redirectTable.get(blobId) !== undefined;
161
+ hasBlob(localId) {
162
+ return this.redirectTable.get(localId) !== undefined;
163
+ }
164
+ /**
165
+ * Lookup the blob storage ID for a given local blob id.
166
+ * @param localId - The local blob id. Likely coming from a handle.
167
+ * @returns The storage ID if found and the blob is not pending, undefined otherwise.
168
+ * @remarks
169
+ * For blobs with pending payloads (localId exists but upload hasn't finished), this is expected to return undefined.
170
+ * Consumers should use the observability APIs on the handle (handle.payloadState, payloadShared event)
171
+ * to understand/wait for storage ID availability.
172
+ * Similarly, when the runtime is detached, this will return undefined as no blobs have been uploaded to storage.
173
+ */
174
+ lookupTemporaryBlobStorageId(localId) {
175
+ if (this.runtime.attachState === AttachState.Detached) {
176
+ return undefined;
177
+ }
178
+ // Get the storage ID from the redirect table
179
+ return this.redirectTable.get(localId);
139
180
  }
140
181
  /**
141
182
  * Retrieve the blob with the given local blob id.
142
- * @param blobId - The local blob id. Likely coming from a handle.
183
+ * @param localId - The local blob id. Likely coming from a handle.
143
184
  * @param payloadPending - Whether we suspect the payload may be pending and not available yet.
144
185
  * @returns A promise which resolves to the blob contents
145
186
  */
146
- async getBlob(blobId, payloadPending) {
187
+ async getBlob(localId, payloadPending) {
147
188
  // Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
148
189
  // an error, failing the call.
149
- this.verifyBlobNotDeleted(blobId);
190
+ this.verifyBlobNotDeleted(localId);
150
191
  // Let runtime know that the corresponding GC node was requested.
151
192
  // Note that this will throw if the blob is inactive or tombstoned and throwing on incorrect usage
152
193
  // is configured.
153
- this.blobRequested(getGCNodePathFromBlobId(blobId));
154
- const pending = this.pendingBlobs.get(blobId);
194
+ this.blobRequested(getGCNodePathFromLocalId(localId));
195
+ const pending = this.pendingBlobs.get(localId);
155
196
  if (pending) {
156
197
  return pending.blob;
157
198
  }
158
- let storageId;
159
- if (this.runtime.attachState === AttachState.Detached) {
160
- assert(this.redirectTable.has(blobId), 0x383 /* requesting unknown blobs */);
161
- // Blobs created while the container is detached are stored in IDetachedBlobStorage.
162
- // The 'IContainerStorageService.readBlob()' call below will retrieve these via localId.
163
- storageId = blobId;
164
- }
165
- else {
166
- const attachedStorageId = this.redirectTable.get(blobId);
167
- if (!payloadPending) {
168
- // Only blob handles explicitly marked with pending payload are permitted to exist without
169
- // yet knowing their storage id. Otherwise they must already be associated with a storage id.
170
- assert(attachedStorageId !== undefined, 0x11f /* "requesting unknown blobs" */);
171
- }
172
- // If we didn't find it in the redirectTable, assume the attach op is coming eventually and wait.
173
- // We do this even if the local client doesn't have the blob payloadPending flag enabled, in case a
174
- // remote client does have it enabled. This wait may be infinite if the uploading client failed
175
- // the upload and doesn't exist anymore.
176
- storageId =
177
- attachedStorageId ??
178
- (await new Promise((resolve) => {
179
- const onProcessBlobAttach = (localId, _storageId) => {
180
- if (localId === blobId) {
181
- this.internalEvents.off("processedBlobAttach", onProcessBlobAttach);
182
- resolve(_storageId);
183
- }
184
- };
185
- this.internalEvents.on("processedBlobAttach", onProcessBlobAttach);
186
- }));
199
+ let storageId = this.redirectTable.get(localId);
200
+ if (storageId === undefined) {
201
+ // Only blob handles explicitly marked with pending payload are permitted to exist without
202
+ // yet knowing their storage id. Otherwise they must already be associated with a storage id.
203
+ // Handles for detached blobs are not payload pending.
204
+ assert(payloadPending, 0x11f /* "requesting unknown blobs" */);
205
+ // If we didn't find it in the redirectTable and it's payloadPending, assume the attach op is coming
206
+ // eventually and wait. We do this even if the local client doesn't have the blob payloadPending flag
207
+ // enabled, in case a remote client does have it enabled. This wait may be infinite if the uploading
208
+ // client failed the upload and doesn't exist anymore.
209
+ storageId = await new Promise((resolve) => {
210
+ const onProcessBlobAttach = (_localId, _storageId) => {
211
+ if (_localId === localId) {
212
+ this.internalEvents.off("processedBlobAttach", onProcessBlobAttach);
213
+ resolve(_storageId);
214
+ }
215
+ };
216
+ this.internalEvents.on("processedBlobAttach", onProcessBlobAttach);
217
+ });
187
218
  }
188
219
  return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "AttachmentReadBlob", id: storageId }, async (event) => {
189
220
  return this.storage.readBlob(storageId).catch((error) => {
@@ -207,15 +238,17 @@ export class BlobManager {
207
238
  this.deletePendingBlobMaybe(localId);
208
239
  }
209
240
  : undefined;
210
- return new BlobHandle(getGCNodePathFromBlobId(localId), this.routeContext, async () => this.getBlob(localId, false), false, // payloadPending
241
+ return new BlobHandle(getGCNodePathFromLocalId(localId), this.routeContext, async () => this.getBlob(localId, false), false, // payloadPending
211
242
  callback);
212
243
  }
213
244
  async createBlobDetached(blob) {
245
+ const localId = this.localIdGenerator();
214
246
  // Blobs created while the container is detached are stored in IDetachedBlobStorage.
215
- // The 'IContainerStorageService.createBlob()' call below will respond with a localId.
216
- const response = await this.storage.createBlob(blob);
217
- this.setRedirection(response.id, undefined);
218
- return this.getBlobHandle(response.id);
247
+ // The 'IContainerStorageService.createBlob()' call below will respond with a pseudo storage ID.
248
+ // That pseudo storage ID will be replaced with the real storage ID at attach time.
249
+ const { id: detachedStorageId } = await this.storage.createBlob(blob);
250
+ this.setRedirection(localId, detachedStorageId);
251
+ return this.getBlobHandle(localId);
219
252
  }
220
253
  async createBlob(blob, signal) {
221
254
  if (this.runtime.attachState === AttachState.Detached) {
@@ -237,7 +270,7 @@ export class BlobManager {
237
270
  }
238
271
  // Create a local ID for the blob. After uploading it to storage and before returning it, a local ID to
239
272
  // storage ID mapping is created.
240
- const localId = this.localBlobIdGenerator();
273
+ const localId = this.localIdGenerator();
241
274
  const pendingEntry = {
242
275
  blob,
243
276
  handleP: new Deferred(),
@@ -259,8 +292,8 @@ export class BlobManager {
259
292
  });
260
293
  }
261
294
  createBlobWithPayloadPending(blob) {
262
- const localId = this.localBlobIdGenerator();
263
- const blobHandle = new BlobHandle(getGCNodePathFromBlobId(localId), this.routeContext, async () => blob, true, // payloadPending
295
+ const localId = this.localIdGenerator();
296
+ const blobHandle = new BlobHandle(getGCNodePathFromLocalId(localId), this.routeContext, async () => blob, true, // payloadPending
264
297
  () => {
265
298
  const pendingEntry = {
266
299
  blob,
@@ -375,7 +408,7 @@ export class BlobManager {
375
408
  if (!entry.opsent) {
376
409
  this.sendBlobAttachOp(localId, response.id);
377
410
  }
378
- const storageIds = getStorageIds(this.redirectTable, this.runtime.attachState);
411
+ const storageIds = getStorageIds(this.redirectTable);
379
412
  if (storageIds.has(response.id)) {
380
413
  // The blob is de-duped. Set up a local ID to storage ID mapping and return the blob. Since this is
381
414
  // an existing blob, we don't have to wait for the op to be ack'd since this step has already
@@ -408,7 +441,7 @@ export class BlobManager {
408
441
  */
409
442
  reSubmit(metadata) {
410
443
  assert(isBlobMetadata(metadata), 0xc01 /* Expected blob metadata for a BlobAttach op */);
411
- const { localId, blobId } = metadata;
444
+ const { localId, blobId: storageId } = metadata;
412
445
  // Any blob that we're actively trying to advance to attached state must have a
413
446
  // pendingBlobs entry. Decline to resubmit for anything else.
414
447
  // For example, we might be asked to resubmit stashed ops for blobs that never had
@@ -416,22 +449,22 @@ export class BlobManager {
416
449
  // try to attach them since they won't be accessible to the customer and would just
417
450
  // be considered garbage immediately.
418
451
  if (this.pendingBlobs.has(localId)) {
419
- this.sendBlobAttachOp(localId, blobId);
452
+ this.sendBlobAttachOp(localId, storageId);
420
453
  }
421
454
  }
422
455
  processBlobAttachMessage(message, local) {
423
456
  assert(isBlobMetadata(message.metadata), 0xc02 /* Expected blob metadata for a BlobAttach op */);
424
- const { localId, blobId } = message.metadata;
457
+ const { localId, blobId: storageId } = message.metadata;
425
458
  const pendingEntry = this.pendingBlobs.get(localId);
426
459
  if (pendingEntry?.abortSignal?.aborted) {
427
460
  this.deletePendingBlob(localId);
428
461
  return;
429
462
  }
430
- this.setRedirection(localId, blobId);
463
+ this.setRedirection(localId, storageId);
431
464
  // set identity (id -> id) entry
432
- this.setRedirection(blobId, blobId);
465
+ this.setRedirection(storageId, storageId);
433
466
  if (local) {
434
- const waitingBlobs = this.opsInFlight.get(blobId);
467
+ const waitingBlobs = this.opsInFlight.get(storageId);
435
468
  if (waitingBlobs !== undefined) {
436
469
  // For each op corresponding to this storage ID that we are waiting for, resolve the pending blob.
437
470
  // This is safe because the server will keep the blob alive and the op containing the local ID to
@@ -439,14 +472,14 @@ export class BlobManager {
439
472
  for (const pendingLocalId of waitingBlobs) {
440
473
  const entry = this.pendingBlobs.get(pendingLocalId);
441
474
  assert(entry !== undefined, 0x38f /* local online BlobAttach op with no pending blob entry */);
442
- this.setRedirection(pendingLocalId, blobId);
475
+ this.setRedirection(pendingLocalId, storageId);
443
476
  entry.acked = true;
444
477
  const blobHandle = this.getBlobHandle(pendingLocalId);
445
478
  blobHandle.notifyShared();
446
479
  entry.handleP.resolve(blobHandle);
447
480
  this.deletePendingBlobMaybe(pendingLocalId);
448
481
  }
449
- this.opsInFlight.delete(blobId);
482
+ this.opsInFlight.delete(storageId);
450
483
  }
451
484
  const localEntry = this.pendingBlobs.get(localId);
452
485
  if (localEntry) {
@@ -457,10 +490,10 @@ export class BlobManager {
457
490
  this.deletePendingBlobMaybe(localId);
458
491
  }
459
492
  }
460
- this.internalEvents.emit("processedBlobAttach", localId, blobId);
493
+ this.internalEvents.emit("processedBlobAttach", localId, storageId);
461
494
  }
462
495
  summarize(telemetryContext) {
463
- return summarizeBlobManagerState(this.redirectTable, this.runtime.attachState);
496
+ return summarizeBlobManagerState(this.redirectTable);
464
497
  }
465
498
  /**
466
499
  * Generates data used for garbage collection. Each blob uploaded represents a node in the GC graph as it can be
@@ -471,13 +504,12 @@ export class BlobManager {
471
504
  getGCData(fullGC = false) {
472
505
  const gcData = { gcNodes: {} };
473
506
  for (const [localId, storageId] of this.redirectTable) {
474
- assert(!!storageId, 0x390 /* Must be attached to get GC data */);
475
- // Only return local ids as GC nodes because a blob can only be referenced via its local id. The storage
476
- // id entries have the same key and value, ignore them.
477
- // The outbound routes are empty because a blob node cannot reference other nodes. It can only be referenced
478
- // by adding its handle to a referenced DDS.
507
+ // Don't report the identity mappings to GC - these exist to service old handles that referenced the storage
508
+ // IDs directly. We'll implicitly clean them up if all of their localId references get GC'd first.
479
509
  if (localId !== storageId) {
480
- gcData.gcNodes[getGCNodePathFromBlobId(localId)] = [];
510
+ // The outbound routes are empty because a blob node cannot reference other nodes. It can only be referenced
511
+ // by adding its handle to a referenced DDS.
512
+ gcData.gcNodes[getGCNodePathFromLocalId(localId)] = [];
481
513
  }
482
514
  }
483
515
  return gcData;
@@ -496,55 +528,53 @@ export class BlobManager {
496
528
  * Delete blobs with the given routes from the redirect table.
497
529
  *
498
530
  * @remarks
499
- * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<blobId>`. The blob ids are all local ids.
531
+ * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<localId>`.
500
532
  * Deleting the blobs involves 2 steps:
501
533
  *
502
534
  * 1. The redirect table entry for the local ids are deleted.
503
535
  *
504
- * 2. If the storage ids corresponding to the deleted local ids are not in-use anymore, the redirect table entries
505
- * for the storage ids are deleted as well.
536
+ * 2. If the storage ids corresponding to the deleted local ids are not referenced by any further local ids, the
537
+ * identity mappings in the redirect table are deleted as well.
506
538
  *
507
539
  * Note that this does not delete the blobs from storage service immediately. Deleting the blobs from redirect table
508
- * will remove them the next summary. The service would them delete them some time in the future.
540
+ * will ensure we don't create an attachment blob for them at the next summary. The service would then delete them
541
+ * some time in the future.
509
542
  */
510
543
  deleteBlobsFromRedirectTable(blobRoutes) {
511
- if (blobRoutes.length === 0) {
512
- return;
513
- }
514
- // This tracks the storage ids of local ids that are deleted. After the local ids have been deleted, if any of
515
- // these storage ids are unused, they will be deleted as well.
544
+ // maybeUnusedStorageIds is used to compute the set of storage IDs that *used to have a local ID*, but that
545
+ // local ID is being deleted.
516
546
  const maybeUnusedStorageIds = new Set();
517
547
  for (const route of blobRoutes) {
518
- const blobId = getBlobIdFromGCNodePath(route);
548
+ const localId = getLocalIdFromGCNodePath(route);
519
549
  // If the blob hasn't already been deleted, log an error because this should never happen.
520
550
  // If the blob has already been deleted, log a telemetry event. This can happen because multiple GC
521
551
  // sweep ops can contain the same data store. It would be interesting to track how often this happens.
522
552
  const alreadyDeleted = this.isBlobDeleted(route);
523
- if (!this.redirectTable.has(blobId)) {
553
+ const storageId = this.redirectTable.get(localId);
554
+ if (storageId === undefined) {
524
555
  this.mc.logger.sendTelemetryEvent({
525
556
  eventName: "DeletedAttachmentBlobNotFound",
526
557
  category: alreadyDeleted ? "generic" : "error",
527
- blobId,
558
+ blobId: localId,
528
559
  details: { alreadyDeleted },
529
560
  });
530
561
  continue;
531
562
  }
532
- const storageId = this.redirectTable.get(blobId);
533
- assert(!!storageId, 0x5bb /* Must be attached to run GC */);
534
563
  maybeUnusedStorageIds.add(storageId);
535
- this.redirectTable.delete(blobId);
564
+ this.redirectTable.delete(localId);
536
565
  }
537
- // Find out storage ids that are in-use and remove them from maybeUnusedStorageIds. A storage id is in-use if
538
- // the redirect table has a local id -> storage id entry for it.
539
- for (const [localId, storageId] of this.redirectTable.entries()) {
540
- assert(!!storageId, 0x5bc /* Must be attached to run GC */);
541
- // For every storage id, the redirect table has a id -> id entry. These do not make the storage id in-use.
542
- if (maybeUnusedStorageIds.has(storageId) && localId !== storageId) {
566
+ // Remove any storage IDs that still have local IDs referring to them (excluding the identity mapping).
567
+ for (const [localId, storageId] of this.redirectTable) {
568
+ if (localId !== storageId) {
543
569
  maybeUnusedStorageIds.delete(storageId);
544
570
  }
545
571
  }
546
- // For unused storage ids, delete their id -> id entries from the redirect table.
547
- // This way they'll be absent from the next summary, and the service is free to delete them from storage.
572
+ // Now delete any identity mappings (storage ID -> storage ID) from the redirect table that used to be
573
+ // referenced by a distinct local ID. This way they'll be absent from the next summary, and the service
574
+ // is free to delete them from storage.
575
+ // WARNING: This can potentially delete identity mappings that are still referenced, if storage deduping
576
+ // has let us add a local ID -> storage ID mapping that is later deleted. AB#47337 tracks this issue
577
+ // and possible solutions.
548
578
  for (const storageId of maybeUnusedStorageIds) {
549
579
  this.redirectTable.delete(storageId);
550
580
  }
@@ -553,11 +583,11 @@ export class BlobManager {
553
583
  * Verifies that the blob with given id is not deleted, i.e., it has not been garbage collected. If the blob is GC'd,
554
584
  * log an error and throw if necessary.
555
585
  */
556
- verifyBlobNotDeleted(blobId) {
557
- if (!this.isBlobDeleted(getGCNodePathFromBlobId(blobId))) {
586
+ verifyBlobNotDeleted(localId) {
587
+ if (!this.isBlobDeleted(getGCNodePathFromLocalId(localId))) {
558
588
  return;
559
589
  }
560
- const request = { url: blobId };
590
+ const request = { url: localId };
561
591
  const error = responseToException(createResponseError(404, `Blob was deleted`, request), request);
562
592
  // Only log deleted events. Tombstone events are logged by garbage collector.
563
593
  this.mc.logger.sendErrorEvent({
@@ -566,16 +596,6 @@ export class BlobManager {
566
596
  }, error);
567
597
  throw error;
568
598
  }
569
- setRedirectTable(table) {
570
- assert(this.runtime.attachState === AttachState.Detached, 0x252 /* "redirect table can only be set in detached container" */);
571
- assert(this.redirectTable.size === table.size, 0x391 /* Redirect table size must match BlobManager's local ID count */);
572
- for (const [localId, storageId] of table) {
573
- assert(this.redirectTable.has(localId), 0x254 /* "unrecognized id in redirect table" */);
574
- this.setRedirection(localId, storageId);
575
- // set identity (id -> id) entry
576
- this.setRedirection(storageId, storageId);
577
- }
578
- }
579
599
  /**
580
600
  * To be used in getPendingLocalState flow. Get a serializable record of the blobs that are
581
601
  * pending upload and/or their BlobAttach op, which can be given to a new BlobManager to
@@ -605,15 +625,15 @@ export class BlobManager {
605
625
  }
606
626
  }
607
627
  /**
608
- * For a blobId, returns its path in GC's graph. The node path is of the format `/<blobManagerBasePath>/<blobId>`.
628
+ * For a localId, returns its path in GC's graph. The node path is of the format `/<blobManagerBasePath>/<localId>`.
609
629
  * This path must match the path of the blob handle returned by the createBlob API because blobs are marked
610
630
  * referenced by storing these handles in a referenced DDS.
611
631
  */
612
- const getGCNodePathFromBlobId = (blobId) => `/${blobManagerBasePath}/${blobId}`;
632
+ const getGCNodePathFromLocalId = (localId) => `/${blobManagerBasePath}/${localId}`;
613
633
  /**
614
- * For a given GC node path, return the blobId. The node path is of the format `/<basePath>/<blobId>`.
634
+ * For a given GC node path, return the localId. The node path is of the format `/<basePath>/<localId>`.
615
635
  */
616
- const getBlobIdFromGCNodePath = (nodePath) => {
636
+ const getLocalIdFromGCNodePath = (nodePath) => {
617
637
  const pathParts = nodePath.split("/");
618
638
  assert(areBlobPathParts(pathParts), 0x5bd /* Invalid blob node path */);
619
639
  return pathParts[2];