@fluidframework/container-runtime 1.2.1 → 2.0.0-internal.1.0.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 (136) hide show
  1. package/dist/batchTracker.js +1 -1
  2. package/dist/batchTracker.js.map +1 -1
  3. package/dist/blobManager.d.ts +81 -25
  4. package/dist/blobManager.d.ts.map +1 -1
  5. package/dist/blobManager.js +301 -100
  6. package/dist/blobManager.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +66 -49
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +129 -164
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/dataStore.js +29 -24
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +3 -4
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +16 -23
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +6 -3
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +13 -5
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/garbageCollection.d.ts.map +1 -1
  22. package/dist/garbageCollection.js +17 -12
  23. package/dist/garbageCollection.js.map +1 -1
  24. package/dist/opProperties.d.ts +7 -0
  25. package/dist/opProperties.d.ts.map +1 -0
  26. package/dist/opProperties.js +20 -0
  27. package/dist/opProperties.js.map +1 -0
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/packageVersion.d.ts.map +1 -1
  30. package/dist/packageVersion.js +1 -1
  31. package/dist/packageVersion.js.map +1 -1
  32. package/dist/runningSummarizer.d.ts +28 -4
  33. package/dist/runningSummarizer.d.ts.map +1 -1
  34. package/dist/runningSummarizer.js +93 -26
  35. package/dist/runningSummarizer.js.map +1 -1
  36. package/dist/summarizer.d.ts +0 -2
  37. package/dist/summarizer.d.ts.map +1 -1
  38. package/dist/summarizer.js +34 -15
  39. package/dist/summarizer.js.map +1 -1
  40. package/dist/summarizerHeuristics.d.ts +26 -4
  41. package/dist/summarizerHeuristics.d.ts.map +1 -1
  42. package/dist/summarizerHeuristics.js +95 -18
  43. package/dist/summarizerHeuristics.js.map +1 -1
  44. package/dist/summarizerTypes.d.ts +30 -10
  45. package/dist/summarizerTypes.d.ts.map +1 -1
  46. package/dist/summarizerTypes.js.map +1 -1
  47. package/dist/summaryCollection.js +1 -1
  48. package/dist/summaryCollection.js.map +1 -1
  49. package/dist/summaryFormat.d.ts +0 -5
  50. package/dist/summaryFormat.d.ts.map +1 -1
  51. package/dist/summaryFormat.js.map +1 -1
  52. package/dist/summaryGenerator.d.ts +1 -0
  53. package/dist/summaryGenerator.d.ts.map +1 -1
  54. package/dist/summaryGenerator.js +11 -9
  55. package/dist/summaryGenerator.js.map +1 -1
  56. package/lib/batchTracker.js +1 -1
  57. package/lib/batchTracker.js.map +1 -1
  58. package/lib/blobManager.d.ts +81 -25
  59. package/lib/blobManager.d.ts.map +1 -1
  60. package/lib/blobManager.js +302 -101
  61. package/lib/blobManager.js.map +1 -1
  62. package/lib/containerRuntime.d.ts +66 -49
  63. package/lib/containerRuntime.d.ts.map +1 -1
  64. package/lib/containerRuntime.js +131 -166
  65. package/lib/containerRuntime.js.map +1 -1
  66. package/lib/dataStore.js +29 -24
  67. package/lib/dataStore.js.map +1 -1
  68. package/lib/dataStoreContext.d.ts +3 -4
  69. package/lib/dataStoreContext.d.ts.map +1 -1
  70. package/lib/dataStoreContext.js +17 -24
  71. package/lib/dataStoreContext.js.map +1 -1
  72. package/lib/dataStores.d.ts +6 -3
  73. package/lib/dataStores.d.ts.map +1 -1
  74. package/lib/dataStores.js +13 -5
  75. package/lib/dataStores.js.map +1 -1
  76. package/lib/garbageCollection.d.ts.map +1 -1
  77. package/lib/garbageCollection.js +17 -12
  78. package/lib/garbageCollection.js.map +1 -1
  79. package/lib/opProperties.d.ts +7 -0
  80. package/lib/opProperties.d.ts.map +1 -0
  81. package/lib/opProperties.js +16 -0
  82. package/lib/opProperties.js.map +1 -0
  83. package/lib/packageVersion.d.ts +1 -1
  84. package/lib/packageVersion.d.ts.map +1 -1
  85. package/lib/packageVersion.js +1 -1
  86. package/lib/packageVersion.js.map +1 -1
  87. package/lib/runningSummarizer.d.ts +28 -4
  88. package/lib/runningSummarizer.d.ts.map +1 -1
  89. package/lib/runningSummarizer.js +93 -26
  90. package/lib/runningSummarizer.js.map +1 -1
  91. package/lib/summarizer.d.ts +0 -2
  92. package/lib/summarizer.d.ts.map +1 -1
  93. package/lib/summarizer.js +36 -17
  94. package/lib/summarizer.js.map +1 -1
  95. package/lib/summarizerHeuristics.d.ts +26 -4
  96. package/lib/summarizerHeuristics.d.ts.map +1 -1
  97. package/lib/summarizerHeuristics.js +95 -18
  98. package/lib/summarizerHeuristics.js.map +1 -1
  99. package/lib/summarizerTypes.d.ts +30 -10
  100. package/lib/summarizerTypes.d.ts.map +1 -1
  101. package/lib/summarizerTypes.js.map +1 -1
  102. package/lib/summaryCollection.js +1 -1
  103. package/lib/summaryCollection.js.map +1 -1
  104. package/lib/summaryFormat.d.ts +0 -5
  105. package/lib/summaryFormat.d.ts.map +1 -1
  106. package/lib/summaryFormat.js.map +1 -1
  107. package/lib/summaryGenerator.d.ts +1 -0
  108. package/lib/summaryGenerator.d.ts.map +1 -1
  109. package/lib/summaryGenerator.js +11 -9
  110. package/lib/summaryGenerator.js.map +1 -1
  111. package/package.json +55 -21
  112. package/src/batchTracker.ts +1 -1
  113. package/src/blobManager.ts +364 -119
  114. package/src/containerRuntime.ts +232 -216
  115. package/src/dataStore.ts +49 -37
  116. package/src/dataStoreContext.ts +16 -23
  117. package/src/dataStores.ts +27 -16
  118. package/src/garbageCollection.ts +13 -7
  119. package/src/opProperties.ts +19 -0
  120. package/src/packageVersion.ts +1 -1
  121. package/src/runningSummarizer.ts +108 -23
  122. package/src/summarizer.ts +47 -28
  123. package/src/summarizerHeuristics.ts +133 -19
  124. package/src/summarizerTypes.ts +37 -10
  125. package/src/summaryCollection.ts +1 -1
  126. package/src/summaryFormat.ts +0 -6
  127. package/src/summaryGenerator.ts +40 -22
  128. package/dist/opTelemetry.d.ts +0 -22
  129. package/dist/opTelemetry.d.ts.map +0 -1
  130. package/dist/opTelemetry.js +0 -59
  131. package/dist/opTelemetry.js.map +0 -1
  132. package/lib/opTelemetry.d.ts +0 -22
  133. package/lib/opTelemetry.d.ts.map +0 -1
  134. package/lib/opTelemetry.js +0 -55
  135. package/lib/opTelemetry.js.map +0 -1
  136. package/src/opTelemetry.ts +0 -71
