@fluidframework/container-runtime 2.0.0-internal.1.4.2 → 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
@@ -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";
@@ -47,7 +48,6 @@ export var ContainerMessageType;
47
48
  })(ContainerMessageType || (ContainerMessageType = {}));
48
49
  export const DefaultSummaryConfiguration = {
49
50
  state: "enabled",
50
- idleTime: 15 * 1000,
51
51
  minIdleTime: 0,
52
52
  maxIdleTime: 30 * 1000,
53
53
  maxTime: 60 * 1000,
@@ -77,6 +77,11 @@ export var RuntimeHeaders;
77
77
  })(RuntimeHeaders || (RuntimeHeaders = {}));
78
78
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
79
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;
80
85
  /**
81
86
  * @deprecated - use ContainerRuntimeMessage instead
82
87
  */
@@ -94,21 +99,27 @@ export var RuntimeMessage;
94
99
  * @deprecated - please use version in driver-utils
95
100
  */
96
101
  export function isRuntimeMessage(message) {
97
- if (Object.values(RuntimeMessage).includes(message.type)) {
98
- return true;
99
- }
100
- return false;
102
+ return Object.values(RuntimeMessage).includes(message.type);
101
103
  }
102
104
  /**
103
105
  * Unpacks runtime messages
104
106
  *
105
- * @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.
106
108
  * @param message - message (as it observed in storage / service)
107
109
  * @returns unpacked runtime message
108
110
  *
109
111
  * @internal
110
112
  */
