@fluidframework/container-runtime 0.57.0-51086 → 0.57.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 (46) hide show
  1. package/dist/containerRuntime.d.ts +2 -2
  2. package/dist/containerRuntime.d.ts.map +1 -1
  3. package/dist/containerRuntime.js +12 -20
  4. package/dist/containerRuntime.js.map +1 -1
  5. package/dist/dataStoreContext.d.ts +4 -0
  6. package/dist/dataStoreContext.d.ts.map +1 -1
  7. package/dist/dataStoreContext.js +1 -0
  8. package/dist/dataStoreContext.js.map +1 -1
  9. package/dist/dataStores.d.ts +1 -1
  10. package/dist/dataStores.d.ts.map +1 -1
  11. package/dist/dataStores.js +2 -2
  12. package/dist/dataStores.js.map +1 -1
  13. package/dist/garbageCollection.d.ts +24 -11
  14. package/dist/garbageCollection.d.ts.map +1 -1
  15. package/dist/garbageCollection.js +94 -53
  16. package/dist/garbageCollection.js.map +1 -1
  17. package/dist/packageVersion.d.ts +1 -1
  18. package/dist/packageVersion.d.ts.map +1 -1
  19. package/dist/packageVersion.js +1 -1
  20. package/dist/packageVersion.js.map +1 -1
  21. package/lib/containerRuntime.d.ts +2 -2
  22. package/lib/containerRuntime.d.ts.map +1 -1
  23. package/lib/containerRuntime.js +12 -20
  24. package/lib/containerRuntime.js.map +1 -1
  25. package/lib/dataStoreContext.d.ts +4 -0
  26. package/lib/dataStoreContext.d.ts.map +1 -1
  27. package/lib/dataStoreContext.js +1 -0
  28. package/lib/dataStoreContext.js.map +1 -1
  29. package/lib/dataStores.d.ts +1 -1
  30. package/lib/dataStores.d.ts.map +1 -1
  31. package/lib/dataStores.js +2 -2
  32. package/lib/dataStores.js.map +1 -1
  33. package/lib/garbageCollection.d.ts +24 -11
  34. package/lib/garbageCollection.d.ts.map +1 -1
  35. package/lib/garbageCollection.js +94 -53
  36. package/lib/garbageCollection.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/package.json +13 -13
  42. package/src/containerRuntime.ts +29 -25
  43. package/src/dataStoreContext.ts +5 -0
  44. package/src/dataStores.ts +8 -3
  45. package/src/garbageCollection.ts +114 -57
  46. package/src/packageVersion.ts +1 -1
@@ -93,6 +93,7 @@ interface IUnreferencedEvent {
93
93
  id: string;
94
94
  age: number;
95
95
  timeout: number;
96
+ lastSummaryTime?: number;
96
97
  externalRequest?: boolean;
97
98
  viaHandle?: boolean;
98
99
  };
@@ -105,6 +106,8 @@ export interface IGarbageCollectionRuntime {
105
106
  getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
106
107
  /** After GC has run, called to notify the runtime of routes that are used in it. */
107
108
  updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number): void;
109
+ /** Called when the runtime should close because of an error. */
110
+ closeFn(error?: ICriticalContainerError): void;
108
111
  }
109
112
 
110
113
  /** Defines the contract for the garbage collector. */