@@ -32,7 +32,6 @@ const garbageCollection_1 = require("./garbageCollection");
32
32
  const dataStore_1 = require("./dataStore");
33
33
  const batchTracker_1 = require("./batchTracker");
34
34
  const serializedSnapshotStorage_1 = require("./serializedSnapshotStorage");
35
- const opTelemetry_1 = require("./opTelemetry");
36
35
  var ContainerMessageType;
37
36
  (function (ContainerMessageType) {
38
37
  // An op to be delivered to store
@@ -50,14 +49,18 @@ var ContainerMessageType;
50
49
  })(ContainerMessageType = exports.ContainerMessageType || (exports.ContainerMessageType = {}));
51
50
  exports.DefaultSummaryConfiguration = {
52
51
  state: "enabled",
53
- idleTime: 5000 * 3,
54
- maxTime: 5000 * 12,
52
+ idleTime: 15 * 1000,
53
+ minIdleTime: 0,
54
+ maxIdleTime: 30 * 1000,
55
+ maxTime: 60 * 1000,
55
56
  maxOps: 100,
56
57
  minOpsForLastSummaryAttempt: 10,
57
- maxAckWaitTime: 6 * 10 * 1000,
58
+ maxAckWaitTime: 10 * 60 * 1000,
58
59
  maxOpsSinceLastSummary: 7000,
59
- initialSummarizerDelayMs: 5000,
60
+ initialSummarizerDelayMs: 5 * 1000,
60
61
  summarizerClientElection: false,
62
+ nonRuntimeOpWeight: 0.1,
63
+ runtimeOpWeight: 1.0,
61
64
  };
62
65
  /**
63
66
  * Accepted header keys for requests coming to the runtime.
@@ -74,7 +77,6 @@ var RuntimeHeaders;
74
77
  /** True if the request is coming from an IFluidHandle. */
75
78
  RuntimeHeaders["viaHandle"] = "viaHandle";
