@fluidframework/container-runtime 2.0.0-internal.2.1.1 → 2.0.0-internal.2.2.0

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 (161) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/blobManager.d.ts +20 -5
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +57 -15
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +39 -40
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +115 -278
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStoreContext.d.ts +3 -1
  11. package/dist/dataStoreContext.d.ts.map +1 -1
  12. package/dist/dataStoreContext.js +21 -3
  13. package/dist/dataStoreContext.js.map +1 -1
  14. package/dist/dataStores.d.ts +8 -5
  15. package/dist/dataStores.d.ts.map +1 -1
  16. package/dist/dataStores.js +26 -13
  17. package/dist/dataStores.js.map +1 -1
  18. package/dist/garbageCollection.d.ts +15 -17
  19. package/dist/garbageCollection.d.ts.map +1 -1
  20. package/dist/garbageCollection.js +92 -106
  21. package/dist/garbageCollection.js.map +1 -1
  22. package/dist/garbageCollectionConstants.d.ts +19 -0
  23. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  24. package/dist/garbageCollectionConstants.js +34 -0
  25. package/dist/garbageCollectionConstants.js.map +1 -0
  26. package/dist/gcSweepReadyUsageDetection.js +2 -2
  27. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  28. package/dist/index.d.ts +4 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +8 -6
  31. package/dist/index.js.map +1 -1
  32. package/dist/opLifecycle/batchManager.d.ts +30 -0
  33. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  34. package/dist/{batchManager.js → opLifecycle/batchManager.js} +25 -10
  35. package/dist/opLifecycle/batchManager.js.map +1 -0
  36. package/dist/opLifecycle/definitions.d.ts +40 -0
  37. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  38. package/dist/opLifecycle/definitions.js +7 -0
  39. package/dist/opLifecycle/definitions.js.map +1 -0
  40. package/dist/opLifecycle/index.d.ts +12 -0
  41. package/dist/opLifecycle/index.d.ts.map +1 -0
  42. package/dist/opLifecycle/index.js +21 -0
  43. package/dist/opLifecycle/index.js.map +1 -0
  44. package/dist/opLifecycle/opCompressor.d.ts +18 -0
  45. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  46. package/dist/opLifecycle/opCompressor.js +53 -0
  47. package/dist/opLifecycle/opCompressor.js.map +1 -0
  48. package/dist/opLifecycle/opDecompressor.d.ts +20 -0
  49. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  50. package/dist/opLifecycle/opDecompressor.js +72 -0
  51. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  52. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  53. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  54. package/dist/opLifecycle/opSplitter.js +61 -0
  55. package/dist/opLifecycle/opSplitter.js.map +1 -0
  56. package/dist/opLifecycle/outbox.d.ts +47 -0
  57. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  58. package/dist/opLifecycle/outbox.js +153 -0
  59. package/dist/opLifecycle/outbox.js.map +1 -0
  60. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  61. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  62. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  63. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  64. package/dist/packageVersion.d.ts +1 -1
  65. package/dist/packageVersion.js +1 -1
  66. package/dist/packageVersion.js.map +1 -1
  67. package/dist/summaryFormat.js +2 -2
  68. package/dist/summaryFormat.js.map +1 -1
  69. package/lib/blobManager.d.ts +20 -5
  70. package/lib/blobManager.d.ts.map +1 -1
  71. package/lib/blobManager.js +59 -17
  72. package/lib/blobManager.js.map +1 -1
  73. package/lib/containerRuntime.d.ts +39 -40
  74. package/lib/containerRuntime.d.ts.map +1 -1
  75. package/lib/containerRuntime.js +113 -275
  76. package/lib/containerRuntime.js.map +1 -1
  77. package/lib/dataStoreContext.d.ts +3 -1
  78. package/lib/dataStoreContext.d.ts.map +1 -1
  79. package/lib/dataStoreContext.js +23 -5
  80. package/lib/dataStoreContext.js.map +1 -1
  81. package/lib/dataStores.d.ts +8 -5
  82. package/lib/dataStores.d.ts.map +1 -1
  83. package/lib/dataStores.js +28 -15
  84. package/lib/dataStores.js.map +1 -1
  85. package/lib/garbageCollection.d.ts +15 -17
  86. package/lib/garbageCollection.d.ts.map +1 -1
  87. package/lib/garbageCollection.js +72 -86
  88. package/lib/garbageCollection.js.map +1 -1
  89. package/lib/garbageCollectionConstants.d.ts +19 -0
  90. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  91. package/lib/garbageCollectionConstants.js +31 -0
  92. package/lib/garbageCollectionConstants.js.map +1 -0
  93. package/lib/gcSweepReadyUsageDetection.js +1 -1
  94. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  95. package/lib/index.d.ts +4 -2
  96. package/lib/index.d.ts.map +1 -1
  97. package/lib/index.js +3 -2
  98. package/lib/index.js.map +1 -1
  99. package/lib/opLifecycle/batchManager.d.ts +30 -0
  100. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  101. package/lib/{batchManager.js → opLifecycle/batchManager.js} +25 -10
  102. package/lib/opLifecycle/batchManager.js.map +1 -0
  103. package/lib/opLifecycle/definitions.d.ts +40 -0
  104. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  105. package/lib/opLifecycle/definitions.js +6 -0
  106. package/lib/opLifecycle/definitions.js.map +1 -0
  107. package/lib/opLifecycle/index.d.ts +12 -0
  108. package/lib/opLifecycle/index.d.ts.map +1 -0
  109. package/lib/opLifecycle/index.js +11 -0
  110. package/lib/opLifecycle/index.js.map +1 -0
  111. package/lib/opLifecycle/opCompressor.d.ts +18 -0
  112. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  113. package/lib/opLifecycle/opCompressor.js +49 -0
  114. package/lib/opLifecycle/opCompressor.js.map +1 -0
  115. package/lib/opLifecycle/opDecompressor.d.ts +20 -0
  116. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  117. package/lib/opLifecycle/opDecompressor.js +68 -0
  118. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  119. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  120. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  121. package/lib/opLifecycle/opSplitter.js +57 -0
  122. package/lib/opLifecycle/opSplitter.js.map +1 -0
  123. package/lib/opLifecycle/outbox.d.ts +47 -0
  124. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  125. package/lib/opLifecycle/outbox.js +149 -0
  126. package/lib/opLifecycle/outbox.js.map +1 -0
  127. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  128. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  129. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  130. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  131. package/lib/packageVersion.d.ts +1 -1
  132. package/lib/packageVersion.js +1 -1
  133. package/lib/packageVersion.js.map +1 -1
  134. package/lib/summaryFormat.js +1 -1
  135. package/lib/summaryFormat.js.map +1 -1
  136. package/package.json +35 -21
  137. package/prettier.config.cjs +8 -0
  138. package/src/blobManager.ts +74 -19
  139. package/src/containerRuntime.ts +144 -341
  140. package/src/dataStoreContext.ts +33 -5
  141. package/src/dataStores.ts +32 -16
  142. package/src/garbageCollection.ts +106 -82
  143. package/src/garbageCollectionConstants.ts +35 -0
  144. package/src/gcSweepReadyUsageDetection.ts +1 -1
  145. package/src/index.ts +6 -4
  146. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +41 -23
  147. package/src/opLifecycle/definitions.ts +44 -0
  148. package/src/opLifecycle/index.ts +17 -0
  149. package/src/opLifecycle/opCompressor.ts +64 -0
  150. package/src/opLifecycle/opDecompressor.ts +84 -0
  151. package/src/opLifecycle/opSplitter.ts +78 -0
  152. package/src/opLifecycle/outbox.ts +204 -0
  153. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  154. package/src/packageVersion.ts +1 -1
  155. package/src/summaryFormat.ts +1 -1
  156. package/dist/batchManager.d.ts +0 -36
  157. package/dist/batchManager.d.ts.map +0 -1
  158. package/dist/batchManager.js.map +0 -1
  159. package/lib/batchManager.d.ts +0 -36
  160. package/lib/batchManager.d.ts.map +0 -1
  161. package/lib/batchManager.js.map +0 -1
