@fluidframework/container-runtime 0.59.3003 → 0.59.4000-71128

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 (51) hide show
  1. package/dist/batchTracker.js +1 -1
  2. package/dist/batchTracker.js.map +1 -1
  3. package/dist/containerRuntime.d.ts.map +1 -1
  4. package/dist/containerRuntime.js +10 -7
  5. package/dist/containerRuntime.js.map +1 -1
  6. package/dist/dataStoreContext.js +1 -1
  7. package/dist/dataStoreContext.js.map +1 -1
  8. package/dist/dataStores.js +3 -3
  9. package/dist/dataStores.js.map +1 -1
  10. package/dist/garbageCollection.d.ts +23 -10
  11. package/dist/garbageCollection.d.ts.map +1 -1
  12. package/dist/garbageCollection.js +161 -51
  13. package/dist/garbageCollection.js.map +1 -1
  14. package/dist/opTelemetry.js +2 -2
  15. package/dist/opTelemetry.js.map +1 -1
  16. package/dist/packageVersion.d.ts +1 -1
  17. package/dist/packageVersion.d.ts.map +1 -1
  18. package/dist/packageVersion.js +1 -1
  19. package/dist/packageVersion.js.map +1 -1
  20. package/dist/summaryFormat.js +1 -1
  21. package/dist/summaryFormat.js.map +1 -1
  22. package/lib/batchTracker.js +1 -1
  23. package/lib/batchTracker.js.map +1 -1
  24. package/lib/containerRuntime.d.ts.map +1 -1
  25. package/lib/containerRuntime.js +12 -9
  26. package/lib/containerRuntime.js.map +1 -1
  27. package/lib/dataStoreContext.js +1 -1
  28. package/lib/dataStoreContext.js.map +1 -1
  29. package/lib/dataStores.js +3 -3
  30. package/lib/dataStores.js.map +1 -1
  31. package/lib/garbageCollection.d.ts +23 -10
  32. package/lib/garbageCollection.d.ts.map +1 -1
  33. package/lib/garbageCollection.js +139 -48
  34. package/lib/garbageCollection.js.map +1 -1
  35. package/lib/opTelemetry.js +2 -2
  36. package/lib/opTelemetry.js.map +1 -1
  37. package/lib/packageVersion.d.ts +1 -1
  38. package/lib/packageVersion.d.ts.map +1 -1
  39. package/lib/packageVersion.js +1 -1
  40. package/lib/packageVersion.js.map +1 -1
  41. package/lib/summaryFormat.js +1 -1
  42. package/lib/summaryFormat.js.map +1 -1
  43. package/package.json +22 -19
  44. package/src/batchTracker.ts +1 -1
  45. package/src/containerRuntime.ts +21 -8
  46. package/src/dataStoreContext.ts +1 -1
  47. package/src/dataStores.ts +3 -3
  48. package/src/garbageCollection.ts +191 -47
  49. package/src/opTelemetry.ts +2 -2
  50. package/src/packageVersion.ts +1 -1
  51. package/src/summaryFormat.ts +1 -1
@@ -16,15 +16,17 @@ import {
16
16
  runGarbageCollection,
17
17
  unpackChildNodesGCDetails,
18
18
  } from "@fluidframework/garbage-collector";
19
- import { ISnapshotTree } from "@fluidframework/protocol-definitions";
19
+ import { ISnapshotTree, SummaryType } from "@fluidframework/protocol-definitions";
20
20
  import {
21
21
  gcBlobKey,
22
22
  IGarbageCollectionData,
23
23
  IGarbageCollectionState,
24
24
  IGarbageCollectionDetailsBase,
25
- ISummaryTreeWithStats,
25
+ IGarbageCollectionNodeData,
26
+ ISummarizeResult,
26
27
  } from "@fluidframework/runtime-definitions";
