@fluidframework/container-runtime 0.58.3000-61081 → 0.59.1001-62246

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 (97) hide show
  1. package/dist/blobManager.d.ts +13 -1
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +52 -0
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/connectionTelemetry.js +7 -7
  6. package/dist/connectionTelemetry.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +27 -3
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +100 -14
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/dataStore.js +8 -1
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +9 -3
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +22 -6
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +13 -5
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +39 -18
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/deltaScheduler.d.ts +4 -5
  22. package/dist/deltaScheduler.d.ts.map +1 -1
  23. package/dist/deltaScheduler.js +54 -35
  24. package/dist/deltaScheduler.js.map +1 -1
  25. package/dist/garbageCollection.d.ts +31 -27
  26. package/dist/garbageCollection.d.ts.map +1 -1
  27. package/dist/garbageCollection.js +76 -75
  28. package/dist/garbageCollection.js.map +1 -1
  29. package/dist/orderedClientElection.d.ts +6 -57
  30. package/dist/orderedClientElection.d.ts.map +1 -1
  31. package/dist/orderedClientElection.js +25 -140
  32. package/dist/orderedClientElection.js.map +1 -1
  33. package/dist/packageVersion.d.ts +1 -1
  34. package/dist/packageVersion.js +1 -1
  35. package/dist/packageVersion.js.map +1 -1
  36. package/dist/summarizerClientElection.d.ts +0 -2
  37. package/dist/summarizerClientElection.d.ts.map +1 -1
  38. package/dist/summarizerClientElection.js +2 -7
  39. package/dist/summarizerClientElection.js.map +1 -1
  40. package/dist/summaryManager.d.ts.map +1 -1
  41. package/dist/summaryManager.js +3 -14
  42. package/dist/summaryManager.js.map +1 -1
  43. package/lib/blobManager.d.ts +13 -1
  44. package/lib/blobManager.d.ts.map +1 -1
  45. package/lib/blobManager.js +52 -0
  46. package/lib/blobManager.js.map +1 -1
  47. package/lib/connectionTelemetry.js +7 -7
  48. package/lib/connectionTelemetry.js.map +1 -1
  49. package/lib/containerRuntime.d.ts +27 -3
  50. package/lib/containerRuntime.d.ts.map +1 -1
  51. package/lib/containerRuntime.js +101 -15
  52. package/lib/containerRuntime.js.map +1 -1
  53. package/lib/dataStore.js +8 -1
  54. package/lib/dataStore.js.map +1 -1
  55. package/lib/dataStoreContext.d.ts +9 -3
  56. package/lib/dataStoreContext.d.ts.map +1 -1
  57. package/lib/dataStoreContext.js +22 -6
  58. package/lib/dataStoreContext.js.map +1 -1
  59. package/lib/dataStores.d.ts +13 -5
  60. package/lib/dataStores.d.ts.map +1 -1
  61. package/lib/dataStores.js +39 -18
  62. package/lib/dataStores.js.map +1 -1
  63. package/lib/deltaScheduler.d.ts +4 -5
  64. package/lib/deltaScheduler.d.ts.map +1 -1
  65. package/lib/deltaScheduler.js +54 -35
  66. package/lib/deltaScheduler.js.map +1 -1
  67. package/lib/garbageCollection.d.ts +31 -27
  68. package/lib/garbageCollection.d.ts.map +1 -1
  69. package/lib/garbageCollection.js +75 -74
  70. package/lib/garbageCollection.js.map +1 -1
  71. package/lib/orderedClientElection.d.ts +6 -57
  72. package/lib/orderedClientElection.d.ts.map +1 -1
  73. package/lib/orderedClientElection.js +25 -140
  74. package/lib/orderedClientElection.js.map +1 -1
  75. package/lib/packageVersion.d.ts +1 -1
  76. package/lib/packageVersion.js +1 -1
  77. package/lib/packageVersion.js.map +1 -1
  78. package/lib/summarizerClientElection.d.ts +0 -2
  79. package/lib/summarizerClientElection.d.ts.map +1 -1
  80. package/lib/summarizerClientElection.js +2 -7
  81. package/lib/summarizerClientElection.js.map +1 -1
  82. package/lib/summaryManager.d.ts.map +1 -1
  83. package/lib/summaryManager.js +3 -14
  84. package/lib/summaryManager.js.map +1 -1
  85. package/package.json +33 -21
  86. package/src/blobManager.ts +60 -1
  87. package/src/connectionTelemetry.ts +7 -7
  88. package/src/containerRuntime.ts +106 -17
  89. package/src/dataStore.ts +7 -1
  90. package/src/dataStoreContext.ts +22 -7
  91. package/src/dataStores.ts +40 -19
  92. package/src/deltaScheduler.ts +65 -39
  93. package/src/garbageCollection.ts +92 -78
  94. package/src/orderedClientElection.ts +25 -154
  95. package/src/packageVersion.ts +1 -1
  96. package/src/summarizerClientElection.ts +2 -7
  97. package/src/summaryManager.ts +4 -15
