@fluidframework/container-runtime 0.56.0 → 0.57.0-51086

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.map +1 -1
  2. package/dist/blobManager.js +9 -1
  3. package/dist/blobManager.js.map +1 -1
  4. package/dist/connectionTelemetry.d.ts.map +1 -1
  5. package/dist/connectionTelemetry.js +6 -6
  6. package/dist/connectionTelemetry.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +65 -25
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +149 -79
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/dataStore.d.ts +62 -0
  12. package/dist/dataStore.d.ts.map +1 -0
  13. package/dist/dataStore.js +135 -0
  14. package/dist/dataStore.js.map +1 -0
  15. package/dist/dataStoreContext.js.map +1 -1
  16. package/dist/dataStores.d.ts +9 -5
  17. package/dist/dataStores.d.ts.map +1 -1
  18. package/dist/dataStores.js +14 -19
  19. package/dist/dataStores.js.map +1 -1
  20. package/dist/garbageCollection.d.ts +47 -21
  21. package/dist/garbageCollection.d.ts.map +1 -1
  22. package/dist/garbageCollection.js +195 -61
  23. package/dist/garbageCollection.js.map +1 -1
  24. package/dist/index.d.ts +3 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +4 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/packageVersion.d.ts.map +1 -1
  30. package/dist/packageVersion.js +1 -1
  31. package/dist/packageVersion.js.map +1 -1
  32. package/dist/runningSummarizer.d.ts +1 -0
  33. package/dist/runningSummarizer.d.ts.map +1 -1
  34. package/dist/runningSummarizer.js +23 -15
  35. package/dist/runningSummarizer.js.map +1 -1
  36. package/dist/summarizerTypes.d.ts +6 -6
  37. package/dist/summarizerTypes.d.ts.map +1 -1
  38. package/dist/summarizerTypes.js.map +1 -1
  39. package/dist/summaryGenerator.d.ts +2 -1
  40. package/dist/summaryGenerator.d.ts.map +1 -1
  41. package/dist/summaryGenerator.js +46 -28
  42. package/dist/summaryGenerator.js.map +1 -1
  43. package/lib/blobManager.d.ts.map +1 -1
  44. package/lib/blobManager.js +9 -1
  45. package/lib/blobManager.js.map +1 -1
  46. package/lib/connectionTelemetry.d.ts.map +1 -1
  47. package/lib/connectionTelemetry.js +6 -6
  48. package/lib/connectionTelemetry.js.map +1 -1
  49. package/lib/containerRuntime.d.ts +65 -25
  50. package/lib/containerRuntime.d.ts.map +1 -1
  51. package/lib/containerRuntime.js +150 -80
  52. package/lib/containerRuntime.js.map +1 -1
  53. package/lib/dataStore.d.ts +62 -0
  54. package/lib/dataStore.d.ts.map +1 -0
  55. package/lib/dataStore.js +130 -0
  56. package/lib/dataStore.js.map +1 -0
  57. package/lib/dataStoreContext.js.map +1 -1
  58. package/lib/dataStores.d.ts +9 -5
  59. package/lib/dataStores.d.ts.map +1 -1
  60. package/lib/dataStores.js +13 -18
  61. package/lib/dataStores.js.map +1 -1
  62. package/lib/garbageCollection.d.ts +47 -21
  63. package/lib/garbageCollection.d.ts.map +1 -1
  64. package/lib/garbageCollection.js +197 -63
  65. package/lib/garbageCollection.js.map +1 -1
  66. package/lib/index.d.ts +3 -2
  67. package/lib/index.d.ts.map +1 -1
  68. package/lib/index.js +2 -1
  69. package/lib/index.js.map +1 -1
  70. package/lib/packageVersion.d.ts +1 -1
  71. package/lib/packageVersion.d.ts.map +1 -1
  72. package/lib/packageVersion.js +1 -1
  73. package/lib/packageVersion.js.map +1 -1
  74. package/lib/runningSummarizer.d.ts +1 -0
  75. package/lib/runningSummarizer.d.ts.map +1 -1
  76. package/lib/runningSummarizer.js +23 -15
  77. package/lib/runningSummarizer.js.map +1 -1
  78. package/lib/summarizerTypes.d.ts +6 -6
  79. package/lib/summarizerTypes.d.ts.map +1 -1
  80. package/lib/summarizerTypes.js.map +1 -1
  81. package/lib/summaryGenerator.d.ts +2 -1
  82. package/lib/summaryGenerator.d.ts.map +1 -1
  83. package/lib/summaryGenerator.js +46 -28
  84. package/lib/summaryGenerator.js.map +1 -1
  85. package/package.json +13 -13
  86. package/src/blobManager.ts +12 -1
  87. package/src/connectionTelemetry.ts +7 -6
  88. package/src/containerRuntime.ts +231 -103
  89. package/src/dataStore.ts +187 -0
  90. package/src/dataStoreContext.ts +1 -1
  91. package/src/dataStores.ts +18 -38
  92. package/src/garbageCollection.ts +283 -105
  93. package/src/index.ts +3 -1
  94. package/src/packageVersion.ts +1 -1
  95. package/src/runningSummarizer.ts +25 -16
  96. package/src/summarizerTypes.ts +6 -8
  97. package/src/summaryGenerator.ts +72 -23