@@ -137,6 +140,7 @@ export interface IGarbageCollector {
137
140
  nodeUpdated(
138
141
  nodePath: string,
139
142
  reason: "Loaded" | "Changed",
143
+ timestampMs?: number,
140
144
  packagePath?: readonly string[],
141
145
  requestHeaders?: IRequestHeader,
142
146
  ): void;
@@ -155,23 +159,43 @@ class UnreferencedStateTracker {
155
159
  return this._inactive;
156
160
  }
157
161
 
158
- private readonly timer: Timer | undefined;
162
+ private timer: Timer | undefined;
159
163
 
160
164
  constructor(
161
165
  public readonly unreferencedTimestampMs: number,
162
- inactiveTimeoutMs: number,
166
+ private readonly inactiveTimeoutMs: number,
167
+ currentReferenceTimestampMs?: number,
163
168
  ) {
164
- // If the timeout has already expired, the node should become inactive immediately. Otherwise, start a timer of
165
- // inactiveTimeoutMs after which the node will become inactive.
166
- if (inactiveTimeoutMs <= 0) {
169
+ // If there is no current reference timestamp, don't track the node's inactive state. This will happen later
170
+ // when updateTracking is called with a reference timestamp.
171
+ if (currentReferenceTimestampMs !== undefined) {
172
+ this.updateTracking(currentReferenceTimestampMs);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Updates the tracking state based on the provided timestamp.
178
+ */
179
+ public updateTracking(currentReferenceTimestampMs: number) {
180
+ const unreferencedDurationMs = currentReferenceTimestampMs - this.unreferencedTimestampMs;
181
+ // If the timeout has already expired, the node has become inactive.
182
+ if (unreferencedDurationMs > this.inactiveTimeoutMs) {
167
183
  this._inactive = true;
168
- } else {
169
- this.timer = new Timer(inactiveTimeoutMs, () => { this._inactive = true; });
170
- this.timer.start();
184
+ this.timer?.clear();
185
+ return;
186
+ }
187
+
188
+ // The node isn't inactive yet. Restart a timer for the duration remaining for it to become inactive.
189
+ const remainingDurationMs = this.inactiveTimeoutMs - unreferencedDurationMs;
190
+ if (this.timer === undefined) {
191
+ this.timer = new Timer(remainingDurationMs, () => { this._inactive = true; });
171
192
  }
193
+ this.timer.restart(remainingDurationMs);
172
194
  }
173
195
 
174
- /** Stop tracking this node. Reset the unreferenced timer, if any, and reset inactive state. */
196
+ /**
197
+ * Stop tracking this node. Reset the unreferenced timer, if any, and reset inactive state.
198
+ */
175
199
  public stopTracking() {
176
200
  this.timer?.clear();
177
201
  this._inactive = false;
@@ -188,8 +212,8 @@ export class GarbageCollector implements IGarbageCollector {
188
212
  gcOptions: IGCRuntimeOptions,
189
213
  deleteUnusedRoutes: (unusedRoutes: string[]) => void,
190
214
  getNodePackagePath: (nodeId: string) => readonly string[] | undefined,
191
- getCurrentTimestampMs: () => number,
192
- closeFn: (error?: ICriticalContainerError) => void,
215
+ getCurrentReferenceTimestampMs: () => number | undefined,
216
+ getLastSummaryTimestampMs: () => number | undefined,
193
217
  baseSnapshot: ISnapshotTree | undefined,
194
218
  readAndParseBlob: ReadAndParseBlob,
195
219
  baseLogger: ITelemetryLogger,
@@ -201,8 +225,8 @@ export class GarbageCollector implements IGarbageCollector {
201
225
  gcOptions,
202
226
  deleteUnusedRoutes,
203
227
  getNodePackagePath,
204
- getCurrentTimestampMs,
205
- closeFn,
228
+ getCurrentReferenceTimestampMs,
229
+ getLastSummaryTimestampMs,
206
230
  baseSnapshot,
207
231
  readAndParseBlob,
208
232
  baseLogger,
@@ -307,9 +331,14 @@ export class GarbageCollector implements IGarbageCollector {
307
331
  private readonly deleteUnusedRoutes: (unusedRoutes: string[]) => void,
308
332
  /** For a given node path, returns the node's package path. */
309
333
  private readonly getNodePackagePath: (nodePath: string) => readonly string[] | undefined,
310
- /** Returns the current timestamp to be assigned to nodes that become unreferenced. */
311
- private readonly getCurrentTimestampMs: () => number,
312
- private readonly closeFn: (error?: ICriticalContainerError) => void,
334
+ /**
335
+ * Returns a referenced timestamp to be used to track unreferenced nodes. This is a server generated timestamp
336
+ * and may not be available if there aren't any ops processed yet. If so, we skip tracking unreferenced state
337
+ * such as time when node becomes unreferenced or inactive.
338
+ */
339
+ private readonly getCurrentReferenceTimestampMs: () => number | undefined,
340
+ /** Returns the timestamp of the last summary generated for this container. */
341
+ private readonly getLastSummaryTimestampMs: () => number | undefined,
313
342
  baseSnapshot: ISnapshotTree | undefined,
314
343
  readAndParseBlob: ReadAndParseBlob,
315
344
  baseLogger: ITelemetryLogger,
@@ -346,7 +375,7 @@ export class GarbageCollector implements IGarbageCollector {
346
375
  const timeoutMs = this.sessionExpiryTimeoutMs;
347
376
  setLongTimeout(timeoutMs,
348
377
  () => {
349
- this.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
378
+ this.provider.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
350
379
  },
351
380
  (timer) => {
352
381
  this.sessionExpiryTimer = timer;
@@ -448,10 +477,13 @@ export class GarbageCollector implements IGarbageCollector {
448
477
  return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
449
478
  });
450
479
 
451
- // Set up the initializer which initializes the base GC state from the base snapshot. Use lazy promise because
452
- // we only do this once - the very first time we run GC.
480
+ /**
481
+ * Set up the initializer which initializes the base GC state from the base snapshot. Note that the reference
482
+ * timestamp maybe from old ops which were not summarized and stored in the file. So, the unreferenced state
483
+ * may be out of date. This is fine because the state is updated every time GC runs based on the time then.
484
+ */
453
485
  this.initializeBaseStateP = new LazyPromise<void>(async () => {
454
- const currentTimestampMs = this.getCurrentTimestampMs();
486
+ const currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs();
455
487
  const baseState = await baseSummaryStateP;
456
488
  if (baseState === undefined) {
457
489
  return;
@@ -459,16 +491,13 @@ export class GarbageCollector implements IGarbageCollector {
459
491
 
460
492
  const gcNodes: { [ id: string ]: string[] } = {};
461
493
  for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
462
- const unreferencedTimestampMs = nodeData.unreferencedTimestampMs;
463
- if (unreferencedTimestampMs !== undefined) {
464
- // Get how long it has been since the node was unreferenced. Start a timeout for the remaining time
465
- // left for it to be eligible for deletion.
466
- const unreferencedDurationMs = currentTimestampMs - unreferencedTimestampMs;
494
+ if (nodeData.unreferencedTimestampMs !== undefined) {
467
495
  this.unreferencedNodesState.set(
468
496
  nodeId,
469
497
  new UnreferencedStateTracker(
470
- unreferencedTimestampMs,
471
- this.deleteTimeoutMs - unreferencedDurationMs,
498
+ nodeData.unreferencedTimestampMs,
499
+ this.deleteTimeoutMs,
500
+ currentReferenceTimestampMs,
472
501
  ),
473
502
  );
474
503
  }
@@ -571,10 +600,10 @@ export class GarbageCollector implements IGarbageCollector {
571
600
  this.updateStateSinceLastRun(gcData);
572
601
 
573
602
  // Update the current state of the system based on the GC run.
574
- const currentTimestampMs = this.getCurrentTimestampMs();
575
- this.updateCurrentState(gcData, gcResult, currentTimestampMs);
603
+ const currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs();
604
+ this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
576
605
 
577
- this.provider.updateUsedRoutes(gcResult.referencedNodeIds, currentTimestampMs);
606
+ this.provider.updateUsedRoutes(gcResult.referencedNodeIds, currentReferenceTimestampMs);
578
607
 
579
608
  if (runSweep) {
580
609
  // Placeholder for running sweep logic.
@@ -653,12 +682,14 @@ export class GarbageCollector implements IGarbageCollector {
653
682
  * Called when a node with the given id is updated. If the node is inactive, log an error.
654
683
  * @param nodePath - The id of the node that changed.
655
684
  * @param reason - Whether the node was loaded or changed.
685
+ * @param timestampMs - The timestamp when the node changed.
656
686
  * @param packagePath - The package path of the node. This may not be available if the node hasn't been loaded yet.
657
687
  * @param requestHeaders - If the node was loaded via request path, the headers in the request.
658
688
  */
659
689
  public nodeUpdated(
660
690
  nodePath: string,
661
691
  reason: "Loaded" | "Changed",
692
+ timestampMs?: number,
662
693
  packagePath?: readonly string[],
663
694
  requestHeaders?: IRequestHeader,
664
695
  ) {
@@ -669,7 +700,7 @@ export class GarbageCollector implements IGarbageCollector {
669
700
  this.logIfInactive(
670
701
  reason,
671
702
  nodePath,
672
- this.getCurrentTimestampMs(),
703
+ timestampMs,
673
704
  packagePath,
674
705
  requestHeaders,
675
706
  );
@@ -695,8 +726,6 @@ export class GarbageCollector implements IGarbageCollector {
695
726
  this.logIfInactive(
696
727
  "Revived",
697
728
  toNodePath,
698
- this.getCurrentTimestampMs(),
699
- this.getNodePackagePath(toNodePath),
700
729
  );
701
730
  }
702
731
 
@@ -725,28 +754,16 @@ export class GarbageCollector implements IGarbageCollector {
725
754
  * 3. Clears tracking for nodes that were unreferenced but became referenced in this run.
726
755
  * @param gcData - The data representing the reference graph on which GC is run.
727
756
  * @param gcResult - The result of the GC run on the gcData.
728
- * @param currentTimestampMs - The current timestamp to be used for unreferenced nodes' timestamp.
757
+ * @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
729
758
  */
730
- private updateCurrentState(gcData: IGarbageCollectionData, gcResult: IGCResult, currentTimestampMs: number) {
759
+ private updateCurrentState(
760
+ gcData: IGarbageCollectionData,
761
+ gcResult: IGCResult,
762
+ currentReferenceTimestampMs?: number,
763
+ ) {
731
764
  this.gcDataFromLastRun = cloneGCData(gcData);
732
765
  this.referencesSinceLastRun.clear();
733
766
 
734
- // Iterate through the deleted nodes and start tracking if they became unreferenced in this run.
735
- for (const nodeId of gcResult.deletedNodeIds) {
736
- // The time when the node became unreferenced. This is added to the current GC state.
737
- let unreferencedTimestampMs: number = currentTimestampMs;
738
- const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
739
- if (nodeStateTracker !== undefined) {
740
- unreferencedTimestampMs = nodeStateTracker.unreferencedTimestampMs;
741
- } else {
742
- // Start tracking this node as it became unreferenced in this run.
743
- this.unreferencedNodesState.set(
744
- nodeId,
745
- new UnreferencedStateTracker(unreferencedTimestampMs, this.deleteTimeoutMs),
746
- );
747
- }
748
- }
749
-
750
767
  // Iterate through the referenced nodes and stop tracking if they were unreferenced before.
751
768
  for (const nodeId of gcResult.referencedNodeIds) {
752
769
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
@@ -757,6 +774,36 @@ export class GarbageCollector implements IGarbageCollector {
757
774
  this.unreferencedNodesState.delete(nodeId);
758
775
  }
759
776
  }
777
+
778
+ /**
779
+ * If there is no current reference time, skip tracking when a node becomes unreferenced. This would happen
780
+ * if no ops have been processed ever and we still try to run GC. If so, there is nothing interesting to track
781
+ * anyway.
782
+ */
783
+ if (currentReferenceTimestampMs === undefined) {
784
+ return;
785
+ }
786
+
787
+ /**
788
+ * If a node became unreferenced in this run, start tracking it.
789
+ * If a node was already unreferenced, update its tracking information. Since the current reference time is
790
+ * from the ops seen, this will ensure that we keep updating the unreferenced state as time moves forward.
791
+ */
792
+ for (const nodeId of gcResult.deletedNodeIds) {
793
+ const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
794
+ if (nodeStateTracker === undefined) {
795
+ this.unreferencedNodesState.set(
796
+ nodeId,
797
+ new UnreferencedStateTracker(
798
+ currentReferenceTimestampMs,
799
+ this.deleteTimeoutMs,
800
+ currentReferenceTimestampMs,
801
+ ),
802
+ );
803
+ } else {
804
+ nodeStateTracker.updateTracking(currentReferenceTimestampMs);
805
+ }
806
+ }
760
807
  }
761
808
 
762
809
  /**
@@ -939,16 +986,21 @@ export class GarbageCollector implements IGarbageCollector {
939
986
  }
940
987
 
941
988
  /**
942
- * Logs an event if a node is inactive and is used. If the package data for the node exists, log immediately. Else,
943
- * queue it and it will be logged the next time GC runs as the package data should be available then.
989
+ * Logs an event if a node is inactive and is used.
944
990
  */
945
991
  private logIfInactive(
946
992
  eventSuffix: "Changed" | "Loaded" | "Revived",
947
993
  nodeId: string,
948
- timestampMs: number,
994
+ currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs(),
949
995
  packagePath?: readonly string[],
950
996
  requestHeaders?: IRequestHeader,
951
997
  ) {
998
+ // If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
999
+ // logging as nothing interesting would have happened worth logging.
1000
+ if (currentReferenceTimestampMs === undefined) {
1001
+ return;
1002
+ }
1003
+
952
1004
  const eventName = `inactiveObject_${eventSuffix}`;
953
1005
  // We log a particular event for a given node only once so that it is not too noisy.
954
1006
  const uniqueEventId = `${nodeId}-${eventName}`;
@@ -958,15 +1010,20 @@ export class GarbageCollector implements IGarbageCollector {
958
1010
  const event: IUnreferencedEvent = {
959
1011
  eventName,
960
1012
  id: nodeId,
961
- age: timestampMs - nodeState.unreferencedTimestampMs,
1013
+ age: currentReferenceTimestampMs - nodeState.unreferencedTimestampMs,
962
1014
  timeout: this.deleteTimeoutMs,
1015
+ lastSummaryTime: this.getLastSummaryTimestampMs(),
963
1016
  externalRequest: requestHeaders?.[RuntimeHeaders.externalRequest],
964
1017
  viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
965
1018
  };
966
- if (packagePath !== undefined) {
1019
+
1020
+ // If the package data for the node exists, log immediately. Otherwise, queue it and it will be logged the
1021
+ // next time GC runs as the package data should be available then.
1022
+ const pkg = packagePath ?? this.getNodePackagePath(nodeId);
1023
+ if (pkg !== undefined) {
967
1024
  this.mc.logger.sendErrorEvent({
968
1025
  ...event,
969
- pkg: { value: `/${packagePath.join("/")}`, tag: TelemetryDataTag.PackageData },
1026
+ pkg: { value: `/${pkg.join("/")}`, tag: TelemetryDataTag.PackageData },
970
1027
  });
971
1028
  } else {
972
1029
  this.pendingEventsQueue.push(event);
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "0.57.0-51086";
9
+ export const pkgVersion = "0.57.0";