76
79
  })(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
77
- const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
78
80
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
79
81
  // Feature gate for the max op size. If the value is negative, chunking is enabled
80
82
  // and all ops over 16k would be chunked. If the value is positive, all ops with
@@ -84,10 +86,6 @@ const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
84
86
  // in order to account for some extra overhead from serialization
85
87
  // to not reach the 1MB limits in socket.io and Kafka.
86
88
  const defaultMaxOpSizeInBytes = 768000;
87
- // By default, the size of the contents for the incoming ops is tracked.
88
- // However, in certain situations, this may incur a performance hit.
89
- // The feature-gate below can be used to disable this feature.
90
- const disableOpTrackingKey = "Fluid.ContainerRuntime.DisableOpTracking";
91
89
  const defaultFlushMode = runtime_definitions_1.FlushMode.TurnBased;
92
90
  var RuntimeMessage;
93
91
  (function (RuntimeMessage) {
@@ -380,7 +378,7 @@ exports.getDeviceSpec = getDeviceSpec;
380
378
  */
381
379
  class ContainerRuntime extends common_utils_1.TypedEventEmitter {
382
380
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
383
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
381
+ var _a, _b, _c, _d, _e, _f;
384
382
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, exports.DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
385
383
  super();
386
384
  this.context = context;
@@ -441,20 +439,20 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
441
439
  this.chunkMap = new Map(chunks);
442
440
  this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
443
441
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
442
+ if (this.summaryConfiguration.state === "enabled") {
443
+ this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
444
+ }
444
445
  this.summariesDisabled = this.isSummariesDisabled();
445
446
  this.heuristicsDisabled = this.isHeuristicsDisabled();
446
447
  this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
447
448
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
448
449
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
449
- this._aliasingEnabled =
450
- ((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
451
- ((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
452
- this._maxOpSizeInBytes = ((_e = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _e !== void 0 ? _e : defaultMaxOpSizeInBytes);
450
+ this._maxOpSizeInBytes = ((_c = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _c !== void 0 ? _c : defaultMaxOpSizeInBytes);
453
451
  this.maxConsecutiveReconnects =
454
- (_f = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _f !== void 0 ? _f : this.defaultMaxConsecutiveReconnects;
452
+ (_d = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _d !== void 0 ? _d : this.defaultMaxConsecutiveReconnects;
455
453
  this._flushMode = runtimeOptions.flushMode;
456
454
  const pendingRuntimeState = context.pendingLocalState;
457
- const baseSnapshot = (_g = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _g !== void 0 ? _g : context.baseSnapshot;
455
+ const baseSnapshot = (_e = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _e !== void 0 ? _e : context.baseSnapshot;
458
456
  this.garbageCollector = garbageCollection_1.GarbageCollector.create({
459
457
  runtime: this,
460
458
  gcOptions: this.runtimeOptions.gcOptions,
@@ -488,7 +486,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
488
486
  this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
489
487
  }
490
488
  this.dataStores = new dataStores_1.DataStores((0, dataStores_1.getSummaryForDatastores)(baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getBaseGCDetails(), (path, timestampMs, packagePath) => this.garbageCollector.nodeUpdated(path, "Changed", timestampMs, packagePath), new Map(dataStoreAliasMap), this.garbageCollector.writeDataAtRoot);
491
- this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, this.logger);
489
+ this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId }), (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this);
492
490
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
493
491
  this.deltaSender = this.deltaManager;
494
492
  this.pendingStateManager = new pendingStateManager_1.PendingStateManager({
@@ -579,9 +577,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
579
577
  createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
580
578
  createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
581
579
  };
582
- // back-compat 0.59.3000 - Older document may either write summaryCount or not write it at all. If it does
583
- // not write it, initialize summaryNumber to 0.
584
- loadSummaryNumber = (_j = (_h = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _h !== void 0 ? _h : metadata === null || metadata === void 0 ? void 0 : metadata.summaryCount) !== null && _j !== void 0 ? _j : 0;
580
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
581
+ // the count is reset to 0.
582
+ loadSummaryNumber = (_f = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _f !== void 0 ? _f : 0;
585
583
  }
586
584
  else {
587
585
  this.createContainerMetadata = {
@@ -594,7 +592,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
594
592
  this.logger.sendTelemetryEvent(Object.assign(Object.assign(Object.assign({ eventName: "ContainerLoadStats" }, this.createContainerMetadata), this.dataStores.containerLoadStats), { summaryNumber: loadSummaryNumber, summaryFormatVersion: metadata === null || metadata === void 0 ? void 0 : metadata.summaryFormatVersion, disableIsolatedChannels: metadata === null || metadata === void 0 ? void 0 : metadata.disableIsolatedChannels, gcVersion: metadata === null || metadata === void 0 ? void 0 : metadata.gcFeature }));
595
593
  (0, connectionTelemetry_1.ReportOpPerfTelemetry)(this.context.clientId, this.deltaManager, this.logger);
596
594
  (0, batchTracker_1.BindBatchTracker)(this, this.logger);
597
- this.opTracker = new opTelemetry_1.OpTracker(this.deltaManager, this.mc.config.getBoolean(disableOpTrackingKey) === true);
598
595
  }
599
596
  get IContainerRuntime() { return this; }
600
597
  get IFluidRouter() { return this; }
@@ -617,7 +614,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
617
614
  runtimeVersion: packageVersion_1.pkgVersion,
618
615
  },
619
616
  });
620
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
617
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
621
618
  const pendingRuntimeState = context.pendingLocalState;
622
619
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
623
620
  const storage = !pendingRuntimeState ?
@@ -670,7 +667,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
670
667
  summaryOptions,
671
668
  gcOptions,
672
669
  loadSequenceNumberVerification,
673
- useDataStoreAliasing,
674
670
  flushMode,
675
671
  enableOfflineLoad,
676
672
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
@@ -863,12 +859,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
863
859
  return this.resolveHandle(requestParser.createSubRequest(1));
864
860
  }
865
861
  if (id === blobManager_1.BlobManager.basePath && requestParser.isLeaf(2)) {
866
- const handle = await this.blobManager.getBlob(requestParser.pathParts[1]);
867
- if (handle) {
862
+ const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
863
+ if (blob) {
868
864
  return {
869
865
  status: 200,
870
866
  mimeType: "fluid/object",
871
- value: handle.get(),
867
+ value: blob,
872
868
  };
873
869
  }
874
870
  else {
@@ -891,13 +887,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
891
887
  }
892
888
  internalId(maybeAlias) {
893
889
  var _a;
894
- return (_a = this.dataStores.aliases().get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
890
+ return (_a = this.dataStores.aliases.get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
895
891
  }
896
892
  async getDataStoreFromRequest(id, request) {
897
893
  var _a, _b, _c;
898
894
  const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
899
895
  ? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
900
896
  : true;
897
+ await this.dataStores.waitIfPendingAlias(id);
901
898
  const internalId = this.internalId(id);
902
899
  const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
903
900
  /**
@@ -927,8 +924,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
927
924
  addMetadataToSummary(summaryTree) {
928
925
  var _a;
929
926
  const metadata = Object.assign(Object.assign(Object.assign(Object.assign({}, this.createContainerMetadata), {
930
- // back-compat 0.59.3000: This is renamed to summaryNumber. Can be removed when 0.59.3000 saturates.
931
- summaryCount: this.nextSummaryNumber,
932
927
  // Increment the summary number for the next summary that will be generated.
933
928
  summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1, disableIsolatedChannels: this.disableIsolatedChannels || undefined }), this.garbageCollector.getMetadata()), {
934
929
  // The last message processed at the time of summary. If there are no new messages, use the message from the
@@ -943,7 +938,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
943
938
  const content = JSON.stringify([...this.chunkMap]);
944
939
  (0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.chunksBlobName, content);
945
940
  }
946
- const dataStoreAliases = this.dataStores.aliases();
941
+ const dataStoreAliases = this.dataStores.aliases;
947
942
  if (dataStoreAliases.size > 0) {
948
943
  (0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.aliasBlobName, JSON.stringify([...dataStoreAliases]));
949
944
  }
@@ -1040,6 +1035,35 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1040
1035
  }
1041
1036
  }
1042
1037
  setConnectionState(connected, clientId) {
1038
+ if (connected === false && this.delayConnectClientId !== undefined) {
1039
+ this.delayConnectClientId = undefined;
1040
+ this.mc.logger.sendTelemetryEvent({
1041
+ eventName: "UnsuccessfulConnectedTransition",
1042
+ });
1043
+ // Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
1044
+ return;
1045
+ }
1046
+ // If attachment blobs were added while disconnected, we need to delay
1047
+ // propagation of the "connected" event until we have uploaded them to
1048
+ // ensure we don't submit ops referencing a blob that has not been uploaded
1049
+ const connecting = connected && !this._connected && !this.deltaManager.readOnlyInfo.readonly;
1050
+ if (connecting && this.blobManager.hasPendingOfflineUploads) {
1051
+ (0, common_utils_1.assert)(!this.delayConnectClientId, 0x392 /* Connect event delay must be canceled before subsequent connect event */);
1052
+ (0, common_utils_1.assert)(!!clientId, 0x393 /* Must have clientId when connecting */);
1053
+ this.delayConnectClientId = clientId;
1054
+ this.blobManager.onConnected().then(() => {
1055
+ // make sure we didn't reconnect before the promise resolved
1056
+ if (this.delayConnectClientId === clientId && !this.disposed) {
1057
+ this.delayConnectClientId = undefined;
1058
+ this.setConnectionStateCore(connected, clientId);
1059
+ }
1060
+ }, (error) => this.closeFn(error));
1061
+ return;
1062
+ }
1063
+ this.setConnectionStateCore(connected, clientId);
1064
+ }
1065
+ setConnectionStateCore(connected, clientId) {
1066
+ (0, common_utils_1.assert)(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
1043
1067
  this.verifyNotClosed();
1044
1068
  // There might be no change of state due to Container calling this API after loading runtime.
1045
1069
  const changeOfState = this._connected !== connected;
@@ -1070,7 +1094,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1070
1094
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, connected, clientId);
1071
1095
  }
1072
1096
  process(messageArg, local) {
1073
- var _a, _b;
1097
+ var _a;
1074
1098
  this.verifyNotClosed();
1075
1099
  // If it's not message for runtime, bail out right away.
1076
1100
  if (!(0, driver_utils_1.isUnpackedRuntimeMessage)(messageArg)) {
@@ -1117,8 +1141,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1117
1141
  this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
1118
1142
  break;
1119
1143
  case ContainerMessageType.BlobAttach:
1120
- (0, common_utils_1.assert)((_b = message === null || message === void 0 ? void 0 : message.metadata) === null || _b === void 0 ? void 0 : _b.blobId, 0x12a /* "Missing blob id on metadata" */);
1121
- this.blobManager.processBlobAttachOp(message.metadata.blobId, local);
1144
+ this.blobManager.processBlobAttachOp(message, local);
1122
1145
  break;
1123
1146
  default:
1124
1147
  }
@@ -1188,6 +1211,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1188
1211
  this.dataStores.processSignal(envelope.address, transformed, local);
1189
1212
  }
1190
1213
  async getRootDataStore(id, wait = true) {
1214
+ return this.getRootDataStoreChannel(id, wait);
1215
+ }
1216
+ async getRootDataStoreChannel(id, wait = true) {
1217
+ await this.dataStores.waitIfPendingAlias(id);
1191
1218
  const internalId = this.internalId(id);
1192
1219
  const context = await this.dataStores.getDataStore(internalId, wait);
1193
1220
  (0, common_utils_1.assert)(await context.isRoot(), 0x12b /* "did not get root data store" */);
@@ -1278,67 +1305,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1278
1305
  }
1279
1306
  async createDataStore(pkg) {
1280
1307
  const internalId = (0, uuid_1.v4)();
1281
- return (0, dataStore_1.channelToDataStore)(await this._createDataStore(pkg, false /* isRoot */, internalId), internalId, this, this.dataStores, this.mc.logger);
1282
- }
1283
- /**
1284
- * Creates a root datastore directly with a user generated id and attaches it to storage.
1285
- * It is vulnerable to name collisions and should not be used.
1286
- *
1287
- * This method will be removed. See #6465.
1288
- */
1289
- async createRootDataStoreLegacy(pkg, rootDataStoreId) {
1290
- const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
1291
- // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
1292
- // older versions, we still have to call bindToContext.
1293
- if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
1294
- fluidDataStore.makeVisibleAndAttachGraph();
1295
- }
1296
- else {
1297
- fluidDataStore.bindToContext();
1298
- }
1299
- return fluidDataStore;
1300
- }
1301
- /**
1302
- * @deprecated - will be removed in an upcoming release. See #9660.
1303
- */
1304
- async createRootDataStore(pkg, rootDataStoreId) {
1305
- if (rootDataStoreId.includes("/")) {
1306
- throw new container_utils_1.UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
1307
- }
1308
- return this._aliasingEnabled === true ?
1309
- this.createAndAliasDataStore(pkg, rootDataStoreId) :
1310
- this.createRootDataStoreLegacy(pkg, rootDataStoreId);
1311
- }
1312
- /**
1313
- * Creates a data store then attempts to alias it.
1314
- * If aliasing fails, it will raise an exception.
1315
- *
1316
- * This method will be removed. See #6465.
1317
- *
1318
- * @param pkg - Package name of the data store
1319
- * @param alias - Alias to be assigned to the data store
1320
- * @param props - Properties for the data store
1321
- * @returns - An aliased data store which can can be found / loaded by alias.
1322
- */
1323
- async createAndAliasDataStore(pkg, alias, props) {
1324
- const internalId = (0, uuid_1.v4)();
1325
- const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
1326
- const aliasedDataStore = (0, dataStore_1.channelToDataStore)(dataStore, internalId, this, this.dataStores, this.mc.logger);
1327
- const result = await aliasedDataStore.trySetAlias(alias);
1328
- if (result !== "Success") {
1329
- throw new container_utils_1.GenericError("dataStoreAliasFailure", undefined /* error */, {
1330
- alias: {
1331
- value: alias,
1332
- tag: telemetry_utils_1.TelemetryDataTag.UserData,
1333
- },
1334
- internalId: {
1335
- value: internalId,
1336
- tag: telemetry_utils_1.TelemetryDataTag.PackageData,
1337
- },
1338
- aliasResult: result,
1339
- });
1340
- }
1341
- return aliasedDataStore;
1308
+ return (0, dataStore_1.channelToDataStore)(await this._createDataStore(pkg, internalId), internalId, this, this.dataStores, this.mc.logger);
1342
1309
  }
1343
1310
  createDetachedRootDataStore(pkg, rootDataStoreId) {
1344
1311
  if (rootDataStoreId.includes("/")) {
@@ -1349,38 +1316,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1349
1316
  createDetachedDataStore(pkg) {
1350
1317
  return this.dataStores.createDetachedDataStoreCore(pkg, false);
1351
1318
  }
1352
- /**
1353
- * Creates a possibly root datastore directly with a possibly user generated id and attaches it to storage.
1354
- * It is vulnerable to name collisions if both aforementioned conditions are true, and should not be used.
1355
- *
1356
- * This method will be removed. See #6465.
1357
- */
1358
- async _createDataStoreWithPropsLegacy(pkg, props, id = (0, uuid_1.v4)(), isRoot = false) {
1359
- const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
1360
- if (isRoot) {
1361
- // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
1362
- // For older versions, we still have to call bindToContext.
1363
- if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
1364
- fluidDataStore.makeVisibleAndAttachGraph();
1365
- }
1366
- else {
1367
- fluidDataStore.bindToContext();
1368
- }
1369
- this.logger.sendTelemetryEvent({
1370
- eventName: "Root datastore with props",
1371
- hasProps: props !== undefined,
1372
- });
1373
- }
1319
+ async _createDataStoreWithProps(pkg, props, id = (0, uuid_1.v4)()) {
1320
+ const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props).realize();
1374
1321
  return (0, dataStore_1.channelToDataStore)(fluidDataStore, id, this, this.dataStores, this.mc.logger);
1375
1322
  }
1376
- async _createDataStoreWithProps(pkg, props, id = (0, uuid_1.v4)(), isRoot = false) {
1377
- return this._aliasingEnabled === true && isRoot ?
1378
- this.createAndAliasDataStore(pkg, id, props) :
1379
- this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
1380
- }
1381
- async _createDataStore(pkg, isRoot, id = (0, uuid_1.v4)(), props) {
1323
+ async _createDataStore(pkg, id = (0, uuid_1.v4)(), props) {
1382
1324
  return this.dataStores
1383
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props)
1325
+ ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
1384
1326
  .realize();
1385
1327
  }
1386
1328
  canSendOps() {
@@ -1646,7 +1588,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1646
1588
  * @param options - options controlling how the summary is generated or submitted
1647
1589
  */
1648
1590
  async submitSummary(options) {
1649
- var _a, _b, _c;
1591
+ var _a, _b;
1650
1592
  const { fullTree, refreshLatestAck, summaryLogger } = options;
1651
1593
  // The summary number for this summary. This will be updated during the summary process, so get it now and
1652
1594
  // use it for all events logged during this summary.
@@ -1654,16 +1596,19 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1654
1596
  const summaryNumberLogger = telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, {
1655
1597
  all: { summaryNumber },
1656
1598
  });
1599
+ let latestSnapshotVersionId;
1657
1600
  if (refreshLatestAck) {
1658
- const latestSummaryRefSeq = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
1659
- if (latestSummaryRefSeq > this.deltaManager.lastSequenceNumber) {
1601
+ const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
1602
+ const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
1603
+ latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
1604
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
1660
1605
  // We need to catch up to the latest summary's reference sequence number before pausing.
1661
1606
  await telemetry_utils_1.PerformanceEvent.timedExecAsync(summaryNumberLogger, {
1662
1607
  eventName: "WaitingForSeq",
1663
1608
  lastSequenceNumber: this.deltaManager.lastSequenceNumber,
1664
- targetSequenceNumber: latestSummaryRefSeq,
1609
+ targetSequenceNumber: latestSnapshotRefSeq,
1665
1610
  lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
1666
- }, async () => waitForSeq(this.deltaManager, latestSummaryRefSeq), { start: true, end: true, cancel: "error" });
1611
+ }, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
1667
1612
  }
1668
1613
  }
1669
1614
  try {
@@ -1671,17 +1616,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1671
1616
  const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
1672
1617
  const minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
1673
1618
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
1674
- // We should be here is we haven't processed be here. If we are of if the last message's sequence number
1675
- // doesn't match the last processed sequence number, log an error.
1676
- if (summaryRefSeqNum !== ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber)) {
1677
- summaryNumberLogger.sendErrorEvent({
1678
- eventName: "LastSequenceMismatch",
1679
- error: message,
1680
- });
1681
- }
1619
+ const lastAck = this.summaryCollection.latestAck;
1682
1620
  this.summarizerNode.startSummary(summaryRefSeqNum, summaryNumberLogger);
1683
1621
  // Helper function to check whether we should still continue between each async step.
1684
1622
  const checkContinue = () => {
1623
+ var _a;
1685
1624
  // Do not check for loss of connectivity directly! Instead leave it up to
1686
1625
  // RunWhileConnectedCoordinator to control policy in a single place.
1687
1626
  // This will allow easier change of design if we chose to. For example, we may chose to allow
@@ -1705,6 +1644,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1705
1644
  error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
1706
1645
  };
1707
1646
  }
1647
+ (0, common_utils_1.assert)(summaryRefSeqNum === ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber), 0x395 /* it's one and the same thing */);
1648
+ if (lastAck !== this.summaryCollection.latestAck) {
1649
+ return {
1650
+ continue: false,
1651
+ // eslint-disable-next-line max-len
1652
+ error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
1653
+ };
1654
+ }
1708
1655
  return { continue: true };
1709
1656
  };
1710
1657
  let continueResult = checkContinue();
@@ -1749,7 +1696,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1749
1696
  const gcSummaryTreeStats = summaryTree.tree[garbageCollection_1.gcTreeKey]
1750
1697
  ? (0, runtime_utils_1.calculateStats)(summaryTree.tree[garbageCollection_1.gcTreeKey])
1751
1698
  : undefined;
1752
- const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_b = summarizeResult.gcStats) === null || _b === void 0 ? void 0 : _b.updatedDataStoreCount, gcBlobNodeCount: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.blobNodeCount, gcTotalBlobsSize: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.totalBlobSize, opsSizesSinceLastSummary: this.opTracker.opsSizeAccumulator, nonSystemOpsSinceLastSummary: this.opTracker.nonSystemOpCount, summaryNumber }, partialStats);
1699
+ const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_a = summarizeResult.gcStats) === null || _a === void 0 ? void 0 : _a.updatedDataStoreCount, gcBlobNodeCount: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.blobNodeCount, gcTotalBlobsSize: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.totalBlobSize, summaryNumber }, partialStats);
1753
1700
  const generateSummaryData = {
1754
1701
  referenceSequenceNumber: summaryRefSeqNum,
1755
1702
  minimumSequenceNumber,
@@ -1762,18 +1709,34 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1762
1709
  if (!continueResult.continue) {
1763
1710
  return Object.assign(Object.assign({ stage: "generate" }, generateSummaryData), { error: continueResult.error });
1764
1711
  }
1765
- const lastAck = this.summaryCollection.latestAck;
1766
- const summaryContext = lastAck === undefined
1767
- ? {
1712
+ // It may happen that the lastAck it not correct due to missing summaryAck in case of single commit
1713
+ // summary. So if the previous summarizer closes just after submitting the summary and before
1714
+ // submitting the summaryOp then we can't rely on summaryAck. So in case we have
1715
+ // latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
1716
+ // the one fetched from storage as parent as that is the latest.
1717
+ let summaryContext;
1718
+ if ((lastAck === null || lastAck === void 0 ? void 0 : lastAck.summaryAck.contents.handle) !== latestSnapshotVersionId
1719
+ && latestSnapshotVersionId !== undefined) {
1720
+ summaryContext = {
1768
1721
  proposalHandle: undefined,
1769
- ackHandle: (_c = this.context.getLoadedFromVersion()) === null || _c === void 0 ? void 0 : _c.id,
1722
+ ackHandle: latestSnapshotVersionId,
1770
1723
  referenceSequenceNumber: summaryRefSeqNum,
1771
- }
1772
- : {
1724
+ };
1725
+ }
1726
+ else if (lastAck === undefined) {
1727
+ summaryContext = {
1728
+ proposalHandle: undefined,
1729
+ ackHandle: (_b = this.context.getLoadedFromVersion()) === null || _b === void 0 ? void 0 : _b.id,
1730
+ referenceSequenceNumber: summaryRefSeqNum,
1731
+ };
1732
+ }
1733
+ else {
1734
+ summaryContext = {
1773
1735
  proposalHandle: lastAck.summaryOp.contents.handle,
1774
1736
  ackHandle: lastAck.summaryAck.contents.handle,
1775
1737
  referenceSequenceNumber: summaryRefSeqNum,
1776
1738
  };
1739
+ }
1777
1740
  let handle;
1778
1741
  try {
1779
1742
  handle = await this.storage.uploadSummaryWithContext(summarizeResult.summary, summaryContext);
@@ -1803,7 +1766,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1803
1766
  }
1804
1767
  const submitData = Object.assign(Object.assign({ stage: "submit" }, uploadData), { clientSequenceNumber, submitOpDuration: trace.trace().duration });
1805
1768
  this.summarizerNode.completeSummary(handle);
1806
- this.opTracker.reset();
1807
1769
  return submitData;
1808
1770
  }
1809
1771
  finally {
@@ -1914,14 +1876,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1914
1876
  // instead of splitting the content, we will fail by explicitly close the container
1915
1877
  this.closeFn(new container_utils_1.GenericError("OpTooLarge",
1916
1878
  /* error */ undefined, {
1917
- length: {
1918
- value: serializedContent.length,
1919
- tag: telemetry_utils_1.TelemetryDataTag.PackageData,
1920
- },
1921
- limit: {
1922
- value: this._maxOpSizeInBytes,
1923
- tag: telemetry_utils_1.TelemetryDataTag.PackageData,
1924
- },
1879
+ length: serializedContent.length,
1880
+ limit: this._maxOpSizeInBytes,
1925
1881
  }));
1926
1882
  return -1;
1927
1883
  }
@@ -1996,7 +1952,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1996
1952
  case ContainerMessageType.ChunkedOp:
1997
1953
  throw new Error(`chunkedOp not expected here`);
1998
1954
  case ContainerMessageType.BlobAttach:
1999
- this.submit(type, content, localOpMetadata, opMetadata);
1955
+ this.blobManager.reSubmit(opMetadata);
2000
1956
  break;
2001
1957
  case ContainerMessageType.Rejoin:
2002
1958
  this.submit(type, content);
@@ -2019,12 +1975,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2019
1975
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
2020
1976
  async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
2021
1977
  const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
2022
- const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
1978
+ const { snapshotTree } = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
2023
1979
  eventName: "RefreshLatestSummaryGetSnapshot",
2024
1980
  ackHandle,
2025
1981
  summaryRefSeq,
2026
1982
  fetchLatest: false,
2027
- }), readAndParseBlob, summaryLogger);
1983
+ });
1984
+ const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
2028
1985
  // Notify the garbage collector so it can update its latest summary state.
2029
1986
  await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
2030
1987
  }
