@fluidframework/container-runtime 2.0.0-internal.1.4.4 → 2.0.0-internal.2.0.1

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 (114) 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 -82
  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/summarizerHeuristics.d.ts.map +1 -1
  39. package/dist/summarizerHeuristics.js +0 -3
  40. package/dist/summarizerHeuristics.js.map +1 -1
  41. package/dist/summarizerTypes.d.ts +19 -2
  42. package/dist/summarizerTypes.d.ts.map +1 -1
  43. package/dist/summarizerTypes.js.map +1 -1
  44. package/dist/summaryFormat.d.ts +4 -2
  45. package/dist/summaryFormat.d.ts.map +1 -1
  46. package/dist/summaryFormat.js.map +1 -1
  47. package/dist/summaryManager.d.ts.map +1 -1
  48. package/dist/summaryManager.js +10 -6
  49. package/dist/summaryManager.js.map +1 -1
  50. package/lib/batchManager.d.ts +2 -3
  51. package/lib/batchManager.d.ts.map +1 -1
  52. package/lib/batchManager.js +3 -8
  53. package/lib/batchManager.js.map +1 -1
  54. package/lib/containerRuntime.d.ts +43 -16
  55. package/lib/containerRuntime.d.ts.map +1 -1
  56. package/lib/containerRuntime.js +108 -83
  57. package/lib/containerRuntime.js.map +1 -1
  58. package/lib/dataStoreContext.d.ts +4 -20
  59. package/lib/dataStoreContext.d.ts.map +1 -1
  60. package/lib/dataStoreContext.js +18 -48
  61. package/lib/dataStoreContext.js.map +1 -1
  62. package/lib/dataStores.d.ts +2 -5
  63. package/lib/dataStores.d.ts.map +1 -1
  64. package/lib/dataStores.js +3 -11
  65. package/lib/dataStores.js.map +1 -1
  66. package/lib/garbageCollection.d.ts +1 -10
  67. package/lib/garbageCollection.d.ts.map +1 -1
  68. package/lib/garbageCollection.js +42 -50
  69. package/lib/garbageCollection.js.map +1 -1
  70. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  71. package/lib/gcSweepReadyUsageDetection.js +3 -12
  72. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  73. package/lib/index.d.ts +3 -5
  74. package/lib/index.d.ts.map +1 -1
  75. package/lib/index.js +0 -2
  76. package/lib/index.js.map +1 -1
  77. package/lib/packageVersion.d.ts +1 -1
  78. package/lib/packageVersion.js +1 -1
  79. package/lib/packageVersion.js.map +1 -1
  80. package/lib/pendingStateManager.d.ts +6 -26
  81. package/lib/pendingStateManager.d.ts.map +1 -1
  82. package/lib/pendingStateManager.js +42 -62
  83. package/lib/pendingStateManager.js.map +1 -1
  84. package/lib/scheduleManager.js.map +1 -1
  85. package/lib/summarizer.js +7 -2
  86. package/lib/summarizer.js.map +1 -1
  87. package/lib/summarizerHeuristics.d.ts.map +1 -1
  88. package/lib/summarizerHeuristics.js +0 -3
  89. package/lib/summarizerHeuristics.js.map +1 -1
  90. package/lib/summarizerTypes.d.ts +19 -2
  91. package/lib/summarizerTypes.d.ts.map +1 -1
  92. package/lib/summarizerTypes.js.map +1 -1
  93. package/lib/summaryFormat.d.ts +4 -2
  94. package/lib/summaryFormat.d.ts.map +1 -1
  95. package/lib/summaryFormat.js.map +1 -1
  96. package/lib/summaryManager.d.ts.map +1 -1
  97. package/lib/summaryManager.js +10 -6
  98. package/lib/summaryManager.js.map +1 -1
  99. package/package.json +22 -65
  100. package/src/batchManager.ts +7 -11
  101. package/src/containerRuntime.ts +149 -100
  102. package/src/dataStoreContext.ts +20 -62
  103. package/src/dataStores.ts +2 -10
  104. package/src/garbageCollection.ts +45 -55
  105. package/src/gcSweepReadyUsageDetection.ts +2 -10
  106. package/src/index.ts +2 -3
  107. package/src/packageVersion.ts +1 -1
  108. package/src/pendingStateManager.ts +57 -96
  109. package/src/scheduleManager.ts +1 -0
  110. package/src/summarizer.ts +6 -6
  111. package/src/summarizerHeuristics.ts +0 -3
  112. package/src/summarizerTypes.ts +20 -7
  113. package/src/summaryFormat.ts +4 -2
  114. package/src/summaryManager.ts +18 -7