@@ -76,16 +76,33 @@ export interface IGCStats {
76
76
  nodeCount: number;
77
77
  /** The number of data stores in the container. */
78
78
  dataStoreCount: number;
79
+ /** The number of attachment blobs in the container. */
80
+ attachmentBlobCount: number;
79
81
  /** The number of unreferenced nodes in the container. */
80
82
  unrefNodeCount: number;
81
83
  /** The number of unreferenced data stores in the container. */
82
84
  unrefDataStoreCount: number;
85
+ /** The number of unreferenced attachment blobs in the container. */
86
+ unrefAttachmentBlobCount: number;
83
87
  /** The number of nodes whose reference state updated since last GC run. */
84
88
  updatedNodeCount: number;
85
89
  /** The number of data stores whose reference state updated since last GC run. */
86
90
  updatedDataStoreCount: number;
91
+ /** The number of attachment blobs whose reference state updated since last GC run. */
92
+ updatedAttachmentBlobCount: number;
87
93
  }
88
94
 
95
+ /** The types of GC nodes in the GC reference graph. */
96
+ export const GCNodeType = {
97
+ // Nodes that are for data stores.
98
+ DataStore: "DataStore",
99
+ // Nodes that are for attachment blobs, i.e., blobs uploaded via BlobManager.
100
+ Blob: "Blob",
101
+ // Nodes that are neither data store not blobs. For example, root node and DDS nodes.
102
+ Other: "Other",
103
+ };
104
+ export type GCNodeType = typeof GCNodeType[keyof typeof GCNodeType];
105
+
89
106
  /** The event that is logged when unreferenced node is used after a certain time. */
90
107
  interface IUnreferencedEvent {
91
108
  eventName: string;
@@ -105,6 +122,12 @@ export interface IGarbageCollectionRuntime {
105
122
  getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
106
123
  /** After GC has run, called to notify the runtime of routes that are used in it. */
107
124
  updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number): void;
125
+ /** After GC has run, called to delete objects in the runtime whose routes are unused. */
126
+ deleteUnusedRoutes(unusedRoutes: string[]): void;
127
+ /** Returns a referenced timestamp to be used to track unreferenced nodes. */
128
+ getCurrentReferenceTimestampMs(): number | undefined;
129
+ /** Returns the type of the GC node. */
130
+ getNodeType(nodePath: string): GCNodeType;
108
131
  /** Called when the runtime should close because of an error. */
109
132
  closeFn(error?: ICriticalContainerError): void;
110
133
  }
@@ -131,8 +154,8 @@ export interface IGarbageCollector {
131
154
  ): Promise<IGCStats>;
132
155
  /** Summarizes the GC data and returns it as a summary tree. */
133
156
  summarize(): ISummaryTreeWithStats | undefined;
134
- /** Returns a map of each data store id to its GC details in the base summary. */
135
- getDataStoreBaseGCDetails(): Promise<Map<string, IGarbageCollectionDetailsBase>>;
157
+ /** Returns a map of each node id to its base GC details in the base summary. */
158
+ getBaseGCDetails(): Promise<Map<string, IGarbageCollectionDetailsBase>>;
136
159
  /** Called when the latest summary of the system has been refreshed. */
137
160
  latestSummaryStateRefreshed(result: RefreshSummaryResult, readAndParseBlob: ReadAndParseBlob): Promise<void>;
138
161
  /** Called when a node is updated. Used to detect and log when an inactive node is changed or loaded. */