@@ -58,10 +58,16 @@ import {
58
58
  SummarizeInternalFn,
59
59
  ITelemetryContext,
60
60
  } from "@fluidframework/runtime-definitions";
61
- import { addBlobToSummary, convertSummaryTreeToITree } from "@fluidframework/runtime-utils";
61
+ import {
62
+ addBlobToSummary,
63
+ convertSummaryTreeToITree,
64
+ packagePathToTelemetryProperty,
65
+ } from "@fluidframework/runtime-utils";
62
66
  import {
63
67
  ChildLogger,
68
+ loggerToMonitoringContext,
64
69
  LoggingError,
70
+ MonitoringContext,
65
71
  TelemetryDataTag,
66
72
  ThresholdCounter,
67
73
  } from "@fluidframework/telemetry-utils";
@@ -81,6 +87,8 @@ import {
81
87
  getAttributesFormatVersion,
82
88
  getFluidDataStoreAttributes,
83
89
  } from "./summaryFormat";
90
+ import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
91
+ import { summarizerClientType } from "./summarizerClientElection";
84
92
 
85
93
  function createAttributes(
86
94
  pkg: readonly string[],
@@ -200,6 +208,8 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
200
208
  */
201
209
  private _tombstoned = false;
202
210
  public get tombstoned() { return this._tombstoned; }
211
+ /** If true, throw an error when a tombstone data store is used. */
212
+ private readonly throwOnTombstoneUsage: boolean;
203
213
 
204
214
  public get attachState(): AttachState {
205
215
  return this._attachState;
@@ -243,7 +253,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
243
253
  protected _attachState: AttachState;
244
254
  private _isInMemoryRoot: boolean = false;
245
255
  protected readonly summarizerNode: ISummarizerNodeWithGC;
246
- private readonly subLogger: ITelemetryLogger;
256
+ private readonly mc: MonitoringContext;
247
257
  private readonly thresholdOpsCounter: ThresholdCounter;
248
258
  private static readonly pendingOpsCountThreshold = 1000;
249
259
 
@@ -297,8 +307,13 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
297
307
  async () => this.getBaseGCDetails(),
298
308
  );
299
309
 
300
- this.subLogger = ChildLogger.create(this.logger, "FluidDataStoreContext");
301
- this.thresholdOpsCounter = new ThresholdCounter(FluidDataStoreContext.pendingOpsCountThreshold, this.subLogger);
310
+ this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "FluidDataStoreContext"));
311
+ this.thresholdOpsCounter = new ThresholdCounter(FluidDataStoreContext.pendingOpsCountThreshold, this.mc.logger);
312
+
313
+ // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
314
+ this.throwOnTombstoneUsage =
315
+ this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
316
+ this.clientDetails.type !== summarizerClientType;
302
317
  }
