@fluidframework/container-runtime 2.0.0-dev.1.4.6.106135 → 2.0.0-dev.2.2.0.111723

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 (168) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/batchManager.d.ts +11 -6
  3. package/dist/batchManager.d.ts.map +1 -1
  4. package/dist/batchManager.js +23 -13
  5. package/dist/batchManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +74 -20
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +190 -137
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStore.d.ts.map +1 -1
  11. package/dist/dataStore.js +6 -0
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +14 -21
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +71 -57
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStoreContexts.js +1 -1
  18. package/dist/dataStoreContexts.js.map +1 -1
  19. package/dist/dataStores.d.ts +11 -10
  20. package/dist/dataStores.d.ts.map +1 -1
  21. package/dist/dataStores.js +50 -20
  22. package/dist/dataStores.js.map +1 -1
  23. package/dist/garbageCollection.d.ts +36 -19
  24. package/dist/garbageCollection.d.ts.map +1 -1
  25. package/dist/garbageCollection.js +207 -121
  26. package/dist/garbageCollection.js.map +1 -1
  27. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  28. package/dist/gcSweepReadyUsageDetection.js +3 -12
  29. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  30. package/dist/index.d.ts +4 -6
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -5
  33. package/dist/index.js.map +1 -1
  34. package/dist/opCompressor.d.ts +18 -0
  35. package/dist/opCompressor.d.ts.map +1 -0
  36. package/dist/opCompressor.js +50 -0
  37. package/dist/opCompressor.js.map +1 -0
  38. package/dist/opDecompressor.d.ts +20 -0
  39. package/dist/opDecompressor.d.ts.map +1 -0
  40. package/dist/opDecompressor.js +72 -0
  41. package/dist/opDecompressor.js.map +1 -0
  42. package/dist/packageVersion.d.ts +1 -1
  43. package/dist/packageVersion.js +1 -1
  44. package/dist/packageVersion.js.map +1 -1
  45. package/dist/pendingStateManager.d.ts +6 -26
  46. package/dist/pendingStateManager.d.ts.map +1 -1
  47. package/dist/pendingStateManager.js +42 -62
  48. package/dist/pendingStateManager.js.map +1 -1
  49. package/dist/runningSummarizer.d.ts +3 -2
  50. package/dist/runningSummarizer.d.ts.map +1 -1
  51. package/dist/runningSummarizer.js +10 -3
  52. package/dist/runningSummarizer.js.map +1 -1
  53. package/dist/scheduleManager.js.map +1 -1
  54. package/dist/summarizer.js +7 -2
  55. package/dist/summarizer.js.map +1 -1
  56. package/dist/summarizerClientElection.js +1 -1
  57. package/dist/summarizerClientElection.js.map +1 -1
  58. package/dist/summarizerHeuristics.d.ts.map +1 -1
  59. package/dist/summarizerHeuristics.js +0 -3
  60. package/dist/summarizerHeuristics.js.map +1 -1
  61. package/dist/summarizerTypes.d.ts +19 -2
  62. package/dist/summarizerTypes.d.ts.map +1 -1
  63. package/dist/summarizerTypes.js.map +1 -1
  64. package/dist/summaryFormat.d.ts +4 -2
  65. package/dist/summaryFormat.d.ts.map +1 -1
  66. package/dist/summaryFormat.js.map +1 -1
  67. package/dist/summaryGenerator.d.ts.map +1 -1
  68. package/dist/summaryGenerator.js +3 -2
  69. package/dist/summaryGenerator.js.map +1 -1
  70. package/dist/summaryManager.d.ts.map +1 -1
  71. package/dist/summaryManager.js +10 -6
  72. package/dist/summaryManager.js.map +1 -1
  73. package/garbageCollection.md +27 -22
  74. package/lib/batchManager.d.ts +11 -6
  75. package/lib/batchManager.d.ts.map +1 -1
  76. package/lib/batchManager.js +23 -13
  77. package/lib/batchManager.js.map +1 -1
  78. package/lib/containerRuntime.d.ts +74 -20
  79. package/lib/containerRuntime.d.ts.map +1 -1
  80. package/lib/containerRuntime.js +189 -136
  81. package/lib/containerRuntime.js.map +1 -1
  82. package/lib/dataStore.d.ts.map +1 -1
  83. package/lib/dataStore.js +6 -0
  84. package/lib/dataStore.js.map +1 -1
  85. package/lib/dataStoreContext.d.ts +14 -21
  86. package/lib/dataStoreContext.d.ts.map +1 -1
  87. package/lib/dataStoreContext.js +75 -61
  88. package/lib/dataStoreContext.js.map +1 -1
  89. package/lib/dataStoreContexts.js +1 -1
  90. package/lib/dataStoreContexts.js.map +1 -1
  91. package/lib/dataStores.d.ts +11 -10
  92. package/lib/dataStores.d.ts.map +1 -1
  93. package/lib/dataStores.js +53 -23
  94. package/lib/dataStores.js.map +1 -1
  95. package/lib/garbageCollection.d.ts +36 -19
  96. package/lib/garbageCollection.d.ts.map +1 -1
  97. package/lib/garbageCollection.js +207 -121
  98. package/lib/garbageCollection.js.map +1 -1
  99. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  100. package/lib/gcSweepReadyUsageDetection.js +3 -12
  101. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  102. package/lib/index.d.ts +4 -6
  103. package/lib/index.d.ts.map +1 -1
  104. package/lib/index.js +2 -4
  105. package/lib/index.js.map +1 -1
  106. package/lib/opCompressor.d.ts +18 -0
  107. package/lib/opCompressor.d.ts.map +1 -0
  108. package/lib/opCompressor.js +46 -0
  109. package/lib/opCompressor.js.map +1 -0
  110. package/lib/opDecompressor.d.ts +20 -0
  111. package/lib/opDecompressor.d.ts.map +1 -0
  112. package/lib/opDecompressor.js +68 -0
  113. package/lib/opDecompressor.js.map +1 -0
  114. package/lib/packageVersion.d.ts +1 -1
  115. package/lib/packageVersion.js +1 -1
  116. package/lib/packageVersion.js.map +1 -1
  117. package/lib/pendingStateManager.d.ts +6 -26
  118. package/lib/pendingStateManager.d.ts.map +1 -1
  119. package/lib/pendingStateManager.js +42 -62
  120. package/lib/pendingStateManager.js.map +1 -1
  121. package/lib/runningSummarizer.d.ts +3 -2
  122. package/lib/runningSummarizer.d.ts.map +1 -1
  123. package/lib/runningSummarizer.js +10 -3
  124. package/lib/runningSummarizer.js.map +1 -1
  125. package/lib/scheduleManager.js.map +1 -1
  126. package/lib/summarizer.js +7 -2
  127. package/lib/summarizer.js.map +1 -1
  128. package/lib/summarizerClientElection.js +1 -1
  129. package/lib/summarizerClientElection.js.map +1 -1
  130. package/lib/summarizerHeuristics.d.ts.map +1 -1
  131. package/lib/summarizerHeuristics.js +0 -3
  132. package/lib/summarizerHeuristics.js.map +1 -1
  133. package/lib/summarizerTypes.d.ts +19 -2
  134. package/lib/summarizerTypes.d.ts.map +1 -1
  135. package/lib/summarizerTypes.js.map +1 -1
  136. package/lib/summaryFormat.d.ts +4 -2
  137. package/lib/summaryFormat.d.ts.map +1 -1
  138. package/lib/summaryFormat.js.map +1 -1
  139. package/lib/summaryGenerator.d.ts.map +1 -1
  140. package/lib/summaryGenerator.js +3 -2
  141. package/lib/summaryGenerator.js.map +1 -1
  142. package/lib/summaryManager.d.ts.map +1 -1
  143. package/lib/summaryManager.js +10 -6
  144. package/lib/summaryManager.js.map +1 -1
  145. package/package.json +37 -63
  146. package/prettier.config.cjs +8 -0
  147. package/src/batchManager.ts +32 -15
  148. package/src/containerRuntime.ts +260 -156
  149. package/src/dataStore.ts +13 -1
  150. package/src/dataStoreContext.ts +100 -76
  151. package/src/dataStoreContexts.ts +1 -1
  152. package/src/dataStores.ts +61 -23
  153. package/src/garbageCollection.ts +255 -126
  154. package/src/gcSweepReadyUsageDetection.ts +2 -10
  155. package/src/index.ts +4 -4
  156. package/src/opCompressor.ts +59 -0
  157. package/src/opDecompressor.ts +82 -0
  158. package/src/packageVersion.ts +1 -1
  159. package/src/pendingStateManager.ts +57 -96
  160. package/src/runningSummarizer.ts +11 -3
  161. package/src/scheduleManager.ts +1 -0
  162. package/src/summarizer.ts +6 -6
  163. package/src/summarizerClientElection.ts +1 -1
  164. package/src/summarizerHeuristics.ts +0 -3
  165. package/src/summarizerTypes.ts +20 -7
  166. package/src/summaryFormat.ts +4 -2
  167. package/src/summaryGenerator.ts +3 -2
  168. package/src/summaryManager.ts +18 -7