@@ -3,11 +3,12 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
6
- import { ClientSessionExpiredError } from "@fluidframework/container-utils";
6
+ import { ClientSessionExpiredError, DataProcessingError } from "@fluidframework/container-utils";
7
7
  import { cloneGCData, concatGarbageCollectionStates, concatGarbageCollectionData, runGarbageCollection, unpackChildNodesGCDetails, } from "@fluidframework/garbage-collector";
8
8
  import { gcBlobKey, } from "@fluidframework/runtime-definitions";
9
9
  import { SummaryTreeBuilder, } from "@fluidframework/runtime-utils";
10
- import { ChildLogger, loggerToMonitoringContext, PerformanceEvent, } from "@fluidframework/telemetry-utils";
10
+ import { ChildLogger, loggerToMonitoringContext, PerformanceEvent, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
11
+ import { RuntimeHeaders } from ".";
11
12
  import { getSummaryForDatastores } from "./dataStores";
12
13
  import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
13
14
  /** This is the current version of garbage collection. */
@@ -24,7 +25,11 @@ const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
24
25
  const runSweepKey = "Fluid.GarbageCollection.RunSweep";
25
26
  // Feature gate key to write GC data at the root of the summary tree.
26
27
  const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
28
+ // Feature gate key to expire a session after a set period of time.
29
+ const runSessionExpiry = "Fluid.GarbageCollection.RunSessionExpiry";
27
30
  const defaultDeleteTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
31
+ const defaultSessionExpiryDurationMs = 30 * 24 * 60 * 60 * 1000; // 30 days
32
+ ;
28
33
  /**
29
34
  * Helper class that tracks the state of an unreferenced node such as the time it was unreferenced. It also sets
30
35
  * the node's state to inactive if it remains unreferenced for a given amount of time (inactiveTimeoutMs).
@@ -32,37 +37,25 @@ const defaultDeleteTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
32
37
  class UnreferencedStateTracker {
33
38
  constructor(unreferencedTimestampMs, inactiveTimeoutMs) {
34
39
  this.unreferencedTimestampMs = unreferencedTimestampMs;
35
- this.inactive = false;
36
- // Keeps track of all inactive events that are logged. This is used to limit the log generation for each event to 1
37
- // so that it is not noisy.
38
- this.inactiveEventsLogged = new Set();
40
+ this._inactive = false;
39
41
  // If the timeout has already expired, the node should become inactive immediately. Otherwise, start a timer of
40
42
  // inactiveTimeoutMs after which the node will become inactive.
41
43
  if (inactiveTimeoutMs <= 0) {
42
- this.inactive = true;
44
+ this._inactive = true;
43
45
  }
44
46
  else {
45
- this.timer = new Timer(inactiveTimeoutMs, () => { this.inactive = true; });
47
+ this.timer = new Timer(inactiveTimeoutMs, () => { this._inactive = true; });
46
48
  this.timer.start();
47
49
  }
48
50
  }
51
+ get inactive() {
52
+ return this._inactive;
53
+ }
49
54
  /** Stop tracking this node. Reset the unreferenced timer, if any, and reset inactive state. */
50
55
  stopTracking() {
51
56
  var _a;
52
57
  (_a = this.timer) === null || _a === void 0 ? void 0 : _a.clear();
53
- this.inactive = false;
54
- }
55
- /** Logs an error with the given properties if the node is inactive. */
56
- logIfInactive(logger, eventName, currentTimestampMs, deleteTimeoutMs, inactiveNodeId) {
57
- if (this.inactive && !this.inactiveEventsLogged.has(eventName)) {
58
- logger.sendErrorEvent({
59
- eventName,
60
- age: currentTimestampMs - this.unreferencedTimestampMs,
61
- timeout: deleteTimeoutMs,
62
- id: inactiveNodeId,
63
- });
64
- this.inactiveEventsLogged.add(eventName);
65
- }
58
+ this._inactive = false;
66
59
  }
67
60
  }
