@fluidframework/container-runtime 1.2.7 → 2.0.0-dev.1.3.0.96595

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 (221) hide show
  1. package/.mocharc.js +12 -0
  2. package/dist/batchManager.d.ts +37 -0
  3. package/dist/batchManager.d.ts.map +1 -0
  4. package/dist/batchManager.js +73 -0
  5. package/dist/batchManager.js.map +1 -0
  6. package/dist/batchTracker.d.ts +1 -2
  7. package/dist/batchTracker.d.ts.map +1 -1
  8. package/dist/batchTracker.js +2 -3
  9. package/dist/batchTracker.js.map +1 -1
  10. package/dist/blobManager.d.ts +87 -25
  11. package/dist/blobManager.d.ts.map +1 -1
  12. package/dist/blobManager.js +317 -99
  13. package/dist/blobManager.js.map +1 -1
  14. package/dist/containerRuntime.d.ts +109 -124
  15. package/dist/containerRuntime.d.ts.map +1 -1
  16. package/dist/containerRuntime.js +349 -542
  17. package/dist/containerRuntime.js.map +1 -1
  18. package/dist/dataStore.js +29 -24
  19. package/dist/dataStore.js.map +1 -1
  20. package/dist/dataStoreContext.d.ts +20 -14
  21. package/dist/dataStoreContext.d.ts.map +1 -1
  22. package/dist/dataStoreContext.js +49 -58
  23. package/dist/dataStoreContext.js.map +1 -1
  24. package/dist/dataStores.d.ts +12 -5
  25. package/dist/dataStores.d.ts.map +1 -1
  26. package/dist/dataStores.js +21 -20
  27. package/dist/dataStores.js.map +1 -1
  28. package/dist/deltaScheduler.d.ts +6 -4
  29. package/dist/deltaScheduler.d.ts.map +1 -1
  30. package/dist/deltaScheduler.js +6 -4
  31. package/dist/deltaScheduler.js.map +1 -1
  32. package/dist/garbageCollection.d.ts +74 -14
  33. package/dist/garbageCollection.d.ts.map +1 -1
  34. package/dist/garbageCollection.js +249 -170
  35. package/dist/garbageCollection.js.map +1 -1
  36. package/dist/gcSweepReadyUsageDetection.d.ts +53 -0
  37. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -0
  38. package/dist/gcSweepReadyUsageDetection.js +126 -0
  39. package/dist/gcSweepReadyUsageDetection.js.map +1 -0
  40. package/dist/index.d.ts +2 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +3 -2
  43. package/dist/index.js.map +1 -1
  44. package/dist/opProperties.d.ts +7 -0
  45. package/dist/opProperties.d.ts.map +1 -0
  46. package/dist/opProperties.js +20 -0
  47. package/dist/opProperties.js.map +1 -0
  48. package/dist/orderedClientElection.d.ts +28 -10
  49. package/dist/orderedClientElection.d.ts.map +1 -1
  50. package/dist/orderedClientElection.js +14 -4
  51. package/dist/orderedClientElection.js.map +1 -1
  52. package/dist/packageVersion.d.ts +1 -1
  53. package/dist/packageVersion.d.ts.map +1 -1
  54. package/dist/packageVersion.js +1 -1
  55. package/dist/packageVersion.js.map +1 -1
  56. package/dist/pendingStateManager.d.ts +0 -11
  57. package/dist/pendingStateManager.d.ts.map +1 -1
  58. package/dist/pendingStateManager.js +24 -46
  59. package/dist/pendingStateManager.js.map +1 -1
  60. package/dist/runningSummarizer.d.ts +14 -4
  61. package/dist/runningSummarizer.d.ts.map +1 -1
  62. package/dist/runningSummarizer.js +68 -26
  63. package/dist/runningSummarizer.js.map +1 -1
  64. package/dist/scheduleManager.d.ts +31 -0
  65. package/dist/scheduleManager.d.ts.map +1 -0
  66. package/dist/scheduleManager.js +243 -0
  67. package/dist/scheduleManager.js.map +1 -0
  68. package/dist/summarizer.d.ts +0 -2
  69. package/dist/summarizer.d.ts.map +1 -1
  70. package/dist/summarizer.js +1 -12
  71. package/dist/summarizer.js.map +1 -1
  72. package/dist/summarizerHeuristics.d.ts +26 -4
  73. package/dist/summarizerHeuristics.d.ts.map +1 -1
  74. package/dist/summarizerHeuristics.js +95 -18
  75. package/dist/summarizerHeuristics.js.map +1 -1
  76. package/dist/summarizerTypes.d.ts +45 -18
  77. package/dist/summarizerTypes.d.ts.map +1 -1
  78. package/dist/summarizerTypes.js +1 -1
  79. package/dist/summarizerTypes.js.map +1 -1
  80. package/dist/summaryCollection.d.ts +1 -0
  81. package/dist/summaryCollection.d.ts.map +1 -1
  82. package/dist/summaryCollection.js +31 -15
  83. package/dist/summaryCollection.js.map +1 -1
  84. package/dist/summaryFormat.d.ts +0 -5
  85. package/dist/summaryFormat.d.ts.map +1 -1
  86. package/dist/summaryFormat.js.map +1 -1
  87. package/dist/summaryGenerator.d.ts +1 -0
  88. package/dist/summaryGenerator.d.ts.map +1 -1
  89. package/dist/summaryGenerator.js +11 -9
  90. package/dist/summaryGenerator.js.map +1 -1
  91. package/dist/summaryManager.d.ts +2 -2
  92. package/dist/summaryManager.d.ts.map +1 -1
  93. package/dist/summaryManager.js +22 -7
  94. package/dist/summaryManager.js.map +1 -1
  95. package/lib/batchManager.d.ts +37 -0
  96. package/lib/batchManager.d.ts.map +1 -0
  97. package/lib/batchManager.js +69 -0
  98. package/lib/batchManager.js.map +1 -0
  99. package/lib/batchTracker.d.ts +1 -2
  100. package/lib/batchTracker.d.ts.map +1 -1
  101. package/lib/batchTracker.js +2 -3
  102. package/lib/batchTracker.js.map +1 -1
  103. package/lib/blobManager.d.ts +87 -25
  104. package/lib/blobManager.d.ts.map +1 -1
  105. package/lib/blobManager.js +319 -101
  106. package/lib/blobManager.js.map +1 -1
  107. package/lib/containerRuntime.d.ts +109 -124
  108. package/lib/containerRuntime.d.ts.map +1 -1
  109. package/lib/containerRuntime.js +355 -547
  110. package/lib/containerRuntime.js.map +1 -1
  111. package/lib/dataStore.js +29 -24
  112. package/lib/dataStore.js.map +1 -1
  113. package/lib/dataStoreContext.d.ts +20 -14
  114. package/lib/dataStoreContext.d.ts.map +1 -1
  115. package/lib/dataStoreContext.js +46 -55
  116. package/lib/dataStoreContext.js.map +1 -1
  117. package/lib/dataStores.d.ts +12 -5
  118. package/lib/dataStores.d.ts.map +1 -1
  119. package/lib/dataStores.js +21 -20
  120. package/lib/dataStores.js.map +1 -1
  121. package/lib/deltaScheduler.d.ts +6 -4
  122. package/lib/deltaScheduler.d.ts.map +1 -1
  123. package/lib/deltaScheduler.js +6 -4
  124. package/lib/deltaScheduler.js.map +1 -1
  125. package/lib/garbageCollection.d.ts +74 -14
  126. package/lib/garbageCollection.d.ts.map +1 -1
  127. package/lib/garbageCollection.js +238 -160
  128. package/lib/garbageCollection.js.map +1 -1
  129. package/lib/gcSweepReadyUsageDetection.d.ts +53 -0
  130. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -0
  131. package/lib/gcSweepReadyUsageDetection.js +121 -0
  132. package/lib/gcSweepReadyUsageDetection.js.map +1 -0
  133. package/lib/index.d.ts +2 -1
  134. package/lib/index.d.ts.map +1 -1
  135. package/lib/index.js +2 -1
  136. package/lib/index.js.map +1 -1
  137. package/lib/opProperties.d.ts +7 -0
  138. package/lib/opProperties.d.ts.map +1 -0
  139. package/lib/opProperties.js +16 -0
  140. package/lib/opProperties.js.map +1 -0
  141. package/lib/orderedClientElection.d.ts +28 -10
  142. package/lib/orderedClientElection.d.ts.map +1 -1
  143. package/lib/orderedClientElection.js +14 -4
  144. package/lib/orderedClientElection.js.map +1 -1
  145. package/lib/packageVersion.d.ts +1 -1
  146. package/lib/packageVersion.d.ts.map +1 -1
  147. package/lib/packageVersion.js +1 -1
  148. package/lib/packageVersion.js.map +1 -1
  149. package/lib/pendingStateManager.d.ts +0 -11
  150. package/lib/pendingStateManager.d.ts.map +1 -1
  151. package/lib/pendingStateManager.js +24 -46
  152. package/lib/pendingStateManager.js.map +1 -1
  153. package/lib/runningSummarizer.d.ts +14 -4
  154. package/lib/runningSummarizer.d.ts.map +1 -1
  155. package/lib/runningSummarizer.js +68 -26
  156. package/lib/runningSummarizer.js.map +1 -1
  157. package/lib/scheduleManager.d.ts +31 -0
  158. package/lib/scheduleManager.d.ts.map +1 -0
  159. package/lib/scheduleManager.js +239 -0
  160. package/lib/scheduleManager.js.map +1 -0
  161. package/lib/summarizer.d.ts +0 -2
  162. package/lib/summarizer.d.ts.map +1 -1
  163. package/lib/summarizer.js +1 -12
  164. package/lib/summarizer.js.map +1 -1
  165. package/lib/summarizerHeuristics.d.ts +26 -4
  166. package/lib/summarizerHeuristics.d.ts.map +1 -1
  167. package/lib/summarizerHeuristics.js +95 -18
  168. package/lib/summarizerHeuristics.js.map +1 -1
  169. package/lib/summarizerTypes.d.ts +45 -18
  170. package/lib/summarizerTypes.d.ts.map +1 -1
  171. package/lib/summarizerTypes.js +1 -1
  172. package/lib/summarizerTypes.js.map +1 -1
  173. package/lib/summaryCollection.d.ts +1 -0
  174. package/lib/summaryCollection.d.ts.map +1 -1
  175. package/lib/summaryCollection.js +31 -15
  176. package/lib/summaryCollection.js.map +1 -1
  177. package/lib/summaryFormat.d.ts +0 -5
  178. package/lib/summaryFormat.d.ts.map +1 -1
  179. package/lib/summaryFormat.js.map +1 -1
  180. package/lib/summaryGenerator.d.ts +1 -0
  181. package/lib/summaryGenerator.d.ts.map +1 -1
  182. package/lib/summaryGenerator.js +11 -9
  183. package/lib/summaryGenerator.js.map +1 -1
  184. package/lib/summaryManager.d.ts +2 -2
  185. package/lib/summaryManager.d.ts.map +1 -1
  186. package/lib/summaryManager.js +22 -7
  187. package/lib/summaryManager.js.map +1 -1
  188. package/package.json +65 -24
  189. package/src/batchManager.ts +91 -0
  190. package/src/batchTracker.ts +2 -3
  191. package/src/blobManager.ts +385 -118
  192. package/src/containerRuntime.ts +529 -740
  193. package/src/dataStore.ts +49 -37
  194. package/src/dataStoreContext.ts +44 -56
  195. package/src/dataStores.ts +34 -30
  196. package/src/deltaScheduler.ts +6 -4
  197. package/src/garbageCollection.ts +297 -206
  198. package/src/gcSweepReadyUsageDetection.ts +139 -0
  199. package/src/index.ts +1 -2
  200. package/src/opProperties.ts +19 -0
  201. package/src/orderedClientElection.ts +31 -10
  202. package/src/packageVersion.ts +1 -1
  203. package/src/pendingStateManager.ts +27 -59
  204. package/src/runningSummarizer.ts +75 -22
  205. package/src/scheduleManager.ts +314 -0
  206. package/src/summarizer.ts +1 -18
  207. package/src/summarizerHeuristics.ts +133 -19
  208. package/src/summarizerTypes.ts +53 -18
  209. package/src/summaryCollection.ts +33 -18
  210. package/src/summaryFormat.ts +0 -6
  211. package/src/summaryGenerator.ts +40 -22
  212. package/src/summaryManager.ts +22 -7
  213. package/dist/opTelemetry.d.ts +0 -22
  214. package/dist/opTelemetry.d.ts.map +0 -1
  215. package/dist/opTelemetry.js +0 -59
  216. package/dist/opTelemetry.js.map +0 -1
  217. package/lib/opTelemetry.d.ts +0 -22
  218. package/lib/opTelemetry.d.ts.map +0 -1
  219. package/lib/opTelemetry.js +0 -55
  220. package/lib/opTelemetry.js.map +0 -1
  221. package/src/opTelemetry.ts +0 -71
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.ScheduleManager = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.DefaultSummaryConfiguration = exports.ContainerMessageType = void 0;
3
+ exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.DefaultSummaryConfiguration = exports.ContainerMessageType = void 0;
4
4
  const container_definitions_1 = require("@fluidframework/container-definitions");