@@ -30,6 +30,7 @@ import { channelToDataStore, isDataStoreAliasMessage, } from "./dataStore";
30
30
  import { BindBatchTracker } from "./batchTracker";
31
31
  import { SerializedSnapshotStorage } from "./serializedSnapshotStorage";
32
32
  import { ScheduleManager } from "./scheduleManager";
33
+ import { OpDecompressor } from "./opDecompressor";
33
34
  export var ContainerMessageType;
34
35
  (function (ContainerMessageType) {
35
36
  // An op to be delivered to store
@@ -58,6 +59,7 @@ export const DefaultSummaryConfiguration = {
58
59
  summarizerClientElection: false,
59
60
  nonRuntimeOpWeight: 0.1,
60
61
  runtimeOpWeight: 1.0,
62
+ nonRuntimeHeuristicThreshold: 20,
61
63
  };
62
64
  /**
63
65
  * Accepted header keys for requests coming to the runtime.
@@ -74,8 +76,20 @@ export var RuntimeHeaders;
74
76
  /** True if the request is coming from an IFluidHandle. */
75
77
  RuntimeHeaders["viaHandle"] = "viaHandle";
76
78
  })(RuntimeHeaders || (RuntimeHeaders = {}));
79
+ /**
80
+ * Available compression algorithms for op compression.
81
+ */
82
+ export var CompressionAlgorithms;
83
+ (function (CompressionAlgorithms) {
84
+ CompressionAlgorithms["lz4"] = "lz4";
85
+ })(CompressionAlgorithms || (CompressionAlgorithms = {}));
77
86
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
78
87
  const defaultFlushMode = FlushMode.TurnBased;