27
28
  import {
29
+ mergeStats,
28
30
  ReadAndParseBlob,
29
31
  RefreshSummaryResult,
30
32
  SummaryTreeBuilder,
@@ -37,8 +39,10 @@ import {
37
39
  TelemetryDataTag,
38
40
  } from "@fluidframework/telemetry-utils";
39
41
 
42
+ import * as semver from "semver";
40
43
  import { IGCRuntimeOptions, RuntimeHeaders } from "./containerRuntime";
41
44
  import { getSummaryForDatastores } from "./dataStores";
45
+ import { pkgVersion } from "./packageVersion";
42
46
  import {
43
47
  getGCVersion,
44
48
  GCVersion,
@@ -66,13 +70,17 @@ const runSweepKey = "Fluid.GarbageCollection.RunSweep";
66
70
  // Feature gate key to write GC data at the root of the summary tree.
67
71
  const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
68
72
  // Feature gate key to expire a session after a set period of time.
69
- const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
73
+ export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
70
74
  // Feature gate key to disable expiring session after a set period of time, even if expiry value is present
71
- const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
75
+ export const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
72
76
  // Feature gate key to log error messages if GC reference validation fails.
73
- const logUnknownOutboundReferencesKey = "Fluid.GarbageCollection.LogUnknownOutboundReferences";
77
+ export const logUnknownOutboundReferencesKey = "Fluid.GarbageCollection.LogUnknownOutboundReferences";
78
+ // Feature gate key to write the gc blob as a handle if the data is the same.
79
+ export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
80
+ // Feature gate key to limit which versions can write the gc blob as a handle if the data is the same.
81
+ export const trackGCStateMinimumVersionKey = "Fluid.GarbageCollection.TrackGCState.MinVersion";
74
82
 
75
- const defaultDeleteTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
83
+ const defaultInactiveTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
76
84
  export const defaultSessionExpiryDurationMs = 30 * 24 * 60 * 60 * 1000; // 30 days
77
85
 
78
86
  /** The statistics of the system state after a garbage collection run. */
@@ -114,8 +122,10 @@ export type GCNodeType = typeof GCNodeType[keyof typeof GCNodeType];
114
122
  interface IUnreferencedEvent {
115
123
  eventName: string;
116
124
  id: string;
125
+ type: GCNodeType;
117
126
  age: number;
118
127
  timeout: number;
128
+ completedGCRuns: number;
119
129
  lastSummaryTime?: number;
120
130
  externalRequest?: boolean;
121
131
  viaHandle?: boolean;
@@ -147,12 +157,13 @@ export interface IGarbageCollector {
147
157
  readonly summaryStateNeedsReset: boolean;
148
158
  /** Tells whether GC data should be written to the root of the summary tree. */
149
159
  readonly writeDataAtRoot: boolean;
160
+ readonly trackGCState: boolean;
150
161
  /** Run garbage collection and update the reference / used state of the system. */
151
162
  collectGarbage(
152
163
  options: { logger?: ITelemetryLogger; runGC?: boolean; runSweep?: boolean; fullGC?: boolean; },
153
164
  ): Promise<IGCStats>;
154
165
  /** Summarizes the GC data and returns it as a summary tree. */
155
- summarize(): ISummaryTreeWithStats | undefined;
166
+ summarize(fullTree: boolean, trackState: boolean): ISummarizeResult | undefined;
156
167
  /** Returns the garbage collector specific metadata to be written into the summary. */
157
168
  getMetadata(): IGCMetadata;
158
169
  /** Returns a map of each node id to its base GC details in the base summary. */
@@ -252,7 +263,8 @@ export class GarbageCollector implements IGarbageCollector {
252
263
  readAndParseBlob: ReadAndParseBlob,
253
264
  baseLogger: ITelemetryLogger,
254
265
  existing: boolean,
255
- metadata?: IContainerRuntimeMetadata,
266
+ metadata: IContainerRuntimeMetadata | undefined,
267
+ isSummarizerClient: boolean,
256
268
  ): IGarbageCollector {
257
269
  return new GarbageCollector(
258
270
  provider,
@@ -264,6 +276,7 @@ export class GarbageCollector implements IGarbageCollector {
264
276
  baseLogger,
265
277
  existing,
266
278
  metadata,
279
+ isSummarizerClient,
267
280
  );
268
281
  }
269
282
 
@@ -307,6 +320,8 @@ export class GarbageCollector implements IGarbageCollector {
307
320
  */
308
321
  private readonly shouldRunSweep: boolean;
309
322
 
323
+ public readonly trackGCState: boolean;
324
+
310
325
  private readonly testMode: boolean;
311
326
  private readonly mc: MonitoringContext;
312
327
 
@@ -337,6 +352,14 @@ export class GarbageCollector implements IGarbageCollector {
337
352
 
338
353
  // Keeps track of the GC state from the last run.
339
354
  private previousGCDataFromLastRun: IGarbageCollectionData | undefined;
355
+ /**
356
+ * Keeps track of the serialized GC blob from the latest summary successfully submitted to the server.
357
+ */
358
+ private latestSerializedSummaryState: string | undefined;
359
+ /**
360
+ * Keeps track of the serialized GC blob from the last GC run of the client.
361
+ */
362
+ private pendingSerializedSummaryState: string | undefined;
340
363
  // Keeps a list of references (edges in the GC graph) between GC runs. Each entry has a node id and a list of
341
364
  // outbound routes from that node.
342
365
  private readonly newReferencesSinceLastRun: Map<string, string[]> = new Map();
@@ -345,8 +368,8 @@ export class GarbageCollector implements IGarbageCollector {
345
368
  private readonly initializeBaseStateP: Promise<void>;
346
369
  // The map of data store ids to their GC details in the base summary returned in getDataStoreGCDetails().
347
370
  private readonly baseGCDetailsP: Promise<Map<string, IGarbageCollectionDetailsBase>>;
348
- // The time after which an unreferenced node can be deleted. Currently, we only set the node's state to expired.
349
- private readonly deleteTimeoutMs: number;
371
+ // The time after which an unreferenced node is inactive.
372
+ private readonly inactiveTimeoutMs: number;
350
373
  // Map of node ids to their unreferenced state tracker.
351
374
  private readonly unreferencedNodesState: Map<string, UnreferencedStateTracker> = new Map();
352
375
  // The timeout responsible for closing the container when the session has expired
@@ -358,6 +381,9 @@ export class GarbageCollector implements IGarbageCollector {
358
381
  // Queue for unreferenced events that should be logged the next time GC runs.
359
382
  private readonly pendingEventsQueue: IUnreferencedEvent[] = [];
360
383
 
384
+ // The number of times GC has successfully completed on this instance of GarbageCollector.
385
+ private completedRuns = 0;
386
+
361
387
  protected constructor(
362
388
  private readonly runtime: IGarbageCollectionRuntime,
363
389
  private readonly gcOptions: IGCRuntimeOptions,
@@ -369,12 +395,12 @@ export class GarbageCollector implements IGarbageCollector {
369
395
  readAndParseBlob: ReadAndParseBlob,
370
396
  baseLogger: ITelemetryLogger,
371
397
  existing: boolean,
372
- metadata?: IContainerRuntimeMetadata,
398
+ metadata: IContainerRuntimeMetadata | undefined,
399
+ private readonly isSummarizerClient: boolean = true,
373
400
  ) {
374
401
  this.mc = loggerToMonitoringContext(
375
- ChildLogger.create(baseLogger, "GarbageCollector"));
376
-
377
- this.deleteTimeoutMs = this.gcOptions.deleteTimeoutMs ?? defaultDeleteTimeoutMs;
402
+ ChildLogger.create(baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }),
403
+ );
378
404
 
379
405
  let prevSummaryGCVersion: number | undefined;
380
406
 
@@ -446,6 +472,11 @@ export class GarbageCollector implements IGarbageCollector {
446
472
  && !gcOptions.disableGC
447
473
  );
448
474
 
475
+ const minimumVersion = this.mc.config.getString(trackGCStateMinimumVersionKey);
476
+ const shouldTrackStateForVersion = meetsMinimumVersionRequirement(pkgVersion, minimumVersion);
477
+
478
+ this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true && shouldTrackStateForVersion;
479
+
449
480
  /**
450
481
  * Whether sweep should run or not. The following conditions have to be met to run sweep:
451
482
  * 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
@@ -456,6 +487,12 @@ export class GarbageCollector implements IGarbageCollector {
456
487
  this.mc.config.getBoolean(runSweepKey) ?? (this.sessionExpiryTimeoutMs !== undefined && this.sweepEnabled)
457
488
  );
458
489
 
490
+ // Override inactive timeout if test config or gc options to override it is set.
491
+ this.inactiveTimeoutMs =
492
+ this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs") ??
493
+ this.gcOptions.inactiveTimeoutMs ??
494
+ defaultInactiveTimeoutMs;
495
+
459
496
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
460
497
  this.testMode = this.mc.config.getBoolean(gcTestModeKey) ?? gcOptions.runGCInTestMode === true;
461
498
 
@@ -485,7 +522,14 @@ export class GarbageCollector implements IGarbageCollector {
485
522
  if (gcSnapshotTree !== undefined) {
486
523
  // If the GC tree is written at root, we should also do the same.
487
524
  this._writeDataAtRoot = true;
488
- return getGCStateFromSnapshot(gcSnapshotTree, readAndParseBlob);
525
+ const baseGCState = await getGCStateFromSnapshot(
526
+ gcSnapshotTree,
527
+ readAndParseBlob,
528
+ );
529
+ if (this.trackGCState) {
530
+ this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(baseGCState));
531
+ }
532
+ return baseGCState;
489
533
  }
490
534
 
491
535
  // back-compat - Older documents will have the GC blobs in each data store's summary tree. Get them and
@@ -524,7 +568,7 @@ export class GarbageCollector implements IGarbageCollector {
524
568
  gcState.gcNodes[rootId] = { outboundRoutes: Array.from(outboundRoutes) };
525
569
  }
526
570
  assert(gcState.gcNodes[dsRootId] !== undefined,
527
- 0x2a9 /* `GC nodes for data store ${dsId} not in GC blob` */);
571
+ 0x2a9 /* GC nodes for data store not in GC blob */);
528
572
  gcState.gcNodes[dsRootId].unreferencedTimestampMs = gcSummaryDetails.unrefTimestamp;
529
573
  }
530
574
 
@@ -552,7 +596,7 @@ export class GarbageCollector implements IGarbageCollector {
552
596
  nodeId,
553
597
  new UnreferencedStateTracker(
554
598
  nodeData.unreferencedTimestampMs,
555
- this.deleteTimeoutMs,
599
+ this.inactiveTimeoutMs,
556
600
  currentReferenceTimestampMs,
557
601
  ),
558
602
  );
@@ -597,21 +641,35 @@ export class GarbageCollector implements IGarbageCollector {
597
641
  return baseGCDetailsMap;
598
642
  });
599
643
 
600
- // Initialize the base state. The base GC data is used to detect and log when inactive / deleted objects are
601
- // used in the container.
644
+ // Log all the GC options and the state determined by the garbage collector. This is interesting only for the
645
+ // summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
646
+ const gcConfigProps = JSON.stringify({
647
+ gcEnabled: this.gcEnabled,
648
+ sweepEnabled: this.sweepEnabled,
649
+ runGC: this.shouldRunGC,
650
+ runSweep: this.shouldRunSweep,
651
+ writeAtRoot: this._writeDataAtRoot,
652
+ testMode: this.testMode,
653
+ sessionExpiry: this.sessionExpiryTimeoutMs,
654
+ inactiveTimeout: this.inactiveTimeoutMs,
655
+ existing,
656
+ ...this.gcOptions,
657
+ });
658
+ if (this.isSummarizerClient) {
659
+ this.mc.logger.sendTelemetryEvent({
660
+ eventName: "GarbageCollectorLoaded",
661
+ gcConfigs: gcConfigProps,
662
+ });
663
+ }
664
+
665
+ // Initialize the base state that is used to detect when inactive objects are used.
602
666
  if (this.shouldRunGC) {
603
667
  this.initializeBaseStateP.catch((error) => {
604
668
  const dpe = DataProcessingError.wrapIfUnrecognized(
605
669
  error,
606
670
  "FailedToInitializeGC",
607
671
  );
608
- dpe.addTelemetryProperties({
609
- gcEnabled: this.gcEnabled,
610
- runSweep: this.shouldRunSweep,
611
- writeAtRoot: this._writeDataAtRoot,
612
- testMode: this.testMode,
613
- sessionExpiry: this.sessionExpiryTimeoutMs,
614
- });
672
+ dpe.addTelemetryProperties({ gcConfigs: gcConfigProps });
615
673
  throw dpe;
616
674
  });
617
675
  }
@@ -632,11 +690,14 @@ export class GarbageCollector implements IGarbageCollector {
632
690
  },
633
691
  ): Promise<IGCStats> {
634
692
  const {
635
- logger = this.mc.logger,
636
693
  runSweep = this.shouldRunSweep,
637
694
  fullGC = this.gcOptions.runFullGC === true || this.summaryStateNeedsReset,
638
695
  } = options;
639
696
 
697
+ const logger = options.logger
698
+ ? ChildLogger.create(options.logger, undefined, { all: { completedGCRuns: () => this.completedRuns } })
699
+ : this.mc.logger;
700
+
640
701
  return PerformanceEvent.timedExecAsync(logger, { eventName: "GarbageCollection" }, async (event) => {
641
702
  await this.initializeBaseStateP;
642
703
 
@@ -671,7 +732,11 @@ export class GarbageCollector implements IGarbageCollector {
671
732
  if (this.testMode) {
672
733
  this.runtime.deleteUnusedRoutes(gcResult.deletedNodeIds);
673
734
  }
735
+
674
736
  event.end({ ...gcStats });
737
+
738
+ this.completedRuns++;
739
+
675
740
  return gcStats;
676
741
  },
677
742
  { end: true, cancel: "error" });
@@ -682,7 +747,10 @@ export class GarbageCollector implements IGarbageCollector {
682
747
  * We current write the entire GC state in a single blob. This can be modified later to write multiple
683
748
  * blobs. All the blob keys should start with `gcBlobPrefix`.
684
749
  */
685
- public summarize(): ISummaryTreeWithStats | undefined {
750
+ public summarize(
751
+ fullTree: boolean,
752
+ trackState: boolean,
753
+ ): ISummarizeResult | undefined {
686
754
  if (!this.shouldRunGC || this.previousGCDataFromLastRun === undefined) {
687
755
  return;
688
756
  }
@@ -695,8 +763,35 @@ export class GarbageCollector implements IGarbageCollector {
695
763
  };
696
764
  }
697
765
 
766
+ const newSerializedSummaryState = JSON.stringify(generateSortedGCState(gcState));
767
+
768
+ /**
769
+ * As an optimization if the GC tree hasn't changed and we're tracking the gc state, return a tree handle
770
+ * instead of returning the whole GC tree. If there are changes, then we want to return the whole tree.
771
+ */
772
+ if (this.trackGCState) {
773
+ this.pendingSerializedSummaryState = newSerializedSummaryState;
774
+ if (
775
+ this.latestSerializedSummaryState !== undefined &&
776
+ this.latestSerializedSummaryState === newSerializedSummaryState &&
777
+ !fullTree &&
778
+ trackState
779
+ ) {
780
+ const stats = mergeStats();
781
+ stats.handleNodeCount++;
782
+ return {
783
+ summary: {
784
+ type: SummaryType.Handle,
785
+ handle: `/${gcTreeKey}`,
786
+ handleType: SummaryType.Tree,
787
+ },
788
+ stats,
789
+ };
790
+ }
791
+ }
792
+
698
793
  const builder = new SummaryTreeBuilder();
699
- builder.addBlob(`${gcBlobPrefix}_root`, JSON.stringify(gcState));
794
+ builder.addBlob(`${gcBlobPrefix}_root`, newSerializedSummaryState);
700
795
  return builder.getSummaryTree();
701
796
  }
702
797
 
@@ -740,11 +835,32 @@ export class GarbageCollector implements IGarbageCollector {
740
835
  // Basically, it was written in the current GC version.
741
836
  if (result.wasSummaryTracked) {
742
837
  this.latestSummaryGCVersion = this.currentGCVersion;
838
+ if (this.trackGCState) {
839
+ this.latestSerializedSummaryState = this.pendingSerializedSummaryState;
840
+ this.pendingSerializedSummaryState = undefined;
841
+ }
743
842
  return;
744
843
  }
745
- // If the summary was not tracked by this client, update latest GC version from the snapshot in the result as
746
- // that is now the latest summary.
747
- await this.updateSummaryGCVersionFromSnapshot(result.snapshot, readAndParseBlob);
844
+ // If the summary was not tracked by this client, update latest GC version and blob from the snapshot in the
845
+ // result as that is now the latest summary.
846
+ const snapshot = result.snapshot;
847
+ const metadataBlobId = snapshot.blobs[metadataBlobName];
848
+ if (metadataBlobId) {
849
+ const metadata = await readAndParseBlob<IContainerRuntimeMetadata>(metadataBlobId);
850
+ this.latestSummaryGCVersion = getGCVersion(metadata);
851
+ }
852
+
853
+ const gcSnapshotTree = snapshot.trees[gcTreeKey];
854
+ if (gcSnapshotTree !== undefined && this.trackGCState) {
855
+ const latestGCState = await getGCStateFromSnapshot(
856
+ gcSnapshotTree,
857
+ readAndParseBlob,
858
+ );
859
+ this.latestSerializedSummaryState = JSON.stringify(generateSortedGCState(latestGCState));
860
+ } else {
861
+ this.latestSerializedSummaryState = undefined;
862
+ }
863
+ this.pendingSerializedSummaryState = undefined;
748
864
  }
749
865
 
750
866
  /**
@@ -805,17 +921,6 @@ export class GarbageCollector implements IGarbageCollector {
805
921
  }
806
922
  }
807
923
 
808
- /**
809
- * Update the latest summary GC version from the metadata blob in the given snapshot.
810
- */
811
- private async updateSummaryGCVersionFromSnapshot(snapshot: ISnapshotTree, readAndParseBlob: ReadAndParseBlob) {
812
- const metadataBlobId = snapshot.blobs[metadataBlobName];
813
- if (metadataBlobId) {
814
- const metadata = await readAndParseBlob<IContainerRuntimeMetadata>(metadataBlobId);
815
- this.latestSummaryGCVersion = getGCVersion(metadata);
816
- }
817
- }
818
-
819
924
  /**
820
925
  * Updates the state of the system as per the current GC run. It does the following:
821
926
  * 1. Sets up the current GC state as per the gcData.
@@ -865,7 +970,7 @@ export class GarbageCollector implements IGarbageCollector {
865
970
  nodeId,
866
971
  new UnreferencedStateTracker(
867
972
  currentReferenceTimestampMs,
868
- this.deleteTimeoutMs,
973
+ this.inactiveTimeoutMs,
869
974
  currentReferenceTimestampMs,
870
975
  ),
871
976
  );
@@ -1090,7 +1195,7 @@ export class GarbageCollector implements IGarbageCollector {
1090
1195
  * Logs an event if a node is inactive and is used.
1091
1196
  */
1092
1197
  private logIfInactive(
1093
- eventSuffix: "Changed" | "Loaded" | "Revived",
1198
+ eventType: "Changed" | "Loaded" | "Revived",
1094
1199
  nodeId: string,
1095
1200
  currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs(),
1096
1201
  packagePath?: readonly string[],
@@ -1102,17 +1207,33 @@ export class GarbageCollector implements IGarbageCollector {
1102
1207
  return;
1103
1208
  }
1104
1209
 
1105
- const eventName = `inactiveObject_${eventSuffix}`;
1210
+ // We only care about data stores and attachment blobs for this telemetry since GC only marks these objects
1211
+ // as unreferenced. Also, if an inactive DDS is used, the corresponding data store store will also be used.
1212
+ const nodeType = this.runtime.getNodeType(nodeId);
1213
+ if (nodeType !== GCNodeType.DataStore && nodeType !== GCNodeType.Blob) {
1214
+ return;
1215
+ }
1216
+
1217
+ // For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
1218
+ // summarizer clients if they are based off of user actions (such as scrolling to content for these objects).
1219
+ if (!this.isSummarizerClient && eventType !== "Loaded") {
1220
+ return;
1221
+ }
1222
+
1223
+ const eventName = `inactiveObject_${eventType}`;
1106
1224
  // We log a particular event for a given node only once so that it is not too noisy.
1107
1225
  const uniqueEventId = `${nodeId}-${eventName}`;
1108
1226
  const nodeState = this.unreferencedNodesState.get(nodeId);
1109
1227
  if (nodeState?.inactive && !this.loggedUnreferencedEvents.has(uniqueEventId)) {
1110
1228
  this.loggedUnreferencedEvents.add(uniqueEventId);
1229
+ // Save all the properties at this point in time so that if we log this later, these values are preserved.
1111
1230
  const event: IUnreferencedEvent = {
1112
1231
  eventName,
1113
1232
  id: nodeId,
1233
+ type: nodeType,
1114
1234
  age: currentReferenceTimestampMs - nodeState.unreferencedTimestampMs,
1115
- timeout: this.deleteTimeoutMs,
1235
+ timeout: this.inactiveTimeoutMs,
1236
+ completedGCRuns: this.completedRuns,
1116
1237
  lastSummaryTime: this.getLastSummaryTimestampMs(),
1117
1238
  externalRequest: requestHeaders?.[RuntimeHeaders.externalRequest],
1118
1239
  viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
@@ -1160,6 +1281,17 @@ async function getGCStateFromSnapshot(
1160
1281
  return rootGCState;
1161
1282
  }
1162
1283
 
1284
+ function generateSortedGCState(gcState: IGarbageCollectionState): IGarbageCollectionState {
1285
+ const sortableArray: [string, IGarbageCollectionNodeData][] = Object.entries(gcState.gcNodes);
1286
+ sortableArray.sort(([a], [b]) => a.localeCompare(b));
1287
+ const sortedGCState: IGarbageCollectionState = { gcNodes: {} };
1288
+ for (const [nodeId, nodeData] of sortableArray) {
1289
+ nodeData.outboundRoutes.sort();
1290
+ sortedGCState.gcNodes[nodeId] = nodeData;
1291
+ }
1292
+ return sortedGCState;
1293
+ }
1294
+
1163
1295
  /**
1164
1296
  * setLongTimeout is used for timeouts longer than setTimeout's ~24.8 day max
1165
1297
  * @param timeoutMs - the total time the timeout needs to last in ms
@@ -1182,3 +1314,15 @@ function setLongTimeout(
1182
1314
  }
1183
1315
  setTimerFn(timer);
1184
1316
  }
1317
+
1318
+ /**
1319
+ * meetsMinimumVersionRequirement is used determining if a feature version should be run. This is similar to feature
1320
+ * flags. The advantage of this is that if we ship a bug in version 0.1.1 and fix it in version 0.2.1. We can keep this
1321
+ * feature disabled for version 0.1.1 and enabled for 0.2.1. Older versions will run without the feature and new
1322
+ * versions will run with the feature.
1323
+ * @param currentVersion - the total time the timeout needs to last in ms
1324
+ * @param minimumVersion - the function to execute when the timer ends
1325
+ */
1326
+ function meetsMinimumVersionRequirement(currentVersion: string, minimumVersion: string | undefined) {
1327
+ return minimumVersion === undefined || semver.compare(currentVersion, minimumVersion) >= 0;
1328
+ }
@@ -9,7 +9,7 @@ import {
9
9
  ISequencedDocumentMessage,
10
10
  ISequencedDocumentSystemMessage,
11
11
  } from "@fluidframework/protocol-definitions";
12
- import { isSystemMessage } from "@fluidframework/protocol-base";
12
+ import { isRuntimeMessage } from "@fluidframework/driver-utils";
13
13
 
14
14
  export class OpTracker {
15
15
  /**
@@ -49,7 +49,7 @@ export class OpTracker {
49
49
  });
50
50
 
51
51
  deltaManager.on("op", (message: ISequencedDocumentMessage) => {
52
- this._nonSystemOpCount += isSystemMessage(message) ? 0 : 1;
52
+ this._nonSystemOpCount += !isRuntimeMessage(message) ? 0 : 1;
53
53
  const id = OpTracker.messageId(message);
54
54
  this._opsSizeAccumulator += this.messageSize[id] ?? 0;
55
55
  this.messageSize.delete(id);
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "0.59.3003";
9
+ export const pkgVersion = "0.59.4000-71128";
@@ -231,6 +231,6 @@ export async function getFluidDataStoreAttributes(
231
231
  // snapshotFormatVersion is at least "0.1" (1), so we don't expect it to be anything else.
232
232
  const formatVersion = getAttributesFormatVersion(attributes);
233
233
  assert(formatVersion > 0,
234
- 0x1d5 /* `Invalid snapshot format version ${attributes.snapshotFormatVersion}` */);
234
+ 0x1d5 /* Invalid snapshot format version */);
235
235
  return attributes;
236
236
  }