68
61
  /**
@@ -73,12 +66,15 @@ export class GarbageCollector {
73
66
  constructor(provider, gcOptions,
74
67
  /** After GC has run, called to delete objects in the runtime whose routes are unused. */
75
68
  deleteUnusedRoutes,
69
+ /** For a given node path, returns the node's package path. */
70
+ getNodePackagePath,
76
71
  /** Returns the current timestamp to be assigned to nodes that become unreferenced. */
77
72
  getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata) {
78
73
  var _a, _b, _c, _d, _e;
79
74
  this.provider = provider;
80
75
  this.gcOptions = gcOptions;
81
76
  this.deleteUnusedRoutes = deleteUnusedRoutes;
77
+ this.getNodePackagePath = getNodePackagePath;
82
78
  this.getCurrentTimestampMs = getCurrentTimestampMs;
83
79
  this.closeFn = closeFn;
84
80
  /**
@@ -103,6 +99,11 @@ export class GarbageCollector {
103
99
  this.referencesSinceLastRun = new Map();
104
100
  // Map of node ids to their unreferenced state tracker.
105
101
  this.unreferencedNodesState = new Map();
102
+ // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
103
+ // per event per node.
104
+ this.loggedUnreferencedEvents = new Set();
105
+ // Queue for unreferenced events that should be logged the next time GC runs.
106
+ this.pendingEventsQueue = [];
106
107
  this.mc = loggerToMonitoringContext(ChildLogger.create(baseLogger, "GarbageCollector"));
107
108
  this.deleteTimeoutMs = (_a = this.gcOptions.deleteTimeoutMs) !== null && _a !== void 0 ? _a : defaultDeleteTimeoutMs;
108
109
  let prevSummaryGCVersion;
@@ -119,12 +120,19 @@ export class GarbageCollector {
119
120
  else {
120
121
  // For new documents, GC has to be exlicitly enabled via the gcAllowed flag in GC options.
121
122
  this.gcEnabled = gcOptions.gcAllowed === true;
122
- this.sessionExpiryTimeoutMs = this.gcOptions.gcTestSessionTimeoutMs;
123
+ // Set the Session Expiry only if the flag is enabled or the test option is set.
124
+ if (this.mc.config.getBoolean(runSessionExpiry) && this.gcEnabled) {
125
+ this.sessionExpiryTimeoutMs = defaultSessionExpiryDurationMs;
126
+ }
123
127
  }
124
128
  // If session expiry is enabled, we need to close the container when the timeout expires
125
129
  if (this.sessionExpiryTimeoutMs !== undefined) {
126
- const expiryMs = this.sessionExpiryTimeoutMs;
127
- this.sessionExpiryTimer = setTimeout(() => this.closeFn(new ClientSessionExpiredError(`Client session expired.`, expiryMs)), expiryMs);
130
+ const timeoutMs = this.sessionExpiryTimeoutMs;
131
+ setLongTimeout(timeoutMs, () => {
132
+ this.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
133
+ }, (timer) => {
134
+ this.sessionExpiryTimer = timer;
135
+ });
128
136
  }
129
137
  // For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
130
138
  // latest tracked GC version. For new documents, we will be writing the first summary with the current version.
@@ -253,9 +261,22 @@ export class GarbageCollector {
253
261
  }
254
262
  return dataStoreGCDetailsMap;
255
263
  });
264
+ // Initialize the base state. The base GC data is used to detect and log when inactive / deleted objects are
265
+ // used in the container.
266
+ if (this.shouldRunGC) {
267
+ this.initializeBaseStateP.catch((error) => {
268
+ throw new DataProcessingError(error === null || error === void 0 ? void 0 : error.message, "FailedToInitializeGC", {
269
+ gcEnabled: this.gcEnabled,
270
+ runSweep: this.shouldRunSweep,
271
+ writeAtRoot: this._writeDataAtRoot,
272
+ testMode: this.testMode,
273
+ sessionExpiry: this.sessionExpiryTimeoutMs,
274
+ });
275
+ });
276
+ }
256
277
  }
257
- static create(provider, gcOptions, deleteUnusedRoutes, getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata) {
258
- return new GarbageCollector(provider, gcOptions, deleteUnusedRoutes, getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata);
278
+ static create(provider, gcOptions, deleteUnusedRoutes, getNodePackagePath, getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata) {
279
+ return new GarbageCollector(provider, gcOptions, deleteUnusedRoutes, getNodePackagePath, getCurrentTimestampMs, closeFn, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata);
259
280
  }
260
281
  /**
261
282
  * This tracks two things:
@@ -290,29 +311,26 @@ export class GarbageCollector {
290
311
  await this.initializeBaseStateP;
291
312
  // Let the runtime update its pending state before GC runs.
292
313
  await this.provider.updateStateBeforeGC();
293
- const gcStats = {};
294
314
  // Get the runtime's GC data and run GC on the reference graph in it.
295
315
  const gcData = await this.provider.getGCData(fullGC);
296
- this.updateStateSinceLatestRun(gcData);
297
316
  const gcResult = runGarbageCollection(gcData.gcNodes, ["/"], logger);
298
- const currentTimestampMs = this.getCurrentTimestampMs();
317
+ const gcStats = this.generateStatsAndLogEvents(gcResult);
318
+ // Update the state since the last GC run. There can be nodes that were referenced between the last and
319
+ // the current run. We need to identify than and update their unreferenced state if needed.
320
+ this.updateStateSinceLastRun(gcData);
299
321
  // Update the current state of the system based on the GC run.
322
+ const currentTimestampMs = this.getCurrentTimestampMs();
300
323
  this.updateCurrentState(gcData, gcResult, currentTimestampMs);
301
- const dataStoreUsedStateStats = this.provider.updateUsedRoutes(gcResult.referencedNodeIds, currentTimestampMs);
324
+ this.provider.updateUsedRoutes(gcResult.referencedNodeIds, currentTimestampMs);
302
325
  if (runSweep) {
303
326
  // Placeholder for running sweep logic.
304
327
  }
305
- // Update stats to be reported in the peformance event.
306
- gcStats.deletedNodes = gcResult.deletedNodeIds.length;
307
- gcStats.totalNodes = gcResult.referencedNodeIds.length + gcResult.deletedNodeIds.length;
308
- gcStats.deletedDataStores = dataStoreUsedStateStats.unusedNodeCount;
309
- gcStats.totalDataStores = dataStoreUsedStateStats.totalNodeCount;
310
328
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
311
329
  // involving access to deleted data.
312
330
  if (this.testMode) {
313
331
  this.deleteUnusedRoutes(gcResult.deletedNodeIds);
314
332
  }
315
- event.end(gcStats);
333
+ event.end(Object.assign({}, gcStats));
316
334
  return gcStats;
317
335
  }, { end: true, cancel: "error" });
318
336
  }
@@ -366,32 +384,41 @@ export class GarbageCollector {
366
384
  await this.updateSummaryGCVersionFromSnapshot(result.snapshot, readAndParseBlob);
367
385
  }
368
386
  /**
369
- * Called when a node with the given id is changed. If the node is inactive, log an error.
387
+ * Called when a node with the given id is updated. If the node is inactive, log an error.
388
+ * @param nodePath - The id of the node that changed.
389
+ * @param reason - Whether the node was loaded or changed.
390
+ * @param packagePath - The package path of the node. This may not be available if the node hasn't been loaded yet.
391
+ * @param requestHeaders - If the node was loaded via request path, the headers in the request.
370
392
  */
371
- nodeChanged(id) {
372
- var _a;
373
- // Prefix "/" if needed to make it relative to the root.
374
- const nodeId = id.startsWith("/") ? id : `/${id}`;
375
- (_a = this.unreferencedNodesState.get(nodeId)) === null || _a === void 0 ? void 0 : _a.logIfInactive(this.mc.logger, "inactiveObjectChanged", this.getCurrentTimestampMs(), this.deleteTimeoutMs, nodeId);
376
- }
377
- dispose() {
378
- if (this.sessionExpiryTimer !== undefined) {
379
- clearTimeout(this.sessionExpiryTimer);
380
- this.sessionExpiryTimer = undefined;
393
+ nodeUpdated(nodePath, reason, packagePath, requestHeaders) {
394
+ if (!this.shouldRunGC) {
395
+ return;
381
396
  }
397
+ this.logIfInactive(reason, nodePath, this.getCurrentTimestampMs(), packagePath, requestHeaders);
382
398
  }
383
399
  /**
384
400
  * Called when an outbound reference is added to a node. This is used to identify all nodes that have been
385
401
  * referenced between summaries so that their unreferenced timestamp can be reset.
386
402
  *
387
- * @param fromNodeId - The node from which the reference is added.
388
- * @param toNodeId - The node to which the reference is added.
403
+ * @param fromNodePath - The node from which the reference is added.
404
+ * @param toNodePath - The node to which the reference is added.
389
405
  */
390
- addedOutboundReference(fromNodeId, toNodeId) {
406
+ addedOutboundReference(fromNodePath, toNodePath) {
391
407
  var _a;
392
- const outboundRoutes = (_a = this.referencesSinceLastRun.get(fromNodeId)) !== null && _a !== void 0 ? _a : [];
393
- outboundRoutes.push(toNodeId);
394
- this.referencesSinceLastRun.set(fromNodeId, outboundRoutes);
408
+ if (!this.shouldRunGC) {
409
+ return;
410
+ }
411
+ const outboundRoutes = (_a = this.referencesSinceLastRun.get(fromNodePath)) !== null && _a !== void 0 ? _a : [];
412
+ outboundRoutes.push(toNodePath);
413
+ this.referencesSinceLastRun.set(fromNodePath, outboundRoutes);
414
+ // If the node that got referenced is inactive, log an event as that may indicate use-after-delete.
415
+ this.logIfInactive("Revived", toNodePath, this.getCurrentTimestampMs(), this.getNodePackagePath(toNodePath));
416
+ }
417
+ dispose() {
418
+ if (this.sessionExpiryTimer !== undefined) {
419
+ clearTimeout(this.sessionExpiryTimer);
420
+ this.sessionExpiryTimer = undefined;
421
+ }
395
422
  }
396
423
  /**
397
424
  * Update the latest summary GC version from the metadata blob in the given snapshot.
@@ -432,9 +459,6 @@ export class GarbageCollector {
432
459
  for (const nodeId of gcResult.referencedNodeIds) {
433
460
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
434
461
  if (nodeStateTracker !== undefined) {
435
- // If this node has been unreferenced for longer than deleteTimeoutMs and is being referenced,
436
- // log an error as this may mean the deleteTimeoutMs is not long enough.
437
- nodeStateTracker.logIfInactive(this.mc.logger, "inactiveObjectRevived", currentTimestampMs, this.deleteTimeoutMs, nodeId);
438
462
  // Stop tracking so as to clear out any running timers.
439
463
  nodeStateTracker.stopTracking();
440
464
  // Delete the node as we don't need to track it any more.
@@ -450,7 +474,7 @@ export class GarbageCollector {
450
474
  * This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
451
475
  * If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
452
476
  */
453
- updateStateSinceLatestRun(currentGCData) {
477
+ updateStateSinceLastRun(currentGCData) {
454
478
  // If we haven't run GC before or no references were added since the last run, there is nothing to do.
455
479
  if (this.gcDataFromLastRun === undefined || this.referencesSinceLastRun.size === 0) {
456
480
  return;
@@ -506,8 +530,7 @@ export class GarbageCollector {
506
530
  * @param currentGCData - The GC data (reference graph) from the current GC run.
507
531
  */
508
532
  validateReferenceCorrectness(currentGCData) {
509
- assert(this.gcDataFromLastRun !== undefined, 0x2b7
510
- /* "Can't validate correctness without GC data from last run" */ );
533
+ assert(this.gcDataFromLastRun !== undefined, 0x2b7);
511
534
  // Get a list of all the outbound routes (or references) in the current GC data.
512
535
  const currentReferences = [];
513
536
  for (const [nodeId, outboundRoutes] of Object.entries(currentGCData.gcNodes)) {
@@ -531,9 +554,9 @@ export class GarbageCollector {
531
554
  // Validate that the current reference graph doesn't have references that we are not already aware of. If this
532
555
  // happens, it might indicate data corruption since we may delete objects prematurely.
533
556
  currentReferences.forEach((route) => {
534
- // Validate references for data stores only whose routes are of the format "/dataStoreId". Currently, layers
535
- // below data stores don't have GC implemented so there is no guarantee their references will be notified.
536
- if (route.split("/").length === 2 && !explicitReferences.includes(route)) {
557
+ // Validate references for data stores only. Currently, layers below data stores don't have GC implemented
558
+ // so there is no guarantee their references will be notified.
559
+ if (isDataStoreNode(route) && !explicitReferences.includes(route)) {
537
560
  /**
538
561
  * The following log will be enabled once this issue is resolved:
539
562
  * https://github.com/microsoft/FluidFramework/issues/8878.
@@ -547,11 +570,95 @@ export class GarbageCollector {
547
570
  }
548
571
  });
549
572
  }
573
+ /**
574
+ * Generates the stats of a garbage collection run from the given results of the run. Also, logs any pending events
575
+ * in the pendingEventsQueue.
576
+ * @param gcResult - The result of a GC run.
577
+ * @returns the GC stats of the GC run.
578
+ */
579
+ generateStatsAndLogEvents(gcResult) {
580
+ // Log pending events for unreferenced nodes after GC has run. We should have the package data available for
581
+ // them now since the GC run should have loaded these nodes.
582
+ let event = this.pendingEventsQueue.shift();
583
+ while (event !== undefined) {
584
+ const pkg = this.getNodePackagePath(event.id);
585
+ this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, event), { pkg: pkg ? { value: `/${pkg.join("/")}`, tag: TelemetryDataTag.PackageData } : undefined }));
586
+ event = this.pendingEventsQueue.shift();
587
+ }
588
+ const gcStats = {
589
+ nodeCount: 0,
590
+ dataStoreCount: 0,
591
+ unrefNodeCount: 0,
592
+ unrefDataStoreCount: 0,
593
+ updatedNodeCount: 0,
594
+ updatedDataStoreCount: 0,
595
+ };
596
+ for (const nodeId of gcResult.referencedNodeIds) {
597
+ gcStats.nodeCount++;
598
+ const isDataStore = isDataStoreNode(nodeId);
599
+ if (isDataStore) {
600
+ gcStats.dataStoreCount++;
601
+ }
602
+ // If a referenced node has an entry in `unreferencedNodesState`, it was previously unreferenced. So, its
603
+ // reference state updated from the last GC run.
604
+ if (this.unreferencedNodesState.has(nodeId)) {
605
+ gcStats.updatedNodeCount++;
606
+ if (isDataStore) {
607
+ gcStats.updatedDataStoreCount++;
608
+ }
609
+ }
610
+ }
611
+ for (const nodeId of gcResult.deletedNodeIds) {
612
+ gcStats.nodeCount++;
613
+ gcStats.unrefNodeCount++;
614
+ const isDataStore = isDataStoreNode(nodeId);
615
+ if (isDataStore) {
616
+ gcStats.dataStoreCount++;
617
+ gcStats.unrefDataStoreCount++;
618
+ }
619
+ // If an unreferenced node doesn't an entry in `unreferencedNodesState`, it was previously referenced. So,
620
+ // its reference state updated from the last GC run.
621
+ if (!this.unreferencedNodesState.has(nodeId)) {
622
+ gcStats.updatedNodeCount++;
623
+ if (isDataStore) {
624
+ gcStats.updatedDataStoreCount++;
625
+ }
626
+ }
627
+ }
628
+ return gcStats;
629
+ }
630
+ /**
631
+ * Logs an event if a node is inactive and is used. If the package data for the node exists, log immediately. Else,
632
+ * queue it and it will be logged the next time GC runs as the package data should be available then.
633
+ */
634
+ logIfInactive(eventSuffix, nodeId, timestampMs, packagePath, requestHeaders) {
635
+ const eventName = `inactiveObject_${eventSuffix}`;
636
+ // We log a particular event for a given node only once so that it is not too noisy.
637
+ const uniqueEventId = `${nodeId}-${eventName}`;
638
+ const nodeState = this.unreferencedNodesState.get(nodeId);
639
+ if ((nodeState === null || nodeState === void 0 ? void 0 : nodeState.inactive) && !this.loggedUnreferencedEvents.has(uniqueEventId)) {
640
+ this.loggedUnreferencedEvents.add(uniqueEventId);
641
+ const event = {
642
+ eventName,
643
+ id: nodeId,
644
+ age: timestampMs - nodeState.unreferencedTimestampMs,
645
+ timeout: this.deleteTimeoutMs,
646
+ externalRequest: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.externalRequest],
647
+ viaHandle: requestHeaders === null || requestHeaders === void 0 ? void 0 : requestHeaders[RuntimeHeaders.viaHandle],
648
+ };
649
+ if (packagePath !== undefined) {
650
+ this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, event), { pkg: { value: `/${packagePath.join("/")}`, tag: TelemetryDataTag.PackageData } }));
651
+ }
652
+ else {
653
+ this.pendingEventsQueue.push(event);
654
+ }
655
+ }
656
+ }
550
657
  }
551
658
  /**
552
659
  * Gets the garbage collection state from the given snapshot tree. The GC state may be written into multiple blobs.
553
660
  * Merge the GC state from all such blobs and return the merged GC state.
554
- */
661
+ */
555
662
  async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
556
663
  let rootGCState = { gcNodes: {} };
557
664
  for (const key of Object.keys(gcSnapshotTree.blobs)) {
@@ -570,4 +677,31 @@ async function getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob) {
570
677
  }
571
678
  return rootGCState;
572
679
  }
680
+ /**
681
+ * setLongTimeout is used for timeouts longer than setTimeout's ~24.8 day max
682
+ * @param timeoutMs - the total time the timeout needs to last in ms
683
+ * @param timeoutFn - the function to execute when the timer ends
684
+ * @param setTimerFn - the function used to update your timer variable
685
+ */
686
+ function setLongTimeout(timeoutMs, timeoutFn, setTimerFn) {
687
+ // The setTimeout max is 24.8 days before looping occurs.
688
+ const maxTimeout = 2147483647;
689
+ let timer;
690
+ if (timeoutMs > maxTimeout) {
691
+ const newTimeoutMs = timeoutMs - maxTimeout;
692
+ timer = setTimeout(() => setLongTimeout(newTimeoutMs, timeoutFn, setTimerFn), maxTimeout);
693
+ }
694
+ else {
695
+ timer = setTimeout(() => timeoutFn(), timeoutMs);
696
+ }
697
+ setTimerFn(timer);
698
+ }
699
+ /**
700
+ * Given a GC nodeId, tells whether it belongs to a data store or not.
701
+ */
702
+ function isDataStoreNode(nodeId) {
703
+ const pathParts = nodeId.split("/");
704
+ // Data store ids are in the format "/dataStoreId".
705
+ return pathParts.length === 2 && pathParts[1] !== "" ? true : false;
706
+ }
573
707
  //# sourceMappingURL=garbageCollection.js.map