5
5
  const common_utils_1 = require("@fluidframework/common-utils");
6
6
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
@@ -16,9 +16,9 @@ const containerHandleContext_1 = require("./containerHandleContext");
16
16
  const dataStoreRegistry_1 = require("./dataStoreRegistry");
17
17
  const summarizer_1 = require("./summarizer");
18
18
  const summaryManager_1 = require("./summaryManager");
19
- const deltaScheduler_1 = require("./deltaScheduler");
20
19
  const connectionTelemetry_1 = require("./connectionTelemetry");
21
20
  const pendingStateManager_1 = require("./pendingStateManager");
21
+ const batchManager_1 = require("./batchManager");
22
22
  const packageVersion_1 = require("./packageVersion");
23
23
  const blobManager_1 = require("./blobManager");
24
24
  const dataStores_1 = require("./dataStores");
@@ -32,7 +32,7 @@ const garbageCollection_1 = require("./garbageCollection");
32
32
  const dataStore_1 = require("./dataStore");
33
33
  const batchTracker_1 = require("./batchTracker");
34
34
  const serializedSnapshotStorage_1 = require("./serializedSnapshotStorage");
35
- const opTelemetry_1 = require("./opTelemetry");
35
+ const scheduleManager_1 = require("./scheduleManager");
36
36
  var ContainerMessageType;
37
37
  (function (ContainerMessageType) {
38
38
  // An op to be delivered to store
@@ -50,14 +50,18 @@ var ContainerMessageType;
50
50
  })(ContainerMessageType = exports.ContainerMessageType || (exports.ContainerMessageType = {}));
51
51
  exports.DefaultSummaryConfiguration = {
52
52
  state: "enabled",
53
- idleTime: 5000 * 3,
54
- maxTime: 5000 * 12,
53
+ idleTime: 15 * 1000,
54
+ minIdleTime: 0,
55
+ maxIdleTime: 30 * 1000,
56
+ maxTime: 60 * 1000,
55
57
  maxOps: 100,
56
58
  minOpsForLastSummaryAttempt: 10,
57
- maxAckWaitTime: 6 * 10 * 1000,
59
+ maxAckWaitTime: 10 * 60 * 1000,
58
60
  maxOpsSinceLastSummary: 7000,
59
- initialSummarizerDelayMs: 5000,
61
+ initialSummarizerDelayMs: 5 * 1000,
60
62
  summarizerClientElection: false,
63
+ nonRuntimeOpWeight: 0.1,
64
+ runtimeOpWeight: 1.0,
61
65
  };
62
66
  /**
63
67
  * Accepted header keys for requests coming to the runtime.
@@ -74,21 +78,11 @@ var RuntimeHeaders;
74
78
  /** True if the request is coming from an IFluidHandle. */
75
79
  RuntimeHeaders["viaHandle"] = "viaHandle";
