@fluidframework/container-runtime 2.0.0-internal.1.4.1 → 2.0.0-internal.2.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 (107) hide show
  1. package/dist/batchManager.d.ts +2 -3
  2. package/dist/batchManager.d.ts.map +1 -1
  3. package/dist/batchManager.js +3 -8
  4. package/dist/batchManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +43 -16
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +107 -83
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.d.ts +4 -20
  10. package/dist/dataStoreContext.d.ts.map +1 -1
  11. package/dist/dataStoreContext.js +17 -47
  12. package/dist/dataStoreContext.js.map +1 -1
  13. package/dist/dataStores.d.ts +2 -5
  14. package/dist/dataStores.d.ts.map +1 -1
  15. package/dist/dataStores.js +3 -11
  16. package/dist/dataStores.js.map +1 -1
  17. package/dist/garbageCollection.d.ts +1 -10
  18. package/dist/garbageCollection.d.ts.map +1 -1
  19. package/dist/garbageCollection.js +43 -51
  20. package/dist/garbageCollection.js.map +1 -1
  21. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  22. package/dist/gcSweepReadyUsageDetection.js +3 -12
  23. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  24. package/dist/index.d.ts +3 -5
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -5
  27. package/dist/index.js.map +1 -1
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/packageVersion.js +1 -1
  30. package/dist/packageVersion.js.map +1 -1
  31. package/dist/pendingStateManager.d.ts +6 -26
  32. package/dist/pendingStateManager.d.ts.map +1 -1
  33. package/dist/pendingStateManager.js +42 -62
  34. package/dist/pendingStateManager.js.map +1 -1
  35. package/dist/scheduleManager.js.map +1 -1
  36. package/dist/summarizer.js +7 -2
  37. package/dist/summarizer.js.map +1 -1
  38. package/dist/summarizerTypes.d.ts +19 -2
  39. package/dist/summarizerTypes.d.ts.map +1 -1
  40. package/dist/summarizerTypes.js.map +1 -1
  41. package/dist/summaryFormat.d.ts +4 -2
  42. package/dist/summaryFormat.d.ts.map +1 -1
  43. package/dist/summaryFormat.js.map +1 -1
  44. package/dist/summaryManager.d.ts.map +1 -1
  45. package/dist/summaryManager.js +10 -6
  46. package/dist/summaryManager.js.map +1 -1
  47. package/lib/batchManager.d.ts +2 -3
  48. package/lib/batchManager.d.ts.map +1 -1
  49. package/lib/batchManager.js +3 -8
  50. package/lib/batchManager.js.map +1 -1
  51. package/lib/containerRuntime.d.ts +43 -16
  52. package/lib/containerRuntime.d.ts.map +1 -1
  53. package/lib/containerRuntime.js +108 -84
  54. package/lib/containerRuntime.js.map +1 -1
  55. package/lib/dataStoreContext.d.ts +4 -20
  56. package/lib/dataStoreContext.d.ts.map +1 -1
  57. package/lib/dataStoreContext.js +18 -48
  58. package/lib/dataStoreContext.js.map +1 -1
  59. package/lib/dataStores.d.ts +2 -5
  60. package/lib/dataStores.d.ts.map +1 -1
  61. package/lib/dataStores.js +3 -11
  62. package/lib/dataStores.js.map +1 -1
  63. package/lib/garbageCollection.d.ts +1 -10
  64. package/lib/garbageCollection.d.ts.map +1 -1
  65. package/lib/garbageCollection.js +42 -50
  66. package/lib/garbageCollection.js.map +1 -1
  67. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  68. package/lib/gcSweepReadyUsageDetection.js +3 -12
  69. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  70. package/lib/index.d.ts +3 -5
  71. package/lib/index.d.ts.map +1 -1
  72. package/lib/index.js +0 -2
  73. package/lib/index.js.map +1 -1
  74. package/lib/packageVersion.d.ts +1 -1
  75. package/lib/packageVersion.js +1 -1
  76. package/lib/packageVersion.js.map +1 -1
  77. package/lib/pendingStateManager.d.ts +6 -26
  78. package/lib/pendingStateManager.d.ts.map +1 -1
  79. package/lib/pendingStateManager.js +42 -62
  80. package/lib/pendingStateManager.js.map +1 -1
  81. package/lib/scheduleManager.js.map +1 -1
  82. package/lib/summarizer.js +7 -2
  83. package/lib/summarizer.js.map +1 -1
  84. package/lib/summarizerTypes.d.ts +19 -2
  85. package/lib/summarizerTypes.d.ts.map +1 -1
  86. package/lib/summarizerTypes.js.map +1 -1
  87. package/lib/summaryFormat.d.ts +4 -2
  88. package/lib/summaryFormat.d.ts.map +1 -1
  89. package/lib/summaryFormat.js.map +1 -1
  90. package/lib/summaryManager.d.ts.map +1 -1
  91. package/lib/summaryManager.js +10 -6
  92. package/lib/summaryManager.js.map +1 -1
  93. package/package.json +40 -38
  94. package/src/batchManager.ts +7 -11
  95. package/src/containerRuntime.ts +149 -102
  96. package/src/dataStoreContext.ts +20 -62
  97. package/src/dataStores.ts +2 -10
  98. package/src/garbageCollection.ts +45 -55
  99. package/src/gcSweepReadyUsageDetection.ts +2 -10
  100. package/src/index.ts +2 -3
  101. package/src/packageVersion.ts +1 -1
  102. package/src/pendingStateManager.ts +57 -96
  103. package/src/scheduleManager.ts +1 -0
  104. package/src/summarizer.ts +6 -6
  105. package/src/summarizerTypes.ts +20 -7
  106. package/src/summaryFormat.ts +4 -2
  107. package/src/summaryManager.ts +18 -7
