@fluidframework/container-runtime 1.2.3-83900 → 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
@@ -1,7 +1,7 @@
1
1
  import { AttachState, LoaderHeader, } from "@fluidframework/container-definitions";
2
2
  import { assert, Trace, TypedEventEmitter, unreachableCase, performance, } from "@fluidframework/common-utils";
3
- import { ChildLogger, raiseConnectedEvent, PerformanceEvent, TaggedLoggerAdapter, loggerToMonitoringContext, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
4
- import { DriverHeader } from "@fluidframework/driver-definitions";
3
+ import { ChildLogger, raiseConnectedEvent, PerformanceEvent, TaggedLoggerAdapter, loggerToMonitoringContext, } from "@fluidframework/telemetry-utils";
4
+ import { DriverHeader, FetchSource, } from "@fluidframework/driver-definitions";
5
5
  import { readAndParse, isUnpackedRuntimeMessage } from "@fluidframework/driver-utils";
6
6
  import { DataCorruptionError, DataProcessingError, GenericError, UsageError, extractSafePropertiesFromMessage, } from "@fluidframework/container-utils";
7
7
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
@@ -29,7 +29,6 @@ import { GarbageCollector, GCNodeType, gcTreeKey, } from "./garbageCollection";
29
29
  import { channelToDataStore, isDataStoreAliasMessage, } from "./dataStore";
30
30
  import { BindBatchTracker } from "./batchTracker";
31
31
  import { SerializedSnapshotStorage } from "./serializedSnapshotStorage";
32
- import { OpTracker } from "./opTelemetry";
33
32
  export var ContainerMessageType;
34
33
  (function (ContainerMessageType) {
35
34
  // An op to be delivered to store
@@ -47,14 +46,18 @@ export var ContainerMessageType;
47
46
  })(ContainerMessageType || (ContainerMessageType = {}));
48
47
  export const DefaultSummaryConfiguration = {
49
48
  state: "enabled",
50
- idleTime: 5000 * 3,
51
- maxTime: 5000 * 12,
49
+ idleTime: 15 * 1000,
50
+ minIdleTime: 0,
51
+ maxIdleTime: 30 * 1000,
52
+ maxTime: 60 * 1000,
52
53
  maxOps: 100,
53
54
  minOpsForLastSummaryAttempt: 10,
54
- maxAckWaitTime: 6 * 10 * 1000,
55
+ maxAckWaitTime: 10 * 60 * 1000,
55
56
  maxOpsSinceLastSummary: 7000,
56
- initialSummarizerDelayMs: 5000,
57
+ initialSummarizerDelayMs: 5 * 1000,
57
58
  summarizerClientElection: false,
59
+ nonRuntimeOpWeight: 0.1,
60
+ runtimeOpWeight: 1.0,
58
61
  };
59
62
  /**
60
63
  * Accepted header keys for requests coming to the runtime.
@@ -71,7 +74,6 @@ export var RuntimeHeaders;
71
74
  /** True if the request is coming from an IFluidHandle. */
72
75
  RuntimeHeaders["viaHandle"] = "viaHandle";
73
76
  })(RuntimeHeaders || (RuntimeHeaders = {}));
74
- const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
75
77
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
76
78
  // Feature gate for the max op size. If the value is negative, chunking is enabled
77
79
  // and all ops over 16k would be chunked. If the value is positive, all ops with
@@ -81,10 +83,6 @@ const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
81
83
  // in order to account for some extra overhead from serialization
82
84
  // to not reach the 1MB limits in socket.io and Kafka.
83
85
  const defaultMaxOpSizeInBytes = 768000;
84
- // By default, the size of the contents for the incoming ops is tracked.
85
- // However, in certain situations, this may incur a performance hit.
86
- // The feature-gate below can be used to disable this feature.
87
- const disableOpTrackingKey = "Fluid.ContainerRuntime.DisableOpTracking";
88
86
  const defaultFlushMode = FlushMode.TurnBased;
89
87
  export var RuntimeMessage;