76
80
  })(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
77
- const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
78
81
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
79
- // Feature gate for the max op size. If the value is negative, chunking is enabled
80
- // and all ops over 16k would be chunked. If the value is positive, all ops with
81
- // a size strictly larger will be rejected and the container closed with an error.
82
- const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
83
- // By default, we should reject any op larger than 768KB,
84
- // in order to account for some extra overhead from serialization
85
- // to not reach the 1MB limits in socket.io and Kafka.
86
- const defaultMaxOpSizeInBytes = 768000;
87
- // By default, the size of the contents for the incoming ops is tracked.
88
- // However, in certain situations, this may incur a performance hit.
89
- // The feature-gate below can be used to disable this feature.
90
- const disableOpTrackingKey = "Fluid.ContainerRuntime.DisableOpTracking";
91
82
  const defaultFlushMode = runtime_definitions_1.FlushMode.TurnBased;
83
+ /**
84
+ * @deprecated - use ContainerRuntimeMessage instead
85
+ */
92
86
  var RuntimeMessage;
93
87
  (function (RuntimeMessage) {
94
88
  RuntimeMessage["FluidDataStoreOp"] = "component";
@@ -99,6 +93,9 @@ var RuntimeMessage;
99
93
  RuntimeMessage["Alias"] = "alias";
100
94
  RuntimeMessage["Operation"] = "op";
101
95
  })(RuntimeMessage = exports.RuntimeMessage || (exports.RuntimeMessage = {}));
96
+ /**
97
+ * @deprecated - please use version in driver-utils
98
+ */
102
99
  function isRuntimeMessage(message) {
103
100
  if (Object.values(RuntimeMessage).includes(message.type)) {
104
101
  return true;
@@ -106,6 +103,15 @@ function isRuntimeMessage(message) {
106
103
  return false;
107
104
  }
108
105
  exports.isRuntimeMessage = isRuntimeMessage;
106
+ /**
107
+ * Unpacks runtime messages
108
+ *
109
+ * @remarks This API makes no promises regarding backward-compatability. This is internal API.
110
+ * @param message - message (as it observed in storage / service)
111
+ * @returns unpacked runtime message
112
+ *
113
+ * @internal
114
+ */
109
115
  function unpackRuntimeMessage(message) {
110
116
  if (message.type === protocol_definitions_1.MessageType.Operation) {
111
117
  // legacy op format?
@@ -119,240 +125,17 @@ function unpackRuntimeMessage(message) {
119
125
  message.type = innerContents.type;
120
126
  message.contents = innerContents.contents;
121
127
  }
122
- (0, common_utils_1.assert)((0, driver_utils_1.isUnpackedRuntimeMessage)(message), 0x122 /* "Message to unpack is not proper runtime message" */);
128
+ return true;
123
129
  }
124
130
  else {
125
131
  // Legacy format, but it's already "unpacked",
126
132
  // i.e. message.type is actually ContainerMessageType.
133
+ // Or it's non-runtime message.
127
134
  // Nothing to do in such case.
135
+ return false;
128
136
  }
129
- return message;
130
137
  }
131
138
  exports.unpackRuntimeMessage = unpackRuntimeMessage;
132
- /**
133
- * This class controls pausing and resuming of inbound queue to ensure that we never
134
- * start processing ops in a batch IF we do not have all ops in the batch.
135
- */
136
- class ScheduleManagerCore {
137
- constructor(deltaManager, logger) {
138
- this.deltaManager = deltaManager;
139
- this.logger = logger;
140
- this.localPaused = false;
141
- this.timePaused = 0;
142
- this.batchCount = 0;
143
- // Listen for delta manager sends and add batch metadata to messages
144
- this.deltaManager.on("prepareSend", (messages) => {
145
- if (messages.length === 0) {
146
- return;
147
- }
148
- // First message will have the batch flag set to true if doing a batched send
149
- const firstMessageMetadata = messages[0].metadata;
150
- if (!(firstMessageMetadata === null || firstMessageMetadata === void 0 ? void 0 : firstMessageMetadata.batch)) {
151
- return;
152
- }
153
- // If the batch contains only a single op, clear the batch flag.
154
- if (messages.length === 1) {
155
- delete firstMessageMetadata.batch;
156
- return;
157
- }
158
- // Set the batch flag to false on the last message to indicate the end of the send batch
159
- const lastMessage = messages[messages.length - 1];
160
- lastMessage.metadata = Object.assign(Object.assign({}, lastMessage.metadata), { batch: false });
161
- });
162
- // Listen for updates and peek at the inbound
163
- this.deltaManager.inbound.on("push", (message) => {
164
- this.trackPending(message);
165
- });
166
- // Start with baseline - empty inbound queue.
167
- (0, common_utils_1.assert)(!this.localPaused, 0x293 /* "initial state" */);
168
- const allPending = this.deltaManager.inbound.toArray();
169
- for (const pending of allPending) {
170
- this.trackPending(pending);
171
- }
172
- // We are intentionally directly listening to the "op" to inspect system ops as well.
173
- // If we do not observe system ops, we are likely to hit 0x296 assert when system ops
174
- // precedes start of incomplete batch.
175
- this.deltaManager.on("op", (message) => this.afterOpProcessing(message.sequenceNumber));
176
- }
177
- /**
178
- * The only public function in this class - called when we processed an op,
179
- * to make decision if op processing should be paused or not afer that.
180
- */
181
- afterOpProcessing(sequenceNumber) {
182
- (0, common_utils_1.assert)(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
183
- // If the inbound queue is ever empty, nothing to do!
184
- if (this.deltaManager.inbound.length === 0) {
185
- (0, common_utils_1.assert)(this.pauseSequenceNumber === undefined, 0x295 /* "there should be no pending batch if we have no ops" */);
186
- return;
187
- }
188
- // The queue is
189
- // 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
190
- // - here (processing ops until reaching start of incomplete batch)
191
- // - in trackPending(), when queue was empty and start of batch showed up.
192
- // 2. resumed when batch end comes in (in trackPending())
193
- // do we have incomplete batch to worry about?
194
- if (this.pauseSequenceNumber !== undefined) {
195
- (0, common_utils_1.assert)(sequenceNumber < this.pauseSequenceNumber, 0x296 /* "we should never start processing incomplete batch!" */);
196
- // If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
197
- if (sequenceNumber + 1 === this.pauseSequenceNumber) {
198
- this.pauseQueue();
199
- }
200
- }
201
- }
202
- pauseQueue() {
203
- (0, common_utils_1.assert)(!this.localPaused, 0x297 /* "always called from resumed state" */);
204
- this.localPaused = true;
205
- this.timePaused = common_utils_1.performance.now();
206
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
207
- this.deltaManager.inbound.pause();
208
- }
209
- resumeQueue(startBatch, messageEndBatch) {
210
- const endBatch = messageEndBatch.sequenceNumber;
211
- const duration = this.localPaused ? (common_utils_1.performance.now() - this.timePaused) : undefined;
212
- this.batchCount++;
213
- if (this.batchCount % 1000 === 1) {
214
- this.logger.sendTelemetryEvent({
215
- eventName: "BatchStats",
216
- sequenceNumber: endBatch,
217
- length: endBatch - startBatch + 1,
218
- msnDistance: endBatch - messageEndBatch.minimumSequenceNumber,
219
- duration,
220
- batchCount: this.batchCount,
221
- interrupted: this.localPaused,
222
- });
223
- }
224
- // Return early if no change in value
225
- if (!this.localPaused) {
226
- return;
227
- }
228
- this.localPaused = false;
229
- // Random round number - we want to know when batch waiting paused op processing.
230
- if (duration !== undefined && duration > connectionTelemetry_1.latencyThreshold) {
231
- this.logger.sendErrorEvent({
232
- eventName: "MaxBatchWaitTimeExceeded",
233
- duration,
234
- sequenceNumber: endBatch,
235
- length: endBatch - startBatch,
236
- });
237
- }
238
- this.deltaManager.inbound.resume();
239
- }
240
- /**
241
- * Called for each incoming op (i.e. inbound "push" notification)
242
- */
243
- trackPending(message) {
244
- (0, common_utils_1.assert)(this.deltaManager.inbound.length !== 0, 0x298 /* "we have something in the queue that generates this event" */);
245
- (0, common_utils_1.assert)((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined), 0x299 /* "non-synchronized state" */);
246
- const metadata = message.metadata;
247
- const batchMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.batch;
248
- // Protocol messages are never part of a runtime batch of messages
249
- if (!(0, driver_utils_1.isUnpackedRuntimeMessage)(message)) {
250
- // Protocol messages should never show up in the middle of the batch!
251
- (0, common_utils_1.assert)(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
252
- (0, common_utils_1.assert)(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
253
- (0, common_utils_1.assert)(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
254
- return;
255
- }
256
- if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
257
- (0, common_utils_1.assert)(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
258
- return;
259
- }
260
- // If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
261
- // If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
262
- // the previous one
263
- if (this.currentBatchClientId !== undefined || batchMetadata === false) {
264
- if (this.currentBatchClientId !== message.clientId) {
265
- // "Batch not closed, yet message from another client!"
266
- throw new container_utils_1.DataCorruptionError("OpBatchIncomplete", Object.assign({ runtimeVersion: packageVersion_1.pkgVersion, batchClientId: this.currentBatchClientId }, (0, container_utils_1.extractSafePropertiesFromMessage)(message)));
267
- }
268
- }
269
- // The queue is
270
- // 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
271
- // - in afterOpProcessing() - processing ops until reaching start of incomplete batch
272
- // - here (batchMetadata == false below), when queue was empty and start of batch showed up.
273
- // 2. resumed when batch end comes in (batchMetadata === true case below)
274
- if (batchMetadata) {
275
- (0, common_utils_1.assert)(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
276
- (0, common_utils_1.assert)(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
277
- this.pauseSequenceNumber = message.sequenceNumber;
278
- this.currentBatchClientId = message.clientId;
279
- // Start of the batch
280
- // Only pause processing if queue has no other ops!
281
- // If there are any other ops in the queue, processing will be stopped when they are processed!
282
- if (this.deltaManager.inbound.length === 1) {
283
- this.pauseQueue();
284
- }
285
- }
286
- else if (batchMetadata === false) {
287
- (0, common_utils_1.assert)(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
288
- // Batch is complete, we can process it!
289
- this.resumeQueue(this.pauseSequenceNumber, message);
290
- this.pauseSequenceNumber = undefined;
291
- this.currentBatchClientId = undefined;
292
- }
293
- else {
294
- // Continuation of current batch. Do nothing
295
- (0, common_utils_1.assert)(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
296
- }
297
- }
298
- }
299
- /**
300
- * This class has the following responsibilities:
301
- * 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
302
- * As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
303
- * 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
304
- * unless all ops of the batch are in.
305
- */
306
- class ScheduleManager {
307
- constructor(deltaManager, emitter, logger) {
308
- this.deltaManager = deltaManager;
309
- this.emitter = emitter;
310
- this.logger = logger;
311
- this.hitError = false;
312
- this.deltaScheduler = new deltaScheduler_1.DeltaScheduler(this.deltaManager, telemetry_utils_1.ChildLogger.create(this.logger, "DeltaScheduler"));
313
- void new ScheduleManagerCore(deltaManager, logger);
314
- }
315
- beforeOpProcessing(message) {
316
- var _a;
317
- if (this.batchClientId !== message.clientId) {
318
- (0, common_utils_1.assert)(this.batchClientId === undefined, 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
319
- // This could be the beginning of a new batch or an individual message.
320
- this.emitter.emit("batchBegin", message);
321
- this.deltaScheduler.batchBegin(message);
322
- const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
323
- if (batch) {
324
- this.batchClientId = message.clientId;
325
- }
326
- else {
327
- this.batchClientId = undefined;
328
- }
329
- }
330
- }
331
- afterOpProcessing(error, message) {
332
- var _a;
333
- // If this is no longer true, we need to revisit what we do where we set this.hitError.
334
- (0, common_utils_1.assert)(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
335
- if (error) {
336
- // We assume here that loader will close container and stop processing all future ops.
337
- // This is implicit dependency. If this flow changes, this code might no longer be correct.
338
- this.hitError = true;
339
- this.batchClientId = undefined;
340
- this.emitter.emit("batchEnd", error, message);
341
- this.deltaScheduler.batchEnd(message);
342
- return;
343
- }
344
- const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
345
- // If no batchClientId has been set then we're in an individual batch. Else, if we get
346
- // batch end metadata, this is end of the current batch.
347
- if (this.batchClientId === undefined || batch === false) {
348
- this.batchClientId = undefined;
349
- this.emitter.emit("batchEnd", undefined, message);
350
- this.deltaScheduler.batchEnd(message);
351
- return;
352
- }
353
- }
354
- }
355
- exports.ScheduleManager = ScheduleManager;
356
139
  /**
357
140
  * Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
358
141
  * special-case for document dirty state. Ultimately we should have no special-cases from the
@@ -380,7 +163,7 @@ exports.getDeviceSpec = getDeviceSpec;
380
163
  */
381
164
  class ContainerRuntime extends common_utils_1.TypedEventEmitter {
382
165
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
383
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
166
+ var _a, _b, _c, _d;
384
167
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, exports.DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
385
168
  super();
386
169
  this.context = context;
@@ -391,9 +174,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
391
174
  this._storage = _storage;
392
175
  this.requestHandler = requestHandler;
393
176
  this.summaryConfiguration = summaryConfiguration;
394
- this.defaultMaxConsecutiveReconnects = 15;
177
+ this.defaultMaxConsecutiveReconnects = 7;
395
178
  this._orderSequentiallyCalls = 0;
396
- this.needsFlush = false;
397
179
  this.flushTrigger = false;
398
180
  this.savedOps = [];
399
181
  this.consecutiveReconnects = 0;
@@ -406,6 +188,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
406
188
  signalTimestamp: 0,
407
189
  trackingSignalSequenceNumber: undefined,
408
190
  };
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();
409
197
  this.summarizeOnDemand = (...args) => {
410
198
  if (this.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
411
199
  return this.summarizer.summarizeOnDemand(...args);
@@ -435,26 +223,23 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
435
223
  }
436
224
  };
437
225
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
438
- // Default to false (enabled).
439
- this.disableIsolatedChannels = (_b = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _b !== void 0 ? _b : false;
440
226
  this._connected = this.context.connected;
441
227
  this.chunkMap = new Map(chunks);
442
228
  this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
443
229
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
230
+ if (this.summaryConfiguration.state === "enabled") {
231
+ this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
232
+ }
444
233
  this.summariesDisabled = this.isSummariesDisabled();
445
234
  this.heuristicsDisabled = this.isHeuristicsDisabled();
446
235
  this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
447
236
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
448
237
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
449
- this._aliasingEnabled =
450
- ((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
451
- ((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
452
- this._maxOpSizeInBytes = ((_e = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _e !== void 0 ? _e : defaultMaxOpSizeInBytes);
453
238
  this.maxConsecutiveReconnects =
454
- (_f = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _f !== void 0 ? _f : this.defaultMaxConsecutiveReconnects;
239
+ (_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
455
240
  this._flushMode = runtimeOptions.flushMode;
456
241
  const pendingRuntimeState = context.pendingLocalState;
457
- const baseSnapshot = (_g = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _g !== void 0 ? _g : context.baseSnapshot;
242
+ const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
458
243
  this.garbageCollector = garbageCollection_1.GarbageCollector.create({
459
244
  runtime: this,
460
245
  gcOptions: this.runtimeOptions.gcOptions,
@@ -466,6 +251,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
466
251
  getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
467
252
  getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
468
253
  readAndParseBlob: async (id) => (0, driver_utils_1.readAndParse)(this.storage, id),
254
+ getContainerDiagnosticId: () => this.context.id,
255
+ activeConnection: () => this.deltaManager.active,
469
256
  });
470
257
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
471
258
  this.summarizerNode = (0, runtime_utils_1.createRootSummarizerNodeWithGC)(telemetry_utils_1.ChildLogger.create(this.logger, "SummarizerNode"),
@@ -488,8 +275,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
488
275
  this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
489
276
  }
490
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);
491
- this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, this.logger);
492
- this.scheduleManager = new ScheduleManager(context.deltaManager, this, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
278
+ this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
279
+ if (!this.disposed) {
280
+ this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
281
+ }
282
+ }, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
283
+ this.scheduleManager = new scheduleManager_1.ScheduleManager(context.deltaManager, this, () => this.clientId, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
493
284
  this.deltaSender = this.deltaManager;
494
285
  this.pendingStateManager = new pendingStateManager_1.PendingStateManager({
495
286
  applyStashedOp: this.applyStashedOp.bind(this),
@@ -499,7 +290,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
499
290
  flush: this.flush.bind(this),
500
291
  flushMode: () => this.flushMode,
501
292
  reSubmit: this.reSubmit.bind(this),
502
- rollback: this.rollback.bind(this),
503
293
  setFlushMode: (mode) => this.setFlushMode(mode),
504
294
  }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
505
295
  this.context.quorum.on("removeMember", (clientId) => {
@@ -579,9 +369,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
579
369
  createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
580
370
  createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
581
371
  };
582
- // back-compat 0.59.3000 - Older document may either write summaryCount or not write it at all. If it does
583
- // not write it, initialize summaryNumber to 0.
584
- loadSummaryNumber = (_j = (_h = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _h !== void 0 ? _h : metadata === null || metadata === void 0 ? void 0 : metadata.summaryCount) !== null && _j !== void 0 ? _j : 0;
372
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
373
+ // the count is reset to 0.
374
+ loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
585
375
  }
586
376
  else {
587
377
  this.createContainerMetadata = {
@@ -594,7 +384,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
594
384
  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 }));
595
385
  (0, connectionTelemetry_1.ReportOpPerfTelemetry)(this.context.clientId, this.deltaManager, this.logger);
596
386
  (0, batchTracker_1.BindBatchTracker)(this, this.logger);
597
- this.opTracker = new opTelemetry_1.OpTracker(this.deltaManager, this.mc.config.getBoolean(disableOpTrackingKey) === true);
598
387
  }
599
388
  get IContainerRuntime() { return this; }
600
389
  get IFluidRouter() { return this; }
@@ -617,7 +406,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
617
406
  runtimeVersion: packageVersion_1.pkgVersion,
618
407
  },
619
408
  });
620
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
409
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
621
410
  const pendingRuntimeState = context.pendingLocalState;
622
411
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
623
412
  const storage = !pendingRuntimeState ?
@@ -670,7 +459,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
670
459
  summaryOptions,
671
460
  gcOptions,
672
461
  loadSequenceNumberVerification,
673
- useDataStoreAliasing,
674
462
  flushMode,
675
463
  enableOfflineLoad,
676
464
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
@@ -728,6 +516,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
728
516
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
729
517
  }
730
518
  get disposed() { return this._disposed; }
519
+ get emptyBatch() {
520
+ return this.pendingBatch.empty && this.pendingAttachBatch.empty;
521
+ }
731
522
  get summarizer() {
732
523
  (0, common_utils_1.assert)(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
733
524
  return this._summarizer;
@@ -759,12 +550,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
759
550
  if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
760
551
  return true;
761
552
  }
762
- if (this.summaryConfiguration.state !== "disabled") {
763
- return this.summaryConfiguration.summarizerClientElection === true;
764
- }
765
- else {
766
- return false;
767
- }
553
+ return this.summaryConfiguration.state !== "disabled"
554
+ ? this.summaryConfiguration.summarizerClientElection === true
555
+ : false;
768
556
  }
769
557
  getMaxOpsSinceLastSummary() {
770
558
  // back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
@@ -772,12 +560,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
772
560
  if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
773
561
  return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
774
562
  }
775
- if (this.summaryConfiguration.state !== "disabled") {
776
- return this.summaryConfiguration.maxOpsSinceLastSummary;
777
- }
778
- else {
779
- return 0;
780
- }
563
+ return this.summaryConfiguration.state !== "disabled"
564
+ ? this.summaryConfiguration.maxOpsSinceLastSummary
565
+ : 0;
781
566
  }
782
567
  getInitialSummarizerDelayMs() {
783
568
  // back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
@@ -785,12 +570,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
785
570
  if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
786
571
  return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
787
572
  }
788
- if (this.summaryConfiguration.state !== "disabled") {
789
- return this.summaryConfiguration.initialSummarizerDelayMs;
790
- }
791
- else {
792
- return 0;
793
- }
573
+ return this.summaryConfiguration.state !== "disabled"
574
+ ? this.summaryConfiguration.initialSummarizerDelayMs
575
+ : 0;
794
576
  }
795
577
  dispose(error) {
796
578
  var _a;
@@ -863,17 +645,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
863
645
  return this.resolveHandle(requestParser.createSubRequest(1));
864
646
  }
865
647
  if (id === blobManager_1.BlobManager.basePath && requestParser.isLeaf(2)) {
866
- const handle = await this.blobManager.getBlob(requestParser.pathParts[1]);
867
- if (handle) {
868
- return {
648
+ const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
649
+ return blob
650
+ ? {
869
651
  status: 200,
870
652
  mimeType: "fluid/object",
871
- value: handle.get(),
872
- };
873
- }
874
- else {
875
- return (0, runtime_utils_1.create404Response)(request);
876
- }
653
+ value: blob,
654
+ } : (0, runtime_utils_1.create404Response)(request);
877
655
  }
878
656
  else if (requestParser.pathParts.length > 0) {
879
657
  const dataStore = await this.getDataStoreFromRequest(id, request);
@@ -891,13 +669,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
891
669
  }
892
670
  internalId(maybeAlias) {
893
671
  var _a;
894
- return (_a = this.dataStores.aliases().get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
672
+ return (_a = this.dataStores.aliases.get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
895
673
  }
896
674
  async getDataStoreFromRequest(id, request) {
897
675
  var _a, _b, _c;
898
676
  const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
899
677
  ? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
900
678
  : true;
679
+ await this.dataStores.waitIfPendingAlias(id);
901
680
  const internalId = this.internalId(id);
902
681
  const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
903
682
  /**
@@ -927,10 +706,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
927
706
  addMetadataToSummary(summaryTree) {
928
707
  var _a;
929
708
  const metadata = Object.assign(Object.assign(Object.assign(Object.assign({}, this.createContainerMetadata), {
930
- // back-compat 0.59.3000: This is renamed to summaryNumber. Can be removed when 0.59.3000 saturates.
931
- summaryCount: this.nextSummaryNumber,
932
709
  // Increment the summary number for the next summary that will be generated.
933
- summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1, disableIsolatedChannels: this.disableIsolatedChannels || undefined }), this.garbageCollector.getMetadata()), {
710
+ summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1 }), this.garbageCollector.getMetadata()), {
934
711
  // The last message processed at the time of summary. If there are no new messages, use the message from the
935
712
  // last summary.
936
713
  message: (_a = (0, summaryFormat_1.extractSummaryMetadataMessage)(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary });
@@ -943,7 +720,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
943
720
  const content = JSON.stringify([...this.chunkMap]);
944
721
  (0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.chunksBlobName, content);
945
722
  }
946
- const dataStoreAliases = this.dataStores.aliases();
723
+ const dataStoreAliases = this.dataStores.aliases;
947
724
  if (dataStoreAliases.size > 0) {
948
725
  (0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.aliasBlobName, JSON.stringify([...dataStoreAliases]));
949
726
  }
@@ -974,7 +751,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
974
751
  // Feature disabled, we never stop reconnecting
975
752
  return true;
976
753
  }
977
- if (!this.pendingStateManager.hasPendingMessages()) {
754
+ if (!this.hasPendingMessages()) {
978
755
  // If there are no pending messages, we can always reconnect
979
756
  this.resetReconnectCount();
980
757
  return true;
@@ -1040,22 +817,55 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1040
817
  }
1041
818
  }
1042
819
  setConnectionState(connected, clientId) {
820
+ if (connected === false && this.delayConnectClientId !== undefined) {
821
+ this.delayConnectClientId = undefined;
822
+ this.mc.logger.sendTelemetryEvent({
823
+ eventName: "UnsuccessfulConnectedTransition",
824
+ });
825
+ // Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
826
+ return;
827
+ }
828
+ // If attachment blobs were added while disconnected, we need to delay
829
+ // propagation of the "connected" event until we have uploaded them to
830
+ // ensure we don't submit ops referencing a blob that has not been uploaded
831
+ const connecting = connected && !this._connected && !this.deltaManager.readOnlyInfo.readonly;
832
+ if (connecting && this.blobManager.hasPendingOfflineUploads) {
833
+ (0, common_utils_1.assert)(!this.delayConnectClientId, 0x392 /* Connect event delay must be canceled before subsequent connect event */);
834
+ (0, common_utils_1.assert)(!!clientId, 0x393 /* Must have clientId when connecting */);
835
+ this.delayConnectClientId = clientId;
836
+ this.blobManager.onConnected().then(() => {
837
+ // make sure we didn't reconnect before the promise resolved
838
+ if (this.delayConnectClientId === clientId && !this.disposed) {
839
+ this.delayConnectClientId = undefined;
840
+ this.setConnectionStateCore(connected, clientId);
841
+ }
842
+ }, (error) => this.closeFn(error));
843
+ return;
844
+ }
845
+ this.setConnectionStateCore(connected, clientId);
846
+ }
847
+ setConnectionStateCore(connected, clientId) {
848
+ (0, common_utils_1.assert)(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
1043
849
  this.verifyNotClosed();
1044
850
  // There might be no change of state due to Container calling this API after loading runtime.
1045
851
  const changeOfState = this._connected !== connected;
1046
- const reconnection = changeOfState && connected;
852
+ const reconnection = changeOfState && !connected;
1047
853
  this._connected = connected;
1048
854
  if (!connected) {
1049
855
  this._perfSignalData.signalsLost = 0;
1050
856
  this._perfSignalData.signalTimestamp = 0;
1051
857
  this._perfSignalData.trackingSignalSequenceNumber = undefined;
1052
858
  }
859
+ else {
860
+ (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x3cd /* Connection is possible only if container exists in storage */);
861
+ }
862
+ // Fail while disconnected
1053
863
  if (reconnection) {
1054
864
  this.consecutiveReconnects++;
1055
865
  if (!this.shouldContinueReconnecting()) {
1056
- this.closeFn(
1057
- // pre-0.58 error message: MaxReconnectsWithNoProgress
1058
- container_utils_1.DataProcessingError.create("Runtime detected too many reconnects with no progress syncing local ops", "setConnectionState", undefined, {
866
+ this.closeFn(container_utils_1.DataProcessingError.create(
867
+ // eslint-disable-next-line max-len
868
+ "Runtime detected too many reconnects with no progress syncing local ops. Batch of ops is likely too large (over 1Mb)", "setConnectionState", undefined, {
1059
869
  dataLoss: 1,
1060
870
  attempts: this.consecutiveReconnects,
1061
871
  pendingMessages: this.pendingStateManager.pendingMessagesCount,
@@ -1067,46 +877,48 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1067
877
  this.replayPendingStates();
1068
878
  }
1069
879
  this.dataStores.setConnectionState(connected, clientId);
880
+ this.garbageCollector.setConnectionState(connected, clientId);
1070
881
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, connected, clientId);
1071
882
  }
1072
883
  process(messageArg, local) {
1073
- var _a, _b;
884
+ var _a;
1074
885
  this.verifyNotClosed();
1075
- // If it's not message for runtime, bail out right away.
1076
- if (!(0, driver_utils_1.isUnpackedRuntimeMessage)(messageArg)) {
1077
- return;
1078
- }
1079
- if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
1080
- this.savedOps.push(messageArg);
1081
- }
1082
886
  // Do shallow copy of message, as methods below will modify it.
1083
887
  // There might be multiple container instances receiving same message
1084
888
  // We do not need to make deep copy, as each layer will just replace message.content itself,
1085
889
  // but would not modify contents details
1086
890
  let message = Object.assign({}, messageArg);
891
+ // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
892
+ // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
893
+ // Old ops may contain empty string (I assume noops).
894
+ if (typeof message.contents === "string" && message.contents !== "") {
895
+ message.contents = JSON.parse(message.contents);
896
+ }
897
+ // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
898
+ // This format was not shipped to production workflows.
899
+ const runtimeMessage = unpackRuntimeMessage(message);
900
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
901
+ this.savedOps.push(messageArg);
902
+ }
1087
903
  // Surround the actual processing of the operation with messages to the schedule manager indicating
1088
904
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
1089
905
  // messages once a batch has been fully processed.
1090
906
  this.scheduleManager.beforeOpProcessing(message);
1091
907
  try {
1092
- message = unpackRuntimeMessage(message);
1093
908
  // Chunk processing must come first given that we will transform the message to the unchunked version
1094
909
  // once all pieces are available
1095
910
  message = this.processRemoteChunkedMessage(message);
1096
911
  let localOpMetadata;
1097
- if (local) {
1098
- // Call the PendingStateManager to process local messages.
1099
- // Do not process local chunked ops until all pieces are available.
1100
- if (message.type !== ContainerMessageType.ChunkedOp) {
1101
- localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
1102
- }
912
+ if (local && runtimeMessage) {
913
+ localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
1103
914
  }
1104
915
  // If there are no more pending messages after processing a local message,
1105
916
  // the document is no longer dirty.
1106
- if (!this.pendingStateManager.hasPendingMessages()) {
917
+ if (!this.hasPendingMessages()) {
1107
918
  this.updateDocumentDirtyState(false);
1108
919
  }
1109
- switch (message.type) {
920
+ const type = message.type;
921
+ switch (type) {
1110
922
  case ContainerMessageType.Attach:
1111
923
  this.dataStores.processAttachMessage(message, local);
1112
924
  break;
@@ -1117,12 +929,18 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1117
929
  this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
1118
930
  break;
1119
931
  case ContainerMessageType.BlobAttach:
1120
- (0, common_utils_1.assert)((_b = message === null || message === void 0 ? void 0 : message.metadata) === null || _b === void 0 ? void 0 : _b.blobId, 0x12a /* "Missing blob id on metadata" */);
1121
- this.blobManager.processBlobAttachOp(message.metadata.blobId, local);
932
+ this.blobManager.processBlobAttachOp(message, local);
933
+ break;
934
+ case ContainerMessageType.ChunkedOp:
935
+ case ContainerMessageType.Rejoin:
1122
936
  break;
1123
937
  default:
938
+ (0, common_utils_1.assert)(!runtimeMessage, 0x3ce /* Runtime message of unknown type */);
939
+ }
940
+ // For back-compat, notify only about runtime messages for now.
941
+ if (runtimeMessage) {
942
+ this.emit("op", message, runtimeMessage);
1124
943
  }
1125
- this.emit("op", message);
1126
944
  this.scheduleManager.afterOpProcessing(undefined, message);
1127
945
  if (local) {
1128
946
  // If we have processed a local op, this means that the container is
@@ -1188,6 +1006,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1188
1006
  this.dataStores.processSignal(envelope.address, transformed, local);
1189
1007
  }
1190
1008
  async getRootDataStore(id, wait = true) {
1009
+ return this.getRootDataStoreChannel(id, wait);
1010
+ }
1011
+ async getRootDataStoreChannel(id, wait = true) {
1012
+ await this.dataStores.waitIfPendingAlias(id);
1191
1013
  const internalId = this.internalId(id);
1192
1014
  const context = await this.dataStores.getDataStore(internalId, wait);
1193
1015
  (0, common_utils_1.assert)(await context.isRoot(), 0x12b /* "did not get root data store" */);
@@ -1212,25 +1034,57 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1212
1034
  }
1213
1035
  flush() {
1214
1036
  (0, common_utils_1.assert)(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1215
- if (!this.deltaSender) {
1216
- return;
1037
+ this.flushBatch(this.pendingAttachBatch.popBatch());
1038
+ this.flushBatch(this.pendingBatch.popBatch());
1039
+ (0, common_utils_1.assert)(this.emptyBatch, 0x3cf /* reentrancy */);
1040
+ }
1041
+ flushBatch(batch) {
1042
+ const length = batch.length;
1043
+ if (length > 1) {
1044
+ batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
1045
+ batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
1046
+ // This assert fires for the following reason (there might be more cases like that):
1047
+ // AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
1048
+ // i.e. in the middle of op processing!
1049
+ // Sending ops while processing ops is not good idea - it's not defined when
1050
+ // referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
1051
+ // If we send ops in response to processing multiple ops, then we for sure hit this assert!
1052
+ // Tracked via ADO #1834
1053
+ // assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
1054
+ // "Batch should be generated synchronously, without processing ops in the middle!");
1217
1055
  }
1218
- // Let the PendingStateManager know that there was an attempt to flush messages.
1219
- // Note that this should happen before the `this.needsFlush` check below because in the scenario where we are
1220
- // not connected, `this.needsFlush` will be false but the PendingStateManager might have pending messages and
1221
- // hence needs to track this.
1222
- this.pendingStateManager.onFlush();
1223
- // If flush has already been called then exit early
1224
- if (!this.needsFlush) {
1225
- return;
1226
- }
1227
- this.needsFlush = false;
1056
+ let clientSequenceNumber = -1;
1228
1057
  // Did we disconnect in the middle of turn-based batch?
1229
1058
  // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
1230
- if (!this.canSendOps()) {
1231
- return;
1059
+ if (this.canSendOps()) {
1060
+ if (this.context.submitBatchFn !== undefined) {
1061
+ const batchToSend = [];
1062
+ for (const message of batch) {
1063
+ batchToSend.push({ contents: message.contents, metadata: message.metadata });
1064
+ }
1065
+ // returns clientSequenceNumber of last message in a batch
1066
+ clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1067
+ }
1068
+ else {
1069
+ // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1070
+ // version that has support for batches (submitBatchFn)
1071
+ for (const message of batch) {
1072
+ clientSequenceNumber = this.context.submitFn(protocol_definitions_1.MessageType.Operation, message.deserializedContent, true, // batch
1073
+ message.metadata);
1074
+ }
1075
+ this.deltaSender.flush();
1076
+ }
1077
+ // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
1078
+ clientSequenceNumber -= batch.length - 1;
1079
+ (0, common_utils_1.assert)(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
1232
1080
  }
1233
- return this.deltaSender.flush();
1081
+ // Let the PendingStateManager know that a message was submitted.
1082
+ // In future, need to shift toward keeping batch as a whole!
1083
+ for (const message of batch) {
1084
+ this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
1085
+ clientSequenceNumber++;
1086
+ }
1087
+ this.pendingStateManager.onFlush();
1234
1088
  }
1235
1089
  orderSequentially(callback) {
1236
1090
  // If flush mode is already TurnBased we are either
@@ -1255,7 +1109,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1255
1109
  trackOrderSequentiallyCalls(callback) {
1256
1110
  let checkpoint;
1257
1111
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1258
- checkpoint = this.pendingStateManager.checkpoint();
1112
+ // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1113
+ // 1. It would not help, as we flush attach ops as they become available.
1114
+ // 2. There is no way to undo process of data store creation.
1115
+ checkpoint = this.pendingBatch.checkpoint();
1259
1116
  }
1260
1117
  try {
1261
1118
  this._orderSequentiallyCalls++;
@@ -1264,7 +1121,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1264
1121
  catch (error) {
1265
1122
  if (checkpoint) {
1266
1123
  // This will throw and close the container if rollback fails
1267
- checkpoint.rollback();
1124
+ try {
1125
+ checkpoint.rollback((message) => this.rollback(message.deserializedContent.type, message.deserializedContent.contents, message.localOpMetadata));
1126
+ }
1127
+ catch (err) {
1128
+ const error2 = (0, telemetry_utils_1.wrapError)(err, (message) => {
1129
+ return container_utils_1.DataProcessingError.create(`RollbackError: ${message}`, "checkpointRollback", undefined);
1130
+ });
1131
+ this.closeFn(error2);
1132
+ throw error2;
1133
+ }
1268
1134
  }
1269
1135
  else {
1270
1136
  // pre-0.58 error message: orderSequentiallyCallbackException
@@ -1278,67 +1144,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1278
1144
  }
1279
1145
  async createDataStore(pkg) {
1280
1146
  const internalId = (0, uuid_1.v4)();
1281
- return (0, dataStore_1.channelToDataStore)(await this._createDataStore(pkg, false /* isRoot */, internalId), internalId, this, this.dataStores, this.mc.logger);
1282
- }
1283
- /**
1284
- * Creates a root datastore directly with a user generated id and attaches it to storage.
1285
- * It is vulnerable to name collisions and should not be used.
1286
- *
1287
- * This method will be removed. See #6465.
1288
- */
1289
- async createRootDataStoreLegacy(pkg, rootDataStoreId) {
1290
- const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
1291
- // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
1292
- // older versions, we still have to call bindToContext.
1293
- if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
1294
- fluidDataStore.makeVisibleAndAttachGraph();
1295
- }
1296
- else {
1297
- fluidDataStore.bindToContext();
1298
- }
1299
- return fluidDataStore;
1300
- }
1301
- /**
1302
- * @deprecated - will be removed in an upcoming release. See #9660.
1303
- */
1304
- async createRootDataStore(pkg, rootDataStoreId) {
1305
- if (rootDataStoreId.includes("/")) {
1306
- throw new container_utils_1.UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
1307
- }
1308
- return this._aliasingEnabled === true ?
1309
- this.createAndAliasDataStore(pkg, rootDataStoreId) :
1310
- this.createRootDataStoreLegacy(pkg, rootDataStoreId);
1311
- }
1312
- /**
1313
- * Creates a data store then attempts to alias it.
1314
- * If aliasing fails, it will raise an exception.
1315
- *
1316
- * This method will be removed. See #6465.
1317
- *
1318
- * @param pkg - Package name of the data store
1319
- * @param alias - Alias to be assigned to the data store
1320
- * @param props - Properties for the data store
1321
- * @returns - An aliased data store which can can be found / loaded by alias.
1322
- */
1323
- async createAndAliasDataStore(pkg, alias, props) {
1324
- const internalId = (0, uuid_1.v4)();
1325
- const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
1326
- const aliasedDataStore = (0, dataStore_1.channelToDataStore)(dataStore, internalId, this, this.dataStores, this.mc.logger);
1327
- const result = await aliasedDataStore.trySetAlias(alias);
1328
- if (result !== "Success") {
1329
- throw new container_utils_1.GenericError("dataStoreAliasFailure", undefined /* error */, {
1330
- alias: {
1331
- value: alias,
1332
- tag: telemetry_utils_1.TelemetryDataTag.UserData,
1333
- },
1334
- internalId: {
1335
- value: internalId,
1336
- tag: telemetry_utils_1.TelemetryDataTag.PackageData,
1337
- },
1338
- aliasResult: result,
1339
- });
1340
- }
1341
- return aliasedDataStore;
1147
+ return (0, dataStore_1.channelToDataStore)(await this._createDataStore(pkg, internalId), internalId, this, this.dataStores, this.mc.logger);
1342
1148
  }
1343
1149
  createDetachedRootDataStore(pkg, rootDataStoreId) {
1344
1150
  if (rootDataStoreId.includes("/")) {
@@ -1349,38 +1155,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1349
1155
  createDetachedDataStore(pkg) {
1350
1156
  return this.dataStores.createDetachedDataStoreCore(pkg, false);
1351
1157
  }
1352
- /**
1353
- * Creates a possibly root datastore directly with a possibly user generated id and attaches it to storage.
1354
- * It is vulnerable to name collisions if both aforementioned conditions are true, and should not be used.
1355
- *
1356
- * This method will be removed. See #6465.
1357
- */
1358
- async _createDataStoreWithPropsLegacy(pkg, props, id = (0, uuid_1.v4)(), isRoot = false) {
1359
- const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
1360
- if (isRoot) {
1361
- // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
1362
- // For older versions, we still have to call bindToContext.
1363
- if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
1364
- fluidDataStore.makeVisibleAndAttachGraph();
1365
- }
1366
- else {
1367
- fluidDataStore.bindToContext();
1368
- }
1369
- this.logger.sendTelemetryEvent({
1370
- eventName: "Root datastore with props",
1371
- hasProps: props !== undefined,
1372
- });
1373
- }
1158
+ async _createDataStoreWithProps(pkg, props, id = (0, uuid_1.v4)()) {
1159
+ const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props).realize();
1374
1160
  return (0, dataStore_1.channelToDataStore)(fluidDataStore, id, this, this.dataStores, this.mc.logger);
1375
1161
  }
1376
- async _createDataStoreWithProps(pkg, props, id = (0, uuid_1.v4)(), isRoot = false) {
1377
- return this._aliasingEnabled === true && isRoot ?
1378
- this.createAndAliasDataStore(pkg, id, props) :
1379
- this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
1380
- }
1381
- async _createDataStore(pkg, isRoot, id = (0, uuid_1.v4)(), props) {
1162
+ async _createDataStore(pkg, id = (0, uuid_1.v4)(), props) {
1382
1163
  return this.dataStores
1383
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props)
1164
+ ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
1384
1165
  .realize();
1385
1166
  }
1386
1167
  canSendOps() {
@@ -1454,7 +1235,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1454
1235
  (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x12e /* "Container Context should already be in attached state" */);
1455
1236
  this.emit("attached");
1456
1237
  }
1457
- if (attachState === container_definitions_1.AttachState.Attached && !this.pendingStateManager.hasPendingMessages()) {
1238
+ if (attachState === container_definitions_1.AttachState.Attached && !this.hasPendingMessages()) {
1458
1239
  this.updateDocumentDirtyState(false);
1459
1240
  }
1460
1241
  this.dataStores.setAttachState(attachState);
@@ -1472,10 +1253,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1472
1253
  this.blobManager.setRedirectTable(blobRedirectTable);
1473
1254
  }
1474
1255
  const summarizeResult = this.dataStores.createSummary(telemetryContext);
1475
- if (!this.disableIsolatedChannels) {
1476
- // Wrap data store summaries in .channels subtree.
1477
- (0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
1478
- }
1256
+ // Wrap data store summaries in .channels subtree.
1257
+ (0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
1479
1258
  this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */, telemetryContext);
1480
1259
  return summarizeResult.summary;
1481
1260
  }
@@ -1490,12 +1269,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1490
1269
  }
1491
1270
  async summarizeInternal(fullTree, trackState, telemetryContext) {
1492
1271
  const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
1493
- let pathPartsForChildren;
1494
- if (!this.disableIsolatedChannels) {
1495
- // Wrap data store summaries in .channels subtree.
1496
- (0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
1497
- pathPartsForChildren = [runtime_definitions_1.channelsTreeName];
1498
- }
1272
+ // Wrap data store summaries in .channels subtree.
1273
+ (0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
1274
+ const pathPartsForChildren = [runtime_definitions_1.channelsTreeName];
1499
1275
  this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
1500
1276
  return Object.assign(Object.assign({}, summarizeResult), { id: "", pathPartsForChildren });
1501
1277
  }
@@ -1623,7 +1399,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1623
1399
  }
1624
1400
  /**
1625
1401
  * Runs garbage collection and updates the reference / used state of the nodes in the container.
1626
- * @returns the statistics of the garbage collection run.
1402
+ * @returns the statistics of the garbage collection run; undefined if GC did not run.
1627
1403
  */
1628
1404
  async collectGarbage(options) {
1629
1405
  return this.garbageCollector.collectGarbage(options);
@@ -1646,7 +1422,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1646
1422
  * @param options - options controlling how the summary is generated or submitted
1647
1423
  */
1648
1424
  async submitSummary(options) {
1649
- var _a, _b, _c;
1425
+ var _a, _b;
1650
1426
  const { fullTree, refreshLatestAck, summaryLogger } = options;
1651
1427
  // The summary number for this summary. This will be updated during the summary process, so get it now and
1652
1428
  // use it for all events logged during this summary.
@@ -1654,6 +1430,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1654
1430
  const summaryNumberLogger = telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, {
1655
1431
  all: { summaryNumber },
1656
1432
  });
1433
+ (0, common_utils_1.assert)(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1657
1434
  let latestSnapshotVersionId;
1658
1435
  if (refreshLatestAck) {
1659
1436
  const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
@@ -1674,17 +1451,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1674
1451
  const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
1675
1452
  const minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
1676
1453
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
1677
- // We should be here is we haven't processed be here. If we are of if the last message's sequence number
1678
- // doesn't match the last processed sequence number, log an error.
1679
- if (summaryRefSeqNum !== ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber)) {
1680
- summaryNumberLogger.sendErrorEvent({
1681
- eventName: "LastSequenceMismatch",
1682
- error: message,
1683
- });
1684
- }
1454
+ const lastAck = this.summaryCollection.latestAck;
1685
1455
  this.summarizerNode.startSummary(summaryRefSeqNum, summaryNumberLogger);
1686
1456
  // Helper function to check whether we should still continue between each async step.
1687
1457
  const checkContinue = () => {
1458
+ var _a;
1688
1459
  // Do not check for loss of connectivity directly! Instead leave it up to
1689
1460
  // RunWhileConnectedCoordinator to control policy in a single place.
1690
1461
  // This will allow easier change of design if we chose to. For example, we may chose to allow
@@ -1708,6 +1479,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1708
1479
  error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
1709
1480
  };
1710
1481
  }
1482
+ (0, common_utils_1.assert)(summaryRefSeqNum === ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber), 0x395 /* it's one and the same thing */);
1483
+ if (lastAck !== this.summaryCollection.latestAck) {
1484
+ return {
1485
+ continue: false,
1486
+ // eslint-disable-next-line max-len
1487
+ error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
1488
+ };
1489
+ }
1711
1490
  return { continue: true };
1712
1491
  };
1713
1492
  let continueResult = checkContinue();
@@ -1726,7 +1505,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1726
1505
  const forcedFullTree = this.garbageCollector.summaryStateNeedsReset;
1727
1506
  try {
1728
1507
  summarizeResult = await this.summarize({
1729
- fullTree: fullTree || forcedFullTree,
1508
+ fullTree: fullTree !== null && fullTree !== void 0 ? fullTree : forcedFullTree,
1730
1509
  trackState: true,
1731
1510
  summaryLogger: summaryNumberLogger,
1732
1511
  runGC: this.garbageCollector.shouldRunGC,
@@ -1746,13 +1525,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1746
1525
  // Counting dataStores and handles
1747
1526
  // Because handles are unchanged dataStores in the current logic,
1748
1527
  // summarized dataStore count is total dataStore count minus handle count
1749
- const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[runtime_definitions_1.channelsTreeName];
1528
+ const dataStoreTree = summaryTree.tree[runtime_definitions_1.channelsTreeName];
1750
1529
  (0, common_utils_1.assert)(dataStoreTree.type === protocol_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
1751
1530
  const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === protocol_definitions_1.SummaryType.Handle).length;
1752
1531
  const gcSummaryTreeStats = summaryTree.tree[garbageCollection_1.gcTreeKey]
1753
1532
  ? (0, runtime_utils_1.calculateStats)(summaryTree.tree[garbageCollection_1.gcTreeKey])
1754
1533
  : undefined;
1755
- const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_b = summarizeResult.gcStats) === null || _b === void 0 ? void 0 : _b.updatedDataStoreCount, gcBlobNodeCount: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.blobNodeCount, gcTotalBlobsSize: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.totalBlobSize, opsSizesSinceLastSummary: this.opTracker.opsSizeAccumulator, nonSystemOpsSinceLastSummary: this.opTracker.nonSystemOpCount, summaryNumber }, partialStats);
1534
+ const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_a = summarizeResult.gcStats) === null || _a === void 0 ? void 0 : _a.updatedDataStoreCount, gcBlobNodeCount: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.blobNodeCount, gcTotalBlobsSize: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.totalBlobSize, summaryNumber }, partialStats);
1756
1535
  const generateSummaryData = {
1757
1536
  referenceSequenceNumber: summaryRefSeqNum,
1758
1537
  minimumSequenceNumber,
@@ -1770,7 +1549,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1770
1549
  // submitting the summaryOp then we can't rely on summaryAck. So in case we have
1771
1550
  // latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
1772
1551
  // the one fetched from storage as parent as that is the latest.
1773
- const lastAck = this.summaryCollection.latestAck;
1774
1552
  let summaryContext;
1775
1553
  if ((lastAck === null || lastAck === void 0 ? void 0 : lastAck.summaryAck.contents.handle) !== latestSnapshotVersionId
1776
1554
  && latestSnapshotVersionId !== undefined) {
@@ -1783,7 +1561,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1783
1561
  else if (lastAck === undefined) {
1784
1562
  summaryContext = {
1785
1563
  proposalHandle: undefined,
1786
- ackHandle: (_c = this.context.getLoadedFromVersion()) === null || _c === void 0 ? void 0 : _c.id,
1564
+ ackHandle: (_b = this.context.getLoadedFromVersion()) === null || _b === void 0 ? void 0 : _b.id,
1787
1565
  referenceSequenceNumber: summaryRefSeqNum,
1788
1566
  };
1789
1567
  }
@@ -1816,14 +1594,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1816
1594
  }
1817
1595
  let clientSequenceNumber;
1818
1596
  try {
1819
- clientSequenceNumber = this.submitSystemMessage(protocol_definitions_1.MessageType.Summarize, summaryMessage);
1597
+ clientSequenceNumber = this.submitSummaryMessage(summaryMessage);
1820
1598
  }
1821
1599
  catch (error) {
1822
1600
  return Object.assign(Object.assign({ stage: "upload" }, uploadData), { error });
1823
1601
  }
1824
1602
  const submitData = Object.assign(Object.assign({ stage: "submit" }, uploadData), { clientSequenceNumber, submitOpDuration: trace.trace().duration });
1825
1603
  this.summarizerNode.completeSummary(handle);
1826
- this.opTracker.reset();
1827
1604
  return submitData;
1828
1605
  }
1829
1606
  finally {
@@ -1865,7 +1642,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1865
1642
  this.chunkMap.delete(clientId);
1866
1643
  }
1867
1644
  }
1645
+ hasPendingMessages() {
1646
+ return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
1647
+ }
1868
1648
  updateDocumentDirtyState(dirty) {
1649
+ if (this.attachState !== container_definitions_1.AttachState.Attached) {
1650
+ (0, common_utils_1.assert)(dirty, 0x3d2 /* Non-attached container is dirty */);
1651
+ }
1652
+ else {
1653
+ // Other way is not true = see this.isContainerMessageDirtyable()
1654
+ (0, common_utils_1.assert)(!dirty || this.hasPendingMessages(), 0x3d3 /* if doc is dirty, there has to be pending ops */);
1655
+ }
1869
1656
  if (this.dirtyContainer === dirty) {
1870
1657
  return;
1871
1658
  }
@@ -1893,99 +1680,100 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1893
1680
  this.verifyNotClosed();
1894
1681
  return this.blobManager.createBlob(blob);
1895
1682
  }
1896
- submit(type, content, localOpMetadata = undefined, opMetadata = undefined) {
1683
+ submit(type, contents, localOpMetadata = undefined, metadata = undefined) {
1897
1684
  this.verifyNotClosed();
1898
1685
  // There should be no ops in detached container state!
1899
1686
  (0, common_utils_1.assert)(this.attachState !== container_definitions_1.AttachState.Detached, 0x132 /* "sending ops in detached container" */);
1900
- let clientSequenceNumber = -1;
1901
- let opMetadataInternal = opMetadata;
1902
- if (this.canSendOps()) {
1903
- const serializedContent = JSON.stringify(content);
1904
- const maxOpSize = this.context.deltaManager.maxMessageSize;
1905
- // If in TurnBased flush mode we will trigger a flush at the next turn break
1906
- if (this.flushMode === runtime_definitions_1.FlushMode.TurnBased && !this.needsFlush) {
1907
- opMetadataInternal = Object.assign(Object.assign({}, opMetadata), { batch: true });
1908
- this.needsFlush = true;
1909
- // Use Promise.resolve().then() to queue a microtask to detect the end of the turn and force a flush.
1910
- if (!this.flushTrigger) {
1911
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1912
- Promise.resolve().then(() => {
1913
- this.flushTrigger = false;
1914
- this.flush();
1687
+ const deserializedContent = { type, contents };
1688
+ const serializedContent = JSON.stringify(deserializedContent);
1689
+ if (this.deltaManager.readOnlyInfo.readonly) {
1690
+ this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
1691
+ }
1692
+ const message = {
1693
+ contents: serializedContent,
1694
+ deserializedContent,
1695
+ metadata,
1696
+ localOpMetadata,
1697
+ referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
1698
+ };
1699
+ try {
1700
+ // If this is attach message for new data store, and we are in a batch, send this op out of order
1701
+ // Is it safe:
1702
+ // Yes, this should be safe reordering. Newly created data stores are not visible through API surface.
1703
+ // They become visible only when aliased, or handle to some sub-element of newly created datastore
1704
+ // is stored in some DDS, i.e. only after some other op.
1705
+ // Why:
1706
+ // Attach ops are large, and expensive to process. Plus there are scenarios where a lot of new data
1707
+ // stores are created, causing issues like relay service throttling (too many ops) and catastrophic
1708
+ // failure (batch is too large). Pushing them earlier and outside of main batch should alleviate
1709
+ // these issues.
1710
+ // Cons:
1711
+ // 1. With large batches, relay service may throttle clients. Clients may disconnect while throttled.
1712
+ // This change creates new possibility of a lot of newly created data stores never being referenced
1713
+ // because client died before it had a change to submit the rest of the ops. This will create more
1714
+ // garbage that needs to be collected leveraging GC (Garbage Collection) feature.
1715
+ // 2. Sending ops out of order means they are excluded from rollback functionality. This is not an issue
1716
+ // today as rollback can't undo creation of data store. To some extent not sending them is a bigger
1717
+ // issue than sending.
1718
+ // Please note that this does not change file format, so it can be disabled in the future if this
1719
+ // 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) {
1722
+ if (!this.pendingAttachBatch.push(message)) {
1723
+ // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1724
+ // when queue is not empty.
1725
+ // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
1726
+ this.flushBatch(this.pendingAttachBatch.popBatch());
1727
+ if (!this.pendingAttachBatch.push(message)) {
1728
+ throw new container_utils_1.GenericError("BatchTooLarge",
1729
+ /* error */ undefined, {
1730
+ opSize: message.contents.length,
1731
+ count: this.pendingAttachBatch.length,
1732
+ limit: this.pendingAttachBatch.limit,
1733
+ });
1734
+ }
1735
+ }
1736
+ }
1737
+ else {
1738
+ if (!this.pendingBatch.push(message)) {
1739
+ throw new container_utils_1.GenericError("BatchTooLarge",
1740
+ /* error */ undefined, {
1741
+ opSize: message.contents.length,
1742
+ count: this.pendingBatch.length,
1743
+ limit: this.pendingBatch.limit,
1915
1744
  });
1916
1745
  }
1917
1746
  }
1918
- clientSequenceNumber = this.submitMaybeChunkedMessages(type, content, serializedContent, maxOpSize, this._flushMode === runtime_definitions_1.FlushMode.TurnBased, opMetadataInternal);
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;
1756
+ this.flush();
1757
+ });
1758
+ }
1919
1759
  }
1920
- // Let the PendingStateManager know that a message was submitted.
1921
- this.pendingStateManager.onSubmitMessage(type, clientSequenceNumber, this.deltaManager.lastSequenceNumber, content, localOpMetadata, opMetadataInternal);
1922
- if (this.isContainerMessageDirtyable(type, content)) {
1923
- this.updateDocumentDirtyState(true);
1760
+ catch (error) {
1761
+ this.closeFn(error);
1762
+ throw error;
1924
1763
  }
1925
- }
1926
- submitMaybeChunkedMessages(type, content, serializedContent, serverMaxOpSize, batch, opMetadataInternal = undefined) {
1927
- if (this._maxOpSizeInBytes >= 0) {
1928
- // Chunking disabled
1929
- if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
1930
- return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
1931
- }
1932
- // When chunking is disabled, we ignore the server max message size
1933
- // and if the content length is larger than the client configured message size
1934
- // instead of splitting the content, we will fail by explicitly close the container
1935
- this.closeFn(new container_utils_1.GenericError("OpTooLarge",
1936
- /* error */ undefined, {
1937
- length: {
1938
- value: serializedContent.length,
1939
- tag: telemetry_utils_1.TelemetryDataTag.PackageData,
1940
- },
1941
- limit: {
1942
- value: this._maxOpSizeInBytes,
1943
- tag: telemetry_utils_1.TelemetryDataTag.PackageData,
1944
- },
1945
- }));
1946
- return -1;
1947
- }
1948
- // Chunking enabled, fallback on the server's max message size
1949
- // and split the content accordingly
1950
- if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
1951
- return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
1952
- }
1953
- return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
1954
- }
1955
- submitChunkedMessage(type, content, maxOpSize) {
1956
- const contentLength = content.length;
1957
- const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
1958
- let offset = 0;
1959
- let clientSequenceNumber = 0;
1960
- for (let i = 1; i <= chunkN; i = i + 1) {
1961
- const chunkedOp = {
1962
- chunkId: i,
1963
- contents: content.substr(offset, maxOpSize),
1964
- originalType: type,
1965
- totalChunks: chunkN,
1966
- };
1967
- offset += maxOpSize;
1968
- clientSequenceNumber = this.submitRuntimeMessage(ContainerMessageType.ChunkedOp, chunkedOp, false);
1764
+ if (this.isContainerMessageDirtyable(type, contents)) {
1765
+ this.updateDocumentDirtyState(true);
1969
1766
  }
1970
- return clientSequenceNumber;
1971
1767
  }
1972
- submitSystemMessage(type, contents) {
1768
+ submitSummaryMessage(contents) {
1973
1769
  this.verifyNotClosed();
1974
1770
  (0, common_utils_1.assert)(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
1975
1771
  // System message should not be sent in the middle of the batch.
1976
- // That said, we can preserve existing behavior by not flushing existing buffer.
1977
- // That might be not what caller hopes to get, but we can look deeper if telemetry tells us it's a problem.
1978
- const middleOfBatch = this.flushMode === runtime_definitions_1.FlushMode.TurnBased && this.needsFlush;
1979
- if (middleOfBatch) {
1980
- this.mc.logger.sendErrorEvent({ eventName: "submitSystemMessageError", type });
1981
- }
1982
- return this.context.submitFn(type, contents, middleOfBatch);
1983
- }
1984
- submitRuntimeMessage(type, contents, batch, appData) {
1985
- this.verifyNotClosed();
1986
- (0, common_utils_1.assert)(this.connected, 0x259 /* "Container disconnected when trying to submit system message" */);
1987
- const payload = { type, contents };
1988
- return this.context.submitFn(protocol_definitions_1.MessageType.Operation, payload, batch, appData);
1772
+ (0, common_utils_1.assert)(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
1773
+ // back-compat: ADO #1385: Make this call unconditional in the future
1774
+ return this.context.submitSummaryFn !== undefined
1775
+ ? this.context.submitSummaryFn(contents)
1776
+ : this.context.submitFn(protocol_definitions_1.MessageType.Summarize, contents, false);
1989
1777
  }
1990
1778
  /**
1991
1779
  * Throw an error if the runtime is closed. Methods that are expected to potentially
@@ -2016,7 +1804,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2016
1804
  case ContainerMessageType.ChunkedOp:
2017
1805
  throw new Error(`chunkedOp not expected here`);
2018
1806
  case ContainerMessageType.BlobAttach:
2019
- this.submit(type, content, localOpMetadata, opMetadata);
1807
+ this.blobManager.reSubmit(opMetadata);
2020
1808
  break;
2021
1809
  case ContainerMessageType.Rejoin:
2022
1810
  this.submit(type, content);
@@ -2064,7 +1852,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2064
1852
  const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
2065
1853
  eventName: "RefreshLatestSummaryGetSnapshot",
2066
1854
  fetchLatest: true,
2067
- });
1855
+ }, driver_definitions_1.FetchSource.noCache);
2068
1856
  const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
2069
1857
  const latestSnapshotRefSeq = await (0, runtime_utils_1.seqFromTree)(snapshotTree, readAndParseBlob);
2070
1858
  const result = await this.summarizerNode.refreshLatestSummary(undefined, latestSnapshotRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
@@ -2072,11 +1860,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2072
1860
  await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
2073
1861
  return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
2074
1862
  }
2075
- async fetchSnapshotFromStorage(versionId, logger, event) {
1863
+ async fetchSnapshotFromStorage(versionId, logger, event, fetchSource) {
2076
1864
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(logger, event, async (perfEvent) => {
2077
1865
  const stats = {};
2078
1866
  const trace = common_utils_1.Trace.start();
2079
- const versions = await this.storage.getVersions(versionId, 1);
1867
+ const versions = await this.storage.getVersions(versionId, 1, "refreshLatestSummaryAckFromServer", fetchSource);
2080
1868
  (0, common_utils_1.assert)(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
2081
1869
  stats.getVersionDuration = trace.trace().duration;
2082
1870
  const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
@@ -2106,10 +1894,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2106
1894
  if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
2107
1895
  throw new container_utils_1.UsageError("can't get state when offline load disabled");
2108
1896
  }
1897
+ // Flush pending batch.
1898
+ // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
1899
+ // to close current batch.
1900
+ this.flush();
2109
1901
  const previousPendingState = this.context.pendingLocalState;
2110
1902
  if (previousPendingState) {
2111
1903
  return {
2112
1904
  pending: this.pendingStateManager.getLocalState(),
1905
+ pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
2113
1906
  snapshotBlobs: previousPendingState.snapshotBlobs,
2114
1907
  baseSnapshot: previousPendingState.baseSnapshot,
2115
1908
  savedOps: this.savedOps,
@@ -2119,6 +1912,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2119
1912
  (0, common_utils_1.assert)(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
2120
1913
  return {
2121
1914
  pending: this.pendingStateManager.getLocalState(),
1915
+ pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
2122
1916
  snapshotBlobs: this.baseSnapshotBlobs,
2123
1917
  baseSnapshot: this.context.baseSnapshot,
2124
1918
  savedOps: this.savedOps,
@@ -2158,6 +1952,19 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
2158
1952
  // we may not have seen every sequence number (because of system ops) so apply everything once we
2159
1953
  // don't have any more saved ops
2160
1954
  await this.pendingStateManager.applyStashedOpsAt();
1955
+ // If it's not the case, we should take it into account when calculating dirty state.
1956
+ (0, common_utils_1.assert)(this.context.attachState === container_definitions_1.AttachState.Attached, 0x3d5 /* this function is called for attached containers only */);
1957
+ if (!this.hasPendingMessages()) {
1958
+ this.updateDocumentDirtyState(false);
1959
+ }
1960
+ }
1961
+ validateSummaryHeuristicConfiguration(configuration) {
1962
+ // eslint-disable-next-line no-restricted-syntax
1963
+ for (const prop in configuration) {
1964
+ if (typeof configuration[prop] === "number" && configuration[prop] < 0) {
1965
+ throw new container_utils_1.UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
1966
+ }
1967
+ }
2161
1968
  }
2162
1969
  }
2163
1970
  exports.ContainerRuntime = ContainerRuntime;