@fluidframework/container-runtime 2.60.0 → 2.61.0-355054

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 (40) hide show
  1. package/.mocharc.cjs +1 -2
  2. package/container-runtime.test-files.tar +0 -0
  3. package/dist/blobManager/blobManager.d.ts +21 -15
  4. package/dist/blobManager/blobManager.d.ts.map +1 -1
  5. package/dist/blobManager/blobManager.js +105 -102
  6. package/dist/blobManager/blobManager.js.map +1 -1
  7. package/dist/blobManager/blobManagerSnapSum.d.ts +4 -4
  8. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  9. package/dist/blobManager/blobManagerSnapSum.js +30 -33
  10. package/dist/blobManager/blobManagerSnapSum.js.map +1 -1
  11. package/dist/containerRuntime.js +1 -1
  12. package/dist/containerRuntime.js.map +1 -1
  13. package/dist/legacy.d.ts +2 -1
  14. package/dist/packageVersion.d.ts +1 -1
  15. package/dist/packageVersion.d.ts.map +1 -1
  16. package/dist/packageVersion.js +1 -1
  17. package/dist/packageVersion.js.map +1 -1
  18. package/internal.d.ts +1 -1
  19. package/legacy.d.ts +1 -1
  20. package/lib/blobManager/blobManager.d.ts +21 -15
  21. package/lib/blobManager/blobManager.d.ts.map +1 -1
  22. package/lib/blobManager/blobManager.js +105 -102
  23. package/lib/blobManager/blobManager.js.map +1 -1
  24. package/lib/blobManager/blobManagerSnapSum.d.ts +4 -4
  25. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  26. package/lib/blobManager/blobManagerSnapSum.js +26 -29
  27. package/lib/blobManager/blobManagerSnapSum.js.map +1 -1
  28. package/lib/containerRuntime.js +1 -1
  29. package/lib/containerRuntime.js.map +1 -1
  30. package/lib/legacy.d.ts +2 -1
  31. package/lib/packageVersion.d.ts +1 -1
  32. package/lib/packageVersion.d.ts.map +1 -1
  33. package/lib/packageVersion.js +1 -1
  34. package/lib/packageVersion.js.map +1 -1
  35. package/lib/tsdoc-metadata.json +1 -1
  36. package/package.json +26 -26
  37. package/src/blobManager/blobManager.ts +118 -121
  38. package/src/blobManager/blobManagerSnapSum.ts +31 -53
  39. package/src/containerRuntime.ts +1 -1
  40. package/src/packageVersion.ts +1 -1
package/dist/legacy.d.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  export {
12
- // @legacy APIs
12
+ // #region @legacyBeta APIs
13
13
  AllowTombstoneRequestHeaderKey,
14
14
  CompressionAlgorithms,
15
15
  ContainerMessageType,
@@ -65,4 +65,5 @@ export {
65
65
  TombstoneResponseHeaderKey,
66
66
  disabledCompressionConfig,
67
67
  loadContainerRuntime
68
+ // #endregion
68
69
  } from "./index.js";
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/container-runtime";
8
- export declare const pkgVersion = "2.60.0";
8
+ export declare const pkgVersion = "2.61.0-355054";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,sCAAsC,CAAC;AAC3D,eAAO,MAAM,UAAU,WAAW,CAAC"}
1
+ {"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,sCAAsC,CAAC;AAC3D,eAAO,MAAM,UAAU,kBAAkB,CAAC"}
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluidframework/container-runtime";
11
- exports.pkgVersion = "2.60.0";
11
+ exports.pkgVersion = "2.61.0-355054";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,mCAAmC,CAAC;AAC9C,QAAA,UAAU,GAAG,QAAQ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.60.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,mCAAmC,CAAC;AAC9C,QAAA,UAAU,GAAG,eAAe,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.61.0-355054\";\n"]}
package/internal.d.ts CHANGED
@@ -8,4 +8,4 @@
8
8
  * Generated by "flub generate entrypoints" in @fluid-tools/build-cli.
9
9
  */
10
10
 
11
- export * from "./lib/index.js";
11
+ export * from "lib/index.js";
package/legacy.d.ts CHANGED
@@ -8,4 +8,4 @@
8
8
  * Generated by "flub generate entrypoints" in @fluid-tools/build-cli.
9
9
  */
10
10
 