@@ -209,9 +232,7 @@ export class GarbageCollector implements IGarbageCollector {
209
232
  public static create(
210
233
  provider: IGarbageCollectionRuntime,
211
234
  gcOptions: IGCRuntimeOptions,
212
- deleteUnusedRoutes: (unusedRoutes: string[]) => void,
213
- getNodePackagePath: (nodeId: string) => readonly string[] | undefined,
214
- getCurrentReferenceTimestampMs: () => number | undefined,
235
+ getNodePackagePath: (nodePath: string) => readonly string[] | undefined,
215
236
  getLastSummaryTimestampMs: () => number | undefined,
216
237
  baseSnapshot: ISnapshotTree | undefined,
217
238
  readAndParseBlob: ReadAndParseBlob,
@@ -222,9 +243,7 @@ export class GarbageCollector implements IGarbageCollector {
222
243
  return new GarbageCollector(
223
244
  provider,
224
245
  gcOptions,
225
- deleteUnusedRoutes,
226
246
  getNodePackagePath,
227
- getCurrentReferenceTimestampMs,
228
247
  getLastSummaryTimestampMs,
229
248
  baseSnapshot,
230
249
  readAndParseBlob,
@@ -309,7 +328,7 @@ export class GarbageCollector implements IGarbageCollector {
309
328
  // Promise when resolved initializes the base state of the nodes from the base summary state.
310
329
  private readonly initializeBaseStateP: Promise<void>;
311
330
  // The map of data store ids to their GC details in the base summary returned in getDataStoreGCDetails().
312
- private readonly dataStoreGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
331
+ private readonly baseGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
313
332
  // The time after which an unreferenced node can be deleted. Currently, we only set the node's state to expired.
314
333
  private readonly deleteTimeoutMs: number;
315
334
  // Map of node ids to their unreferenced state tracker.
@@ -324,18 +343,10 @@ export class GarbageCollector implements IGarbageCollector {
324
343
  private readonly pendingEventsQueue: IUnreferencedEvent[] = [];
325
344
 
326
345
  protected constructor(
327
- private readonly provider: IGarbageCollectionRuntime,
346
+ private readonly runtime: IGarbageCollectionRuntime,
328
347
  private readonly gcOptions: IGCRuntimeOptions,
329
- /** After GC has run, called to delete objects in the runtime whose routes are unused. */
330
- private readonly deleteUnusedRoutes: (unusedRoutes: string[]) => void,
331
348
  /** For a given node path, returns the node's package path. */
332
349
  private readonly getNodePackagePath: (nodePath: string) => readonly string[] | undefined,
333
- /**
334
- * Returns a referenced timestamp to be used to track unreferenced nodes. This is a server generated timestamp
335
- * and may not be available if there aren't any ops processed yet. If so, we skip tracking unreferenced state
336
- * such as time when node becomes unreferenced or inactive.
337
- */
338
- private readonly getCurrentReferenceTimestampMs: () => number | undefined,
339
350
  /** Returns the timestamp of the last summary generated for this container. */
340
351
  private readonly getLastSummaryTimestampMs: () => number | undefined,
341
352
  baseSnapshot: ISnapshotTree | undefined,
@@ -357,11 +368,11 @@ export class GarbageCollector implements IGarbageCollector {
357
368
  if (existing) {
358
369
  prevSummaryGCVersion = getGCVersion(metadata);
359
370
  // Existing documents which did not have metadata blob or had GC disabled have version as 0. For all
360
- // other exsiting documents, GC is enabled.
371
+ // other existing documents, GC is enabled.
361
372
  this.gcEnabled = prevSummaryGCVersion > 0;
362
373
  this.sessionExpiryTimeoutMs = metadata?.sessionExpiryTimeoutMs;
363
374
  } else {
364
- // For new documents, GC has to be exlicitly enabled via the gcAllowed flag in GC options.
375
+ // For new documents, GC has to be explicitly enabled via the gcAllowed flag in GC options.
365
376
  this.gcEnabled = gcOptions.gcAllowed === true;
366
377
  // Set the Session Expiry only if the flag is enabled or the test option is set.
367
378
  if (this.mc.config.getBoolean(runSessionExpiry) && this.gcEnabled) {
@@ -374,7 +385,7 @@ export class GarbageCollector implements IGarbageCollector {
374
385
  const timeoutMs = this.sessionExpiryTimeoutMs;
375
386
  setLongTimeout(timeoutMs,
376
387
  () => {
377
- this.provider.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
388
+ this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
378
389
  },
379
390
  (timer) => {
380
391
  this.sessionExpiryTimer = timer;
@@ -435,10 +446,10 @@ export class GarbageCollector implements IGarbageCollector {
435
446
  // consolidate into IGarbageCollectionState format.
436
447
  // Add a node for the root node that is not present in older snapshot format.
437
448
  const gcState: IGarbageCollectionState = { gcNodes: { "/": { outboundRoutes: [] } } };
438
- const dataStoreSnaphotTree = getSummaryForDatastores(baseSnapshot, metadata);
439
- assert(dataStoreSnaphotTree !== undefined,
449
+ const dataStoreSnapshotTree = getSummaryForDatastores(baseSnapshot, metadata);
450
+ assert(dataStoreSnapshotTree !== undefined,
440
451
  0x2a8 /* "Expected data store snapshot tree in base snapshot" */);
441
- for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnaphotTree.trees)) {
452
+ for (const [dsId, dsSnapshotTree] of Object.entries(dataStoreSnapshotTree.trees)) {
442
453
  const blobId = dsSnapshotTree.blobs[gcBlobKey];
443
454
  if (blobId === undefined) {
444
455
  continue;
@@ -482,7 +493,7 @@ export class GarbageCollector implements IGarbageCollector {
482
493
  * may be out of date. This is fine because the state is updated every time GC runs based on the time then.
483
494
  */
484
495
  this.initializeBaseStateP = new LazyPromise<void>(async () => {
485
- const currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs();
496
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
486
497
  const baseState = await baseSummaryStateP;
487
498
  if (baseState === undefined) {
488
499
  return;
@@ -505,9 +516,9 @@ export class GarbageCollector implements IGarbageCollector {
505
516
  this.gcDataFromLastRun = { gcNodes };
506
517
  });
507
518
 
508
- // Get the GC details for each data store from the GC state in the base summary. This is returned in
509
- // getDataStoreBaseGCDetails and is used to initialize each data store's GC state.
510
- this.dataStoreGCDetailsP = new LazyPromise<Map<string, IGarbageCollectionDetailsBase>>(async () => {
519
+ // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
520
+ // which the caller uses to initialize each node's GC state.
521
+ this.baseGCDetailsP = new LazyPromise<Map<string, IGarbageCollectionDetailsBase>>(async () => {
511
522
  const baseState = await baseSummaryStateP;
512
523
  if (baseState === undefined) {
513
524
  return new Map();
@@ -526,18 +537,18 @@ export class GarbageCollector implements IGarbageCollector {
526
537
  this.mc.logger,
527
538
  ).referencedNodeIds;
528
539
 
529
- const dataStoreGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
530
- // Currently, the data stores write the GC data. So, we need to update it's base GC details with the
540
+ const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
541
+ // Currently, the nodes may write the GC data. So, we need to update it's base GC details with the
531
542
  // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
532
543
  for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
533
544
  if (nodeData.unreferencedTimestampMs !== undefined) {
534
- const dataStoreGCDetails = dataStoreGCDetailsMap.get(nodeId.slice(1));
545
+ const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
535
546
  if (dataStoreGCDetails !== undefined) {
536
547
  dataStoreGCDetails.unrefTimestamp = nodeData.unreferencedTimestampMs;
537
548
  }
538
549
  }
539
550
  }
540
- return dataStoreGCDetailsMap;
551
+ return baseGCDetailsMap;
541
552
  });
542
553
 
543
554
  // Initialize the base state. The base GC data is used to detect and log when inactive / deleted objects are
@@ -561,7 +572,7 @@ export class GarbageCollector implements IGarbageCollector {
561
572
  }
562
573
 
563
574
  /**
564
- * Runs garbage collection and udpates the reference / used state of the nodes in the container.
575
+ * Runs garbage collection and updates the reference / used state of the nodes in the container.
565
576
  * @returns the number of data stores that have been marked as unreferenced.
566
577
  */
567
578
  public async collectGarbage(
@@ -584,10 +595,10 @@ export class GarbageCollector implements IGarbageCollector {
584
595
  await this.initializeBaseStateP;
585
596
 
586
597
  // Let the runtime update its pending state before GC runs.
587
- await this.provider.updateStateBeforeGC();
598
+ await this.runtime.updateStateBeforeGC();
588
599
 
589
600
  // Get the runtime's GC data and run GC on the reference graph in it.
590
- const gcData = await this.provider.getGCData(fullGC);
601
+ const gcData = await this.runtime.getGCData(fullGC);
591
602
  const gcResult = runGarbageCollection(
592
603
  gcData.gcNodes,
593
604
  [ "/" ],
@@ -600,10 +611,10 @@ export class GarbageCollector implements IGarbageCollector {
600
611
  this.updateStateSinceLastRun(gcData);
601
612
 
602
613
  // Update the current state of the system based on the GC run.
603
- const currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs();
614
+ const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
604
615
  this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
605
616
 
606
- this.provider.updateUsedRoutes(gcResult.referencedNodeIds, currentReferenceTimestampMs);
617
+ this.runtime.updateUsedRoutes(gcResult.referencedNodeIds, currentReferenceTimestampMs);
607
618
 
608
619
  if (runSweep) {
609
620
  // Placeholder for running sweep logic.
@@ -612,7 +623,7 @@ export class GarbageCollector implements IGarbageCollector {
612
623
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
613
624
  // involving access to deleted data.
614
625
  if (this.testMode) {
615
- this.deleteUnusedRoutes(gcResult.deletedNodeIds);
626
+ this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
616
627
  }
617
628
  event.end({ ...gcStats });
618
629
  return gcStats;
@@ -644,11 +655,11 @@ export class GarbageCollector implements IGarbageCollector {
644
655
  }
645
656
 
646
657
  /**
647
- * Returns a map of data store ids to their base GC details generated from the base summary.This is used to
648
- * initialize the GC state of data stores.
658
+ * Returns a map of node ids to their base GC details generated from the base summary. This is used by the caller
659
+ * to initialize the GC state of the nodes.
649
660
  */
650
- public async getDataStoreBaseGCDetails(): Promise<Map<string, IGarbageCollectionDetailsBase>> {
651
- return this.dataStoreGCDetailsP;
661
+ public async getBaseGCDetails(): Promise<Map<string, IGarbageCollectionDetailsBase>> {
662
+ return this.baseGCDetailsP;
652
663
  }
653
664
 
654
665
  /**
@@ -905,7 +916,7 @@ export class GarbageCollector implements IGarbageCollector {
905
916
  currentReferences.forEach((route: string) => {
906
917
  // Validate references for data stores only. Currently, layers below data stores don't have GC implemented
907
918
  // so there is no guarantee their references will be notified.
908
- if (isDataStoreNode(route) && !explicitReferences.includes(route)) {
919
+ if (this.runtime.getNodeType(route) === GCNodeType.DataStore && !explicitReferences.includes(route)) {
909
920
  /**
910
921
  * The following log will be enabled once this issue is resolved:
911
922
  * https://github.com/microsoft/FluidFramework/issues/8878.
@@ -922,7 +933,8 @@ export class GarbageCollector implements IGarbageCollector {
922
933
 
923
934
  /**
924
935
  * Generates the stats of a garbage collection run from the given results of the run. Also, logs any pending events
925
- * in the pendingEventsQueue.
936
+ * in the pendingEventsQueue. This should be called before updating the current state because it generates stats
937
+ * based on previous state of the system.
926
938
  * @param gcResult - The result of a GC run.
927
939
  * @returns the GC stats of the GC run.
928
940
  */
@@ -942,44 +954,55 @@ export class GarbageCollector implements IGarbageCollector {
942
954
  const gcStats: IGCStats = {
943
955
  nodeCount: 0,
944
956
  dataStoreCount: 0,
957
+ attachmentBlobCount: 0,
945
958
  unrefNodeCount: 0,
946
959
  unrefDataStoreCount: 0,
960
+ unrefAttachmentBlobCount: 0,
947
961
  updatedNodeCount: 0,
948
962
  updatedDataStoreCount: 0,
963
+ updatedAttachmentBlobCount: 0,
949
964
  };
950
965
 
951
- for (const nodeId of gcResult.referencedNodeIds) {
966
+ const updateNodeStats = (nodeId: string, referenced: boolean) => {
952
967
  gcStats.nodeCount++;
953
- const isDataStore = isDataStoreNode(nodeId);
954
- if (isDataStore) {
955
- gcStats.dataStoreCount++;
956
- }
957
- // If a referenced node has an entry in `unreferencedNodesState`, it was previously unreferenced. So, its
958
- // reference state updated from the last GC run.
959
- if (this.unreferencedNodesState.has(nodeId)) {
968
+ /**
969
+ * `this.unreferencedNodesState` has the previous unreferenced state of all nodes. `referenced` flag passed
970
+ * here is current state of the give node. Check if the reference state of the changed.
971
+ */
972
+ const stateUpdated = this.unreferencedNodesState.has(nodeId) ? referenced : !referenced;
973
+ if (stateUpdated) {
960
974
  gcStats.updatedNodeCount++;
961
- if (isDataStore) {
962
- gcStats.updatedDataStoreCount++;
963
- }
964
975
  }
965
- }
976
+ if (!referenced) {
977
+ gcStats.unrefNodeCount++;
978
+ }
966
979
 
967
- for (const nodeId of gcResult.deletedNodeIds) {
968
- gcStats.nodeCount++;
969
- gcStats.unrefNodeCount++;
970
- const isDataStore = isDataStoreNode(nodeId);
971
- if (isDataStore) {
980
+ if (this.runtime.getNodeType(nodeId) === GCNodeType.DataStore) {
972
981
  gcStats.dataStoreCount++;
973
- gcStats.unrefDataStoreCount++;
974
- }
975
- // If an unreferenced node doesn't an entry in `unreferencedNodesState`, it was previously referenced. So,
976
- // its reference state updated from the last GC run.
977
- if (!this.unreferencedNodesState.has(nodeId)) {
978
- gcStats.updatedNodeCount++;
979
- if (isDataStore) {
982
+ if (stateUpdated) {
980
983
  gcStats.updatedDataStoreCount++;
981
984
  }
985
+ if (!referenced) {
986
+ gcStats.unrefDataStoreCount++;
987
+ }
988
+ }
989
+ if (this.runtime.getNodeType(nodeId) === GCNodeType.Blob) {
990
+ gcStats.attachmentBlobCount++;
991
+ if (stateUpdated) {
992
+ gcStats.updatedAttachmentBlobCount++;
993
+ }
994
+ if (!referenced) {
995
+ gcStats.unrefAttachmentBlobCount++;
996
+ }
982
997
  }
998
+ };
999
+
1000
+ for (const nodeId of gcResult.referencedNodeIds) {
1001
+ updateNodeStats(nodeId, true /* referenced */);
1002
+ }
1003
+
1004
+ for (const nodeId of gcResult.deletedNodeIds) {
1005
+ updateNodeStats(nodeId, false /* referenced */);
983
1006
  }
984
1007
 
985
1008
  return gcStats;
@@ -991,7 +1014,7 @@ export class GarbageCollector implements IGarbageCollector {
991
1014
  private logIfInactive(
992
1015
  eventSuffix: "Changed" | "Loaded" | "Revived",
993
1016
  nodeId: string,
994
- currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs(),
1017
+ currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs(),
995
1018
  packagePath?: readonly string[],
996
1019
  requestHeaders?: IRequestHeader,
997
1020
  ) {
@@ -1042,7 +1065,7 @@ async function getGCStateFromSnapshot(
1042
1065
  ): Promise<IGarbageCollectionState> {
1043
1066
  let rootGCState: IGarbageCollectionState = { gcNodes: {} };
1044
1067
  for (const key of Object.keys(gcSnapshotTree.blobs)) {
1045
- // Skip blobs that do not stsart with the GC prefix.
1068
+ // Skip blobs that do not start with the GC prefix.
1046
1069
  if (!key.startsWith(gcBlobPrefix)) {
1047
1070
  continue;
1048
1071
  }
@@ -1081,12 +1104,3 @@ function setLongTimeout(
1081
1104
  }
1082
1105
  setTimerFn(timer);
1083
1106
  }
1084
-
1085
- /**
1086
- * Given a GC nodeId, tells whether it belongs to a data store or not.
1087
- */
1088
- function isDataStoreNode(nodeId: string): boolean {
1089
- const pathParts = nodeId.split("/");
1090
- // Data store ids are in the format "/dataStoreId".
1091
- return pathParts.length === 2 && pathParts[1] !== "" ? true : false;
1092
- }