88
+ // The actual limit is 1Mb (socket.io and Kafka limits)
89
+ // We can't estimate it fully, as we
90
+ // - do not know what properties relay service will add
91
+ // - we do not stringify final op, thus we do not know how much escaping will be added.
92
+ const defaultMaxBatchSizeInBytes = 950 * 1024;
79
93
  /**
80
94
  * @deprecated - use ContainerRuntimeMessage instead
81
95
  */
@@ -93,15 +107,12 @@ export var RuntimeMessage;
93
107
  * @deprecated - please use version in driver-utils
94
108
  */
95
109
  export function isRuntimeMessage(message) {
96
- if (Object.values(RuntimeMessage).includes(message.type)) {
97
- return true;
98
- }
99
- return false;
110
+ return Object.values(RuntimeMessage).includes(message.type);
100
111
  }
101
112
  /**
102
113
  * Unpacks runtime messages
103
114
  *
104
- * @remarks This API makes no promises regarding backward-compatability. This is internal API.
115
+ * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
105
116
  * @param message - message (as it observed in storage / service)
106
117
  * @returns unpacked runtime message
107
118
  *
@@ -116,7 +127,6 @@ export function unpackRuntimeMessage(message) {
116
127
  else {
117
128
  // new format
118
129
  const innerContents = message.contents;
119
- assert(innerContents.type !== undefined, 0x121 /* "Undefined inner contents type!" */);
120
130
  message.type = innerContents.type;
121
131
  message.contents = innerContents.contents;
122
132
  }