11
- export * from "./lib/legacy.js";
11
+ export * from "lib/legacy.js";
@@ -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,20 @@ 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;
113
112
  /**
114
113
  * Retrieve the blob with the given local blob id.
115
- * @param blobId - The local blob id. Likely coming from a handle.
114
+ * @param localId - The local blob id. Likely coming from a handle.
116
115
  * @param payloadPending - Whether we suspect the payload may be pending and not available yet.
117
116
  * @returns A promise which resolves to the blob contents
118
117
  */
119
- getBlob(blobId: string, payloadPending: boolean): Promise<ArrayBufferLike>;
118
+ getBlob(localId: string, payloadPending: boolean): Promise<ArrayBufferLike>;
120
119
  private getBlobHandle;
121
120
  private createBlobDetached;
122
121
  createBlob(blob: ArrayBufferLike, signal?: AbortSignal): Promise<IFluidHandleInternalPayloadPending<ArrayBufferLike>>;
@@ -163,16 +162,17 @@ export declare class BlobManager {
163
162
  * Delete blobs with the given routes from the redirect table.
164
163
  *
165
164
  * @remarks
166
- * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<blobId>`. The blob ids are all local ids.
165
+ * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<localId>`.
167
166
  * Deleting the blobs involves 2 steps:
168
167
  *
169
168
  * 1. The redirect table entry for the local ids are deleted.
170
169
  *
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.
170
+ * 2. If the storage ids corresponding to the deleted local ids are not referenced by any further local ids, the
171
+ * identity mappings in the redirect table are deleted as well.
173
172
  *
174
173
  * 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.
174
+ * will ensure we don't create an attachment blob for them at the next summary. The service would then delete them
175
+ * some time in the future.
176
176
  */
177
177
  private deleteBlobsFromRedirectTable;
178
178
  /**
@@ -180,7 +180,13 @@ export declare class BlobManager {
180
180
  * log an error and throw if necessary.
181
181
  */
182
182
  private verifyBlobNotDeleted;
183
- setRedirectTable(table: Map<string, string>): void;
183
+ /**
184
+ * Called in detached state just prior to attaching, this will update the redirect table by
185
+ * converting the pseudo storage IDs into real storage IDs using the provided detachedStorageTable.
186
+ * The provided table must have exactly the same set of pseudo storage IDs as are found in the redirect table.
187
+ * @param detachedStorageTable - A map of pseudo storage IDs to real storage IDs.
188
+ */
189
+ patchRedirectTable(detachedStorageTable: Map<string, string>): void;
184
190
  /**
185
191
  * To be used in getPendingLocalState flow. Get a serializable record of the blobs that are
186
192
  * 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,20 @@ 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
+ const { routeContext, blobManagerLoadInfo, storage, sendBlobAttachOp, blobRequested, isBlobDeleted, runtime, localIdGenerator, createBlobPayloadPending, } = props;
78
78
  this.routeContext = routeContext;
79
79
  this.storage = storage;
80
80
  this.blobRequested = blobRequested;
81
81
  this.isBlobDeleted = isBlobDeleted;
82
82
  this.runtime = runtime;
83
- this.localBlobIdGenerator = localBlobIdGenerator ?? uuid;
83
+ this.localIdGenerator = localIdGenerator ?? uuid;
84
84
  this.createBlobPayloadPending = createBlobPayloadPending;
85
85
  this.mc = createChildMonitoringContext({
86
86
  logger: this.runtime.baseLogger,
87
87
  namespace: "BlobManager",
88
88
  });
89
- this.redirectTable = toRedirectTable(blobManagerLoadInfo, this.mc.logger, this.runtime.attachState);
90
- this.sendBlobAttachOp = (localId, blobId) => {
89
+ this.redirectTable = toRedirectTable(blobManagerLoadInfo, this.mc.logger);
90
+ this.sendBlobAttachOp = (localId, storageId) => {
91
91
  const pendingEntry = this.pendingBlobs.get(localId);
92
92
  assert(pendingEntry !== undefined, 0x725 /* Must have pending blob entry for upcoming op */);
93
93
  if (pendingEntry?.uploadTime && pendingEntry?.minTTLInSeconds) {
@@ -113,7 +113,7 @@ export class BlobManager {
113
113
  }
114
114
  }
115
115
  pendingEntry.opsent = true;
116
- sendBlobAttachOp(localId, blobId);
116
+ sendBlobAttachOp(localId, storageId);
117
117
  };
118
118
  }
