@fluidframework/container-runtime 1.0.1 → 1.1.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 (58) hide show
  1. package/dist/connectionTelemetry.d.ts +19 -0
  2. package/dist/connectionTelemetry.d.ts.map +1 -1
  3. package/dist/connectionTelemetry.js +21 -21
  4. package/dist/connectionTelemetry.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +13 -1
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +99 -11
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStore.d.ts.map +1 -1
  10. package/dist/dataStore.js +14 -3
  11. package/dist/dataStore.js.map +1 -1
  12. package/dist/dataStoreRegistry.d.ts +0 -4
  13. package/dist/dataStoreRegistry.d.ts.map +1 -1
  14. package/dist/dataStoreRegistry.js +12 -1
  15. package/dist/dataStoreRegistry.js.map +1 -1
  16. package/dist/dataStores.d.ts.map +1 -1
  17. package/dist/dataStores.js +4 -4
  18. package/dist/dataStores.js.map +1 -1
  19. package/dist/garbageCollection.d.ts +20 -24
  20. package/dist/garbageCollection.d.ts.map +1 -1
  21. package/dist/garbageCollection.js +40 -117
  22. package/dist/garbageCollection.js.map +1 -1
  23. package/dist/packageVersion.d.ts +1 -1
  24. package/dist/packageVersion.js +1 -1
  25. package/dist/packageVersion.js.map +1 -1
  26. package/lib/connectionTelemetry.d.ts +19 -0
  27. package/lib/connectionTelemetry.d.ts.map +1 -1
  28. package/lib/connectionTelemetry.js +21 -21
  29. package/lib/connectionTelemetry.js.map +1 -1
  30. package/lib/containerRuntime.d.ts +13 -1
  31. package/lib/containerRuntime.d.ts.map +1 -1
  32. package/lib/containerRuntime.js +101 -13
  33. package/lib/containerRuntime.js.map +1 -1
  34. package/lib/dataStore.d.ts.map +1 -1
  35. package/lib/dataStore.js +15 -4
  36. package/lib/dataStore.js.map +1 -1
  37. package/lib/dataStoreRegistry.d.ts +0 -4
  38. package/lib/dataStoreRegistry.d.ts.map +1 -1
  39. package/lib/dataStoreRegistry.js +12 -1
  40. package/lib/dataStoreRegistry.js.map +1 -1
  41. package/lib/dataStores.d.ts.map +1 -1
  42. package/lib/dataStores.js +5 -5
  43. package/lib/dataStores.js.map +1 -1
  44. package/lib/garbageCollection.d.ts +20 -24
  45. package/lib/garbageCollection.d.ts.map +1 -1
  46. package/lib/garbageCollection.js +39 -115
  47. package/lib/garbageCollection.js.map +1 -1
  48. package/lib/packageVersion.d.ts +1 -1
  49. package/lib/packageVersion.js +1 -1
  50. package/lib/packageVersion.js.map +1 -1
  51. package/package.json +20 -48
  52. package/src/connectionTelemetry.ts +58 -37
  53. package/src/containerRuntime.ts +119 -26
  54. package/src/dataStore.ts +21 -4
  55. package/src/dataStoreRegistry.ts +8 -1
  56. package/src/dataStores.ts +6 -5
  57. package/src/garbageCollection.ts +66 -158
  58. package/src/packageVersion.ts +1 -1