@@ -1,5 +1,5 @@
1
1
  import { AttachState, LoaderHeader, } from "@fluidframework/container-definitions";
2
- import { assert, Trace, TypedEventEmitter, unreachableCase, } from "@fluidframework/common-utils";
2
+ import { assert, Trace, TypedEventEmitter, unreachableCase, IsoBuffer, } from "@fluidframework/common-utils";
3
3
  import { ChildLogger, raiseConnectedEvent, PerformanceEvent, TaggedLoggerAdapter, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
4
4
  import { DriverHeader, FetchSource, } from "@fluidframework/driver-definitions";
5
5
  import { readAndParse } from "@fluidframework/driver-utils";
@@ -9,6 +9,7 @@ import { FlushMode, channelsTreeName, } from "@fluidframework/runtime-definition
9
9
  import { addBlobToSummary, addSummarizeResultToSummary, addTreeToSummary, createRootSummarizerNodeWithGC, RequestParser, create404Response, exceptionToResponse, requestFluidObject, responseToException, seqFromTree, calculateStats, TelemetryContext, } from "@fluidframework/runtime-utils";
10
10
  import { GCDataBuilder, trimLeadingAndTrailingSlashes } from "@fluidframework/garbage-collector";
11
11
  import { v4 as uuid } from "uuid";
12
+ import { compress, decompress } from "lz4js";
12
13
  import { ContainerFluidHandleContext } from "./containerHandleContext";
13
14
  import { FluidDataStoreRegistry } from "./dataStoreRegistry";
14
15
  import { Summarizer } from "./summarizer";
@@ -76,6 +77,11 @@ export var RuntimeHeaders;
76
77
  })(RuntimeHeaders || (RuntimeHeaders = {}));
77
78
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
78
79
  const defaultFlushMode = FlushMode.TurnBased;
80
+ // The actual limit is 1Mb (socket.io and Kafka limits)
81
+ // We can't estimate it fully, as we
82
+ // - do not know what properties relay service will add
83
+ // - we do not stringify final op, thus we do not know how much escaping will be added.
84
+ const defaultMaxBatchSizeInBytes = 950 * 1024;
79
85
  /**
80
86
  * @deprecated - use ContainerRuntimeMessage instead
81
87
  */
@@ -93,21 +99,27 @@ export var RuntimeMessage;
93
99
  * @deprecated - please use version in driver-utils
94
100
  */
95
101
  export function isRuntimeMessage(message) {
96
- if (Object.values(RuntimeMessage).includes(message.type)) {
97
- return true;
98
- }
99
- return false;
102
+ return Object.values(RuntimeMessage).includes(message.type);
100
103
  }
101
104
  /**
102
105
  * Unpacks runtime messages
103
106
  *
104
- * @remarks This API makes no promises regarding backward-compatability. This is internal API.
107
+ * @remarks This API makes no promises regarding backward-compatibility. This is internal API.
105
108
  * @param message - message (as it observed in storage / service)
106
109
  * @returns unpacked runtime message
107
110
  *
108
111
  * @internal
109
112
  */