119
119
  get allBlobsAttached() {
@@ -134,56 +134,46 @@ export class BlobManager {
134
134
  uploadTime: pending?.uploadTime,
135
135
  });
136
136
  }
137
- hasBlob(blobId) {
138
- return this.redirectTable.get(blobId) !== undefined;
137
+ hasBlob(localId) {
138
+ return this.redirectTable.get(localId) !== undefined;
139
139
  }
140
140
  /**
141
141
  * Retrieve the blob with the given local blob id.
142
- * @param blobId - The local blob id. Likely coming from a handle.
142
+ * @param localId - The local blob id. Likely coming from a handle.
143
143
  * @param payloadPending - Whether we suspect the payload may be pending and not available yet.
144
144
  * @returns A promise which resolves to the blob contents
145
145
  */
146
- async getBlob(blobId, payloadPending) {
146
+ async getBlob(localId, payloadPending) {
147
147
  // Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
148
148
  // an error, failing the call.
149
- this.verifyBlobNotDeleted(blobId);
149
+ this.verifyBlobNotDeleted(localId);
150
150
  // Let runtime know that the corresponding GC node was requested.
151
151
  // Note that this will throw if the blob is inactive or tombstoned and throwing on incorrect usage
152
152
  // is configured.
153
- this.blobRequested(getGCNodePathFromBlobId(blobId));
154
- const pending = this.pendingBlobs.get(blobId);
153
+ this.blobRequested(getGCNodePathFromLocalId(localId));
154
+ const pending = this.pendingBlobs.get(localId);
155
155
  if (pending) {
156
156
  return pending.blob;
157
157
  }
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
- }));
158
+ let storageId = this.redirectTable.get(localId);
159
+ if (storageId === undefined) {
160
+ // Only blob handles explicitly marked with pending payload are permitted to exist without
161
+ // yet knowing their storage id. Otherwise they must already be associated with a storage id.
162
+ // Handles for detached blobs are not payload pending.
163
+ assert(payloadPending, 0x11f /* "requesting unknown blobs" */);
164
+ // If we didn't find it in the redirectTable and it's payloadPending, assume the attach op is coming
165
+ // eventually and wait. We do this even if the local client doesn't have the blob payloadPending flag
166
+ // enabled, in case a remote client does have it enabled. This wait may be infinite if the uploading
167
+ // client failed the upload and doesn't exist anymore.
168
+ storageId = await new Promise((resolve) => {
169
+ const onProcessBlobAttach = (_localId, _storageId) => {
170
+ if (_localId === localId) {
171
+ this.internalEvents.off("processedBlobAttach", onProcessBlobAttach);
172
+ resolve(_storageId);
173
+ }
174
+ };
175
+ this.internalEvents.on("processedBlobAttach", onProcessBlobAttach);
176
+ });
187
177
  }