@@ -2035,29 +1992,29 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2035
1992
  * @returns downloaded snapshot's reference sequence number
2036
1993
  */
2037
1994
  async refreshLatestSummaryAckFromServer(summaryLogger) {
2038
- const snapshot = await this.fetchSnapshotFromStorage(null, summaryLogger, {
1995
+ const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
2039
1996
  eventName: "RefreshLatestSummaryGetSnapshot",
2040
1997
  fetchLatest: true,
2041
- });
1998
+ }, driver_definitions_1.FetchSource.noCache);
2042
1999
  const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
2043
- const snapshotRefSeq = await (0, runtime_utils_1.seqFromTree)(snapshot, readAndParseBlob);
2044
- const result = await this.summarizerNode.refreshLatestSummary(undefined, snapshotRefSeq, async () => snapshot, readAndParseBlob, summaryLogger);
2000
+ const latestSnapshotRefSeq = await (0, runtime_utils_1.seqFromTree)(snapshotTree, readAndParseBlob);
2001
+ const result = await this.summarizerNode.refreshLatestSummary(undefined, latestSnapshotRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
2045
2002
  // Notify the garbage collector so it can update its latest summary state.
2046
2003
  await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
2047
- return snapshotRefSeq;
2004
+ return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
2048
2005
  }