110
113
  export function unpackRuntimeMessage(message) {
114
+ var _a;
115
+ if ((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.compressed) {
116
+ const contents = IsoBuffer.from(message.contents.contents, "base64");
117
+ const decompressedMessage = decompress(contents);
118
+ const intoString = new TextDecoder().decode(decompressedMessage);
119
+ const asObj = JSON.parse(intoString);
120
+ message.contents.contents = asObj;
121
+ message.metadata.compressed = false;
122
+ }
111
123
  if (message.type === MessageType.Operation) {
112
124
  // legacy op format?
113
125
  if (message.contents.address !== undefined && message.contents.type === undefined) {
@@ -156,7 +168,7 @@ export function getDeviceSpec() {
156
168
  */
157
169
  export class ContainerRuntime extends TypedEventEmitter {
158
170
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
159
- var _a, _b, _c, _d;
171
+ var _a, _b, _c, _d, _e, _f;
160
172
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
161
173
  super();
162
174
  this.context = context;
@@ -169,9 +181,10 @@ export class ContainerRuntime extends TypedEventEmitter {
169
181
  this.summaryConfiguration = summaryConfiguration;
170
182
  this.defaultMaxConsecutiveReconnects = 7;
171
183
  this._orderSequentiallyCalls = 0;
172
- this.flushTrigger = false;
184
+ this.flushMicroTaskExists = false;
173
185
  this.savedOps = [];
174
186
  this.consecutiveReconnects = 0;
187
+ this.compressedOpCount = 0;
175
188
  this._disposed = false;
176
189
  this.emitDirtyDocumentEvent = true;
177
190
  this.defaultTelemetrySignalSampleCount = 100;
@@ -181,12 +194,6 @@ export class ContainerRuntime extends TypedEventEmitter {
181
194
  signalTimestamp: 0,
182
195
  trackingSignalSequenceNumber: undefined,
183
196
  };
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
197
  this.summarizeOnDemand = (...args) => {
191
198
  if (this.clientDetails.type === summarizerClientType) {
192
199
  return this.summarizer.summarizeOnDemand(...args);
@@ -231,8 +238,22 @@ export class ContainerRuntime extends TypedEventEmitter {
231
238
  this.maxConsecutiveReconnects =
232
239
  (_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
233
240
  this._flushMode = runtimeOptions.flushMode;
241
+ // Provide lower soft limit - we want to have some number of ops to get efficiency in compression
242
+ // & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
243
+ // latency of processing a batch.
244
+ // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
245
+ // payloads. That number represents final (compressed) bits (once compression is implemented).
246
+ this.pendingAttachBatch = new BatchManager(runtimeOptions.maxBatchSizeInBytes, 64 * 1024);
247
+ this.pendingBatch = new BatchManager(runtimeOptions.maxBatchSizeInBytes);
234
248
  const pendingRuntimeState = context.pendingLocalState;
235
249
  const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
250
+ const maxSnapshotCacheDurationMs = (_e = (_d = this._storage) === null || _d === void 0 ? void 0 : _d.policies) === null || _e === void 0 ? void 0 : _e.maximumCacheDurationMs;
251
+ if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
252
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
253
+ // which dictates the value is either undefined or exactly 5 days in ms.
254
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
255
+ throw new UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
256
+ }
236
257
  this.garbageCollector = GarbageCollector.create({
237
258
  runtime: this,
238
259
  gcOptions: this.runtimeOptions.gcOptions,
@@ -265,9 +286,9 @@ export class ContainerRuntime extends TypedEventEmitter {
265
286
  gcDisabled: !this.garbageCollector.shouldRunGC,
266
287
  });
267
288
  if (baseSnapshot) {
268
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
289
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
269
290
  }
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);
291
+ 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
292
  this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
272
293
  if (!this.disposed) {
273
294
  this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
@@ -281,10 +302,10 @@ export class ContainerRuntime extends TypedEventEmitter {
281
302
  close: this.closeFn,
282
303
  connected: () => this.connected,
283
304
  flush: this.flush.bind(this),
284
- flushMode: () => this.flushMode,
285
305
  reSubmit: this.reSubmit.bind(this),
286
- setFlushMode: (mode) => this.setFlushMode(mode),
287
- }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
306
+ rollback: this.rollback.bind(this),
307
+ orderSequentially: this.orderSequentially.bind(this),
308
+ }, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
288
309
  this.context.quorum.on("removeMember", (clientId) => {
289
310
  this.clearPartialChunks(clientId);
290
311
  });
@@ -308,7 +329,7 @@ export class ContainerRuntime extends TypedEventEmitter {
308
329
  // if summaries are enabled and we are not the summarizer client.
309
330
  const defaultAction = () => {
310
331
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
311
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
332
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
312
333
  // unregister default to no log on every op after falling behind
313
334
  // and register summary ack handler to re-register this handler
314
335
  // after successful summary
@@ -364,7 +385,7 @@ export class ContainerRuntime extends TypedEventEmitter {
364
385
  };
365
386
  // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
366
387
  // the count is reset to 0.
367
- loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
388
+ loadSummaryNumber = (_f = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _f !== void 0 ? _f : 0;
368
389
  }
369
390
  else {
370
391
  this.createContainerMetadata = {
@@ -399,7 +420,7 @@ export class ContainerRuntime extends TypedEventEmitter {
399
420
  runtimeVersion: pkgVersion,
400
421
  },
401
422
  });
402
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
423
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {}, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
403
424
  const pendingRuntimeState = context.pendingLocalState;
404
425
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
405
426
  const storage = !pendingRuntimeState ?
@@ -454,6 +475,8 @@ export class ContainerRuntime extends TypedEventEmitter {
454
475
  loadSequenceNumberVerification,
455
476
  flushMode,
456
477
  enableOfflineLoad,
478
+ compressionOptions,
479
+ maxBatchSizeInBytes,
457
480
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
458
481
  if (pendingRuntimeState) {
459
482
  await runtime.processSavedOps(pendingRuntimeState);
@@ -727,11 +750,9 @@ export class ContainerRuntime extends TypedEventEmitter {
727
750
  if (Object.keys(blobManagerSummary.summary.tree).length > 0) {
728
751
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
729
752
  }
730
- if (this.garbageCollector.writeDataAtRoot) {
731
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
732
- if (gcSummary !== undefined) {
733
- addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
734
- }
753
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
754
+ if (gcSummary !== undefined) {
755
+ addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
735
756
  }
736
757
  }
737
758
  // Track how many times the container tries to reconnect with pending messages.
@@ -1008,23 +1029,10 @@ export class ContainerRuntime extends TypedEventEmitter {
1008
1029
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
1009
1030
  return context.realize();
1010
1031
  }
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
- }
1032
+ /**
1033
+ * Flush the pending ops manually.
1034
+ * This method is expected to be called at the end of a batch.
1035
+ */
1028
1036
  flush() {
1029
1037
  assert(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1030
1038
  this.flushBatch(this.pendingAttachBatch.popBatch());
@@ -1053,7 +1061,33 @@ export class ContainerRuntime extends TypedEventEmitter {
1053
1061
  if (this.context.submitBatchFn !== undefined) {
1054
1062
  const batchToSend = [];
1055
1063
  for (const message of batch) {
1056
- batchToSend.push({ contents: message.contents, metadata: message.metadata });
1064
+ let contents = message.contents;
1065
+ let metadata = message.metadata;
1066
+ if (this.runtimeOptions.compressionOptions.minimumSize &&
1067
+ this.runtimeOptions.compressionOptions.minimumSize < message.contents.length) {
1068
+ this.compressedOpCount++;
1069
+ const copiedMessage = Object.assign({}, message.deserializedContent);
1070
+ const compressionStart = Date.now();
1071
+ const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(copiedMessage.contents));
1072
+ const compressedContents = compress(contentsAsBuffer);
1073
+ const compressedContent = IsoBuffer.from(compressedContents).toString("base64");
1074
+ const duration = Date.now() - compressionStart;
1075
+ if (this.compressedOpCount % 100) {
1076
+ this.mc.logger.sendPerformanceEvent({
1077
+ eventName: "compressedOp",
1078
+ duration,
1079
+ sizeBeforeCompression: message.contents.length,
1080
+ sizeAfterCompression: compressedContent.length,
1081
+ });
1082
+ }
1083
+ copiedMessage.contents = compressedContent;
1084
+ const stringifiedContents = JSON.stringify(copiedMessage);
1085
+ if (stringifiedContents.length < message.contents.length) {
1086
+ contents = JSON.stringify(copiedMessage);
1087
+ metadata = Object.assign(Object.assign({}, message.metadata), { compressed: true });
1088
+ }
1089
+ }
1090
+ batchToSend.push({ contents, metadata });
1057
1091
  }
1058
1092
  // returns clientSequenceNumber of last message in a batch
1059
1093
  clientSequenceNumber = this.context.submitBatchFn(batchToSend);
@@ -1080,26 +1114,6 @@ export class ContainerRuntime extends TypedEventEmitter {
1080
1114
  this.pendingStateManager.onFlush();
1081
1115
  }
1082
1116
  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
1117
  let checkpoint;
1104
1118
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1105
1119
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
@@ -1134,6 +1148,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1134
1148
  finally {
1135
1149
  this._orderSequentiallyCalls--;
1136
1150
  }
1151
+ if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1152
+ this.flush();
1153
+ }
1137
1154
  }
1138
1155
  async createDataStore(pkg) {
1139
1156
  const internalId = uuid();
@@ -1160,6 +1177,12 @@ export class ContainerRuntime extends TypedEventEmitter {
1160
1177
  canSendOps() {
1161
1178
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1162
1179
  }
1180
+ /**
1181
+ * Are we in the middle of batching ops together?
1182
+ */
1183
+ currentlyBatching() {
1184
+ return this.flushMode === FlushMode.TurnBased || this._orderSequentiallyCalls !== 0;
1185
+ }
1163
1186
  getQuorum() {
1164
1187
  return this.context.quorum;
1165
1188
  }
@@ -1310,10 +1333,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1310
1333
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
1311
1334
  * After GC has run, called to notify this container's nodes of routes that are used in it.
1312
1335
  * @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
1336
  */
1316
- updateUsedRoutes(usedRoutes, gcTimestamp) {
1337
+ updateUsedRoutes(usedRoutes) {
1317
1338
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
1318
1339
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1319
1340
  // always referenced, so the used routes is only self-route (empty string).
@@ -1324,7 +1345,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1324
1345
  dataStoreUsedRoutes.push(route);
1325
1346
  }
1326
1347
  }
1327
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
1348
+ return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1328
1349
  }
1329
1350
  /**
1330
1351
  * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
@@ -1710,8 +1731,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1710
1731
  // issue than sending.
1711
1732
  // Please note that this does not change file format, so it can be disabled in the future if this
1712
1733
  // 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) {
1734
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
1735
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
1715
1736
  if (!this.pendingAttachBatch.push(message)) {
1716
1737
  // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1717
1738
  // when queue is not empty.
@@ -1736,18 +1757,18 @@ export class ContainerRuntime extends TypedEventEmitter {
1736
1757
  limit: this.pendingBatch.limit,
1737
1758
  });
1738
1759
  }
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;
1760
+ if (!this.currentlyBatching()) {
1749
1761
  this.flush();
1750
- });
1762
+ }
1763
+ else if (!this.flushMicroTaskExists) {
1764
+ this.flushMicroTaskExists = true;
1765
+ // Queue a microtask to detect the end of the turn and force a flush.
1766
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1767
+ Promise.resolve().then(() => {
1768
+ this.flushMicroTaskExists = false;
1769
+ this.flush();
1770
+ });
1771
+ }
1751
1772
  }
1752
1773
  }
1753
1774
  catch (error) {
@@ -1818,7 +1839,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1818
1839
  }
1819
1840
  }
1820
1841
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1821
- async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1842
+ async refreshLatestSummaryAck(options) {
1843
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
1822
1844
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
1823
1845
  // The call to fetch the snapshot is very expensive and not always needed.
1824
1846
  // It should only be done by the summarizerNode, if required.
@@ -1891,6 +1913,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1891
1913
  // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
1892
1914
  // to close current batch.
1893
1915
  this.flush();
1916
+ if (this._orderSequentiallyCalls !== 0) {
1917
+ throw new UsageError("can't get state during orderSequentially");
1918
+ }
1894
1919
  const previousPendingState = this.context.pendingLocalState;
1895
1920
  if (previousPendingState) {
1896
1921
  return {