@@ -156,7 +166,7 @@ export function getDeviceSpec() {
156
166
  */
157
167
  export class ContainerRuntime extends TypedEventEmitter {
158
168
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
159
- var _a, _b, _c, _d;
169
+ var _a, _b, _c, _d, _e, _f;
160
170
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
161
171
  super();
162
172
  this.context = context;
@@ -167,9 +177,10 @@ export class ContainerRuntime extends TypedEventEmitter {
167
177
  this._storage = _storage;
168
178
  this.requestHandler = requestHandler;
169
179
  this.summaryConfiguration = summaryConfiguration;
180
+ this.opDecompressor = new OpDecompressor();
170
181
  this.defaultMaxConsecutiveReconnects = 7;
171
182
  this._orderSequentiallyCalls = 0;
172
- this.flushTrigger = false;
183
+ this.flushMicroTaskExists = false;
173
184
  this.savedOps = [];
174
185
  this.consecutiveReconnects = 0;
175
186
  this._disposed = false;
@@ -181,12 +192,6 @@ export class ContainerRuntime extends TypedEventEmitter {
181
192
  signalTimestamp: 0,
182
193
  trackingSignalSequenceNumber: undefined,
183
194
  };
184
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
185
- // but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
186
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
187
- // payloads. That number represents final (compressed) bits (once compression is implemented).
188
- this.pendingAttachBatch = new BatchManager(64 * 1024);
189
- this.pendingBatch = new BatchManager();
190
195
  this.summarizeOnDemand = (...args) => {
191
196
  if (this.clientDetails.type === summarizerClientType) {
192
197
  return this.summarizer.summarizeOnDemand(...args);
@@ -215,6 +220,26 @@ export class ContainerRuntime extends TypedEventEmitter {
215
220
  throw new UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
216
221
  }
217
222
  };
223
+ let loadSummaryNumber;
224
+ // Get the container creation metadata. For new container, we initialize these. For existing containers,
225
+ // get the values from the metadata blob.
226
+ if (existing) {
227
+ this.createContainerMetadata = {
228
+ createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
229
+ createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
230
+ };
231
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
232
+ // the count is reset to 0.
233
+ loadSummaryNumber = (_b = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _b !== void 0 ? _b : 0;
234
+ }
235
+ else {
236
+ this.createContainerMetadata = {
237
+ createContainerRuntimeVersion: pkgVersion,
238
+ createContainerTimestamp: Date.now(),
239
+ };
240
+ loadSummaryNumber = 0;
241
+ }
242
+ this.nextSummaryNumber = loadSummaryNumber + 1;
218
243
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
219
244
  this._connected = this.context.connected;
220
245
  this.chunkMap = new Map(chunks);
@@ -229,10 +254,31 @@ export class ContainerRuntime extends TypedEventEmitter {
229
254
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
230
255
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
231
256
  this.maxConsecutiveReconnects =
232
- (_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
257
+ (_c = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _c !== void 0 ? _c : this.defaultMaxConsecutiveReconnects;
233
258
  this._flushMode = runtimeOptions.flushMode;
259
+ // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
260
+ // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
261
+ // latency of processing a batch.
262
+ // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
263
+ // payloads. That number represents final (compressed) bits (once compression is implemented).
264
+ this.pendingAttachBatch = new BatchManager(this.mc.logger, {
265
+ hardLimit: runtimeOptions.maxBatchSizeInBytes,
266
+ softLimit: 64 * 1024,
267
+ compressionOptions: runtimeOptions.compressionOptions
268
+ });
269
+ this.pendingBatch = new BatchManager(this.mc.logger, {
270
+ hardLimit: runtimeOptions.maxBatchSizeInBytes,
271
+ compressionOptions: runtimeOptions.compressionOptions
272
+ });
234
273
  const pendingRuntimeState = context.pendingLocalState;
235
- const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
274
+ const baseSnapshot = (_d = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _d !== void 0 ? _d : context.baseSnapshot;
275
+ const maxSnapshotCacheDurationMs = (_f = (_e = this._storage) === null || _e === void 0 ? void 0 : _e.policies) === null || _f === void 0 ? void 0 : _f.maximumCacheDurationMs;
276
+ if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
277
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
278
+ // which dictates the value is either undefined or exactly 5 days in ms.
279
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
280
+ throw new UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
281
+ }
236
282
  this.garbageCollector = GarbageCollector.create({
237
283
  runtime: this,
238
284
  gcOptions: this.runtimeOptions.gcOptions,
@@ -240,6 +286,7 @@ export class ContainerRuntime extends TypedEventEmitter {
240
286
  baseLogger: this.mc.logger,
241
287
  existing,
242
288
  metadata,
289
+ createContainerMetadata: this.createContainerMetadata,
243
290
  isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
244
291
  getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
245
292
  getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
@@ -265,9 +312,9 @@ export class ContainerRuntime extends TypedEventEmitter {
265
312
  gcDisabled: !this.garbageCollector.shouldRunGC,
266
313
  });
267
314
  if (baseSnapshot) {
268
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
315
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
269
316
  }
270
- 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);
317
+ 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));
271
318
  this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
272
319
  if (!this.disposed) {
273
320
  this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
@@ -281,10 +328,10 @@ export class ContainerRuntime extends TypedEventEmitter {
281
328
  close: this.closeFn,
282
329
  connected: () => this.connected,
283
330
  flush: this.flush.bind(this),
284
- flushMode: () => this.flushMode,
285
331
  reSubmit: this.reSubmit.bind(this),
286
- setFlushMode: (mode) => this.setFlushMode(mode),
287
- }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
332
+ rollback: this.rollback.bind(this),
333
+ orderSequentially: this.orderSequentially.bind(this),
334
+ }, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
288
335
  this.context.quorum.on("removeMember", (clientId) => {
289
336
  this.clearPartialChunks(clientId);
290
337
  });
@@ -308,7 +355,7 @@ export class ContainerRuntime extends TypedEventEmitter {
308
355
  // if summaries are enabled and we are not the summarizer client.
309
356
  const defaultAction = () => {
310
357
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
311
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
358
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
312
359
  // unregister default to no log on every op after falling behind
313
360
  // and register summary ack handler to re-register this handler
314
361
  // after successful summary
@@ -354,26 +401,6 @@ export class ContainerRuntime extends TypedEventEmitter {
354
401
  });
355
402
  // logging hardware telemetry
356
403
  logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
357
- let loadSummaryNumber;
358
- // Get the container creation metadata. For new container, we initialize these. For existing containers,
359
- // get the values from the metadata blob.
360
- if (existing) {
361
- this.createContainerMetadata = {
362
- createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
363
- createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
364
- };
365
- // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
366
- // the count is reset to 0.
367
- loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
368
- }
369
- else {
370
- this.createContainerMetadata = {
371
- createContainerRuntimeVersion: pkgVersion,
372
- createContainerTimestamp: Date.now(),
373
- };
374
- loadSummaryNumber = 0;
375
- }
376
- this.nextSummaryNumber = loadSummaryNumber + 1;
377
404
  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 }));
378
405
  ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
379
406
  BindBatchTracker(this, this.logger);
@@ -399,7 +426,8 @@ export class ContainerRuntime extends TypedEventEmitter {
399
426
  runtimeVersion: pkgVersion,
400
427
  },
401
428
  });
402
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
429
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = { minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
430
+ compressionAlgorithm: CompressionAlgorithms.lz4 }, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
403
431
  const pendingRuntimeState = context.pendingLocalState;
404
432
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
405
433
  const storage = !pendingRuntimeState ?
@@ -454,13 +482,16 @@ export class ContainerRuntime extends TypedEventEmitter {
454
482
  loadSequenceNumberVerification,
455
483
  flushMode,
456
484
  enableOfflineLoad,
485
+ compressionOptions,
486
+ maxBatchSizeInBytes,
457
487
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
458
488
  if (pendingRuntimeState) {
459
489
  await runtime.processSavedOps(pendingRuntimeState);
460
490
  // delete these once runtime has seen them to save space
461
491
  pendingRuntimeState.savedOps = [];
462
492
  }
463
- await runtime.getSnapshotBlobs();
493
+ // Initialize the base state of the runtime before it's returned.
494
+ await runtime.initializeBaseState();
464
495
  return runtime;
465
496
  }
466
497
  get options() {
@@ -567,6 +598,13 @@ export class ContainerRuntime extends TypedEventEmitter {
567
598
  ? this.summaryConfiguration.initialSummarizerDelayMs
568
599
  : 0;
569
600
  }
601
+ /**
602
+ * Initializes the state from the base snapshot this container runtime loaded from.
603
+ */
604
+ async initializeBaseState() {
605
+ await this.initializeBaseSnapshotBlobs();
606
+ await this.garbageCollector.initializeBaseState();
607
+ }
570
608
  dispose(error) {
571
609
  var _a;
572
610
  if (this._disposed) {
@@ -665,13 +703,16 @@ export class ContainerRuntime extends TypedEventEmitter {
665
703
  return (_a = this.dataStores.aliases.get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
666
704
  }
667
705
  async getDataStoreFromRequest(id, request) {
668
- var _a, _b, _c;
706
+ var _a, _b, _c, _d, _e;
669
707
  const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
670
708
  ? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
671
709
  : true;
710
+ const viaHandle = typeof ((_c = request.headers) === null || _c === void 0 ? void 0 : _c[RuntimeHeaders.viaHandle]) === "boolean"
711
+ ? (_d = request.headers) === null || _d === void 0 ? void 0 : _d[RuntimeHeaders.viaHandle]
712
+ : false;
672
713
  await this.dataStores.waitIfPendingAlias(id);
673
714
  const internalId = this.internalId(id);
674
- const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
715
+ const dataStoreContext = await this.dataStores.getDataStore(internalId, wait, viaHandle);
675
716
  /**
676
717
  * If GC should run and this an external app request with "externalRequest" header, we need to return
677
718
  * an error if the data store being requested is marked as unreferenced as per the data store's base
@@ -680,7 +721,7 @@ export class ContainerRuntime extends TypedEventEmitter {
680
721
  * This is a workaround to handle scenarios where a data store shared with an external app is deleted
681
722
  * and marked as unreferenced by GC. Returning an error will fail to load the data store for the app.
682
723
  */
683
- if (((_c = request.headers) === null || _c === void 0 ? void 0 : _c[RuntimeHeaders.externalRequest]) && this.garbageCollector.shouldRunGC) {
724
+ if (((_e = request.headers) === null || _e === void 0 ? void 0 : _e[RuntimeHeaders.externalRequest]) && this.garbageCollector.shouldRunGC) {
684
725
  // The data store is referenced if used routes in the base summary has a route to self.
685
726
  // Older documents may not have used routes in the summary. They are considered referenced.
686
727
  const usedRoutes = (await dataStoreContext.getBaseGCDetails()).usedRoutes;
@@ -727,11 +768,9 @@ export class ContainerRuntime extends TypedEventEmitter {
727
768
  if (Object.keys(blobManagerSummary.summary.tree).length > 0) {
728
769
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
729
770
  }
730
- if (this.garbageCollector.writeDataAtRoot) {
731
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
732
- if (gcSummary !== undefined) {
733
- addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
734
- }
771
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
772
+ if (gcSummary !== undefined) {
773
+ addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
735
774
  }
736
775
  }
737
776
  // Track how many times the container tries to reconnect with pending messages.
@@ -887,6 +926,7 @@ export class ContainerRuntime extends TypedEventEmitter {
887
926
  if (typeof message.contents === "string" && message.contents !== "") {
888
927
  message.contents = JSON.parse(message.contents);
889
928
  }
929
+ message = this.opDecompressor.processMessage(message);
890
930
  // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
891
931
  // This format was not shipped to production workflows.
892
932
  const runtimeMessage = unpackRuntimeMessage(message);
@@ -1004,27 +1044,14 @@ export class ContainerRuntime extends TypedEventEmitter {
1004
1044
  async getRootDataStoreChannel(id, wait = true) {
1005
1045
  await this.dataStores.waitIfPendingAlias(id);
1006
1046
  const internalId = this.internalId(id);
1007
- const context = await this.dataStores.getDataStore(internalId, wait);
1047
+ const context = await this.dataStores.getDataStore(internalId, wait, false /* viaHandle */);
1008
1048
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
1009
1049
  return context.realize();
1010
1050
  }
1011
- setFlushMode(mode) {
1012
- if (mode === this._flushMode) {
1013
- return;
1014
- }
1015
- this.mc.logger.sendTelemetryEvent({
1016
- eventName: "FlushMode Updated",
1017
- old: this._flushMode,
1018
- new: mode,
1019
- });
1020
- // Flush any pending batches if switching to immediate
1021
- if (mode === FlushMode.Immediate) {
1022
- this.flush();
1023
- }
1024
- this._flushMode = mode;
1025
- // Let the PendingStateManager know that FlushMode has been updated.
1026
- this.pendingStateManager.onFlushModeUpdated(mode);
1027
- }
1051
+ /**
1052
+ * Flush the pending ops manually.
1053
+ * This method is expected to be called at the end of a batch.
1054
+ */
1028
1055
  flush() {
1029
1056
  assert(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1030
1057
  this.flushBatch(this.pendingAttachBatch.popBatch());
@@ -1032,6 +1059,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1032
1059
  assert(this.emptyBatch, 0x3cf /* reentrancy */);
1033
1060
  }
1034
1061
  flushBatch(batch) {
1062
+ var _a;
1035
1063
  const length = batch.length;
1036
1064
  if (length > 1) {
1037
1065
  batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
@@ -1062,6 +1090,10 @@ export class ContainerRuntime extends TypedEventEmitter {
1062
1090
  // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1063
1091
  // version that has support for batches (submitBatchFn)
1064
1092
  for (const message of batch) {
1093
+ // Legacy path doesn't support compressed payloads and will submit uncompressed payload anyways
1094
+ if ((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.compressed) {
1095
+ delete message.metadata.compressed;
1096
+ }
1065
1097
  clientSequenceNumber = this.context.submitFn(MessageType.Operation, message.deserializedContent, true, // batch
1066
1098
  message.metadata);
1067
1099
  }
@@ -1080,26 +1112,6 @@ export class ContainerRuntime extends TypedEventEmitter {
1080
1112
  this.pendingStateManager.onFlush();
1081
1113
  }
1082
1114
  orderSequentially(callback) {
1083
- // If flush mode is already TurnBased we are either
1084
- // nested in another orderSequentially, or
1085
- // the app is flushing manually, in which
1086
- // case this invocation doesn't own
1087
- // flushing.
1088
- if (this.flushMode === FlushMode.TurnBased) {
1089
- this.trackOrderSequentiallyCalls(callback);
1090
- return;
1091
- }
1092
- const savedFlushMode = this.flushMode;
1093
- this.setFlushMode(FlushMode.TurnBased);
1094
- try {
1095
- this.trackOrderSequentiallyCalls(callback);
1096
- this.flush();
1097
- }
1098
- finally {
1099
- this.setFlushMode(savedFlushMode);
1100
- }
1101
- }
1102
- trackOrderSequentiallyCalls(callback) {
1103
1115
  let checkpoint;
1104
1116
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1105
1117
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
@@ -1134,6 +1146,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1134
1146
  finally {
1135
1147
  this._orderSequentiallyCalls--;
1136
1148
  }
1149
+ if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1150
+ this.flush();
1151
+ }
1137
1152
  }
1138
1153
  async createDataStore(pkg) {
1139
1154
  const internalId = uuid();
@@ -1160,6 +1175,12 @@ export class ContainerRuntime extends TypedEventEmitter {
1160
1175
  canSendOps() {
1161
1176
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1162
1177
  }
1178
+ /**
1179
+ * Are we in the middle of batching ops together?
1180
+ */
1181
+ currentlyBatching() {
1182
+ return this.flushMode === FlushMode.TurnBased || this._orderSequentiallyCalls !== 0;
1183
+ }
1163
1184
  getQuorum() {
1164
1185
  return this.context.quorum;
1165
1186
  }
@@ -1310,10 +1331,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1310
1331
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
1311
1332
  * After GC has run, called to notify this container's nodes of routes that are used in it.
1312
1333
  * @param usedRoutes - The routes that are used in all nodes in this Container.
1313
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
1314
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
1315
1334
  */
1316
- updateUsedRoutes(usedRoutes, gcTimestamp) {
1335
+ updateUsedRoutes(usedRoutes) {
1317
1336
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
1318
1337
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1319
1338
  // always referenced, so the used routes is only self-route (empty string).
@@ -1324,14 +1343,16 @@ export class ContainerRuntime extends TypedEventEmitter {
1324
1343
  dataStoreUsedRoutes.push(route);
1325
1344
  }
1326
1345
  }
1327
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
1346
+ return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1328
1347
  }
1329
1348
  /**
1330
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
1331
- * scenarios with accessing deleted content.
1349
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
1350
+ * tombstones.
1332
1351
  * @param unusedRoutes - The routes that are unused in all data stores in this Container.
1352
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
1353
+ * are deleted.
1333
1354
  */
1334
- deleteUnusedRoutes(unusedRoutes) {
1355
+ updateUnusedRoutes(unusedRoutes, tombstone) {
1335
1356
  const blobManagerUnusedRoutes = [];
1336
1357
  const dataStoreUnusedRoutes = [];
1337
1358
  for (const route of unusedRoutes) {
@@ -1342,8 +1363,11 @@ export class ContainerRuntime extends TypedEventEmitter {
1342
1363
  dataStoreUnusedRoutes.push(route);
1343
1364
  }
1344
1365
  }
1345
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
1346
- this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
1366
+ // Todo: Add tombstone for attachment blobs. For now, we ignore attachment blobs that should be tombstoned.
1367
+ if (!tombstone) {
1368
+ this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
1369
+ }
1370
+ this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
1347
1371
  }
1348
1372
  /**
1349
1373
  * Returns a server generated referenced timestamp to be used to track unreferenced nodes by GC.
@@ -1429,15 +1453,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1429
1453
  const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
1430
1454
  const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
1431
1455
  latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
1432
- if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
1433
- // We need to catch up to the latest summary's reference sequence number before pausing.
1434
- await PerformanceEvent.timedExecAsync(summaryNumberLogger, {
1435
- eventName: "WaitingForSeq",
1436
- lastSequenceNumber: this.deltaManager.lastSequenceNumber,
1437
- targetSequenceNumber: latestSnapshotRefSeq,
1438
- lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
1439
- }, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
1440
- }
1456
+ // We might need to catch up to the latest summary's reference sequence number before pausing.
1457
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryNumberLogger);
1441
1458
  }
1442
1459
  try {
1443
1460
  await this.deltaManager.inbound.pause();
@@ -1674,6 +1691,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1674
1691
  return this.blobManager.createBlob(blob);
1675
1692
  }
1676
1693
  submit(type, contents, localOpMetadata = undefined, metadata = undefined) {
1694
+ var _a, _b, _c, _d;
1677
1695
  this.verifyNotClosed();
1678
1696
  // There should be no ops in detached container state!
1679
1697
  assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
@@ -1710,8 +1728,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1710
1728
  // issue than sending.
1711
1729
  // Please note that this does not change file format, so it can be disabled in the future if this
1712
1730
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
1713
- if (type === ContainerMessageType.Attach &&
1714
- this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
1731
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
1732
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
1715
1733
  if (!this.pendingAttachBatch.push(message)) {
1716
1734
  // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1717
1735
  // when queue is not empty.
@@ -1720,9 +1738,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1720
1738
  if (!this.pendingAttachBatch.push(message)) {
1721
1739
  throw new GenericError("BatchTooLarge",
1722
1740
  /* error */ undefined, {
1723
- opSize: message.contents.length,
1741
+ opSize: (_b = ((_a = message.contents) === null || _a === void 0 ? void 0 : _a.length)) !== null && _b !== void 0 ? _b : 0,
1724
1742
  count: this.pendingAttachBatch.length,
1725
- limit: this.pendingAttachBatch.limit,
1743
+ limit: this.pendingAttachBatch.options.hardLimit,
1726
1744
  });
1727
1745
  }
1728
1746
  }
@@ -1731,23 +1749,23 @@ export class ContainerRuntime extends TypedEventEmitter {
1731
1749
  if (!this.pendingBatch.push(message)) {
1732
1750
  throw new GenericError("BatchTooLarge",
1733
1751
  /* error */ undefined, {
1734
- opSize: message.contents.length,
1752
+ opSize: (_d = ((_c = message.contents) === null || _c === void 0 ? void 0 : _c.length)) !== null && _d !== void 0 ? _d : 0,
1735
1753
  count: this.pendingBatch.length,
1736
- limit: this.pendingBatch.limit,
1754
+ limit: this.pendingBatch.options.hardLimit,
1737
1755
  });
1738
1756
  }
1739
- }
1740
- if (this._flushMode !== FlushMode.TurnBased) {
1741
- this.flush();
1742
- }
1743
- else if (!this.flushTrigger) {
1744
- this.flushTrigger = true;
1745
- // Queue a microtask to detect the end of the turn and force a flush.
1746
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1747
- Promise.resolve().then(() => {
1748
- this.flushTrigger = false;
1757
+ if (!this.currentlyBatching()) {
1749
1758
  this.flush();
1750
- });
1759
+ }
1760
+ else if (!this.flushMicroTaskExists) {
1761
+ this.flushMicroTaskExists = true;
1762
+ // Queue a microtask to detect the end of the turn and force a flush.
1763
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1764
+ Promise.resolve().then(() => {
1765
+ this.flushMicroTaskExists = false;
1766
+ this.flush();
1767
+ });
1768
+ }
1751
1769
  }
1752
1770
  }
1753
1771
  catch (error) {
@@ -1817,18 +1835,41 @@ export class ContainerRuntime extends TypedEventEmitter {
1817
1835
  throw new Error(`Can't rollback ${type}`);
1818
1836
  }
1819
1837
  }
1838
+ async waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryLogger) {
1839
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
1840
+ // We need to catch up to the latest summary's reference sequence number before proceeding.
1841
+ await PerformanceEvent.timedExecAsync(summaryLogger, {
1842
+ eventName: "WaitingForSeq",
1843
+ lastSequenceNumber: this.deltaManager.lastSequenceNumber,
1844
+ targetSequenceNumber: latestSnapshotRefSeq,
1845
+ lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
1846
+ }, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
1847
+ }
1848
+ }
1820
1849
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1821
- async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1850
+ async refreshLatestSummaryAck(options) {
1851
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
1822
1852
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
1823
1853
  // The call to fetch the snapshot is very expensive and not always needed.
1824
1854
  // It should only be done by the summarizerNode, if required.
1855
+ // When fetching from storage we will always get the latest version and do not use the ackHandle.
1825
1856
  const snapshotTreeFetcher = async () => {
1826
- const fetchResult = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
1857
+ const fetchResult = await this.fetchSnapshotFromStorage(null, summaryLogger, {
1827
1858
  eventName: "RefreshLatestSummaryGetSnapshot",
1828
1859
  ackHandle,
1829
1860
  summaryRefSeq,
1830
- fetchLatest: false,
1861
+ fetchLatest: true,
1862
+ });
1863
+ const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
1864
+ summaryLogger.sendTelemetryEvent({
1865
+ eventName: "LatestSummaryRetrieved",
1866
+ ackHandle,
1867
+ lastSequenceNumber: latestSnapshotRefSeq,
1868
+ targetSequenceNumber: summaryRefSeq,
1831
1869
  });
1870
+ // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
1871
+ // wait for the delta manager to catch up before refreshing the latest Summary.
1872
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryLogger);
1832
1873
  return fetchResult.snapshotTree;
1833
1874
  };
1834
1875
  const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, snapshotTreeFetcher, readAndParseBlob, summaryLogger);
@@ -1873,7 +1914,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1873
1914
  this.baseSnapshotBlobs = SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
1874
1915
  }
1875
1916
  }
1876
- async getSnapshotBlobs() {
1917
+ async initializeBaseSnapshotBlobs() {
1877
1918
  var _a;
1878
1919
  if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
1879
1920
  this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
@@ -1891,6 +1932,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1891
1932
  // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
1892
1933
  // to close current batch.
1893
1934
  this.flush();
1935
+ if (this._orderSequentiallyCalls !== 0) {
1936
+ throw new UsageError("can't get state during orderSequentially");
1937
+ }
1894
1938
  const previousPendingState = this.context.pendingLocalState;
1895
1939
  if (previousPendingState) {
1896
1940
  return {
@@ -1958,6 +2002,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1958
2002
  throw new UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
1959
2003
  }
1960
2004
  }
2005
+ if (configuration.minIdleTime > configuration.maxIdleTime) {
2006
+ throw new UsageError(`"minIdleTime" [${configuration.minIdleTime}] cannot be greater than "maxIdleTime" [${configuration.maxIdleTime}]`);
2007
+ }
1961
2008
  }
1962
2009
  }
1963
2010
  /**
@@ -1967,12 +2014,18 @@ export class ContainerRuntime extends TypedEventEmitter {
1967
2014
  const waitForSeq = async (deltaManager, targetSeq) => new Promise((resolve, reject) => {
1968
2015
  // TODO: remove cast to any when actual event is determined
1969
2016
  deltaManager.on("closed", reject);
1970
- const handleOp = (message) => {
1971
- if (message.sequenceNumber >= targetSeq) {
1972
- resolve();
1973
- deltaManager.off("op", handleOp);
1974
- }
1975
- };
1976
- deltaManager.on("op", handleOp);
2017
+ // If we already reached target sequence number, simply resolve the promise.
2018
+ if (deltaManager.lastSequenceNumber >= targetSeq) {
2019
+ resolve();
2020
+ }
2021
+ else {
2022
+ const handleOp = (message) => {
2023
+ if (message.sequenceNumber >= targetSeq) {
2024
+ resolve();
2025
+ deltaManager.off("op", handleOp);
2026
+ }
2027
+ };
2028
+ deltaManager.on("op", handleOp);
2029
+ }
1977
2030
  });
1978
2031
  //# sourceMappingURL=containerRuntime.js.map