303
318
 
304
319
  public dispose(): void {
@@ -760,10 +775,23 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
760
775
 
761
776
  if (checkTombstone && this.tombstoned) {
762
777
  const messageString = `Context is tombstoned! Call site [${callSite}]`;
763
- throw new DataCorruptionError(messageString, {
778
+ const error = new DataCorruptionError(messageString, {
764
779
  errorMessage: messageString,
765
780
  ...safeTelemetryProps,
766
781
  });
782
+
783
+ // Always log an error when tombstoned data store is used. However, throw an error only if
784
+ // throwOnTombstoneUsage is set.
785
+ this.mc.logger.sendErrorEvent({
786
+ eventName: "GC_Tombstone_DataStore_Changed",
787
+ callSite,
788
+ pkg: packagePathToTelemetryProperty(this.pkg),
789
+ }, error);
790
+ // Always log an error when tombstoned data store is used. However, throw an error only if
791
+ // throwOnTombstoneUsage is set and the client is not a summarizer.
792
+ if (this.throwOnTombstoneUsage) {
793
+ throw error;
794
+ }
767
795
  }
768
796
  }
769
797
 
package/src/dataStores.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLogger, ITelemetryBaseLogger, IDisposable } from "@fluidframework/common-definitions";
6
+ import { ITelemetryBaseLogger, IDisposable } from "@fluidframework/common-definitions";
7
7
  import { DataCorruptionError, extractSafePropertiesFromMessage } from "@fluidframework/container-utils";
8
8
  import { IFluidHandle } from "@fluidframework/core-interfaces";
9
9
  import { FluidObjectHandle } from "@fluidframework/datastore";
@@ -34,9 +34,10 @@ import {
34
34
  create404Response,
35
35
  createResponseError,
36
36
  responseToException,
37
+ packagePathToTelemetryProperty,
37
38
  SummaryTreeBuilder,
38
39
  } from "@fluidframework/runtime-utils";
39
- import { ChildLogger, LoggingError, TelemetryDataTag } from "@fluidframework/telemetry-utils";
40
+ import { ChildLogger, loggerToMonitoringContext, LoggingError, MonitoringContext, TelemetryDataTag } from "@fluidframework/telemetry-utils";
40
41
  import { AttachState } from "@fluidframework/container-definitions";
41
42
  import { BlobCacheStorageService, buildSnapshotTree } from "@fluidframework/driver-utils";
42
43
  import { assert, Lazy, LazyPromise } from "@fluidframework/common-utils";
@@ -54,6 +55,8 @@ import {
54
55
  import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summaryFormat";
55
56
  import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
56
57
  import { GCNodeType } from "./garbageCollection";
58
+ import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
59
+ import { summarizerClientType } from "./summarizerClientElection";
57
60
 
58
61
  type PendingAliasResolve = (success: boolean) => void;
59
62
 
@@ -67,7 +70,7 @@ export class DataStores implements IDisposable {
67
70
  // 0.24 back-compat attachingBeforeSummary
68
71
  public readonly attachOpFiredForDataStore = new Set<string>();
69
72
 
70
- private readonly logger: ITelemetryLogger;
73
+ private readonly mc: MonitoringContext;
71
74
 
72
75
  private readonly disposeOnce = new Lazy<void>(() => this.contexts.dispose());
73
76
 
@@ -81,6 +84,8 @@ export class DataStores implements IDisposable {
81
84
  // Stores the ids of new data stores between two GC runs. This is used to notify the garbage collector of new
82
85
  // root data stores that are added.
83
86
  private dataStoresSinceLastGC: string[] = [];
87
+ /** If true, throw an error when a tombstone data store is retrieved. */
88
+ private readonly throwOnTombstoneUsage: boolean;
84
89
  // The handle to the container runtime. This is used mainly for GC purposes to represent outbound reference from
85
90
  // the container runtime to other nodes.
86
91
  private readonly containerRuntimeHandle: IFluidHandle;
@@ -100,7 +105,7 @@ export class DataStores implements IDisposable {
100
105
  private readonly aliasMap: Map<string, string>,
101
106
  private readonly contexts: DataStoreContexts = new DataStoreContexts(baseLogger),
102
107
  ) {
103
- this.logger = ChildLogger.create(baseLogger);
108
+ this.mc = loggerToMonitoringContext(ChildLogger.create(baseLogger));
104
109
  this.containerRuntimeHandle = new FluidObjectHandle(this.runtime, "/", this.runtime.IFluidHandleContext);
105
110
 
106
111
  const baseGCDetailsP = new LazyPromise(async () => {
@@ -111,6 +116,10 @@ export class DataStores implements IDisposable {
111
116
  const baseGCDetails = await baseGCDetailsP;
112
117
  return baseGCDetails.get(dataStoreId);
113
118
  };
119
+ // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
120
+ this.throwOnTombstoneUsage =
121
+ this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
122
+ this.runtime.clientDetails.type !== summarizerClientType;
114
123
 
115
124
  // Extract stores stored inside the snapshot
116
125
  const fluidDataStores = new Map<string, ISnapshotTree>();
@@ -280,7 +289,7 @@ export class DataStores implements IDisposable {
280
289
 
281
290
  const context = this.contexts.get(aliasMessage.internalId);
282
291
  if (context === undefined) {
283
- this.logger.sendErrorEvent({
292
+ this.mc.logger.sendErrorEvent({
284
293
  eventName: "AliasFluidDataStoreNotFound",
285
294
  fluidDataStoreId: aliasMessage.internalId,
286
295
  });
@@ -429,14 +438,20 @@ export class DataStores implements IDisposable {
429
438
  }
430
439
 
431
440
  if (context.tombstoned) {
441
+ // The requested data store is removed by gc. Create a 404 gc response exception.
442
+ const error = responseToException(createResponseError(404, "Datastore removed by gc", request), request);
432
443
  // Note: if a user writes a request to look like it's viaHandle, we will also send this telemetry event
433
- this.logger.sendErrorEvent({
434
- eventName: "TombstonedDataStoreRequested",
444
+ this.mc.logger.sendErrorEvent({
445
+ eventName: "GC_Tombstone_DataStore_Requested",
435
446
  url: request.url,
447
+ pkg: packagePathToTelemetryProperty(context.isLoaded ? context.packagePath : undefined),
436
448
  viaHandle,
437
- });
438
- // The requested data store is removed by gc. Throw a 404 gc response exception.
439
- throw responseToException(createResponseError(404, "Datastore removed by gc", request), request);
449
+ }, error);
450
+ // Always log an error when tombstoned data store is used. However, throw an error only if
451
+ // throwOnTombstoneUsage is set.
452
+ if (this.throwOnTombstoneUsage) {
453
+ throw error;
454
+ }
440
455
  }
441
456
 
442
457
  return context;
@@ -447,7 +462,7 @@ export class DataStores implements IDisposable {
447
462
  if (!context) {
448
463
  // Attach message may not have been processed yet
449
464
  assert(!local, 0x163 /* "Missing datastore for local signal" */);
450
- this.logger.sendTelemetryEvent({
465
+ this.mc.logger.sendTelemetryEvent({
451
466
  eventName: "SignalFluidDataStoreNotFound",
452
467
  fluidDataStoreId: {
453
468
  value: address,
@@ -465,7 +480,7 @@ export class DataStores implements IDisposable {
465
480
  try {
466
481
  context.setConnectionState(connected, clientId);
467
482
  } catch (error) {
468
- this.logger.sendErrorEvent({
483
+ this.mc.logger.sendErrorEvent({
469
484
  eventName: "SetConnectionStateError",
470
485
  clientId,
471
486
  fluidDataStore,
@@ -623,12 +638,13 @@ export class DataStores implements IDisposable {
623
638
  }
624
639
 
625
640
  /**
626
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
627
- * scenarios with accessing deleted content.
641
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
642
+ * tombstones.
628
643
  * @param unusedRoutes - The routes that are unused in all data stores in this Container.
629
- * @param tombstone - set the objects corresponding to routes as tombstones.
644
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
645
+ * are deleted.
630
646
  */
631
- public deleteUnusedRoutes(unusedRoutes: string[], tombstone: boolean = false) {
647
+ public updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean) {
632
648
  for (const route of unusedRoutes) {
633
649
  const pathParts = route.split("/");
634
650
  // Delete data store only if its route (/datastoreId) is in unusedRoutes. We don't want to delete a data
@@ -29,6 +29,7 @@ import {
29
29
  } from "@fluidframework/runtime-definitions";
30
30
  import {
31
31
  mergeStats,
32
+ packagePathToTelemetryProperty,
32
33
  ReadAndParseBlob,
33
34
  RefreshSummaryResult,
34
35
  SummaryTreeBuilder,
@@ -44,6 +45,21 @@ import {
44
45
 
45
46
  import { IGCRuntimeOptions, RuntimeHeaders } from "./containerRuntime";
46
47
  import { getSummaryForDatastores } from "./dataStores";
48
+ import {
49
+ defaultInactiveTimeoutMs,
50
+ defaultSessionExpiryDurationMs,
51
+ disableSweepLogKey,
52
+ disableTombstoneKey,
53
+ gcBlobPrefix,
54
+ gcTestModeKey,
55
+ gcTombstoneBlobKey,
56
+ gcTreeKey,
57
+ oneDayMs,
58
+ runGCKey,
59
+ runSessionExpiryKey,
60
+ runSweepKey,
61
+ trackGCStateKey
62
+ } from "./garbageCollectionConstants";
47
63
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
48
64
  import {
49
65
  getGCVersion,
@@ -53,39 +69,12 @@ import {
53
69
  ReadFluidDataStoreAttributes,
54
70
  dataStoreAttributesBlobName,
55
71
  IGCMetadata,
72
+ ICreateContainerMetadata,
56
73
  } from "./summaryFormat";
57
74
 
58
75
  /** This is the current version of garbage collection. */
59
76
  const GCVersion = 1;
60
77
 
61
- // The key for the GC tree in summary.
62
- export const gcTreeKey = "gc";
63
- // They prefix for GC blobs in the GC tree in summary.
64
- export const gcBlobPrefix = "__gc";
65
- // The key for tombstone blob in the GC tree in summary.
66
- export const gcTombstoneBlobKey = "__tombstones";
67
-
68
- // Feature gate key to turn GC on / off.
69
- export const runGCKey = "Fluid.GarbageCollection.RunGC";
70
- // Feature gate key to turn GC sweep on / off.
71
- export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
72
- // Feature gate key to turn GC test mode on / off.
73
- export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
74
- // Feature gate key to expire a session after a set period of time.
75
- export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
76
- // Feature gate key to write the gc blob as a handle if the data is the same.
77
- export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
78
- // Feature gate key to turn GC sweep log off.
79
- export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
80
- // Feature gate key to tombstone datastores.
81
- export const testTombstoneKey = "Fluid.GarbageCollection.Test.Tombstone";
82
-
83
- // One day in milliseconds.
84
- export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
85
-
86
- export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
87
- export const defaultSessionExpiryDurationMs = 30 * oneDayMs; // 30 days
88
-
89
78
  /** The statistics of the system state after a garbage collection run. */
90
79
  export interface IGCStats {
91
80
  /** The number of nodes in the container. */
@@ -129,8 +118,8 @@ export interface IGarbageCollectionRuntime {
129
118
  getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
130
119
  /** After GC has run, called to notify the runtime of routes that are used in it. */
131
120
  updateUsedRoutes(usedRoutes: string[]): void;
132
- /** After GC has run, called to delete objects in the runtime whose routes are unused. */
133
- deleteUnusedRoutes(unusedRoutes: string[]): void;
121
+ /** After GC has run, called to notify the runtime of routes that are unused in it. */
122
+ updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean): void;
134
123
  /** Returns a referenced timestamp to be used to track unreferenced nodes. */
135
124
  getCurrentReferenceTimestampMs(): number | undefined;
136
125
  /** Returns the type of the GC node. */
@@ -146,6 +135,8 @@ export interface IGarbageCollector {
146
135
  /** Tells whether the GC state in summary needs to be reset in the next summary. */
147
136
  readonly summaryStateNeedsReset: boolean;
148
137
  readonly trackGCState: boolean;
138
+ /** Initialize the state from the base snapshot after its creation. */
139
+ initializeBaseState(): Promise<void>;
149
140
  /** Run garbage collection and update the reference / used state of the system. */
150
141
  collectGarbage(
151
142
  options: { logger?: ITelemetryLogger; runSweep?: boolean; fullGC?: boolean; },
@@ -183,6 +174,7 @@ export interface IGarbageCollectorCreateParams {
183
174
  readonly baseLogger: ITelemetryLogger;
184
175
  readonly existing: boolean;
185
176
  readonly metadata: IContainerRuntimeMetadata | undefined;
177
+ readonly createContainerMetadata: ICreateContainerMetadata;
186
178
  readonly baseSnapshot: ISnapshotTree | undefined;
187
179
  readonly isSummarizerClient: boolean;
188
180
  readonly getNodePackagePath: (nodePath: string) => Promise<readonly string[] | undefined>;
@@ -227,6 +219,14 @@ interface IGCSummaryTrackingData {
227
219
  serializedTombstones: string | undefined;
228
220
  }
229
221
 
222
+ /**
223
+ * The GC data that is read from a snapshot. It contains the GC state and tombstone state.
224
+ */
225
+ interface IGCSnapshotData {
226
+ gcState: IGarbageCollectionState;
227
+ tombstones: string[] | undefined;
228
+ }
229
+
230
230
  /**
231
231
  * Helper class that tracks the state of an unreferenced node such as the time it was unreferenced and if it can
232
232
  * be deleted by the sweep phase.
@@ -421,8 +421,10 @@ export class GarbageCollector implements IGarbageCollector {
421
421
  */
422
422
  private pendingSummaryData: IGCSummaryTrackingData | undefined;
423
423
 
424
- // Promise when resolved initializes the base state of the nodes from the base summary state.
425
- private readonly initializeBaseStateP: Promise<void>;
424
+ // Promise when resolved returns the GC data data in the base snapshot.
425
+ private readonly baseSnapshotDataP: Promise<IGCSnapshotData | undefined>;
426
+ // Promise when resolved initializes the GC state from the data in the base snapshot.
427
+ private readonly initializeGCStateFromBaseSnapshotP: Promise<void>;
426
428
  // The map of data store ids to their GC details in the base summary returned in getDataStoreGCDetails().
427
429
  private readonly baseGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
428
430
  // Map of node ids to their unreferenced state tracker.
@@ -440,6 +442,7 @@ export class GarbageCollector implements IGarbageCollector {
440
442
  private completedRuns = 0;
441
443
 
442
444
  private readonly runtime: IGarbageCollectionRuntime;
445
+ private readonly createContainerMetadata: ICreateContainerMetadata;
443
446
  private readonly gcOptions: IGCRuntimeOptions;
444
447
  private readonly isSummarizerClient: boolean;
445
448
 
@@ -481,6 +484,7 @@ export class GarbageCollector implements IGarbageCollector {
481
484
  this.runtime = createParams.runtime;
482
485
  this.isSummarizerClient = createParams.isSummarizerClient;
483
486
  this.gcOptions = createParams.gcOptions;
487
+ this.createContainerMetadata = createParams.createContainerMetadata;
484
488
  this.getNodePackagePath = createParams.getNodePackagePath;
485
489
  this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
486
490
  this.activeConnection = createParams.activeConnection;
@@ -623,16 +627,17 @@ export class GarbageCollector implements IGarbageCollector {
623
627
 
624
628
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
625
629
  this.testMode = this.mc.config.getBoolean(gcTestModeKey) ?? this.gcOptions.runGCInTestMode === true;
626
- this.tombstoneMode = this.mc.config.getBoolean(testTombstoneKey) ?? false;
630
+ // Whether we are running in tombstone mode. This is true by default unless disabled via feature flags.
631
+ this.tombstoneMode = this.mc.config.getBoolean(disableTombstoneKey) !== true;
627
632
 
628
633
  // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
629
634
  // contain GC tree and GC is enabled.
630
635
  const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
631
636
  this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
632
637
 
633
- // Get the GC state from the GC blob in the base snapshot. Use LazyPromise because we only want to do
634
- // this once since it involves fetching blobs from storage which is expensive.
635
- const baseSummaryStateP = new LazyPromise<IGarbageCollectionState | undefined>(async () => {
638
+ // Get the GC data from the base snapshot. Use LazyPromise because we only want to do this once since it
639
+ // it involves fetching blobs from storage which is expensive.
640
+ this.baseSnapshotDataP = new LazyPromise<IGCSnapshotData | undefined>(async () => {
636
641
  if (baseSnapshot === undefined) {
637
642
  return undefined;
638
643
  }
@@ -641,20 +646,10 @@ export class GarbageCollector implements IGarbageCollector {
641
646
  // For newer documents, GC data should be present in the GC tree in the root of the snapshot.
642
647
  const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
643
648
  if (gcSnapshotTree !== undefined) {
644
- const baseGCData = await getGCDataFromSnapshot(
649
+ return getGCDataFromSnapshot(
645
650
  gcSnapshotTree,
646
651
  readAndParseBlob,
647
652
  );
648
- if (baseGCData.tombstones !== undefined && this.tombstoneMode) {
649
- this.tombstones = baseGCData.tombstones;
650
- }
651
- if (this.trackGCState) {
652
- this.latestSummaryData = {
653
- serializedGCState: JSON.stringify(generateSortedGCState(baseGCData.gcState)),
654
- serializedTombstones: JSON.stringify(baseGCData.tombstones),
655
- };
656
- }
657
- return baseGCData.gcState;
658
653
  }
659
654
 
660
655
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
@@ -698,7 +693,7 @@ export class GarbageCollector implements IGarbageCollector {
698
693
  }
699
694
  // If there is only one node (root node just added above), either GC is disabled or we are loading from
700
695
  // the first summary generated by detached container. In both cases, GC was not run - return undefined.
701
- return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
696
+ return Object.keys(gcState.gcNodes).length === 1 ? undefined : { gcState, tombstones: undefined };
702
697
  } catch (error) {
703
698
  const dpe = DataProcessingError.wrapIfUnrecognized(
704
699
  error,
@@ -710,11 +705,11 @@ export class GarbageCollector implements IGarbageCollector {
710
705
  });
711
706
 
712
707
  /**
713
- * Set up the initializer which initializes the base GC state from the base snapshot. Note that the reference
714
- * timestamp maybe from old ops which were not summarized and stored in the file. So, the unreferenced state
715
- * may be out of date. This is fine because the state is updated every time GC runs based on the time then.
708
+ * Set up the initializer which initializes the GC state from the data in base snapshot. This is done when
709
+ * connected in write mode or when GC runs the first time. It sets up all unreferenced nodes from the base
710
+ * GC state and updates their inactive or sweep ready state.
716
711
  */
717
- this.initializeBaseStateP = new LazyPromise<void>(async () => {
712
+ this.initializeGCStateFromBaseSnapshotP = new LazyPromise<void>(async () => {
718
713
  const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
719
714
  /**
720
715
  * If there is no current reference timestamp, skip initialization. We need the current timestamp to track
@@ -733,19 +728,19 @@ export class GarbageCollector implements IGarbageCollector {
733
728
  return;
734
729
  }
735
730
 
736
- const baseState = await baseSummaryStateP;
731
+ const baseSnapshotData = await this.baseSnapshotDataP;
737
732
  /**
738
- * The base state will not be present if the container is loaded from:
733
+ * The base snapshot data will not be present if the container is loaded from:
739
734
  * 1. The first summary created by the detached container.
740
735
  * 2. A summary that was generated with GC disabled.
741
736
  * 3. A summary that was generated before GC even existed.
742
737
  */
743
- if (baseState === undefined) {
738
+ if (baseSnapshotData === undefined) {
744
739
  return;
745
740
  }
746
741
 
747
742
  const gcNodes: { [id: string]: string[]; } = {};
748
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
743
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
749
744
  if (nodeData.unreferencedTimestampMs !== undefined) {
750
745
  this.unreferencedNodesState.set(
751
746
  nodeId,
@@ -760,18 +755,26 @@ export class GarbageCollector implements IGarbageCollector {
760
755
  gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
761
756
  }
762
757
  this.previousGCDataFromLastRun = { gcNodes };
758
+
759
+ // If tracking state across summaries, update latest summary data from the base snapshot's GC data.
760
+ if (this.trackGCState) {
761
+ this.latestSummaryData = {
762
+ serializedGCState: JSON.stringify(generateSortedGCState(baseSnapshotData.gcState)),
763
+ serializedTombstones: JSON.stringify(baseSnapshotData.tombstones),
764
+ };
765
+ }
763
766
  });
764
767
 
765
768
  // Get the GC details for each node from the GC state in the base summary. This is returned in getBaseGCDetails
766
769
  // which the caller uses to initialize each node's GC state.
767
770
  this.baseGCDetailsP = new LazyPromise<Map<string, IGarbageCollectionDetailsBase>>(async () => {
768
- const baseState = await baseSummaryStateP;
769
- if (baseState === undefined) {
771
+ const baseSnapshotData = await this.baseSnapshotDataP;
772
+ if (baseSnapshotData === undefined) {
770
773
  return new Map();
771
774
  }
772
775
 
773
776
  const gcNodes: { [id: string]: string[]; } = {};
774
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
777
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
775
778
  gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
776
779
  }
777
780
  // Run GC on the nodes in the base summary to get the routes used in each node in the container.
@@ -782,7 +785,7 @@ export class GarbageCollector implements IGarbageCollector {
782
785
  const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
783
786
  // Currently, the nodes may write the GC data. So, we need to update its base GC details with the
784
787
  // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
785
- for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
788
+ for (const [nodeId, nodeData] of Object.entries(baseSnapshotData.gcState.gcNodes)) {
786
789
  if (nodeData.unreferencedTimestampMs !== undefined) {
787
790
  const dataStoreGCDetails = baseGCDetailsMap.get(nodeId.slice(1));
788
791
  if (dataStoreGCDetails !== undefined) {
@@ -803,6 +806,27 @@ export class GarbageCollector implements IGarbageCollector {
803
806
  }
804
807
  }
805
808
 
809
+ /**
810
+ * Called during container initialization. Initialize the tombstone state so that object are marked as tombstones
811
+ * before they are loaded or used. This is important to get accurate information of whether tombstoned object are
812
+ * in use or not.
813
+ */
814
+ public async initializeBaseState(): Promise<void> {
815
+ const baseSnapshotData = await this.baseSnapshotDataP;
816
+ /**
817
+ * The base snapshot data or tombstone state will not be present if the container is loaded from:
818
+ * 1. The first summary created by the detached container.
819
+ * 2. A summary that was generated with GC disabled.
820
+ * 3. A summary that was generated before GC even existed.
821
+ * 4. A summary that was generated with tombstone feature disabled.
822
+ */
823
+ if (!this.tombstoneMode || baseSnapshotData?.tombstones === undefined) {
824
+ return;
825
+ }
826
+ this.tombstones = baseSnapshotData.tombstones;
827
+ this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
828
+ }
829
+
806
830
  /**
807
831
  * Called when the connection state of the runtime changes, i.e., it connects or disconnects. GC subscribes to this
808
832
  * to initialize the base state for non-summarizer clients so that they can track inactive / sweep ready nodes.
@@ -824,7 +848,7 @@ export class GarbageCollector implements IGarbageCollector {
824
848
  * sweep in phases and we want to track when inactive and sweep ready objects are used in any client.
825
849
  */
826
850
  if (this.activeConnection() && this.shouldRunGC) {
827
- this.initializeBaseStateP.catch((error) => {});
851
+ this.initializeGCStateFromBaseSnapshotP.catch((error) => {});
828
852
  }
829
853
  }
830
854
 
@@ -880,8 +904,8 @@ export class GarbageCollector implements IGarbageCollector {
880
904
  }
881
905
 
882
906
  private async runPreGCSteps() {
883
- // Ensure that base state has been initialized.
884
- await this.initializeBaseStateP;
907
+ // Ensure that state has been initialized from the base snapshot data.
908
+ await this.initializeGCStateFromBaseSnapshotP;
885
909
  // Let the runtime update its pending state before GC runs.
886
910
  await this.runtime.updateStateBeforeGC();
887
911
  }
@@ -911,24 +935,12 @@ export class GarbageCollector implements IGarbageCollector {
911
935
  // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
912
936
  // involving access to deleted data.
913
937
  if (this.testMode) {
914
- this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
915
- } else {
938
+ this.runtime.updateUnusedRoutes(gcResult.deletedNodeIds, false /* tombstone */);
939
+ } else if (this.tombstoneMode) {
916
940
  // If we are running in GC tombstone mode, tombstone objects for unused routes. This enables testing
917
941
  // scenarios involving access to "deleted" data without actually deleting the data from summaries.
918
942
  // Note: we will not tombstone in test mode
919
- if (this.tombstoneMode) {
920
- const tombstoneRoutes: string[] = [];
921
- // Currently only tombstone datastores
922
- for (const [key, value] of this.unreferencedNodesState.entries()) {
923
- if (
924
- value.state === UnreferencedState.SweepReady &&
925
- this.runtime.getNodeType(key) === GCNodeType.DataStore
926
- ) {
927
- tombstoneRoutes.push(key);
928
- }
929
- }
930
- this.runtime.deleteUnusedRoutes(tombstoneRoutes);
931
- }
943
+ this.runtime.updateUnusedRoutes(this.tombstones, true /* tombstone */);
932
944
  }
933
945
 
934
946
  // Log pending unreferenced events such as a node being used after inactive. This is done after GC runs and
@@ -962,7 +974,9 @@ export class GarbageCollector implements IGarbageCollector {
962
974
  }
963
975
 
964
976
  const serializedGCState = JSON.stringify(generateSortedGCState(gcState));
965
- const serializedTombstones = this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined;
977
+ const serializedTombstones = this.tombstoneMode
978
+ ? (this.tombstones.length > 0 ? JSON.stringify(this.tombstones.sort()) : undefined)
979
+ : undefined;
966
980
 
967
981
  /**
968
982
  * Incremental summary of GC data - If any of the GC state or tombstone state hasn't changed since the last
@@ -1497,6 +1511,7 @@ export class GarbageCollector implements IGarbageCollector {
1497
1511
  : this.sweepTimeoutMs,
1498
1512
  completedGCRuns: this.completedRuns,
1499
1513
  lastSummaryTime: this.getLastSummaryTimestampMs(),
1514
+ ...this.createContainerMetadata,
1500
1515
  externalRequest: requestHeaders?.[RuntimeHeaders.externalRequest],
1501
1516
  viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
1502
1517
  fromId: fromNodeId,
@@ -1505,16 +1520,20 @@ export class GarbageCollector implements IGarbageCollector {
1505
1520
  // For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
1506
1521
  // For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
1507
1522
  // but it's a good signal nonetheless and we can consume it with a grain of salt.
1523
+ // Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
1524
+ // SweepReady errors are usages of Objects that will be deleted by GC Sweep!
1508
1525
  if (this.isSummarizerClient) {
1509
1526
  this.pendingEventsQueue.push({ ...propsToLog, usageType, state });
1510
1527
  } else {
1511
1528
  // For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
1512
1529
  // summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
1530
+ // Events generated:
1531
+ // InactiveObject_Loaded, SweepReadyObject_Loaded
1513
1532
  if (usageType === "Loaded") {
1514
1533
  this.mc.logger.sendErrorEvent({
1515
1534
  ...propsToLog,
1516
1535
  eventName: `${state}Object_${usageType}`,
1517
- pkg: packagePath ? { value: packagePath.join("/"), tag: TelemetryDataTag.CodeArtifact } : undefined,
1536
+ pkg: packagePathToTelemetryProperty(packagePath),
1518
1537
  stack: generateStack(),
1519
1538
  });
1520
1539
  }
@@ -1529,6 +1548,11 @@ export class GarbageCollector implements IGarbageCollector {
1529
1548
  }
1530
1549
 
1531
1550
  private async logUnreferencedEvents(logger: ITelemetryLogger) {
1551
+ // Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
1552
+ // summary time they are then logged.
1553
+ // Events generated:
1554
+ // InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
1555
+ // SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
1532
1556
  for (const eventProps of this.pendingEventsQueue) {
1533
1557
  const { usageType, state, ...propsToLog } = eventProps;
1534
1558
  /**
@@ -1555,13 +1579,13 @@ export class GarbageCollector implements IGarbageCollector {
1555
1579
  }
1556
1580
 
1557
1581
  /**
1558
- * Gets the garbage collection data from the given snapshot tree. It contains GC state and tombstone state.
1582
+ * Gets the base garbage collection state from the given snapshot tree. It contains GC state and tombstone state.
1559
1583
  * The GC state may be written into multiple blobs. Merge the GC state from all such blobs into one.
1560
1584
  */
1561
1585
  async function getGCDataFromSnapshot(
1562
1586
  gcSnapshotTree: ISnapshotTree,
1563
1587
  readAndParseBlob: ReadAndParseBlob,
1564
- ) {
1588
+ ): Promise<IGCSnapshotData> {
1565
1589
  let rootGCState: IGarbageCollectionState = { gcNodes: {} };
1566
1590
  let tombstones: string[] | undefined;
1567
1591
  for (const key of Object.keys(gcSnapshotTree.blobs)) {