188
178
  return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "AttachmentReadBlob", id: storageId }, async (event) => {
189
179
  return this.storage.readBlob(storageId).catch((error) => {
@@ -207,15 +197,17 @@ export class BlobManager {
207
197
  this.deletePendingBlobMaybe(localId);
208
198
  }
209
199
  : undefined;
210
- return new BlobHandle(getGCNodePathFromBlobId(localId), this.routeContext, async () => this.getBlob(localId, false), false, // payloadPending
200
+ return new BlobHandle(getGCNodePathFromLocalId(localId), this.routeContext, async () => this.getBlob(localId, false), false, // payloadPending
211
201
  callback);
212
202
  }
213
203
  async createBlobDetached(blob) {
204
+ const localId = this.localIdGenerator();
214
205
  // 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);
206
+ // The 'IContainerStorageService.createBlob()' call below will respond with a pseudo storage ID.
207
+ // That pseudo storage ID will be replaced with the real storage ID at attach time.
208
+ const { id: detachedStorageId } = await this.storage.createBlob(blob);
209
+ this.setRedirection(localId, detachedStorageId);
210
+ return this.getBlobHandle(localId);
219
211
  }
220
212
  async createBlob(blob, signal) {
221
213
  if (this.runtime.attachState === AttachState.Detached) {
@@ -237,7 +229,7 @@ export class BlobManager {
237
229
  }
238
230
  // Create a local ID for the blob. After uploading it to storage and before returning it, a local ID to
239
231
  // storage ID mapping is created.
240
- const localId = this.localBlobIdGenerator();
232
+ const localId = this.localIdGenerator();
241
233
  const pendingEntry = {
242
234
  blob,
243
235
  handleP: new Deferred(),
@@ -259,8 +251,8 @@ export class BlobManager {
259
251
  });
260
252
  }
261
253
  createBlobWithPayloadPending(blob) {
262
- const localId = this.localBlobIdGenerator();
263
- const blobHandle = new BlobHandle(getGCNodePathFromBlobId(localId), this.routeContext, async () => blob, true, // payloadPending
254
+ const localId = this.localIdGenerator();
255
+ const blobHandle = new BlobHandle(getGCNodePathFromLocalId(localId), this.routeContext, async () => blob, true, // payloadPending
264
256
  () => {
265
257
  const pendingEntry = {
266
258
  blob,
@@ -375,7 +367,7 @@ export class BlobManager {
375
367
  if (!entry.opsent) {
376
368
  this.sendBlobAttachOp(localId, response.id);
377
369
  }
378
- const storageIds = getStorageIds(this.redirectTable, this.runtime.attachState);
370
+ const storageIds = getStorageIds(this.redirectTable);
379
371
  if (storageIds.has(response.id)) {
380
372
  // The blob is de-duped. Set up a local ID to storage ID mapping and return the blob. Since this is
381
373
  // an existing blob, we don't have to wait for the op to be ack'd since this step has already
@@ -408,7 +400,7 @@ export class BlobManager {
408
400
  */
409
401
  reSubmit(metadata) {
410
402
  assert(isBlobMetadata(metadata), 0xc01 /* Expected blob metadata for a BlobAttach op */);
411
- const { localId, blobId } = metadata;
403
+ const { localId, blobId: storageId } = metadata;
412
404
  // Any blob that we're actively trying to advance to attached state must have a
413
405
  // pendingBlobs entry. Decline to resubmit for anything else.
414
406
  // For example, we might be asked to resubmit stashed ops for blobs that never had
@@ -416,22 +408,22 @@ export class BlobManager {
416
408
  // try to attach them since they won't be accessible to the customer and would just
417
409
  // be considered garbage immediately.
418
410
  if (this.pendingBlobs.has(localId)) {
419
- this.sendBlobAttachOp(localId, blobId);
411
+ this.sendBlobAttachOp(localId, storageId);
420
412
  }
421
413
  }
422
414
  processBlobAttachMessage(message, local) {
423
415
  assert(isBlobMetadata(message.metadata), 0xc02 /* Expected blob metadata for a BlobAttach op */);
424
- const { localId, blobId } = message.metadata;
416
+ const { localId, blobId: storageId } = message.metadata;
425
417
  const pendingEntry = this.pendingBlobs.get(localId);
426
418
  if (pendingEntry?.abortSignal?.aborted) {
427
419
  this.deletePendingBlob(localId);
428
420
  return;
429
421
  }
430
- this.setRedirection(localId, blobId);
422
+ this.setRedirection(localId, storageId);
431
423
  // set identity (id -> id) entry
432
- this.setRedirection(blobId, blobId);
424
+ this.setRedirection(storageId, storageId);
433
425
  if (local) {
434
- const waitingBlobs = this.opsInFlight.get(blobId);
426
+ const waitingBlobs = this.opsInFlight.get(storageId);
435
427
  if (waitingBlobs !== undefined) {
436
428
  // For each op corresponding to this storage ID that we are waiting for, resolve the pending blob.
437
429
  // This is safe because the server will keep the blob alive and the op containing the local ID to
@@ -439,14 +431,14 @@ export class BlobManager {
439
431
  for (const pendingLocalId of waitingBlobs) {
440
432
  const entry = this.pendingBlobs.get(pendingLocalId);
441
433
  assert(entry !== undefined, 0x38f /* local online BlobAttach op with no pending blob entry */);
442
- this.setRedirection(pendingLocalId, blobId);
434
+ this.setRedirection(pendingLocalId, storageId);
443
435
  entry.acked = true;
444
436
  const blobHandle = this.getBlobHandle(pendingLocalId);
445
437
  blobHandle.notifyShared();
446
438
  entry.handleP.resolve(blobHandle);
447
439
  this.deletePendingBlobMaybe(pendingLocalId);
448
440
  }
449
- this.opsInFlight.delete(blobId);
441
+ this.opsInFlight.delete(storageId);
450
442
  }
451
443
  const localEntry = this.pendingBlobs.get(localId);
452
444
  if (localEntry) {
@@ -457,10 +449,10 @@ export class BlobManager {
457
449
  this.deletePendingBlobMaybe(localId);
458
450
  }
459
451
  }
460
- this.internalEvents.emit("processedBlobAttach", localId, blobId);
452
+ this.internalEvents.emit("processedBlobAttach", localId, storageId);
461
453
  }
462
454
  summarize(telemetryContext) {
463
- return summarizeBlobManagerState(this.redirectTable, this.runtime.attachState);
455
+ return summarizeBlobManagerState(this.redirectTable);
464
456
  }
465
457
  /**
466
458
  * Generates data used for garbage collection. Each blob uploaded represents a node in the GC graph as it can be
@@ -471,13 +463,12 @@ export class BlobManager {
471
463
  getGCData(fullGC = false) {
472
464
  const gcData = { gcNodes: {} };
473
465
  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.
466
+ // Don't report the identity mappings to GC - these exist to service old handles that referenced the storage
467
+ // IDs directly. We'll implicitly clean them up if all of their localId references get GC'd first.
479
468
  if (localId !== storageId) {
480
- gcData.gcNodes[getGCNodePathFromBlobId(localId)] = [];
469
+ // The outbound routes are empty because a blob node cannot reference other nodes. It can only be referenced
470
+ // by adding its handle to a referenced DDS.
471
+ gcData.gcNodes[getGCNodePathFromLocalId(localId)] = [];
481
472
  }
482
473
  }
483
474
  return gcData;
@@ -496,55 +487,53 @@ export class BlobManager {
496
487
  * Delete blobs with the given routes from the redirect table.
497
488
  *
498
489
  * @remarks
499
- * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<blobId>`. The blob ids are all local ids.
490
+ * The routes are GC nodes paths of format -`/<blobManagerBasePath>/<localId>`.
500
491
  * Deleting the blobs involves 2 steps:
501
492
  *
502
493
  * 1. The redirect table entry for the local ids are deleted.
503
494
  *
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.
495
+ * 2. If the storage ids corresponding to the deleted local ids are not referenced by any further local ids, the
496
+ * identity mappings in the redirect table are deleted as well.
506
497
  *
507
498
  * 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.
499
+ * will ensure we don't create an attachment blob for them at the next summary. The service would then delete them
500
+ * some time in the future.
509
501
  */
510
502
  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.
503
+ // maybeUnusedStorageIds is used to compute the set of storage IDs that *used to have a local ID*, but that
504
+ // local ID is being deleted.
516
505
  const maybeUnusedStorageIds = new Set();
517
506
  for (const route of blobRoutes) {
518
- const blobId = getBlobIdFromGCNodePath(route);
507
+ const localId = getLocalIdFromGCNodePath(route);
519
508
  // If the blob hasn't already been deleted, log an error because this should never happen.
520
509
  // If the blob has already been deleted, log a telemetry event. This can happen because multiple GC
521
510
  // sweep ops can contain the same data store. It would be interesting to track how often this happens.
522
511
  const alreadyDeleted = this.isBlobDeleted(route);
523
- if (!this.redirectTable.has(blobId)) {
512
+ const storageId = this.redirectTable.get(localId);
513
+ if (storageId === undefined) {
524
514
  this.mc.logger.sendTelemetryEvent({
525
515
  eventName: "DeletedAttachmentBlobNotFound",
526
516
  category: alreadyDeleted ? "generic" : "error",
527
- blobId,
517
+ blobId: localId,
528
518
  details: { alreadyDeleted },
529
519
  });
530
520
  continue;
531
521
  }
532
- const storageId = this.redirectTable.get(blobId);
533
- assert(!!storageId, 0x5bb /* Must be attached to run GC */);
534
522
  maybeUnusedStorageIds.add(storageId);
535
- this.redirectTable.delete(blobId);
523
+ this.redirectTable.delete(localId);
536
524
  }
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) {
525
+ // Remove any storage IDs that still have local IDs referring to them (excluding the identity mapping).
526
+ for (const [localId, storageId] of this.redirectTable) {
527
+ if (localId !== storageId) {
543
528
  maybeUnusedStorageIds.delete(storageId);
544
529
  }
545
530
  }
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.
531
+ // Now delete any identity mappings (storage ID -> storage ID) from the redirect table that used to be
532
+ // referenced by a distinct local ID. This way they'll be absent from the next summary, and the service
533
+ // is free to delete them from storage.
534
+ // WARNING: This can potentially delete identity mappings that are still referenced, if storage deduping
535
+ // has let us add a local ID -> storage ID mapping that is later deleted. AB#47337 tracks this issue
536
+ // and possible solutions.
548
537
  for (const storageId of maybeUnusedStorageIds) {
549
538
  this.redirectTable.delete(storageId);
550
539
  }
@@ -553,11 +542,11 @@ export class BlobManager {
553
542
  * 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
543
  * log an error and throw if necessary.
555
544
  */
556
- verifyBlobNotDeleted(blobId) {
557
- if (!this.isBlobDeleted(getGCNodePathFromBlobId(blobId))) {
545
+ verifyBlobNotDeleted(localId) {
546
+ if (!this.isBlobDeleted(getGCNodePathFromLocalId(localId))) {
558
547
  return;
559
548
  }
560
- const request = { url: blobId };
549
+ const request = { url: localId };
561
550
  const error = responseToException(createResponseError(404, `Blob was deleted`, request), request);
562
551
  // Only log deleted events. Tombstone events are logged by garbage collector.
563
552
  this.mc.logger.sendErrorEvent({
@@ -566,14 +555,28 @@ export class BlobManager {
566
555
  }, error);
567
556
  throw error;
568
557
  }
569
- setRedirectTable(table) {
558
+ /**
559
+ * Called in detached state just prior to attaching, this will update the redirect table by
560
+ * converting the pseudo storage IDs into real storage IDs using the provided detachedStorageTable.
561
+ * The provided table must have exactly the same set of pseudo storage IDs as are found in the redirect table.
562
+ * @param detachedStorageTable - A map of pseudo storage IDs to real storage IDs.
563
+ */
564
+ patchRedirectTable(detachedStorageTable) {
570
565
  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);
566
+ // The values of the redirect table are the pseudo storage IDs, which are the keys of the
567
+ // detachedStorageTable. We expect to have a many:1 mapping from local IDs to pseudo
568
+ // storage IDs (many in the case that the storage dedupes the blob).
569
+ assert(new Set(this.redirectTable.values()).size === detachedStorageTable.size, 0x391 /* Redirect table size must match BlobManager's local ID count */);
570
+ // Taking a snapshot of the redirect table entries before iterating, because
571
+ // we will be adding identity mappings to the the redirect table as we iterate
572
+ // and we don't want to include those in the iteration.
573
+ const redirectTableEntries = [...this.redirectTable.entries()];
574
+ for (const [localId, detachedStorageId] of redirectTableEntries) {
575
+ const newStorageId = detachedStorageTable.get(detachedStorageId);
576
+ assert(newStorageId !== undefined, "Couldn't find a matching storage ID");
577
+ this.setRedirection(localId, newStorageId);
575
578
  // set identity (id -> id) entry
576
- this.setRedirection(storageId, storageId);
579
+ this.setRedirection(newStorageId, newStorageId);
577
580
  }
578
581
  }
579
582
  /**
@@ -605,15 +608,15 @@ export class BlobManager {
605
608
  }
606
609
  }
607
610
  /**
608
- * For a blobId, returns its path in GC's graph. The node path is of the format `/<blobManagerBasePath>/<blobId>`.
611
+ * For a localId, returns its path in GC's graph. The node path is of the format `/<blobManagerBasePath>/<localId>`.
609
612
  * This path must match the path of the blob handle returned by the createBlob API because blobs are marked
610
613
  * referenced by storing these handles in a referenced DDS.
611
614
  */
612
- const getGCNodePathFromBlobId = (blobId) => `/${blobManagerBasePath}/${blobId}`;
615
+ const getGCNodePathFromLocalId = (localId) => `/${blobManagerBasePath}/${localId}`;
613
616
  /**
614
- * For a given GC node path, return the blobId. The node path is of the format `/<basePath>/<blobId>`.
617
+ * For a given GC node path, return the localId. The node path is of the format `/<basePath>/<localId>`.
615
618
  */
616
- const getBlobIdFromGCNodePath = (nodePath) => {
619
+ const getLocalIdFromGCNodePath = (nodePath) => {
617
620
  const pathParts = nodePath.split("/");
618
621
  assert(areBlobPathParts(pathParts), 0x5bd /* Invalid blob node path */);
619
622
  return pathParts[2];