@@ -12,6 +12,7 @@ const runtime_definitions_1 = require("@fluidframework/runtime-definitions");
12
12
  const runtime_utils_1 = require("@fluidframework/runtime-utils");
13
13
  const garbage_collector_1 = require("@fluidframework/garbage-collector");
14
14
  const uuid_1 = require("uuid");
15
+ const lz4js_1 = require("lz4js");
15
16
  const containerHandleContext_1 = require("./containerHandleContext");
16
17
  const dataStoreRegistry_1 = require("./dataStoreRegistry");
17
18
  const summarizer_1 = require("./summarizer");
@@ -50,7 +51,6 @@ var ContainerMessageType;
50
51
  })(ContainerMessageType = exports.ContainerMessageType || (exports.ContainerMessageType = {}));
51
52
  exports.DefaultSummaryConfiguration = {
52
53
  state: "enabled",
53
- idleTime: 15 * 1000,
54
54
  minIdleTime: 0,
55
55
  maxIdleTime: 30 * 1000,
56
56
  maxTime: 60 * 1000,
@@ -80,6 +80,11 @@ var RuntimeHeaders;
80
80
  })(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
81
81
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
82
82
  const defaultFlushMode = runtime_definitions_1.FlushMode.TurnBased;
83
+ // The actual limit is 1Mb (socket.io and Kafka limits)
84
+ // We can't estimate it fully, as we
85
+ // - do not know what properties relay service will add
86
+ // - we do not stringify final op, thus we do not know how much escaping will be added.
87
+ const defaultMaxBatchSizeInBytes = 950 * 1024;
83
88
  /**
84
89
  * @deprecated - use ContainerRuntimeMessage instead
85
90
  */
@@ -97,22 +102,28 @@ var RuntimeMessage;
97
102
  * @deprecated - please use version in driver-utils
98
103
  */
99
104
  function isRuntimeMessage(message) {
100
- if (Object.values(RuntimeMessage).includes(message.type)) {
101
- return true;
102
- }
103
- return false;
105
+ return Object.values(RuntimeMessage).includes(message.type);
104
106
  }
105
107
  exports.isRuntimeMessage = isRuntimeMessage;
106
108
  /**
107
109
  * Unpacks runtime messages
108
110
  *
109
- * @remarks This API makes no promises regarding backward-compatability. This is internal API.
111
+ * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
110
112
  * @param message - message (as it observed in storage / service)
111
113
  * @returns unpacked runtime message
112
114
  *
113
115
  * @internal
114
116
  */
115
117
  function unpackRuntimeMessage(message) {
118
+ var _a;
119
+ if ((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.compressed) {
120
+ const contents = common_utils_1.IsoBuffer.from(message.contents.contents, "base64");
121
+ const decompressedMessage = (0, lz4js_1.decompress)(contents);
122
+ const intoString = new TextDecoder().decode(decompressedMessage);
123
+ const asObj = JSON.parse(intoString);
124
+ message.contents.contents = asObj;
125
+ message.metadata.compressed = false;
126
+ }
116
127
  if (message.type === protocol_definitions_1.MessageType.Operation) {
117
128
  // legacy op format?
118
129
  if (message.contents.address !== undefined && message.contents.type === undefined) {
@@ -163,7 +174,7 @@ exports.getDeviceSpec = getDeviceSpec;
163
174
  */
164
175
  class ContainerRuntime extends common_utils_1.TypedEventEmitter {
165
176
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
166
- var _a, _b, _c, _d;
177
+ var _a, _b, _c, _d, _e, _f;
167
178
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, exports.DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
168
179
  super();
169
180
  this.context = context;
@@ -176,9 +187,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
176
187
  this.summaryConfiguration = summaryConfiguration;
177
188
  this.defaultMaxConsecutiveReconnects = 7;
178
189
  this._orderSequentiallyCalls = 0;
179
- this.flushTrigger = false;
190
+ this.flushMicroTaskExists = false;
180
191
  this.savedOps = [];
181
192
  this.consecutiveReconnects = 0;
193
+ this.compressedOpCount = 0;
182
194
  this._disposed = false;
183
195
  this.emitDirtyDocumentEvent = true;
184
196
  this.defaultTelemetrySignalSampleCount = 100;
@@ -188,12 +200,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
188
200
  signalTimestamp: 0,
189
201
  trackingSignalSequenceNumber: undefined,
190
202
  };
191
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
192
- // but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
193
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
194
- // payloads. That number represents final (compressed) bits (once compression is implemented).
195
- this.pendingAttachBatch = new batchManager_1.BatchManager(64 * 1024);
196
- this.pendingBatch = new batchManager_1.BatchManager();
197
203
  this.summarizeOnDemand = (...args) => {
198
204
  if (this.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
199
205
  return this.summarizer.summarizeOnDemand(...args);
@@ -238,8 +244,22 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
238
244
  this.maxConsecutiveReconnects =
239
245
  (_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
240
246
  this._flushMode = runtimeOptions.flushMode;
247
+ // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
248
+ // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
249
+ // latency of processing a batch.
250
+ // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
251
+ // payloads. That number represents final (compressed) bits (once compression is implemented).
252
+ this.pendingAttachBatch = new batchManager_1.BatchManager(runtimeOptions.maxBatchSizeInBytes, 64 * 1024);
253
+ this.pendingBatch = new batchManager_1.BatchManager(runtimeOptions.maxBatchSizeInBytes);
241
254
  const pendingRuntimeState = context.pendingLocalState;
242
255
  const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
256
+ const maxSnapshotCacheDurationMs = (_e = (_d = this._storage) === null || _d === void 0 ? void 0 : _d.policies) === null || _e === void 0 ? void 0 : _e.maximumCacheDurationMs;
257
+ if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
258
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
259
+ // which dictates the value is either undefined or exactly 5 days in ms.
260
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
261
+ throw new container_utils_1.UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
262
+ }
243
263
  this.garbageCollector = garbageCollection_1.GarbageCollector.create({
244
264
  runtime: this,
245
265
  gcOptions: this.runtimeOptions.gcOptions,
@@ -272,9 +292,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
272
292
  gcDisabled: !this.garbageCollector.shouldRunGC,
273
293
  });
274
294
  if (baseSnapshot) {
275
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
295
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
276
296
  }
277
- 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);
297
+ 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));
278
298
  this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
279
299
  if (!this.disposed) {
280
300
  this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
@@ -288,10 +308,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
288
308
  close: this.closeFn,
289
309
  connected: () => this.connected,
290
310
  flush: this.flush.bind(this),
291
- flushMode: () => this.flushMode,
292
311
  reSubmit: this.reSubmit.bind(this),
293
- setFlushMode: (mode) => this.setFlushMode(mode),
294
- }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
312
+ rollback: this.rollback.bind(this),
313
+ orderSequentially: this.orderSequentially.bind(this),
314
+ }, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
295
315
  this.context.quorum.on("removeMember", (clientId) => {
296
316
  this.clearPartialChunks(clientId);
297
317
  });
@@ -315,7 +335,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
315
335
  // if summaries are enabled and we are not the summarizer client.
316
336
  const defaultAction = () => {
317
337
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
318
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
338
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
319
339
  // unregister default to no log on every op after falling behind
320
340
  // and register summary ack handler to re-register this handler
321
341
  // after successful summary
@@ -371,7 +391,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
371
391
  };
372
392
  // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
373
393
  // the count is reset to 0.
374
- loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
394
+ loadSummaryNumber = (_f = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _f !== void 0 ? _f : 0;
375
395
  }
376
396
  else {
377
397
  this.createContainerMetadata = {
@@ -406,7 +426,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
406
426
  runtimeVersion: packageVersion_1.pkgVersion,
407
427
  },
408
428
  });
409
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
429
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {}, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
410
430
  const pendingRuntimeState = context.pendingLocalState;
411
431
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
412
432
  const storage = !pendingRuntimeState ?
@@ -461,6 +481,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
461
481
  loadSequenceNumberVerification,
462
482
  flushMode,
463
483
  enableOfflineLoad,
484
+ compressionOptions,
485
+ maxBatchSizeInBytes,
464
486
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
465
487
  if (pendingRuntimeState) {
466
488
  await runtime.processSavedOps(pendingRuntimeState);
@@ -734,11 +756,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
734
756
  if (Object.keys(blobManagerSummary.summary.tree).length > 0) {
735
757
  (0, runtime_utils_1.addTreeToSummary)(summaryTree, summaryFormat_1.blobsTreeName, blobManagerSummary);
736
758
  }
737
- if (this.garbageCollector.writeDataAtRoot) {
738
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
739
- if (gcSummary !== undefined) {
740
- (0, runtime_utils_1.addSummarizeResultToSummary)(summaryTree, garbageCollection_1.gcTreeKey, gcSummary);
741
- }
759
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
760
+ if (gcSummary !== undefined) {
761
+ (0, runtime_utils_1.addSummarizeResultToSummary)(summaryTree, garbageCollection_1.gcTreeKey, gcSummary);
742
762
  }
743
763
  }
744
764
  // Track how many times the container tries to reconnect with pending messages.
@@ -1015,23 +1035,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1015
1035
  (0, common_utils_1.assert)(await context.isRoot(), 0x12b /* "did not get root data store" */);
1016
1036
  return context.realize();
1017
1037
  }
1018
- setFlushMode(mode) {
1019
- if (mode === this._flushMode) {
1020
- return;
1021
- }
1022
- this.mc.logger.sendTelemetryEvent({
1023
- eventName: "FlushMode Updated",
1024
- old: this._flushMode,
1025
- new: mode,
1026
- });
1027
- // Flush any pending batches if switching to immediate
1028
- if (mode === runtime_definitions_1.FlushMode.Immediate) {
1029
- this.flush();
1030
- }
1031
- this._flushMode = mode;
1032
- // Let the PendingStateManager know that FlushMode has been updated.
1033
- this.pendingStateManager.onFlushModeUpdated(mode);
1034
- }
1038
+ /**
1039
+ * Flush the pending ops manually.
1040
+ * This method is expected to be called at the end of a batch.
1041
+ */
1035
1042
  flush() {
1036
1043
  (0, common_utils_1.assert)(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1037
1044
  this.flushBatch(this.pendingAttachBatch.popBatch());
@@ -1060,7 +1067,33 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1060
1067
  if (this.context.submitBatchFn !== undefined) {
1061
1068
  const batchToSend = [];
1062
1069
  for (const message of batch) {
1063
- batchToSend.push({ contents: message.contents, metadata: message.metadata });
1070
+ let contents = message.contents;
1071
+ let metadata = message.metadata;
1072
+ if (this.runtimeOptions.compressionOptions.minimumSize &&
1073
+ this.runtimeOptions.compressionOptions.minimumSize < message.contents.length) {
1074
+ this.compressedOpCount++;
1075
+ const copiedMessage = Object.assign({}, message.deserializedContent);
1076
+ const compressionStart = Date.now();
1077
+ const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(copiedMessage.contents));
1078
+ const compressedContents = (0, lz4js_1.compress)(contentsAsBuffer);
1079
+ const compressedContent = common_utils_1.IsoBuffer.from(compressedContents).toString("base64");
1080
+ const duration = Date.now() - compressionStart;
1081
+ if (this.compressedOpCount % 100) {
1082
+ this.mc.logger.sendPerformanceEvent({
1083
+ eventName: "compressedOp",
1084
+ duration,
1085
+ sizeBeforeCompression: message.contents.length,
1086
+ sizeAfterCompression: compressedContent.length,
1087
+ });
1088
+ }
1089
+ copiedMessage.contents = compressedContent;
1090
+ const stringifiedContents = JSON.stringify(copiedMessage);
1091
+ if (stringifiedContents.length < message.contents.length) {
1092
+ contents = JSON.stringify(copiedMessage);
1093
+ metadata = Object.assign(Object.assign({}, message.metadata), { compressed: true });
1094
+ }
1095
+ }
1096
+ batchToSend.push({ contents, metadata });
1064
1097
  }
1065
1098
  // returns clientSequenceNumber of last message in a batch
1066
1099
  clientSequenceNumber = this.context.submitBatchFn(batchToSend);
@@ -1087,26 +1120,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1087
1120
  this.pendingStateManager.onFlush();
1088
1121
  }
1089
1122
  orderSequentially(callback) {
1090
- // If flush mode is already TurnBased we are either
1091
- // nested in another orderSequentially, or
1092
- // the app is flushing manually, in which
1093
- // case this invocation doesn't own
1094
- // flushing.
1095
- if (this.flushMode === runtime_definitions_1.FlushMode.TurnBased) {
1096
- this.trackOrderSequentiallyCalls(callback);
1097
- return;
1098
- }
1099
- const savedFlushMode = this.flushMode;
1100
- this.setFlushMode(runtime_definitions_1.FlushMode.TurnBased);
1101
- try {
1102
- this.trackOrderSequentiallyCalls(callback);
1103
- this.flush();
1104
- }
1105
- finally {
1106
- this.setFlushMode(savedFlushMode);
1107
- }
1108
- }
1109
- trackOrderSequentiallyCalls(callback) {
1110
1123
  let checkpoint;
1111
1124
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1112
1125
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
@@ -1141,6 +1154,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1141
1154
  finally {
1142
1155
  this._orderSequentiallyCalls--;
1143
1156
  }
1157
+ if (this.flushMode === runtime_definitions_1.FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1158
+ this.flush();
1159
+ }
1144
1160
  }
1145
1161
  async createDataStore(pkg) {
1146
1162
  const internalId = (0, uuid_1.v4)();
@@ -1167,6 +1183,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1167
1183
  canSendOps() {
1168
1184
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1169
1185
  }
1186
+ /**
1187
+ * Are we in the middle of batching ops together?
1188
+ */
1189
+ currentlyBatching() {
1190
+ return this.flushMode === runtime_definitions_1.FlushMode.TurnBased || this._orderSequentiallyCalls !== 0;
1191
+ }
1170
1192
  getQuorum() {
1171
1193
  return this.context.quorum;
1172
1194
  }
@@ -1317,10 +1339,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1317
1339
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
1318
1340
  * After GC has run, called to notify this container's nodes of routes that are used in it.
1319
1341
  * @param usedRoutes - The routes that are used in all nodes in this Container.
1320
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
1321
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
1322
1342
  */
1323
- updateUsedRoutes(usedRoutes, gcTimestamp) {
1343
+ updateUsedRoutes(usedRoutes) {
1324
1344
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
1325
1345
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1326
1346
  // always referenced, so the used routes is only self-route (empty string).
@@ -1331,7 +1351,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1331
1351
  dataStoreUsedRoutes.push(route);
1332
1352
  }
1333
1353
  }
1334
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
1354
+ return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1335
1355
  }
1336
1356
  /**
1337
1357
  * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
@@ -1717,8 +1737,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1717
1737
  // issue than sending.
1718
1738
  // Please note that this does not change file format, so it can be disabled in the future if this
1719
1739
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
1720
- if (type === ContainerMessageType.Attach &&
1721
- this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
1740
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
1741
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
1722
1742
  if (!this.pendingAttachBatch.push(message)) {
1723
1743
  // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1724
1744
  // when queue is not empty.
@@ -1743,18 +1763,18 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1743
1763
  limit: this.pendingBatch.limit,
1744
1764
  });
1745
1765
  }
1746
- }
1747
- if (this._flushMode !== runtime_definitions_1.FlushMode.TurnBased) {
1748
- this.flush();
1749
- }
1750
- else if (!this.flushTrigger) {
1751
- this.flushTrigger = true;
1752
- // Queue a microtask to detect the end of the turn and force a flush.
1753
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1754
- Promise.resolve().then(() => {
1755
- this.flushTrigger = false;
1766
+ if (!this.currentlyBatching()) {
1756
1767
  this.flush();
1757
- });
1768
+ }
1769
+ else if (!this.flushMicroTaskExists) {
1770
+ this.flushMicroTaskExists = true;
1771
+ // Queue a microtask to detect the end of the turn and force a flush.
1772
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1773
+ Promise.resolve().then(() => {
1774
+ this.flushMicroTaskExists = false;
1775
+ this.flush();
1776
+ });
1777
+ }
1758
1778
  }
1759
1779
  }
1760
1780
  catch (error) {
@@ -1825,7 +1845,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1825
1845
  }
1826
1846
  }
1827
1847
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1828
- async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1848
+ async refreshLatestSummaryAck(options) {
1849
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
1829
1850
  const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
1830
1851
  // The call to fetch the snapshot is very expensive and not always needed.
1831
1852
  // It should only be done by the summarizerNode, if required.
@@ -1898,6 +1919,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1898
1919
  // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
1899
1920
  // to close current batch.
1900
1921
  this.flush();
1922
+ if (this._orderSequentiallyCalls !== 0) {
1923
+ throw new container_utils_1.UsageError("can't get state during orderSequentially");
1924
+ }
1901
1925
  const previousPendingState = this.context.pendingLocalState;
1902
1926
  if (previousPendingState) {
1903
1927
  return {