@@ -11,7 +11,6 @@ import { mergeStats, SummaryTreeBuilder, } from "@fluidframework/runtime-utils";
11
11
  import { ChildLogger, loggerToMonitoringContext, PerformanceEvent, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
12
12
  import { RuntimeHeaders } from "./containerRuntime";
13
13
  import { getSummaryForDatastores } from "./dataStores";
14
- import { pkgVersion } from "./packageVersion";
15
14
  import { getGCVersion, metadataBlobName, dataStoreAttributesBlobName, } from "./summaryFormat";
16
15
  /** This is the current version of garbage collection. */
17
16
  const GCVersion = 1;
@@ -31,12 +30,8 @@ const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
31
30
  export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
32
31
  // Feature gate key to disable expiring session after a set period of time, even if expiry value is present
33
32
  export const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
34
- // Feature gate key to log error messages if GC reference validation fails.
35
- export const logUnknownOutboundReferencesKey = "Fluid.GarbageCollection.LogUnknownOutboundReferences";
36
33
  // Feature gate key to write the gc blob as a handle if the data is the same.
37
34
  export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
38
- // Feature gate key to limit which versions can write the gc blob as a handle if the data is the same.
39
- export const trackGCStateMinimumVersionKey = "Fluid.GarbageCollection.TrackGCState.MinVersion";
40
35
  const defaultInactiveTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
41
36
  export const defaultSessionExpiryDurationMs = 30 * 24 * 60 * 60 * 1000; // 30 days
42
37
  /** The types of GC nodes in the GC reference graph. */
@@ -114,21 +109,12 @@ class UnreferencedStateTracker {
114
109
  * NodeId = "dds1" NodeId = "dds2"
115
110
  */
116
111
  export class GarbageCollector {
117
- constructor(runtime, gcOptions,
118
- /** For a given node path, returns the node's package path. */
119
- getNodePackagePath,
120
- /** Returns the timestamp of the last summary generated for this container. */
121
- getLastSummaryTimestampMs, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata, isSummarizerClient = true) {
112
+ constructor(createParams) {
122
113
  var _a, _b, _c, _d, _e, _f, _g;
123
- this.runtime = runtime;
124
- this.gcOptions = gcOptions;
125
- this.getNodePackagePath = getNodePackagePath;
126
- this.getLastSummaryTimestampMs = getLastSummaryTimestampMs;
127
- this.isSummarizerClient = isSummarizerClient;
128
114
  /**
129
115
  * Tells whether the GC data should be written to the root of the summary tree.
130
116
  */
131
- this._writeDataAtRoot = false;
117
+ this._writeDataAtRoot = true;
132
118
  /**
133
119
  * Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
134
120
  * 1. The base snapshot contains GC state but GC is disabled. This will happen the first time GC is disabled after
@@ -154,7 +140,15 @@ export class GarbageCollector {
154
140
  this.pendingEventsQueue = [];
155
141
  // The number of times GC has successfully completed on this instance of GarbageCollector.
156
142
  this.completedRuns = 0;
157
- this.mc = loggerToMonitoringContext(ChildLogger.create(baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
143
+ this.runtime = createParams.runtime;
144
+ this.isSummarizerClient = createParams.isSummarizerClient;
145
+ this.gcOptions = createParams.gcOptions;
146
+ this.getNodePackagePath = createParams.getNodePackagePath;
147
+ this.getLastSummaryTimestampMs = createParams.getLastSummaryTimestampMs;
148
+ const baseSnapshot = createParams.baseSnapshot;
149
+ const metadata = createParams.metadata;
150
+ const readAndParseBlob = createParams.readAndParseBlob;
151
+ this.mc = loggerToMonitoringContext(ChildLogger.create(createParams.baseLogger, "GarbageCollector", { all: { completedGCRuns: () => this.completedRuns } }));
158
152
  let prevSummaryGCVersion;
159
153
  /**
160
154
  * The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
@@ -163,7 +157,7 @@ export class GarbageCollector {
163
157
  * 3. Whether GC session expiry is enabled or not.
164
158
  * For existing containers, we get this information from the metadata blob of its summary.
165
159
  */
166
- if (existing) {
160
+ if (createParams.existing) {
167
161
  prevSummaryGCVersion = getGCVersion(metadata);
168
162
  // Existing documents which did not have metadata blob or had GC disabled have version as 0. For all
169
163
  // other existing documents, GC is enabled.
@@ -174,14 +168,14 @@ export class GarbageCollector {
174
168
  else {
175
169
  // Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
176
170
  // scenario but explicitly failing makes it clearer and promotes correct usage.
177
- if (gcOptions.sweepAllowed && gcOptions.gcAllowed === false) {
171
+ if (this.gcOptions.sweepAllowed && this.gcOptions.gcAllowed === false) {
178
172
  throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
179
173
  }
180
174
  // For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
181
175
  // flag in GC options to false.
182
- this.gcEnabled = gcOptions.gcAllowed !== false;
176
+ this.gcEnabled = this.gcOptions.gcAllowed !== false;
183
177
  // The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
184
- this.sweepEnabled = gcOptions.sweepAllowed === true;
178
+ this.sweepEnabled = this.gcOptions.sweepAllowed === true;
185
179
  // Set the Session Expiry only if the flag is enabled or the test option is set.
186
180
  if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
187
181
  this.sessionExpiryTimeoutMs = defaultSessionExpiryDurationMs;
@@ -215,10 +209,8 @@ export class GarbageCollector {
215
209
  // GC must be enabled for the document.
216
210
  this.gcEnabled
217
211
  // GC must not be disabled via GC options.
218
- && !gcOptions.disableGC);
219
- const minimumVersion = this.mc.config.getString(trackGCStateMinimumVersionKey);
220
- const shouldTrackStateForVersion = meetsMinimumVersionRequirement(pkgVersion, minimumVersion);
221
- this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true && shouldTrackStateForVersion;
212
+ && !this.gcOptions.disableGC);
213
+ this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
222
214
  /**
223
215
  * Whether sweep should run or not. The following conditions have to be met to run sweep:
224
216
  * 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
@@ -230,19 +222,15 @@ export class GarbageCollector {
230
222
  this.inactiveTimeoutMs =
231
223
  (_e = (_d = this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.InactiveTimeoutMs")) !== null && _d !== void 0 ? _d : this.gcOptions.inactiveTimeoutMs) !== null && _e !== void 0 ? _e : defaultInactiveTimeoutMs;
232
224
  // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
233
- this.testMode = (_f = this.mc.config.getBoolean(gcTestModeKey)) !== null && _f !== void 0 ? _f : gcOptions.runGCInTestMode === true;
234
- /**
235
- * Enable resetting initial state once the following issue is resolved:
236
- * https://github.com/microsoft/FluidFramework/issues/8878.
237
- * Currently, the GC tree is not written at root, so we don't know if the base snapshot contains GC tree or not.
238
- */
239
- // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't contain
240
- // GC tree and GC is enabled.
241
- // const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
242
- // this.initialStateNeedsReset = gcTreePresent ? !this.shouldRunGC : this.shouldRunGC;
243
- // If `writeDataAtRoot` setting is true, write the GC data into the root of the summary tree. We do this so that
244
- // the roll out can be staged. Once its rolled out everywhere, we will start writing at root by default.
245
- this._writeDataAtRoot = (_g = this.mc.config.getBoolean(writeAtRootKey)) !== null && _g !== void 0 ? _g : this.gcOptions.writeDataAtRoot === true;
225
+ this.testMode = (_f = this.mc.config.getBoolean(gcTestModeKey)) !== null && _f !== void 0 ? _f : this.gcOptions.runGCInTestMode === true;
226
+ // GC state is written into root of the summary tree by default. Can be overridden via feature flag for now.
227
+ this._writeDataAtRoot = (_g = this.mc.config.getBoolean(writeAtRootKey)) !== null && _g !== void 0 ? _g : true;
228
+ if (this._writeDataAtRoot) {
229
+ // The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
230
+ // contain GC tree and GC is enabled.
231
+ const gcTreePresent = (baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[gcTreeKey]) !== undefined;
232
+ this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
233
+ }
246
234
  // Get the GC state from the GC blob in the base snapshot. Use LazyPromise because we only want to do
247
235
  // this once since it involves fetching blobs from storage which is expensive.
248
236
  const baseSummaryStateP = new LazyPromise(async () => {
@@ -331,7 +319,7 @@ export class GarbageCollector {
331
319
  // Run GC on the nodes in the base summary to get the routes used in each node in the container.
332
320
  // This is an optimization for space (vs performance) wherein we don't need to store the used routes of
333
321
  // each node in the summary.
334
- const usedRoutes = runGarbageCollection(gcNodes, ["/"], this.mc.logger).referencedNodeIds;
322
+ const usedRoutes = runGarbageCollection(gcNodes, ["/"]).referencedNodeIds;
335
323
  const baseGCDetailsMap = unpackChildNodesGCDetails({ gcData: { gcNodes }, usedRoutes });
336
324
  // Currently, the nodes may write the GC data. So, we need to update it's base GC details with the
337
325
  // unreferenced timestamp. Once we start writing the GC data here, we won't need to do this anymore.
@@ -347,7 +335,7 @@ export class GarbageCollector {
347
335
  });
348
336
  // Log all the GC options and the state determined by the garbage collector. This is interesting only for the
349
337
  // summarizer client since it is the only one that runs GC. It also helps keep the telemetry less noisy.
350
- const gcConfigProps = JSON.stringify(Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, writeAtRoot: this._writeDataAtRoot, testMode: this.testMode, sessionExpiry: this.sessionExpiryTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, existing }, this.gcOptions));
338
+ const gcConfigProps = JSON.stringify(Object.assign({ gcEnabled: this.gcEnabled, sweepEnabled: this.sweepEnabled, runGC: this.shouldRunGC, runSweep: this.shouldRunSweep, writeAtRoot: this._writeDataAtRoot, testMode: this.testMode, sessionExpiry: this.sessionExpiryTimeoutMs, inactiveTimeout: this.inactiveTimeoutMs, existing: createParams.existing }, this.gcOptions));
351
339
  if (this.isSummarizerClient) {
352
340
  this.mc.logger.sendTelemetryEvent({
353
341
  eventName: "GarbageCollectorLoaded",
@@ -363,8 +351,8 @@ export class GarbageCollector {
363
351
  });
364
352
  }
365
353
  }
366
- static create(provider, gcOptions, getNodePackagePath, getLastSummaryTimestampMs, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata, isSummarizerClient) {
367
- return new GarbageCollector(provider, gcOptions, getNodePackagePath, getLastSummaryTimestampMs, baseSnapshot, readAndParseBlob, baseLogger, existing, metadata, isSummarizerClient);
354
+ static create(createParams) {
355
+ return new GarbageCollector(createParams);
368
356
  }
369
357
  /**
370
358
  * Tells whether the GC state needs to be reset in the next summary. We need to do this if:
@@ -396,7 +384,7 @@ export class GarbageCollector {
396
384
  await this.runtime.updateStateBeforeGC();
397
385
  // Get the runtime's GC data and run GC on the reference graph in it.
398
386
  const gcData = await this.runtime.getGCData(fullGC);
399
- const gcResult = runGarbageCollection(gcData.gcNodes, ["/"], logger);
387
+ const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
400
388
  const gcStats = this.generateStatsAndLogEvents(gcResult, logger);
401
389
  // Update the state since the last GC run. There can be nodes that were referenced between the last and
402
390
  // the current run. We need to identify than and update their unreferenced state if needed.
@@ -485,9 +473,6 @@ export class GarbageCollector {
485
473
  * latest summary tracked.
486
474
  */
487
475
  async latestSummaryStateRefreshed(result, readAndParseBlob) {
488
- // After a summary is successfully submitted and ack'd by this client, the GC state should have been reset in
489
- // the summary and doesn't need to be reset anymore.
490
- this.initialStateNeedsReset = false;
491
476
  if (!this.shouldRunGC || !result.latestSummaryUpdated) {
492
477
  return;
493
478
  }
@@ -495,6 +480,7 @@ export class GarbageCollector {
495
480
  // Basically, it was written in the current GC version.
496
481
  if (result.wasSummaryTracked) {
497
482
  this.latestSummaryGCVersion = this.currentGCVersion;
483
+ this.initialStateNeedsReset = false;
498
484
  if (this.trackGCState) {
499
485
  this.latestSerializedSummaryState = this.pendingSerializedSummaryState;
500
486
  this.pendingSerializedSummaryState = undefined;
@@ -617,10 +603,7 @@ export class GarbageCollector {
617
603
  }
618
604
  // Find any references that haven't been identified correctly.
619
605
  const missingExplicitReferences = this.findMissingExplicitReferences(currentGCData, this.previousGCDataFromLastRun, this.newReferencesSinceLastRun);
620
- // The following log will be enabled once this issue is resolved:
621
- // https://github.com/microsoft/FluidFramework/issues/8878.
622
- if (this.mc.config.getBoolean(logUnknownOutboundReferencesKey) === true
623
- && missingExplicitReferences.length > 0) {
606
+ if (this.writeDataAtRoot && missingExplicitReferences.length > 0) {
624
607
  missingExplicitReferences.forEach((missingExplicitReference) => {
625
608
  const event = {
626
609
  eventName: "gcUnknownOutboundReferences",
@@ -664,7 +647,7 @@ export class GarbageCollector {
664
647
  * unreferenced, stop tracking them and remove from unreferenced list.
665
648
  * Some of these nodes may be unreferenced now and if so, the current run will add unreferenced state for them.
666
649
  */
667
- const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"], logger);
650
+ const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
668
651
  for (const nodeId of gcResult.referencedNodeIds) {
669
652
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
670
653
  if (nodeStateTracker !== undefined) {
@@ -744,11 +727,10 @@ export class GarbageCollector {
744
727
  };
745
728
  const updateNodeStats = (nodeId, referenced) => {
746
729
  gcStats.nodeCount++;
747
- /**
748
- * `this.unreferencedNodesState` has the previous unreferenced state of all nodes. `referenced` flag passed
749
- * here is current state of the give node. Check if the reference state of the changed.
750
- */
751
- const stateUpdated = this.unreferencedNodesState.has(nodeId) ? referenced : !referenced;
730
+ // If there is no previous GC data, every node's state is generated and is considered as updated.
731
+ // Otherwise, find out if any node went from referenced to unreferenced or vice-versa.
732
+ const stateUpdated = this.previousGCDataFromLastRun === undefined ||
733
+ this.unreferencedNodesState.has(nodeId) === referenced;
752
734
  if (stateUpdated) {
753
735
  gcStats.updatedNodeCount++;
754
736
  }
@@ -824,7 +806,7 @@ export class GarbageCollector {
824
806
  // next time GC runs as the package data should be available then.
825
807
  const pkg = packagePath !== null && packagePath !== void 0 ? packagePath : this.getNodePackagePath(nodeId);
826
808
  if (pkg !== undefined) {
827
- this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, event), { pkg: { value: `/${pkg.join("/")}`, tag: TelemetryDataTag.PackageData } }));
809
+ this.mc.logger.sendErrorEvent(Object.assign(Object.assign({}, event), { pkg: { value: pkg.join("/"), tag: TelemetryDataTag.PackageData } }));
828
810
  }
829
811
  else {
830
812
  this.pendingEventsQueue.push(event);
@@ -883,62 +865,4 @@ function setLongTimeout(timeoutMs, timeoutFn, setTimerFn) {
883
865
  }
884
866
  setTimerFn(timer);
885
867
  }
886
- /**
887
- * meetsMinimumVersionRequirement is used determining if a feature version should be run. This is similar to feature
888
- * 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
889
- * feature disabled for version 0.1.1 and enabled for 0.2.1. Older versions will run without the feature and new
890
- * versions will run with the feature.
891
- * @param currentVersion - the total time the timeout needs to last in ms
892
- * @param minimumVersion - the function to execute when the timer ends
893
- */
894
- function meetsMinimumVersionRequirement(currentVersion, minimumVersion) {
895
- return minimumVersion === undefined || semverCompare(currentVersion, minimumVersion) >= 0;
896
- }
897
- /**
898
- * Compare semver versions.
899
- * @param currentVersion - assumed to be any valid semver version
900
- * @param minimumVersion - must be [major].[minor].[patch], where major, minor, and patch are all numbers
901
- * as it complicates the algorithm if we allow comparisons against minimum pre-release versions.
902
- * @returns
903
- * 0 if the currentVersion equals the minimumVersion
904
- * 1 if the currentVersion is greater than the minimumVersion
905
- * -1 if the minimumVersion is greater than the currentVersion
906
- */
907
- export function semverCompare(currentVersion, minimumVersion) {
908
- const minimumValues = minimumVersion.split(".").map((value) => {
909
- assert(isNaN(+value) === false, 0x2f8 /* Expected real numbers in minimum version! */);
910
- return Number.parseInt(value, 10);
911
- });
912
- assert(minimumValues.length === 3, 0x2f9 /* Expected minimumVersion to be [major].[minor].[patch] */);
913
- const [minMajor, minMinor, minPatch] = minimumValues;
914
- const currentValuesString = currentVersion.split(/\W/);
915
- assert(currentValuesString.length >= 3, 0x2fa /* Expected version to match semver rules! */);
916
- const currentValues = currentValuesString.slice(0, 3).map((value) => {
917
- assert(isNaN(+value) === false, 0x2fb /* Expected real numbers in minimum version! */);
918
- return Number.parseInt(value, 10);
919
- });
920
- const [cMajor, cMinor, cPatch] = currentValues;
921
- if (cMajor > minMajor) {
922
- return 1;
923
- }
924
- else if (minMajor > cMajor) {
925
- return -1;
926
- }
927
- if (cMinor > minMinor) {
928
- return 1;
929
- }
930
- else if (minMinor > cMinor) {
931
- return -1;
932
- }
933
- if (cPatch > minPatch) {
934
- return 1;
935
- }
936
- else if (minPatch > cPatch) {
937
- return -1;
938
- }
939
- if (currentValuesString.length === 3) {
940
- return 0;
941
- }
942
- return -1;
943
- }
944
868
  //# sourceMappingURL=garbageCollection.js.map