2049
- async fetchSnapshotFromStorage(versionId, logger, event) {
2006
+ async fetchSnapshotFromStorage(versionId, logger, event, fetchSource) {
2050
2007
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(logger, event, async (perfEvent) => {
2051
2008
  const stats = {};
2052
2009
  const trace = common_utils_1.Trace.start();
2053
- const versions = await this.storage.getVersions(versionId, 1);
2010
+ const versions = await this.storage.getVersions(versionId, 1, "refreshLatestSummaryAckFromServer", fetchSource);
2054
2011
  (0, common_utils_1.assert)(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
2055
2012
  stats.getVersionDuration = trace.trace().duration;
2056
2013
  const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
2057
2014
  (0, common_utils_1.assert)(!!maybeSnapshot, 0x138 /* "Failed to get snapshot from storage" */);
2058
2015
  stats.getSnapshotDuration = trace.trace().duration;
2059
2016
  perfEvent.end(stats);
2060
- return maybeSnapshot;
2017
+ return { snapshotTree: maybeSnapshot, versionId: versions[0].id };
2061
2018
  });
2062
2019
  }
2063
2020
  notifyAttaching(snapshot) {
@@ -2133,6 +2090,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2133
2090
  // don't have any more saved ops
2134
2091
  await this.pendingStateManager.applyStashedOpsAt();
2135
2092
  }
2093
+ validateSummaryHeuristicConfiguration(configuration) {
2094
+ // eslint-disable-next-line no-restricted-syntax
2095
+ for (const prop in configuration) {
2096
+ if (typeof configuration[prop] === "number" && configuration[prop] < 0) {
2097
+ throw new container_utils_1.UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
2098
+ }
2099
+ }
2100
+ }
2136
2101
  }
2137
2102
  exports.ContainerRuntime = ContainerRuntime;
2138
2103
  /**