111
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
+ }
112
123
  if (message.type === MessageType.Operation) {
113
124
  // legacy op format?
114
125
  if (message.contents.address !== undefined && message.contents.type === undefined) {
@@ -157,7 +168,7 @@ export function getDeviceSpec() {
157
168
  */
158
169
  export class ContainerRuntime extends TypedEventEmitter {
159
170
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
160
- var _a, _b, _c, _d;
171
+ var _a, _b, _c, _d, _e, _f;
161
172
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
162
173
  super();
163
174
  this.context = context;
@@ -170,9 +181,10 @@ export class ContainerRuntime extends TypedEventEmitter {
170
181
  this.summaryConfiguration = summaryConfiguration;
171
182
  this.defaultMaxConsecutiveReconnects = 7;
172
183
  this._orderSequentiallyCalls = 0;
173
- this.flushTrigger = false;
184
+ this.flushMicroTaskExists = false;
174
185
  this.savedOps = [];
175
186
  this.consecutiveReconnects = 0;
187
+ this.compressedOpCount = 0;
176
188
  this._disposed = false;
177
189
  this.emitDirtyDocumentEvent = true;
178
190
  this.defaultTelemetrySignalSampleCount = 100;
@@ -182,12 +194,6 @@ export class ContainerRuntime extends TypedEventEmitter {
182
194
  signalTimestamp: 0,
183
195
  trackingSignalSequenceNumber: undefined,
184
196
  };
185
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
186
- // but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
187
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
188
- // payloads. That number represents final (compressed) bits (once compression is implemented).
189
- this.pendingAttachBatch = new BatchManager(64 * 1024);
190
- this.pendingBatch = new BatchManager();
191
197
  this.summarizeOnDemand = (...args) => {
192
198
  if (this.clientDetails.type === summarizerClientType) {
193
199
  return this.summarizer.summarizeOnDemand(...args);
@@ -232,8 +238,22 @@ export class ContainerRuntime extends TypedEventEmitter {
232
238
  this.maxConsecutiveReconnects =
233
239
  (_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
234
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);
235
248
  const pendingRuntimeState = context.pendingLocalState;
236
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
+ }
237
257
  this.garbageCollector = GarbageCollector.create({
238
258
  runtime: this,
239
259
  gcOptions: this.runtimeOptions.gcOptions,
@@ -266,9 +286,9 @@ export class ContainerRuntime extends TypedEventEmitter {
266
286
  gcDisabled: !this.garbageCollector.shouldRunGC,
267
287
  });
268
288
  if (baseSnapshot) {
269
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
289
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
270
290
  }
271
- 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));
272
292
  this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
273
293
  if (!this.disposed) {
274
294
  this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
@@ -282,10 +302,10 @@ export class ContainerRuntime extends TypedEventEmitter {
282
302
  close: this.closeFn,
283
303
  connected: () => this.connected,
284
304
  flush: this.flush.bind(this),
285
- flushMode: () => this.flushMode,
286
305
  reSubmit: this.reSubmit.bind(this),
287
- setFlushMode: (mode) => this.setFlushMode(mode),
288
- }, 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);
289
309
  this.context.quorum.on("removeMember", (clientId) => {
290
310
  this.clearPartialChunks(clientId);
291
311
  });
@@ -309,7 +329,7 @@ export class ContainerRuntime extends TypedEventEmitter {
309
329
  // if summaries are enabled and we are not the summarizer client.
310
330
  const defaultAction = () => {
311
331
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
312
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
332
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
313
333
  // unregister default to no log on every op after falling behind
314
334
  // and register summary ack handler to re-register this handler
315
335
  // after successful summary
@@ -365,7 +385,7 @@ export class ContainerRuntime extends TypedEventEmitter {
365
385
  };
366
386
  // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
367
387
  // the count is reset to 0.
368
- 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;
369
389
  }
370
390
  else {
371
391
  this.createContainerMetadata = {
@@ -400,7 +420,7 @@ export class ContainerRuntime extends TypedEventEmitter {
400
420
  runtimeVersion: pkgVersion,
401
421
  },
402
422
  });
403
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
423
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {}, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
404
424
  const pendingRuntimeState = context.pendingLocalState;
405
425
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
406
426
  const storage = !pendingRuntimeState ?
@@ -455,6 +475,8 @@ export class ContainerRuntime extends TypedEventEmitter {
455
475
  loadSequenceNumberVerification,
456
476
  flushMode,
457
477
  enableOfflineLoad,
478
+ compressionOptions,
479
+ maxBatchSizeInBytes,
458
480
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
459
481
  if (pendingRuntimeState) {
460
482
  await runtime.processSavedOps(pendingRuntimeState);
@@ -728,11 +750,9 @@ export class ContainerRuntime extends TypedEventEmitter {
728
750
  if (Object.keys(blobManagerSummary.summary.tree).length > 0) {
729
751
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
730
752
  }
731
- if (this.garbageCollector.writeDataAtRoot) {
732
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
733
- if (gcSummary !== undefined) {
734
- addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
735
- }
753
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
754
+ if (gcSummary !== undefined) {
755
+ addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
736
756
  }
737
757
  }
738
758
  // Track how many times the container tries to reconnect with pending messages.
@@ -1009,23 +1029,10 @@ export class ContainerRuntime extends TypedEventEmitter {
1009
1029
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
1010
1030
  return context.realize();
1011
1031
  }
1012
- setFlushMode(mode) {
1013
- if (mode === this._flushMode) {
1014
- return;
1015
- }
1016
- this.mc.logger.sendTelemetryEvent({
1017
- eventName: "FlushMode Updated",
1018
- old: this._flushMode,
1019
- new: mode,
1020
- });
1021
- // Flush any pending batches if switching to immediate
1022
- if (mode === FlushMode.Immediate) {
1023
- this.flush();
1024
- }
1025
- this._flushMode = mode;
1026
- // Let the PendingStateManager know that FlushMode has been updated.
1027
- this.pendingStateManager.onFlushModeUpdated(mode);
1028
- }
1032
+ /**
1033
+ * Flush the pending ops manually.
1034
+ * This method is expected to be called at the end of a batch.
1035
+ */
1029
1036
  flush() {
1030
1037
  assert(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1031
1038
  this.flushBatch(this.pendingAttachBatch.popBatch());
@@ -1054,7 +1061,33 @@ export class ContainerRuntime extends TypedEventEmitter {
1054
1061
  if (this.context.submitBatchFn !== undefined) {
1055
1062
  const batchToSend = [];
1056
1063
  for (const message of batch) {
1057
- 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 });
1058
1091
  }
1059
1092
  // returns clientSequenceNumber of last message in a batch
1060
1093
  clientSequenceNumber = this.context.submitBatchFn(batchToSend);
@@ -1081,26 +1114,6 @@ export class ContainerRuntime extends TypedEventEmitter {
1081
1114
  this.pendingStateManager.onFlush();
1082
1115
  }
1083
1116
  orderSequentially(callback) {
1084
- // If flush mode is already TurnBased we are either
1085
- // nested in another orderSequentially, or
1086
- // the app is flushing manually, in which
1087
- // case this invocation doesn't own
1088
- // flushing.
1089
- if (this.flushMode === FlushMode.TurnBased) {
1090
- this.trackOrderSequentiallyCalls(callback);
1091
- return;
1092
- }
1093
- const savedFlushMode = this.flushMode;
1094
- this.setFlushMode(FlushMode.TurnBased);
1095
- try {
1096
- this.trackOrderSequentiallyCalls(callback);
1097
- this.flush();
1098
- }
1099
- finally {
1100
- this.setFlushMode(savedFlushMode);
1101
- }
1102
- }
1103
- trackOrderSequentiallyCalls(callback) {
1104
1117
  let checkpoint;
1105
1118
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1106
1119
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
@@ -1135,6 +1148,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1135
1148
  finally {
1136
1149
  this._orderSequentiallyCalls--;
1137
1150
  }
1151
+ if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1152
+ this.flush();
1153
+ }
1138
1154
  }
1139
1155
  async createDataStore(pkg) {
1140
1156
  const internalId = uuid();
@@ -1161,6 +1177,12 @@ export class ContainerRuntime extends TypedEventEmitter {
1161
1177
  canSendOps() {
1162
1178
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1163
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
+ }
1164
1186
  getQuorum() {
1165
1187
  return this.context.quorum;
1166
1188
  }
@@ -1311,10 +1333,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1311
1333
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
1312
1334
  * After GC has run, called to notify this container's nodes of routes that are used in it.
1313
1335
  * @param usedRoutes - The routes that are used in all nodes in this Container.
1314
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
1315
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
1316
1336
  */
1317
- updateUsedRoutes(usedRoutes, gcTimestamp) {
1337
+ updateUsedRoutes(usedRoutes) {
1318
1338
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
1319
1339
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1320
1340
  // always referenced, so the used routes is only self-route (empty string).
@@ -1325,7 +1345,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1325
1345
  dataStoreUsedRoutes.push(route);
1326
1346
  }
1327
1347
  }
1328
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
1348
+ return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1329
1349
  }
1330
1350
  /**
1331
1351
  * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
@@ -1711,8 +1731,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1711
1731
  // issue than sending.
1712
1732
  // Please note that this does not change file format, so it can be disabled in the future if this
1713
1733
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
1714
- if (type === ContainerMessageType.Attach &&
1715
- this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
1734
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
1735
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.enableAttachOpReorder") === true) {
1716
1736
  if (!this.pendingAttachBatch.push(message)) {
1717
1737
  // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1718
1738
  // when queue is not empty.
@@ -1737,18 +1757,18 @@ export class ContainerRuntime extends TypedEventEmitter {
1737
1757
  limit: this.pendingBatch.limit,
1738
1758
  });
1739
1759
  }
1740
- }
1741
- if (this._flushMode !== FlushMode.TurnBased) {
1742
- this.flush();
1743
- }
1744
- else if (!this.flushTrigger) {
1745
- this.flushTrigger = true;
1746
- // Queue a microtask to detect the end of the turn and force a flush.
1747
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1748
- Promise.resolve().then(() => {
1749
- this.flushTrigger = false;
1760
+ if (!this.currentlyBatching()) {
1750
1761
  this.flush();
1751
- });
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
+ }
1752
1772
  }
1753
1773
  }
1754
1774
  catch (error) {
@@ -1819,7 +1839,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1819
1839
  }
1820
1840
  }
1821
1841
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1822
- async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1842
+ async refreshLatestSummaryAck(options) {
1843
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
1823
1844
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
1824
1845
  // The call to fetch the snapshot is very expensive and not always needed.
1825
1846
  // It should only be done by the summarizerNode, if required.
@@ -1892,6 +1913,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1892
1913
  // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
1893
1914
  // to close current batch.
1894
1915
  this.flush();
1916
+ if (this._orderSequentiallyCalls !== 0) {
1917
+ throw new UsageError("can't get state during orderSequentially");
1918
+ }
1895
1919
  const previousPendingState = this.context.pendingLocalState;
1896
1920
  if (previousPendingState) {
1897
1921
  return {