90
88
  (function (RuntimeMessage) {
@@ -373,7 +371,7 @@ export function getDeviceSpec() {
373
371
  */
374
372
  export class ContainerRuntime extends TypedEventEmitter {
375
373
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
376
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
374
+ var _a, _b, _c, _d, _e, _f;
377
375
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
378
376
  super();
379
377
  this.context = context;
@@ -434,20 +432,20 @@ export class ContainerRuntime extends TypedEventEmitter {
434
432
  this.chunkMap = new Map(chunks);
435
433
  this.handleContext = new ContainerFluidHandleContext("", this);
436
434
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
435
+ if (this.summaryConfiguration.state === "enabled") {
436
+ this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
437
+ }
437
438
  this.summariesDisabled = this.isSummariesDisabled();
438
439
  this.heuristicsDisabled = this.isHeuristicsDisabled();
439
440
  this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
440
441
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
441
442
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
442
- this._aliasingEnabled =
443
- ((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
444
- ((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
445
- this._maxOpSizeInBytes = ((_e = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _e !== void 0 ? _e : defaultMaxOpSizeInBytes);
443
+ this._maxOpSizeInBytes = ((_c = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _c !== void 0 ? _c : defaultMaxOpSizeInBytes);
446
444
  this.maxConsecutiveReconnects =
447
- (_f = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _f !== void 0 ? _f : this.defaultMaxConsecutiveReconnects;
445
+ (_d = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _d !== void 0 ? _d : this.defaultMaxConsecutiveReconnects;
448
446
  this._flushMode = runtimeOptions.flushMode;
449
447
  const pendingRuntimeState = context.pendingLocalState;
450
- const baseSnapshot = (_g = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _g !== void 0 ? _g : context.baseSnapshot;
448
+ const baseSnapshot = (_e = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _e !== void 0 ? _e : context.baseSnapshot;
451
449
  this.garbageCollector = GarbageCollector.create({
452
450
  runtime: this,
453
451
  gcOptions: this.runtimeOptions.gcOptions,
@@ -481,7 +479,7 @@ export class ContainerRuntime extends TypedEventEmitter {
481
479
  this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
482
480
  }
483
481
  this.dataStores = new DataStores(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);
484
- this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, this.logger);
482
+ this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId }), (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this);
485
483
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, ChildLogger.create(this.logger, "ScheduleManager"));
486
484
  this.deltaSender = this.deltaManager;
487
485
  this.pendingStateManager = new PendingStateManager({
@@ -572,9 +570,9 @@ export class ContainerRuntime extends TypedEventEmitter {
572
570
  createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
573
571
  createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
574
572
  };
575
- // back-compat 0.59.3000 - Older document may either write summaryCount or not write it at all. If it does
576
- // not write it, initialize summaryNumber to 0.
577
- 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;
573
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
574
+ // the count is reset to 0.
575
+ loadSummaryNumber = (_f = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _f !== void 0 ? _f : 0;
578
576
  }
579
577
  else {
580
578
  this.createContainerMetadata = {
@@ -587,7 +585,6 @@ export class ContainerRuntime extends TypedEventEmitter {
587
585
  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 }));
588
586
  ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
589
587
  BindBatchTracker(this, this.logger);
590
- this.opTracker = new OpTracker(this.deltaManager, this.mc.config.getBoolean(disableOpTrackingKey) === true);
591
588
  }
592
589
  get IContainerRuntime() { return this; }
593
590
  get IFluidRouter() { return this; }
@@ -610,7 +607,7 @@ export class ContainerRuntime extends TypedEventEmitter {
610
607
  runtimeVersion: pkgVersion,
611
608
  },
612
609
  });
613
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
610
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
614
611
  const pendingRuntimeState = context.pendingLocalState;
615
612
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
616
613
  const storage = !pendingRuntimeState ?
@@ -663,7 +660,6 @@ export class ContainerRuntime extends TypedEventEmitter {
663
660
  summaryOptions,
664
661
  gcOptions,
665
662
  loadSequenceNumberVerification,
666
- useDataStoreAliasing,
667
663
  flushMode,
668
664
  enableOfflineLoad,
669
665
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
@@ -856,12 +852,12 @@ export class ContainerRuntime extends TypedEventEmitter {
856
852
  return this.resolveHandle(requestParser.createSubRequest(1));
857
853
  }
858
854
  if (id === BlobManager.basePath && requestParser.isLeaf(2)) {
859
- const handle = await this.blobManager.getBlob(requestParser.pathParts[1]);
860
- if (handle) {
855
+ const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
856
+ if (blob) {
861
857
  return {
862
858
  status: 200,
863
859
  mimeType: "fluid/object",
864
- value: handle.get(),
860
+ value: blob,
865
861
  };
866
862
  }
867
863
  else {
@@ -884,13 +880,14 @@ export class ContainerRuntime extends TypedEventEmitter {
884
880
  }
885
881
  internalId(maybeAlias) {
886
882
  var _a;
887
- return (_a = this.dataStores.aliases().get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
883
+ return (_a = this.dataStores.aliases.get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
888
884
  }
889
885
  async getDataStoreFromRequest(id, request) {
890
886
  var _a, _b, _c;
891
887
  const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
892
888
  ? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
893
889
  : true;
890
+ await this.dataStores.waitIfPendingAlias(id);
894
891
  const internalId = this.internalId(id);
895
892
  const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
896
893
  /**
@@ -920,8 +917,6 @@ export class ContainerRuntime extends TypedEventEmitter {
920
917
  addMetadataToSummary(summaryTree) {
921
918
  var _a;
922
919
  const metadata = Object.assign(Object.assign(Object.assign(Object.assign({}, this.createContainerMetadata), {
923
- // back-compat 0.59.3000: This is renamed to summaryNumber. Can be removed when 0.59.3000 saturates.
924
- summaryCount: this.nextSummaryNumber,
925
920
  // Increment the summary number for the next summary that will be generated.
926
921
  summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1, disableIsolatedChannels: this.disableIsolatedChannels || undefined }), this.garbageCollector.getMetadata()), {
927
922
  // The last message processed at the time of summary. If there are no new messages, use the message from the
@@ -936,7 +931,7 @@ export class ContainerRuntime extends TypedEventEmitter {
936
931
  const content = JSON.stringify([...this.chunkMap]);
937
932
  addBlobToSummary(summaryTree, chunksBlobName, content);
938
933
  }
939
- const dataStoreAliases = this.dataStores.aliases();
934
+ const dataStoreAliases = this.dataStores.aliases;
940
935
  if (dataStoreAliases.size > 0) {
941
936
  addBlobToSummary(summaryTree, aliasBlobName, JSON.stringify([...dataStoreAliases]));
942
937
  }
@@ -1033,6 +1028,35 @@ export class ContainerRuntime extends TypedEventEmitter {
1033
1028
  }
1034
1029
  }
1035
1030
  setConnectionState(connected, clientId) {
1031
+ if (connected === false && this.delayConnectClientId !== undefined) {
1032
+ this.delayConnectClientId = undefined;
1033
+ this.mc.logger.sendTelemetryEvent({
1034
+ eventName: "UnsuccessfulConnectedTransition",
1035
+ });
1036
+ // Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
1037
+ return;
1038
+ }
1039
+ // If attachment blobs were added while disconnected, we need to delay
1040
+ // propagation of the "connected" event until we have uploaded them to
1041
+ // ensure we don't submit ops referencing a blob that has not been uploaded
1042
+ const connecting = connected && !this._connected && !this.deltaManager.readOnlyInfo.readonly;
1043
+ if (connecting && this.blobManager.hasPendingOfflineUploads) {
1044
+ assert(!this.delayConnectClientId, 0x392 /* Connect event delay must be canceled before subsequent connect event */);
1045
+ assert(!!clientId, 0x393 /* Must have clientId when connecting */);
1046
+ this.delayConnectClientId = clientId;
1047
+ this.blobManager.onConnected().then(() => {
1048
+ // make sure we didn't reconnect before the promise resolved
1049
+ if (this.delayConnectClientId === clientId && !this.disposed) {
1050
+ this.delayConnectClientId = undefined;
1051
+ this.setConnectionStateCore(connected, clientId);
1052
+ }
1053
+ }, (error) => this.closeFn(error));
1054
+ return;
1055
+ }
1056
+ this.setConnectionStateCore(connected, clientId);
1057
+ }
1058
+ setConnectionStateCore(connected, clientId) {
1059
+ assert(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
1036
1060
  this.verifyNotClosed();
1037
1061
  // There might be no change of state due to Container calling this API after loading runtime.
1038
1062
  const changeOfState = this._connected !== connected;
@@ -1063,7 +1087,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1063
1087
  raiseConnectedEvent(this.mc.logger, this, connected, clientId);
1064
1088
  }
1065
1089
  process(messageArg, local) {
1066
- var _a, _b;
1090
+ var _a;
1067
1091
  this.verifyNotClosed();
1068
1092
  // If it's not message for runtime, bail out right away.
1069
1093
  if (!isUnpackedRuntimeMessage(messageArg)) {
@@ -1110,8 +1134,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1110
1134
  this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
1111
1135
  break;
1112
1136
  case ContainerMessageType.BlobAttach:
1113
- 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" */);
1114
- this.blobManager.processBlobAttachOp(message.metadata.blobId, local);
1137
+ this.blobManager.processBlobAttachOp(message, local);
1115
1138
  break;
1116
1139
  default:
1117
1140
  }
@@ -1181,6 +1204,10 @@ export class ContainerRuntime extends TypedEventEmitter {
1181
1204
  this.dataStores.processSignal(envelope.address, transformed, local);
1182
1205
  }
1183
1206
  async getRootDataStore(id, wait = true) {
1207
+ return this.getRootDataStoreChannel(id, wait);
1208
+ }
1209
+ async getRootDataStoreChannel(id, wait = true) {
1210
+ await this.dataStores.waitIfPendingAlias(id);
1184
1211
  const internalId = this.internalId(id);
1185
1212
  const context = await this.dataStores.getDataStore(internalId, wait);
1186
1213
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
@@ -1271,67 +1298,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1271
1298
  }
1272
1299
  async createDataStore(pkg) {
1273
1300
  const internalId = uuid();
1274
- return channelToDataStore(await this._createDataStore(pkg, false /* isRoot */, internalId), internalId, this, this.dataStores, this.mc.logger);
1275
- }
1276
- /**
1277
- * Creates a root datastore directly with a user generated id and attaches it to storage.
1278
- * It is vulnerable to name collisions and should not be used.
1279
- *
1280
- * This method will be removed. See #6465.
1281
- */
1282
- async createRootDataStoreLegacy(pkg, rootDataStoreId) {
1283
- const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
1284
- // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
1285
- // older versions, we still have to call bindToContext.
1286
- if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
1287
- fluidDataStore.makeVisibleAndAttachGraph();
1288
- }
1289
- else {
1290
- fluidDataStore.bindToContext();
1291
- }
1292
- return fluidDataStore;
1293
- }
1294
- /**
1295
- * @deprecated - will be removed in an upcoming release. See #9660.
1296
- */
1297
- async createRootDataStore(pkg, rootDataStoreId) {
1298
- if (rootDataStoreId.includes("/")) {
1299
- throw new UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
1300
- }
1301
- return this._aliasingEnabled === true ?
1302
- this.createAndAliasDataStore(pkg, rootDataStoreId) :
1303
- this.createRootDataStoreLegacy(pkg, rootDataStoreId);
1304
- }
1305
- /**
1306
- * Creates a data store then attempts to alias it.
1307
- * If aliasing fails, it will raise an exception.
1308
- *
1309
- * This method will be removed. See #6465.
1310
- *
1311
- * @param pkg - Package name of the data store
1312
- * @param alias - Alias to be assigned to the data store
1313
- * @param props - Properties for the data store
1314
- * @returns - An aliased data store which can can be found / loaded by alias.
1315
- */
1316
- async createAndAliasDataStore(pkg, alias, props) {
1317
- const internalId = uuid();
1318
- const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
1319
- const aliasedDataStore = channelToDataStore(dataStore, internalId, this, this.dataStores, this.mc.logger);
1320
- const result = await aliasedDataStore.trySetAlias(alias);
1321
- if (result !== "Success") {
1322
- throw new GenericError("dataStoreAliasFailure", undefined /* error */, {
1323
- alias: {
1324
- value: alias,
1325
- tag: TelemetryDataTag.UserData,
1326
- },
1327
- internalId: {
1328
- value: internalId,
1329
- tag: TelemetryDataTag.PackageData,
1330
- },
1331
- aliasResult: result,
1332
- });
1333
- }
1334
- return aliasedDataStore;
1301
+ return channelToDataStore(await this._createDataStore(pkg, internalId), internalId, this, this.dataStores, this.mc.logger);
1335
1302
  }
1336
1303
  createDetachedRootDataStore(pkg, rootDataStoreId) {
1337
1304
  if (rootDataStoreId.includes("/")) {
@@ -1342,38 +1309,13 @@ export class ContainerRuntime extends TypedEventEmitter {
1342
1309
  createDetachedDataStore(pkg) {
1343
1310
  return this.dataStores.createDetachedDataStoreCore(pkg, false);
1344
1311
  }
1345
- /**
1346
- * Creates a possibly root datastore directly with a possibly user generated id and attaches it to storage.
1347
- * It is vulnerable to name collisions if both aforementioned conditions are true, and should not be used.
1348
- *
1349
- * This method will be removed. See #6465.
1350
- */
1351
- async _createDataStoreWithPropsLegacy(pkg, props, id = uuid(), isRoot = false) {
1352
- const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
1353
- if (isRoot) {
1354
- // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
1355
- // For older versions, we still have to call bindToContext.
1356
- if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
1357
- fluidDataStore.makeVisibleAndAttachGraph();
1358
- }
1359
- else {
1360
- fluidDataStore.bindToContext();
1361
- }
1362
- this.logger.sendTelemetryEvent({
1363
- eventName: "Root datastore with props",
1364
- hasProps: props !== undefined,
1365
- });
1366
- }
1312
+ async _createDataStoreWithProps(pkg, props, id = uuid()) {
1313
+ const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props).realize();
1367
1314
  return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
1368
1315
  }
1369
- async _createDataStoreWithProps(pkg, props, id = uuid(), isRoot = false) {
1370
- return this._aliasingEnabled === true && isRoot ?
1371
- this.createAndAliasDataStore(pkg, id, props) :
1372
- this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
1373
- }
1374
- async _createDataStore(pkg, isRoot, id = uuid(), props) {
1316
+ async _createDataStore(pkg, id = uuid(), props) {
1375
1317
  return this.dataStores
1376
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props)
1318
+ ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
1377
1319
  .realize();
1378
1320
  }
1379
1321
  canSendOps() {
@@ -1639,7 +1581,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1639
1581
  * @param options - options controlling how the summary is generated or submitted
1640
1582
  */
1641
1583
  async submitSummary(options) {
1642
- var _a, _b, _c;
1584
+ var _a, _b;
1643
1585
  const { fullTree, refreshLatestAck, summaryLogger } = options;
1644
1586
  // The summary number for this summary. This will be updated during the summary process, so get it now and
1645
1587
  // use it for all events logged during this summary.
@@ -1647,16 +1589,19 @@ export class ContainerRuntime extends TypedEventEmitter {
1647
1589
  const summaryNumberLogger = ChildLogger.create(summaryLogger, undefined, {
1648
1590
  all: { summaryNumber },
1649
1591
  });
1592
+ let latestSnapshotVersionId;
1650
1593
  if (refreshLatestAck) {
1651
- const latestSummaryRefSeq = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
1652
- if (latestSummaryRefSeq > this.deltaManager.lastSequenceNumber) {
1594
+ const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
1595
+ const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
1596
+ latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
1597
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
1653
1598
  // We need to catch up to the latest summary's reference sequence number before pausing.
1654
1599
  await PerformanceEvent.timedExecAsync(summaryNumberLogger, {
1655
1600
  eventName: "WaitingForSeq",
1656
1601
  lastSequenceNumber: this.deltaManager.lastSequenceNumber,
1657
- targetSequenceNumber: latestSummaryRefSeq,
1602
+ targetSequenceNumber: latestSnapshotRefSeq,
1658
1603
  lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
1659
- }, async () => waitForSeq(this.deltaManager, latestSummaryRefSeq), { start: true, end: true, cancel: "error" });
1604
+ }, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
1660
1605
  }
1661
1606
  }
1662
1607
  try {
@@ -1664,17 +1609,11 @@ export class ContainerRuntime extends TypedEventEmitter {
1664
1609
  const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
1665
1610
  const minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
1666
1611
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
1667
- // We should be here is we haven't processed be here. If we are of if the last message's sequence number
1668
- // doesn't match the last processed sequence number, log an error.
1669
- if (summaryRefSeqNum !== ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber)) {
1670
- summaryNumberLogger.sendErrorEvent({
1671
- eventName: "LastSequenceMismatch",
1672
- error: message,
1673
- });
1674
- }
1612
+ const lastAck = this.summaryCollection.latestAck;
1675
1613
  this.summarizerNode.startSummary(summaryRefSeqNum, summaryNumberLogger);
1676
1614
  // Helper function to check whether we should still continue between each async step.
1677
1615
  const checkContinue = () => {
1616
+ var _a;
1678
1617
  // Do not check for loss of connectivity directly! Instead leave it up to
1679
1618
  // RunWhileConnectedCoordinator to control policy in a single place.
1680
1619
  // This will allow easier change of design if we chose to. For example, we may chose to allow
@@ -1698,6 +1637,14 @@ export class ContainerRuntime extends TypedEventEmitter {
1698
1637
  error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
1699
1638
  };
1700
1639
  }
1640
+ assert(summaryRefSeqNum === ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber), 0x395 /* it's one and the same thing */);
1641
+ if (lastAck !== this.summaryCollection.latestAck) {
1642
+ return {
1643
+ continue: false,
1644
+ // eslint-disable-next-line max-len
1645
+ error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
1646
+ };
1647
+ }
1701
1648
  return { continue: true };
1702
1649
  };
1703
1650
  let continueResult = checkContinue();
@@ -1742,7 +1689,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1742
1689
  const gcSummaryTreeStats = summaryTree.tree[gcTreeKey]
1743
1690
  ? calculateStats(summaryTree.tree[gcTreeKey])
1744
1691
  : undefined;
1745
- 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);
1692
+ 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);
1746
1693
  const generateSummaryData = {
1747
1694
  referenceSequenceNumber: summaryRefSeqNum,
1748
1695
  minimumSequenceNumber,
@@ -1755,18 +1702,34 @@ export class ContainerRuntime extends TypedEventEmitter {
1755
1702
  if (!continueResult.continue) {
1756
1703
  return Object.assign(Object.assign({ stage: "generate" }, generateSummaryData), { error: continueResult.error });
1757
1704
  }
1758
- const lastAck = this.summaryCollection.latestAck;
1759
- const summaryContext = lastAck === undefined
1760
- ? {
1705
+ // It may happen that the lastAck it not correct due to missing summaryAck in case of single commit
1706
+ // summary. So if the previous summarizer closes just after submitting the summary and before
1707
+ // submitting the summaryOp then we can't rely on summaryAck. So in case we have
1708
+ // latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
1709
+ // the one fetched from storage as parent as that is the latest.
1710
+ let summaryContext;
1711
+ if ((lastAck === null || lastAck === void 0 ? void 0 : lastAck.summaryAck.contents.handle) !== latestSnapshotVersionId
1712
+ && latestSnapshotVersionId !== undefined) {
1713
+ summaryContext = {
1761
1714
  proposalHandle: undefined,
1762
- ackHandle: (_c = this.context.getLoadedFromVersion()) === null || _c === void 0 ? void 0 : _c.id,
1715
+ ackHandle: latestSnapshotVersionId,
1763
1716
  referenceSequenceNumber: summaryRefSeqNum,
1764
- }
1765
- : {
1717
+ };
1718
+ }
1719
+ else if (lastAck === undefined) {
1720
+ summaryContext = {
1721
+ proposalHandle: undefined,
1722
+ ackHandle: (_b = this.context.getLoadedFromVersion()) === null || _b === void 0 ? void 0 : _b.id,
1723
+ referenceSequenceNumber: summaryRefSeqNum,
1724
+ };
1725
+ }
1726
+ else {
1727
+ summaryContext = {
1766
1728
  proposalHandle: lastAck.summaryOp.contents.handle,
1767
1729
  ackHandle: lastAck.summaryAck.contents.handle,
1768
1730
  referenceSequenceNumber: summaryRefSeqNum,
1769
1731
  };
1732
+ }
1770
1733
  let handle;
1771
1734
  try {
1772
1735
  handle = await this.storage.uploadSummaryWithContext(summarizeResult.summary, summaryContext);
@@ -1796,7 +1759,6 @@ export class ContainerRuntime extends TypedEventEmitter {
1796
1759
  }
1797
1760
  const submitData = Object.assign(Object.assign({ stage: "submit" }, uploadData), { clientSequenceNumber, submitOpDuration: trace.trace().duration });
1798
1761
  this.summarizerNode.completeSummary(handle);
1799
- this.opTracker.reset();
1800
1762
  return submitData;
1801
1763
  }
1802
1764
  finally {
@@ -1907,14 +1869,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1907
1869
  // instead of splitting the content, we will fail by explicitly close the container
1908
1870
  this.closeFn(new GenericError("OpTooLarge",
1909
1871
  /* error */ undefined, {
1910
- length: {
1911
- value: serializedContent.length,
1912
- tag: TelemetryDataTag.PackageData,
1913
- },
1914
- limit: {
1915
- value: this._maxOpSizeInBytes,
1916
- tag: TelemetryDataTag.PackageData,
1917
- },
1872
+ length: serializedContent.length,
1873
+ limit: this._maxOpSizeInBytes,
1918
1874
  }));
1919
1875
  return -1;
1920
1876
  }
@@ -1989,7 +1945,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1989
1945
  case ContainerMessageType.ChunkedOp:
1990
1946
  throw new Error(`chunkedOp not expected here`);
1991
1947
  case ContainerMessageType.BlobAttach:
1992
- this.submit(type, content, localOpMetadata, opMetadata);
1948
+ this.blobManager.reSubmit(opMetadata);
1993
1949
  break;
1994
1950
  case ContainerMessageType.Rejoin:
1995
1951
  this.submit(type, content);
@@ -2012,12 +1968,13 @@ export class ContainerRuntime extends TypedEventEmitter {
2012
1968
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
2013
1969
  async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
2014
1970
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
2015
- const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
1971
+ const { snapshotTree } = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
2016
1972
  eventName: "RefreshLatestSummaryGetSnapshot",
2017
1973
  ackHandle,
2018
1974
  summaryRefSeq,
2019
1975
  fetchLatest: false,
2020
- }), readAndParseBlob, summaryLogger);
1976
+ });
1977
+ const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
2021
1978
  // Notify the garbage collector so it can update its latest summary state.
2022
1979
  await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
2023
1980
  }
@@ -2028,29 +1985,29 @@ export class ContainerRuntime extends TypedEventEmitter {
2028
1985
  * @returns downloaded snapshot's reference sequence number
2029
1986
  */
2030
1987
  async refreshLatestSummaryAckFromServer(summaryLogger) {
2031
- const snapshot = await this.fetchSnapshotFromStorage(null, summaryLogger, {
1988
+ const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
2032
1989
  eventName: "RefreshLatestSummaryGetSnapshot",
2033
1990
  fetchLatest: true,
2034
- });
1991
+ }, FetchSource.noCache);
2035
1992
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
2036
- const snapshotRefSeq = await seqFromTree(snapshot, readAndParseBlob);
2037
- const result = await this.summarizerNode.refreshLatestSummary(undefined, snapshotRefSeq, async () => snapshot, readAndParseBlob, summaryLogger);
1993
+ const latestSnapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
1994
+ const result = await this.summarizerNode.refreshLatestSummary(undefined, latestSnapshotRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
2038
1995
  // Notify the garbage collector so it can update its latest summary state.
2039
1996
  await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
2040
- return snapshotRefSeq;
1997
+ return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
2041
1998
  }
2042
- async fetchSnapshotFromStorage(versionId, logger, event) {
1999
+ async fetchSnapshotFromStorage(versionId, logger, event, fetchSource) {
2043
2000
  return PerformanceEvent.timedExecAsync(logger, event, async (perfEvent) => {
2044
2001
  const stats = {};
2045
2002
  const trace = Trace.start();
2046
- const versions = await this.storage.getVersions(versionId, 1);
2003
+ const versions = await this.storage.getVersions(versionId, 1, "refreshLatestSummaryAckFromServer", fetchSource);
2047
2004
  assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
2048
2005
  stats.getVersionDuration = trace.trace().duration;
2049
2006
  const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
2050
2007
  assert(!!maybeSnapshot, 0x138 /* "Failed to get snapshot from storage" */);
2051
2008
  stats.getSnapshotDuration = trace.trace().duration;
2052
2009
  perfEvent.end(stats);
2053
- return maybeSnapshot;
2010
+ return { snapshotTree: maybeSnapshot, versionId: versions[0].id };
2054
2011
  });
2055
2012
  }
2056
2013
  notifyAttaching(snapshot) {
@@ -2126,6 +2083,14 @@ export class ContainerRuntime extends TypedEventEmitter {
2126
2083
  // don't have any more saved ops
2127
2084
  await this.pendingStateManager.applyStashedOpsAt();
2128
2085
  }
2086
+ validateSummaryHeuristicConfiguration(configuration) {
2087
+ // eslint-disable-next-line no-restricted-syntax
2088
+ for (const prop in configuration) {
2089
+ if (typeof configuration[prop] === "number" && configuration[prop] < 0) {
2090
+ throw new UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
2091
+ }
2092
+ }
2093
+ }
2129
2094
  }
2130
2095
  /**
2131
2096
  * Wait for a specific sequence number. Promise should resolve when we reach that number,