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

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 (239) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/blobManager.d.ts +20 -5
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +57 -15
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +88 -51
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +205 -300
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStore.d.ts.map +1 -1
  11. package/dist/dataStore.js +6 -0
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +14 -21
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +71 -57
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStoreContexts.js +1 -1
  18. package/dist/dataStoreContexts.js.map +1 -1
  19. package/dist/dataStores.d.ts +11 -10
  20. package/dist/dataStores.d.ts.map +1 -1
  21. package/dist/dataStores.js +51 -20
  22. package/dist/dataStores.js.map +1 -1
  23. package/dist/garbageCollection.d.ts +40 -32
  24. package/dist/garbageCollection.d.ts.map +1 -1
  25. package/dist/garbageCollection.js +227 -161
  26. package/dist/garbageCollection.js.map +1 -1
  27. package/dist/garbageCollectionConstants.d.ts +19 -0
  28. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  29. package/dist/garbageCollectionConstants.js +34 -0
  30. package/dist/garbageCollectionConstants.js.map +1 -0
  31. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  32. package/dist/gcSweepReadyUsageDetection.js +5 -14
  33. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  34. package/dist/index.d.ts +6 -6
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8 -9
  37. package/dist/index.js.map +1 -1
  38. package/dist/opLifecycle/batchManager.d.ts +30 -0
  39. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  40. package/dist/{batchManager.js → opLifecycle/batchManager.js} +25 -15
  41. package/dist/opLifecycle/batchManager.js.map +1 -0
  42. package/dist/opLifecycle/definitions.d.ts +40 -0
  43. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  44. package/dist/opLifecycle/definitions.js +7 -0
  45. package/dist/opLifecycle/definitions.js.map +1 -0
  46. package/dist/opLifecycle/index.d.ts +12 -0
  47. package/dist/opLifecycle/index.d.ts.map +1 -0
  48. package/dist/opLifecycle/index.js +21 -0
  49. package/dist/opLifecycle/index.js.map +1 -0
  50. package/dist/opLifecycle/opCompressor.d.ts +18 -0
  51. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  52. package/dist/opLifecycle/opCompressor.js +53 -0
  53. package/dist/opLifecycle/opCompressor.js.map +1 -0
  54. package/dist/opLifecycle/opDecompressor.d.ts +20 -0
  55. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  56. package/dist/opLifecycle/opDecompressor.js +72 -0
  57. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  58. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  59. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  60. package/dist/opLifecycle/opSplitter.js +61 -0
  61. package/dist/opLifecycle/opSplitter.js.map +1 -0
  62. package/dist/opLifecycle/outbox.d.ts +47 -0
  63. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  64. package/dist/opLifecycle/outbox.js +153 -0
  65. package/dist/opLifecycle/outbox.js.map +1 -0
  66. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  67. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  68. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  69. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  70. package/dist/packageVersion.d.ts +1 -1
  71. package/dist/packageVersion.js +1 -1
  72. package/dist/packageVersion.js.map +1 -1
  73. package/dist/pendingStateManager.d.ts +6 -26
  74. package/dist/pendingStateManager.d.ts.map +1 -1
  75. package/dist/pendingStateManager.js +42 -62
  76. package/dist/pendingStateManager.js.map +1 -1
  77. package/dist/runningSummarizer.d.ts +3 -2
  78. package/dist/runningSummarizer.d.ts.map +1 -1
  79. package/dist/runningSummarizer.js +10 -3
  80. package/dist/runningSummarizer.js.map +1 -1
  81. package/dist/scheduleManager.js.map +1 -1
  82. package/dist/summarizer.js +7 -2
  83. package/dist/summarizer.js.map +1 -1
  84. package/dist/summarizerClientElection.js +1 -1
  85. package/dist/summarizerClientElection.js.map +1 -1
  86. package/dist/summarizerHeuristics.d.ts.map +1 -1
  87. package/dist/summarizerHeuristics.js +0 -3
  88. package/dist/summarizerHeuristics.js.map +1 -1
  89. package/dist/summarizerTypes.d.ts +19 -2
  90. package/dist/summarizerTypes.d.ts.map +1 -1
  91. package/dist/summarizerTypes.js.map +1 -1
  92. package/dist/summaryFormat.d.ts +4 -2
  93. package/dist/summaryFormat.d.ts.map +1 -1
  94. package/dist/summaryFormat.js +2 -2
  95. package/dist/summaryFormat.js.map +1 -1
  96. package/dist/summaryGenerator.d.ts.map +1 -1
  97. package/dist/summaryGenerator.js +3 -2
  98. package/dist/summaryGenerator.js.map +1 -1
  99. package/dist/summaryManager.d.ts.map +1 -1
  100. package/dist/summaryManager.js +10 -6
  101. package/dist/summaryManager.js.map +1 -1
  102. package/garbageCollection.md +27 -22
  103. package/lib/blobManager.d.ts +20 -5
  104. package/lib/blobManager.d.ts.map +1 -1
  105. package/lib/blobManager.js +59 -17
  106. package/lib/blobManager.js.map +1 -1
  107. package/lib/containerRuntime.d.ts +88 -51
  108. package/lib/containerRuntime.d.ts.map +1 -1
  109. package/lib/containerRuntime.js +203 -297
  110. package/lib/containerRuntime.js.map +1 -1
  111. package/lib/dataStore.d.ts.map +1 -1
  112. package/lib/dataStore.js +6 -0
  113. package/lib/dataStore.js.map +1 -1
  114. package/lib/dataStoreContext.d.ts +14 -21
  115. package/lib/dataStoreContext.d.ts.map +1 -1
  116. package/lib/dataStoreContext.js +75 -61
  117. package/lib/dataStoreContext.js.map +1 -1
  118. package/lib/dataStoreContexts.js +1 -1
  119. package/lib/dataStoreContexts.js.map +1 -1
  120. package/lib/dataStores.d.ts +11 -10
  121. package/lib/dataStores.d.ts.map +1 -1
  122. package/lib/dataStores.js +53 -22
  123. package/lib/dataStores.js.map +1 -1
  124. package/lib/garbageCollection.d.ts +40 -32
  125. package/lib/garbageCollection.d.ts.map +1 -1
  126. package/lib/garbageCollection.js +220 -154
  127. package/lib/garbageCollection.js.map +1 -1
  128. package/lib/garbageCollectionConstants.d.ts +19 -0
  129. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  130. package/lib/garbageCollectionConstants.js +31 -0
  131. package/lib/garbageCollectionConstants.js.map +1 -0
  132. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  133. package/lib/gcSweepReadyUsageDetection.js +4 -13
  134. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  135. package/lib/index.d.ts +6 -6
  136. package/lib/index.d.ts.map +1 -1
  137. package/lib/index.js +3 -4
  138. package/lib/index.js.map +1 -1
  139. package/lib/opLifecycle/batchManager.d.ts +30 -0
  140. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  141. package/lib/{batchManager.js → opLifecycle/batchManager.js} +25 -15
  142. package/lib/opLifecycle/batchManager.js.map +1 -0
  143. package/lib/opLifecycle/definitions.d.ts +40 -0
  144. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  145. package/lib/opLifecycle/definitions.js +6 -0
  146. package/lib/opLifecycle/definitions.js.map +1 -0
  147. package/lib/opLifecycle/index.d.ts +12 -0
  148. package/lib/opLifecycle/index.d.ts.map +1 -0
  149. package/lib/opLifecycle/index.js +11 -0
  150. package/lib/opLifecycle/index.js.map +1 -0
  151. package/lib/opLifecycle/opCompressor.d.ts +18 -0
  152. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  153. package/lib/opLifecycle/opCompressor.js +49 -0
  154. package/lib/opLifecycle/opCompressor.js.map +1 -0
  155. package/lib/opLifecycle/opDecompressor.d.ts +20 -0
  156. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  157. package/lib/opLifecycle/opDecompressor.js +68 -0
  158. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  159. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  160. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  161. package/lib/opLifecycle/opSplitter.js +57 -0
  162. package/lib/opLifecycle/opSplitter.js.map +1 -0
  163. package/lib/opLifecycle/outbox.d.ts +47 -0
  164. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  165. package/lib/opLifecycle/outbox.js +149 -0
  166. package/lib/opLifecycle/outbox.js.map +1 -0
  167. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  168. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  169. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  170. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  171. package/lib/packageVersion.d.ts +1 -1
  172. package/lib/packageVersion.js +1 -1
  173. package/lib/packageVersion.js.map +1 -1
  174. package/lib/pendingStateManager.d.ts +6 -26
  175. package/lib/pendingStateManager.d.ts.map +1 -1
  176. package/lib/pendingStateManager.js +42 -62
  177. package/lib/pendingStateManager.js.map +1 -1
  178. package/lib/runningSummarizer.d.ts +3 -2
  179. package/lib/runningSummarizer.d.ts.map +1 -1
  180. package/lib/runningSummarizer.js +10 -3
  181. package/lib/runningSummarizer.js.map +1 -1
  182. package/lib/scheduleManager.js.map +1 -1
  183. package/lib/summarizer.js +7 -2
  184. package/lib/summarizer.js.map +1 -1
  185. package/lib/summarizerClientElection.js +1 -1
  186. package/lib/summarizerClientElection.js.map +1 -1
  187. package/lib/summarizerHeuristics.d.ts.map +1 -1
  188. package/lib/summarizerHeuristics.js +0 -3
  189. package/lib/summarizerHeuristics.js.map +1 -1
  190. package/lib/summarizerTypes.d.ts +19 -2
  191. package/lib/summarizerTypes.d.ts.map +1 -1
  192. package/lib/summarizerTypes.js.map +1 -1
  193. package/lib/summaryFormat.d.ts +4 -2
  194. package/lib/summaryFormat.d.ts.map +1 -1
  195. package/lib/summaryFormat.js +1 -1
  196. package/lib/summaryFormat.js.map +1 -1
  197. package/lib/summaryGenerator.d.ts.map +1 -1
  198. package/lib/summaryGenerator.js +3 -2
  199. package/lib/summaryGenerator.js.map +1 -1
  200. package/lib/summaryManager.d.ts.map +1 -1
  201. package/lib/summaryManager.js +10 -6
  202. package/lib/summaryManager.js.map +1 -1
  203. package/package.json +32 -71
  204. package/prettier.config.cjs +8 -0
  205. package/src/blobManager.ts +74 -19
  206. package/src/containerRuntime.ts +286 -369
  207. package/src/dataStore.ts +13 -1
  208. package/src/dataStoreContext.ts +100 -76
  209. package/src/dataStoreContexts.ts +1 -1
  210. package/src/dataStores.ts +61 -22
  211. package/src/garbageCollection.ts +282 -163
  212. package/src/garbageCollectionConstants.ts +35 -0
  213. package/src/gcSweepReadyUsageDetection.ts +3 -11
  214. package/src/index.ts +9 -8
  215. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +42 -28
  216. package/src/opLifecycle/definitions.ts +44 -0
  217. package/src/opLifecycle/index.ts +17 -0
  218. package/src/opLifecycle/opCompressor.ts +64 -0
  219. package/src/opLifecycle/opDecompressor.ts +84 -0
  220. package/src/opLifecycle/opSplitter.ts +78 -0
  221. package/src/opLifecycle/outbox.ts +204 -0
  222. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  223. package/src/packageVersion.ts +1 -1
  224. package/src/pendingStateManager.ts +57 -96
  225. package/src/runningSummarizer.ts +11 -3
  226. package/src/scheduleManager.ts +1 -0
  227. package/src/summarizer.ts +6 -6
  228. package/src/summarizerClientElection.ts +1 -1
  229. package/src/summarizerHeuristics.ts +0 -3
  230. package/src/summarizerTypes.ts +20 -7
  231. package/src/summaryFormat.ts +5 -3
  232. package/src/summaryGenerator.ts +3 -2
  233. package/src/summaryManager.ts +18 -7
  234. package/dist/batchManager.d.ts +0 -37
  235. package/dist/batchManager.d.ts.map +0 -1
  236. package/dist/batchManager.js.map +0 -1
  237. package/lib/batchManager.d.ts +0 -37
  238. package/lib/batchManager.d.ts.map +0 -1
  239. package/lib/batchManager.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.DefaultSummaryConfiguration = exports.ContainerMessageType = void 0;
3
+ exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.isRuntimeMessage = exports.RuntimeMessage = exports.CompressionAlgorithms = 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");
@@ -18,7 +18,6 @@ const summarizer_1 = require("./summarizer");
18
18
  const summaryManager_1 = require("./summaryManager");
19
19
  const connectionTelemetry_1 = require("./connectionTelemetry");
20
20
  const pendingStateManager_1 = require("./pendingStateManager");
21
- const batchManager_1 = require("./batchManager");
22
21
  const packageVersion_1 = require("./packageVersion");
23
22
  const blobManager_1 = require("./blobManager");
24
23
  const dataStores_1 = require("./dataStores");
@@ -29,10 +28,12 @@ const summarizerClientElection_1 = require("./summarizerClientElection");
29
28
  const throttler_1 = require("./throttler");
30
29
  const runWhileConnectedCoordinator_1 = require("./runWhileConnectedCoordinator");
31
30
  const garbageCollection_1 = require("./garbageCollection");
31
+ const garbageCollectionConstants_1 = require("./garbageCollectionConstants");
32
32
  const dataStore_1 = require("./dataStore");
33
33
  const batchTracker_1 = require("./batchTracker");
34
34
  const serializedSnapshotStorage_1 = require("./serializedSnapshotStorage");
35
35
  const scheduleManager_1 = require("./scheduleManager");
36
+ const opLifecycle_1 = require("./opLifecycle");
36
37
  var ContainerMessageType;
37
38
  (function (ContainerMessageType) {
38
39
  // An op to be delivered to store
@@ -61,6 +62,7 @@ exports.DefaultSummaryConfiguration = {
61
62
  summarizerClientElection: false,
62
63
  nonRuntimeOpWeight: 0.1,
63
64
  runtimeOpWeight: 1.0,
65
+ nonRuntimeHeuristicThreshold: 20,
64
66
  };
65
67
  /**
66
68
  * Accepted header keys for requests coming to the runtime.
@@ -77,8 +79,20 @@ var RuntimeHeaders;
77
79
  /** True if the request is coming from an IFluidHandle. */
78
80
  RuntimeHeaders["viaHandle"] = "viaHandle";
79
81
  })(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
82
+ /**
83
+ * Available compression algorithms for op compression.
84
+ */
85
+ var CompressionAlgorithms;
86
+ (function (CompressionAlgorithms) {
87
+ CompressionAlgorithms["lz4"] = "lz4";
88
+ })(CompressionAlgorithms = exports.CompressionAlgorithms || (exports.CompressionAlgorithms = {}));
80
89
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
81
90
  const defaultFlushMode = runtime_definitions_1.FlushMode.TurnBased;
91
+ // The actual limit is 1Mb (socket.io and Kafka limits)
92
+ // We can't estimate it fully, as we
93
+ // - do not know what properties relay service will add
94
+ // - we do not stringify final op, thus we do not know how much escaping will be added.
95
+ const defaultMaxBatchSizeInBytes = 950 * 1024;
82
96
  /**
83
97
  * @deprecated - use ContainerRuntimeMessage instead
84
98
  */
@@ -96,45 +110,9 @@ var RuntimeMessage;
96
110
  * @deprecated - please use version in driver-utils
97
111
  */
98
112
  function isRuntimeMessage(message) {
99
- if (Object.values(RuntimeMessage).includes(message.type)) {
100
- return true;
101
- }
102
- return false;
113
+ return Object.values(RuntimeMessage).includes(message.type);
103
114
  }
104
115
  exports.isRuntimeMessage = isRuntimeMessage;
105
- /**
106
- * Unpacks runtime messages
107
- *
108
- * @remarks This API makes no promises regarding backward-compatability. This is internal API.
109
- * @param message - message (as it observed in storage / service)
110
- * @returns unpacked runtime message
111
- *
112
- * @internal
113
- */
114
- function unpackRuntimeMessage(message) {
115
- if (message.type === protocol_definitions_1.MessageType.Operation) {
116
- // legacy op format?
117
- if (message.contents.address !== undefined && message.contents.type === undefined) {
118
- message.type = ContainerMessageType.FluidDataStoreOp;
119
- }
120
- else {
121
- // new format
122
- const innerContents = message.contents;
123
- (0, common_utils_1.assert)(innerContents.type !== undefined, 0x121 /* "Undefined inner contents type!" */);
124
- message.type = innerContents.type;
125
- message.contents = innerContents.contents;
126
- }
127
- return true;
128
- }
129
- else {
130
- // Legacy format, but it's already "unpacked",
131
- // i.e. message.type is actually ContainerMessageType.
132
- // Or it's non-runtime message.
133
- // Nothing to do in such case.
134
- return false;
135
- }
136
- }
137
- exports.unpackRuntimeMessage = unpackRuntimeMessage;
138
116
  /**
139
117
  * Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
140
118
  * special-case for document dirty state. Ultimately we should have no special-cases from the
@@ -161,8 +139,11 @@ exports.getDeviceSpec = getDeviceSpec;
161
139
  * It will define the store level mappings.
162
140
  */
163
141
  class ContainerRuntime extends common_utils_1.TypedEventEmitter {
142
+ /**
143
+ * @internal
144
+ */
164
145
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
165
- var _a, _b, _c, _d;
146
+ var _a, _b, _c, _d, _e, _f;
166
147
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, exports.DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
167
148
  super();
168
149
  this.context = context;
@@ -175,7 +156,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
175
156
  this.summaryConfiguration = summaryConfiguration;
176
157
  this.defaultMaxConsecutiveReconnects = 7;
177
158
  this._orderSequentiallyCalls = 0;
178
- this.flushTrigger = false;
159
+ this.flushMicroTaskExists = false;
179
160
  this.savedOps = [];
180
161
  this.consecutiveReconnects = 0;
181
162
  this._disposed = false;
@@ -187,12 +168,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
187
168
  signalTimestamp: 0,
188
169
  trackingSignalSequenceNumber: undefined,
189
170
  };
190
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
191
- // but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
192
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
193
- // payloads. That number represents final (compressed) bits (once compression is implemented).
194
- this.pendingAttachBatch = new batchManager_1.BatchManager(64 * 1024);
195
- this.pendingBatch = new batchManager_1.BatchManager();
196
171
  this.summarizeOnDemand = (...args) => {
197
172
  if (this.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
198
173
  return this.summarizer.summarizeOnDemand(...args);
@@ -221,9 +196,29 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
221
196
  throw new container_utils_1.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
222
197
  }
223
198
  };
199
+ let loadSummaryNumber;
200
+ // Get the container creation metadata. For new container, we initialize these. For existing containers,
201
+ // get the values from the metadata blob.
202
+ if (existing) {
203
+ this.createContainerMetadata = {
204
+ createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
205
+ createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
206
+ };
207
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
208
+ // the count is reset to 0.
209
+ loadSummaryNumber = (_b = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _b !== void 0 ? _b : 0;
210
+ }
211
+ else {
212
+ this.createContainerMetadata = {
213
+ createContainerRuntimeVersion: packageVersion_1.pkgVersion,
214
+ createContainerTimestamp: Date.now(),
215
+ };
216
+ loadSummaryNumber = 0;
217
+ }
218
+ this.nextSummaryNumber = loadSummaryNumber + 1;
224
219
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
225
220
  this._connected = this.context.connected;
226
- this.chunkMap = new Map(chunks);
221
+ this.remoteMessageProcessor = new opLifecycle_1.RemoteMessageProcessor(new opLifecycle_1.OpSplitter(chunks), new opLifecycle_1.OpDecompressor());
227
222
  this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
228
223
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
229
224
  if (this.summaryConfiguration.state === "enabled") {
@@ -235,10 +230,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
235
230
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
236
231
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
237
232
  this.maxConsecutiveReconnects =
238
- (_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
233
+ (_c = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _c !== void 0 ? _c : this.defaultMaxConsecutiveReconnects;
239
234
  this._flushMode = runtimeOptions.flushMode;
240
235
  const pendingRuntimeState = context.pendingLocalState;
241
- const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
236
+ const baseSnapshot = (_d = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _d !== void 0 ? _d : context.baseSnapshot;
237
+ const maxSnapshotCacheDurationMs = (_f = (_e = this._storage) === null || _e === void 0 ? void 0 : _e.policies) === null || _f === void 0 ? void 0 : _f.maximumCacheDurationMs;
238
+ if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
239
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
240
+ // which dictates the value is either undefined or exactly 5 days in ms.
241
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
242
+ throw new container_utils_1.UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
243
+ }
242
244
  this.garbageCollector = garbageCollection_1.GarbageCollector.create({
243
245
  runtime: this,
244
246
  gcOptions: this.runtimeOptions.gcOptions,
@@ -246,6 +248,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
246
248
  baseLogger: this.mc.logger,
247
249
  existing,
248
250
  metadata,
251
+ createContainerMetadata: this.createContainerMetadata,
249
252
  isSummarizerClient: this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType,
250
253
  getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
251
254
  getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
@@ -271,28 +274,37 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
271
274
  gcDisabled: !this.garbageCollector.shouldRunGC,
272
275
  });
273
276
  if (baseSnapshot) {
274
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
277
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
275
278
  }
276
- 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);
279
+ 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));
277
280
  this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
278
281
  if (!this.disposed) {
279
282
  this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
280
283
  }
281
284
  }, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
282
285
  this.scheduleManager = new scheduleManager_1.ScheduleManager(context.deltaManager, this, () => this.clientId, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
283
- this.deltaSender = this.deltaManager;
284
286
  this.pendingStateManager = new pendingStateManager_1.PendingStateManager({
285
287
  applyStashedOp: this.applyStashedOp.bind(this),
286
288
  clientId: () => this.clientId,
287
289
  close: this.closeFn,
288
290
  connected: () => this.connected,
289
291
  flush: this.flush.bind(this),
290
- flushMode: () => this.flushMode,
291
292
  reSubmit: this.reSubmit.bind(this),
292
- setFlushMode: (mode) => this.setFlushMode(mode),
293
- }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
293
+ rollback: this.rollback.bind(this),
294
+ orderSequentially: this.orderSequentially.bind(this),
295
+ }, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
296
+ this.outbox = new opLifecycle_1.Outbox({
297
+ shouldSend: () => this.canSendOps(),
298
+ pendingStateManager: this.pendingStateManager,
299
+ containerContext: this.context,
300
+ compressor: new opLifecycle_1.OpCompressor(this.mc.logger),
301
+ config: {
302
+ compressionOptions: runtimeOptions.compressionOptions,
303
+ maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
304
+ },
305
+ });
294
306
  this.context.quorum.on("removeMember", (clientId) => {
295
- this.clearPartialChunks(clientId);
307
+ this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
296
308
  });
297
309
  this.summaryCollection = new summaryCollection_1.SummaryCollection(this.deltaManager, this.logger);
298
310
  this.dirtyContainer = this.context.attachState !== container_definitions_1.AttachState.Attached
@@ -314,7 +326,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
314
326
  // if summaries are enabled and we are not the summarizer client.
315
327
  const defaultAction = () => {
316
328
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
317
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
329
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
318
330
  // unregister default to no log on every op after falling behind
319
331
  // and register summary ack handler to re-register this handler
320
332
  // after successful summary
@@ -360,26 +372,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
360
372
  });
361
373
  // logging hardware telemetry
362
374
  logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
363
- let loadSummaryNumber;
364
- // Get the container creation metadata. For new container, we initialize these. For existing containers,
365
- // get the values from the metadata blob.
366
- if (existing) {
367
- this.createContainerMetadata = {
368
- createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
369
- createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
370
- };
371
- // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
372
- // the count is reset to 0.
373
- loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
374
- }
375
- else {
376
- this.createContainerMetadata = {
377
- createContainerRuntimeVersion: packageVersion_1.pkgVersion,
378
- createContainerTimestamp: Date.now(),
379
- };
380
- loadSummaryNumber = 0;
381
- }
382
- this.nextSummaryNumber = loadSummaryNumber + 1;
383
375
  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 }));
384
376
  (0, connectionTelemetry_1.ReportOpPerfTelemetry)(this.context.clientId, this.deltaManager, this.logger);
385
377
  (0, batchTracker_1.BindBatchTracker)(this, this.logger);
@@ -393,8 +385,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
393
385
  * @param requestHandler - Request handlers for the container runtime
394
386
  * @param runtimeOptions - Additional options to be passed to the runtime
395
387
  * @param existing - (optional) When loading from an existing snapshot. Precedes context.existing if provided
388
+ * @param containerRuntimeCtor - (optional) Constructor to use to create the ContainerRuntime instance. This
389
+ * allows mixin classes to leverage this method to define their own async initializer.
396
390
  */
397
- static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing) {
391
+ static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing, containerRuntimeCtor = ContainerRuntime) {
398
392
  var _a, _b, _c;
399
393
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
400
394
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
@@ -405,7 +399,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
405
399
  runtimeVersion: packageVersion_1.pkgVersion,
406
400
  },
407
401
  });
408
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
402
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {
403
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
404
+ compressionAlgorithm: CompressionAlgorithms.lz4
405
+ }, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
409
406
  const pendingRuntimeState = context.pendingLocalState;
410
407
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
411
408
  const storage = !pendingRuntimeState ?
@@ -454,19 +451,22 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
454
451
  }
455
452
  }
456
453
  }
457
- const runtime = new ContainerRuntime(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
454
+ const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
458
455
  summaryOptions,
459
456
  gcOptions,
460
457
  loadSequenceNumberVerification,
461
458
  flushMode,
462
459
  enableOfflineLoad,
460
+ compressionOptions,
461
+ maxBatchSizeInBytes,
463
462
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
464
463
  if (pendingRuntimeState) {
465
464
  await runtime.processSavedOps(pendingRuntimeState);
466
465
  // delete these once runtime has seen them to save space
467
466
  pendingRuntimeState.savedOps = [];
468
467
  }
469
- await runtime.getSnapshotBlobs();
468
+ // Initialize the base state of the runtime before it's returned.
469
+ await runtime.initializeBaseState();
470
470
  return runtime;
471
471
  }
472
472
  get options() {
@@ -515,9 +515,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
515
515
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
516
516
  }
517
517
  get disposed() { return this._disposed; }
518
- get emptyBatch() {
519
- return this.pendingBatch.empty && this.pendingAttachBatch.empty;
520
- }
521
518
  get summarizer() {
522
519
  (0, common_utils_1.assert)(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
523
520
  return this._summarizer;
@@ -573,6 +570,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
573
570
  ? this.summaryConfiguration.initialSummarizerDelayMs
574
571
  : 0;
575
572
  }
573
+ /**
574
+ * Initializes the state from the base snapshot this container runtime loaded from.
575
+ */
576
+ async initializeBaseState() {
577
+ await this.initializeBaseSnapshotBlobs();
578
+ await this.garbageCollector.initializeBaseState();
579
+ }
576
580
  dispose(error) {
577
581
  var _a;
578
582
  if (this._disposed) {
@@ -671,13 +675,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
671
675
  return (_a = this.dataStores.aliases.get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
672
676
  }
673
677
  async getDataStoreFromRequest(id, request) {
674
- var _a, _b, _c;
678
+ var _a, _b, _c, _d, _e;
675
679
  const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
676
680
  ? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
677
681
  : true;
682
+ const viaHandle = typeof ((_c = request.headers) === null || _c === void 0 ? void 0 : _c[RuntimeHeaders.viaHandle]) === "boolean"
683
+ ? (_d = request.headers) === null || _d === void 0 ? void 0 : _d[RuntimeHeaders.viaHandle]
684
+ : false;
678
685
  await this.dataStores.waitIfPendingAlias(id);
679
686
  const internalId = this.internalId(id);
680
- const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
687
+ const dataStoreContext = await this.dataStores.getDataStore(internalId, wait, viaHandle);
681
688
  /**
682
689
  * If GC should run and this an external app request with "externalRequest" header, we need to return
683
690
  * an error if the data store being requested is marked as unreferenced as per the data store's base
@@ -686,7 +693,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
686
693
  * This is a workaround to handle scenarios where a data store shared with an external app is deleted
687
694
  * and marked as unreferenced by GC. Returning an error will fail to load the data store for the app.
688
695
  */
689
- if (((_c = request.headers) === null || _c === void 0 ? void 0 : _c[RuntimeHeaders.externalRequest]) && this.garbageCollector.shouldRunGC) {
696
+ if (((_e = request.headers) === null || _e === void 0 ? void 0 : _e[RuntimeHeaders.externalRequest]) && this.garbageCollector.shouldRunGC) {
690
697
  // The data store is referenced if used routes in the base summary has a route to self.
691
698
  // Older documents may not have used routes in the summary. They are considered referenced.
692
699
  const usedRoutes = (await dataStoreContext.getBaseGCDetails()).usedRoutes;
@@ -715,8 +722,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
715
722
  addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
716
723
  var _a;
717
724
  this.addMetadataToSummary(summaryTree);
718
- if (this.chunkMap.size > 0) {
719
- const content = JSON.stringify([...this.chunkMap]);
725
+ if (this.remoteMessageProcessor.partialMessages.size > 0) {
726
+ const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
720
727
  (0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.chunksBlobName, content);
721
728
  }
722
729
  const dataStoreAliases = this.dataStores.aliases;
@@ -733,11 +740,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
733
740
  if (Object.keys(blobManagerSummary.summary.tree).length > 0) {
734
741
  (0, runtime_utils_1.addTreeToSummary)(summaryTree, summaryFormat_1.blobsTreeName, blobManagerSummary);
735
742
  }
736
- if (this.garbageCollector.writeDataAtRoot) {
737
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
738
- if (gcSummary !== undefined) {
739
- (0, runtime_utils_1.addSummarizeResultToSummary)(summaryTree, garbageCollection_1.gcTreeKey, gcSummary);
740
- }
743
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
744
+ if (gcSummary !== undefined) {
745
+ (0, runtime_utils_1.addSummarizeResultToSummary)(summaryTree, garbageCollectionConstants_1.gcTreeKey, gcSummary);
741
746
  }
742
747
  }
743
748
  // Track how many times the container tries to reconnect with pending messages.
@@ -882,31 +887,21 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
882
887
  process(messageArg, local) {
883
888
  var _a;
884
889
  this.verifyNotClosed();
885
- // Do shallow copy of message, as methods below will modify it.
886
- // There might be multiple container instances receiving same message
887
- // We do not need to make deep copy, as each layer will just replace message.content itself,
888
- // but would not modify contents details
889
- let message = Object.assign({}, messageArg);
890
- // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
891
- // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
892
- // Old ops may contain empty string (I assume noops).
893
- if (typeof message.contents === "string" && message.contents !== "") {
894
- message.contents = JSON.parse(message.contents);
895
- }
896
- // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
897
- // This format was not shipped to production workflows.
898
- const runtimeMessage = unpackRuntimeMessage(message);
899
890
  if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
900
891
  this.savedOps.push(messageArg);
901
892
  }
893
+ // Whether or not the message is actually a runtime message.
894
+ // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
895
+ // or something different, like a system message.
896
+ const runtimeMessage = messageArg.type === protocol_definitions_1.MessageType.Operation;
897
+ // Do shallow copy of message, as the processing flow will modify it.
898
+ const messageCopy = Object.assign({}, messageArg);
899
+ const message = this.remoteMessageProcessor.process(messageCopy);
902
900
  // Surround the actual processing of the operation with messages to the schedule manager indicating
903
901
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
904
902
  // messages once a batch has been fully processed.
905
903
  this.scheduleManager.beforeOpProcessing(message);
906
904
  try {
907
- // Chunk processing must come first given that we will transform the message to the unchunked version
908
- // once all pieces are available
909
- message = this.processRemoteChunkedMessage(message);
910
905
  let localOpMetadata;
911
906
  if (local && runtimeMessage) {
912
907
  localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
@@ -1010,112 +1005,31 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1010
1005
  async getRootDataStoreChannel(id, wait = true) {
1011
1006
  await this.dataStores.waitIfPendingAlias(id);
1012
1007
  const internalId = this.internalId(id);
1013
- const context = await this.dataStores.getDataStore(internalId, wait);
1008
+ const context = await this.dataStores.getDataStore(internalId, wait, false /* viaHandle */);
1014
1009
  (0, common_utils_1.assert)(await context.isRoot(), 0x12b /* "did not get root data store" */);
1015
1010
  return context.realize();
1016
1011
  }
1017
- setFlushMode(mode) {
1018
- if (mode === this._flushMode) {
1019
- return;
1020
- }
1021
- this.mc.logger.sendTelemetryEvent({
1022
- eventName: "FlushMode Updated",
1023
- old: this._flushMode,
1024
- new: mode,
1025
- });
1026
- // Flush any pending batches if switching to immediate
1027
- if (mode === runtime_definitions_1.FlushMode.Immediate) {
1028
- this.flush();
1029
- }
1030
- this._flushMode = mode;
1031
- // Let the PendingStateManager know that FlushMode has been updated.
1032
- this.pendingStateManager.onFlushModeUpdated(mode);
1033
- }
1012
+ /**
1013
+ * Flush the pending ops manually.
1014
+ * This method is expected to be called at the end of a batch.
1015
+ */
1034
1016
  flush() {
1035
1017
  (0, common_utils_1.assert)(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1036
- this.flushBatch(this.pendingAttachBatch.popBatch());
1037
- this.flushBatch(this.pendingBatch.popBatch());
1038
- (0, common_utils_1.assert)(this.emptyBatch, 0x3cf /* reentrancy */);
1039
- }
1040
- flushBatch(batch) {
1041
- const length = batch.length;
1042
- if (length > 1) {
1043
- batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
1044
- batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
1045
- // This assert fires for the following reason (there might be more cases like that):
1046
- // AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
1047
- // i.e. in the middle of op processing!
1048
- // Sending ops while processing ops is not good idea - it's not defined when
1049
- // referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
1050
- // If we send ops in response to processing multiple ops, then we for sure hit this assert!
1051
- // Tracked via ADO #1834
1052
- // assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
1053
- // "Batch should be generated synchronously, without processing ops in the middle!");
1054
- }
1055
- let clientSequenceNumber = -1;
1056
- // Did we disconnect in the middle of turn-based batch?
1057
- // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
1058
- if (this.canSendOps()) {
1059
- if (this.context.submitBatchFn !== undefined) {
1060
- const batchToSend = [];
1061
- for (const message of batch) {
1062
- batchToSend.push({ contents: message.contents, metadata: message.metadata });
1063
- }
1064
- // returns clientSequenceNumber of last message in a batch
1065
- clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1066
- }
1067
- else {
1068
- // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1069
- // version that has support for batches (submitBatchFn)
1070
- for (const message of batch) {
1071
- clientSequenceNumber = this.context.submitFn(protocol_definitions_1.MessageType.Operation, message.deserializedContent, true, // batch
1072
- message.metadata);
1073
- }
1074
- this.deltaSender.flush();
1075
- }
1076
- // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
1077
- clientSequenceNumber -= batch.length - 1;
1078
- (0, common_utils_1.assert)(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
1079
- }
1080
- // Let the PendingStateManager know that a message was submitted.
1081
- // In future, need to shift toward keeping batch as a whole!
1082
- for (const message of batch) {
1083
- this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
1084
- clientSequenceNumber++;
1085
- }
1086
- this.pendingStateManager.onFlush();
1018
+ this.outbox.flush();
1019
+ (0, common_utils_1.assert)(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1087
1020
  }
1088
1021
  orderSequentially(callback) {
1089
- // If flush mode is already TurnBased we are either
1090
- // nested in another orderSequentially, or
1091
- // the app is flushing manually, in which
1092
- // case this invocation doesn't own
1093
- // flushing.
1094
- if (this.flushMode === runtime_definitions_1.FlushMode.TurnBased) {
1095
- this.trackOrderSequentiallyCalls(callback);
1096
- return;
1097
- }
1098
- const savedFlushMode = this.flushMode;
1099
- this.setFlushMode(runtime_definitions_1.FlushMode.TurnBased);
1100
- try {
1101
- this.trackOrderSequentiallyCalls(callback);
1102
- this.flush();
1103
- }
1104
- finally {
1105
- this.setFlushMode(savedFlushMode);
1106
- }
1107
- }
1108
- trackOrderSequentiallyCalls(callback) {
1109
1022
  let checkpoint;
1023
+ let result;
1110
1024
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1111
1025
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1112
1026
  // 1. It would not help, as we flush attach ops as they become available.
1113
1027
  // 2. There is no way to undo process of data store creation.
1114
- checkpoint = this.pendingBatch.checkpoint();
1028
+ checkpoint = this.outbox.checkpoint().mainBatch;
1115
1029
  }
1116
1030
  try {
1117
1031
  this._orderSequentiallyCalls++;
1118
- callback();
1032
+ result = callback();
1119
1033
  }
1120
1034
  catch (error) {
1121
1035
  if (checkpoint) {
@@ -1140,6 +1054,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1140
1054
  finally {
1141
1055
  this._orderSequentiallyCalls--;
1142
1056
  }
1057
+ if (this.flushMode === runtime_definitions_1.FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1058
+ this.flush();
1059
+ }
1060
+ return result;
1143
1061
  }
1144
1062
  async createDataStore(pkg) {
1145
1063
  const internalId = (0, uuid_1.v4)();
@@ -1166,6 +1084,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1166
1084
  canSendOps() {
1167
1085
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1168
1086
  }
1087
+ /**
1088
+ * Are we in the middle of batching ops together?
1089
+ */
1090
+ currentlyBatching() {
1091
+ return this.flushMode === runtime_definitions_1.FlushMode.TurnBased || this._orderSequentiallyCalls !== 0;
1092
+ }
1169
1093
  getQuorum() {
1170
1094
  return this.context.quorum;
1171
1095
  }
@@ -1316,28 +1240,33 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1316
1240
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
1317
1241
  * After GC has run, called to notify this container's nodes of routes that are used in it.
1318
1242
  * @param usedRoutes - The routes that are used in all nodes in this Container.
1319
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
1320
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
1321
1243
  */
1322
- updateUsedRoutes(usedRoutes, gcTimestamp) {
1244
+ updateUsedRoutes(usedRoutes) {
1323
1245
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
1324
1246
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1325
1247
  // always referenced, so the used routes is only self-route (empty string).
1326
1248
  this.summarizerNode.updateUsedRoutes([""]);
1249
+ const blobManagerUsedRoutes = [];
1327
1250
  const dataStoreUsedRoutes = [];
1328
1251
  for (const route of usedRoutes) {
1329
- if (route.split("/")[1] !== blobManager_1.BlobManager.basePath) {
1252
+ if (this.isBlobPath(route)) {
1253
+ blobManagerUsedRoutes.push(route);
1254
+ }
1255
+ else {
1330
1256
  dataStoreUsedRoutes.push(route);
1331
1257
  }
1332
1258
  }
1333
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
1259
+ this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
1260
+ this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1334
1261
  }
1335
1262
  /**
1336
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
1337
- * scenarios with accessing deleted content.
1338
- * @param unusedRoutes - The routes that are unused in all data stores in this Container.
1263
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
1264
+ * tombstones.
1265
+ * @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
1266
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
1267
+ * are deleted.
1339
1268
  */
1340
- deleteUnusedRoutes(unusedRoutes) {
1269
+ updateUnusedRoutes(unusedRoutes, tombstone) {
1341
1270
  const blobManagerUnusedRoutes = [];
1342
1271
  const dataStoreUnusedRoutes = [];
1343
1272
  for (const route of unusedRoutes) {
@@ -1348,8 +1277,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1348
1277
  dataStoreUnusedRoutes.push(route);
1349
1278
  }
1350
1279
  }
1351
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
1352
- this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
1280
+ this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
1281
+ this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
1353
1282
  }
1354
1283
  /**
1355
1284
  * Returns a server generated referenced timestamp to be used to track unreferenced nodes by GC.
@@ -1429,21 +1358,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1429
1358
  const summaryNumberLogger = telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, {
1430
1359
  all: { summaryNumber },
1431
1360
  });
1432
- (0, common_utils_1.assert)(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1361
+ (0, common_utils_1.assert)(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1433
1362
  let latestSnapshotVersionId;
1434
1363
  if (refreshLatestAck) {
1435
1364
  const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
1436
1365
  const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
1437
1366
  latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
1438
- if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
1439
- // We need to catch up to the latest summary's reference sequence number before pausing.
1440
- await telemetry_utils_1.PerformanceEvent.timedExecAsync(summaryNumberLogger, {
1441
- eventName: "WaitingForSeq",
1442
- lastSequenceNumber: this.deltaManager.lastSequenceNumber,
1443
- targetSequenceNumber: latestSnapshotRefSeq,
1444
- lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
1445
- }, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
1446
- }
1367
+ // We might need to catch up to the latest summary's reference sequence number before pausing.
1368
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryNumberLogger);
1447
1369
  }
1448
1370
  try {
1449
1371
  await this.deltaManager.inbound.pause();
@@ -1527,8 +1449,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1527
1449
  const dataStoreTree = summaryTree.tree[runtime_definitions_1.channelsTreeName];
1528
1450
  (0, common_utils_1.assert)(dataStoreTree.type === protocol_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
1529
1451
  const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === protocol_definitions_1.SummaryType.Handle).length;
1530
- const gcSummaryTreeStats = summaryTree.tree[garbageCollection_1.gcTreeKey]
1531
- ? (0, runtime_utils_1.calculateStats)(summaryTree.tree[garbageCollection_1.gcTreeKey])
1452
+ const gcSummaryTreeStats = summaryTree.tree[garbageCollectionConstants_1.gcTreeKey]
1453
+ ? (0, runtime_utils_1.calculateStats)(summaryTree.tree[garbageCollectionConstants_1.gcTreeKey])
1532
1454
  : undefined;
1533
1455
  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);
1534
1456
  const generateSummaryData = {
@@ -1609,40 +1531,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1609
1531
  this.deltaManager.inbound.resume();
1610
1532
  }
1611
1533
  }
1612
- processRemoteChunkedMessage(message) {
1613
- if (message.type !== ContainerMessageType.ChunkedOp) {
1614
- return message;
1615
- }
1616
- const clientId = message.clientId;
1617
- const chunkedContent = message.contents;
1618
- this.addChunk(clientId, chunkedContent);
1619
- if (chunkedContent.chunkId === chunkedContent.totalChunks) {
1620
- const newMessage = Object.assign({}, message);
1621
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1622
- const serializedContent = this.chunkMap.get(clientId).join("");
1623
- newMessage.contents = JSON.parse(serializedContent);
1624
- newMessage.type = chunkedContent.originalType;
1625
- this.clearPartialChunks(clientId);
1626
- return newMessage;
1627
- }
1628
- return message;
1629
- }
1630
- addChunk(clientId, chunkedContent) {
1631
- let map = this.chunkMap.get(clientId);
1632
- if (map === undefined) {
1633
- map = [];
1634
- this.chunkMap.set(clientId, map);
1635
- }
1636
- (0, common_utils_1.assert)(chunkedContent.chunkId === map.length + 1, 0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
1637
- map.push(chunkedContent.contents);
1638
- }
1639
- clearPartialChunks(clientId) {
1640
- if (this.chunkMap.has(clientId)) {
1641
- this.chunkMap.delete(clientId);
1642
- }
1643
- }
1644
1534
  hasPendingMessages() {
1645
- return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
1535
+ return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
1646
1536
  }
1647
1537
  updateDocumentDirtyState(dirty) {
1648
1538
  if (this.attachState !== container_definitions_1.AttachState.Attached) {
@@ -1686,7 +1576,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1686
1576
  const deserializedContent = { type, contents };
1687
1577
  const serializedContent = JSON.stringify(deserializedContent);
1688
1578
  if (this.deltaManager.readOnlyInfo.readonly) {
1689
- this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
1579
+ this.logger.sendTelemetryEvent({ eventName: "SubmitOpInReadonly", connected: this.connected });
1690
1580
  }
1691
1581
  const message = {
1692
1582
  contents: serializedContent,
@@ -1716,44 +1606,24 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1716
1606
  // issue than sending.
1717
1607
  // Please note that this does not change file format, so it can be disabled in the future if this
1718
1608
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
1719
- if (type === ContainerMessageType.Attach &&
1609
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
1720
1610
  this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
1721
- if (!this.pendingAttachBatch.push(message)) {
1722
- // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1723
- // when queue is not empty.
1724
- // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
1725
- this.flushBatch(this.pendingAttachBatch.popBatch());
1726
- if (!this.pendingAttachBatch.push(message)) {
1727
- throw new container_utils_1.GenericError("BatchTooLarge",
1728
- /* error */ undefined, {
1729
- opSize: message.contents.length,
1730
- count: this.pendingAttachBatch.length,
1731
- limit: this.pendingAttachBatch.limit,
1732
- });
1733
- }
1734
- }
1611
+ this.outbox.submitAttach(message);
1735
1612
  }
1736
1613
  else {
1737
- if (!this.pendingBatch.push(message)) {
1738
- throw new container_utils_1.GenericError("BatchTooLarge",
1739
- /* error */ undefined, {
1740
- opSize: message.contents.length,
1741
- count: this.pendingBatch.length,
1742
- limit: this.pendingBatch.limit,
1743
- });
1744
- }
1614
+ this.outbox.submit(message);
1745
1615
  }
1746
- if (this._flushMode !== runtime_definitions_1.FlushMode.TurnBased) {
1616
+ if (!this.currentlyBatching()) {
1747
1617
  this.flush();
1748
1618
  }
1749
- else if (!this.flushTrigger) {
1750
- this.flushTrigger = true;
1619
+ else if (!this.flushMicroTaskExists) {
1620
+ this.flushMicroTaskExists = true;
1751
1621
  // Queue a microtask to detect the end of the turn and force a flush.
1752
1622
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1753
1623
  Promise.resolve().then(() => {
1754
- this.flushTrigger = false;
1624
+ this.flushMicroTaskExists = false;
1755
1625
  this.flush();
1756
- });
1626
+ }).catch((error) => { this.closeFn(error); });
1757
1627
  }
1758
1628
  }
1759
1629
  catch (error) {
@@ -1768,7 +1638,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1768
1638
  this.verifyNotClosed();
1769
1639
  (0, common_utils_1.assert)(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
1770
1640
  // System message should not be sent in the middle of the batch.
1771
- (0, common_utils_1.assert)(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
1641
+ (0, common_utils_1.assert)(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
1772
1642
  // back-compat: ADO #1385: Make this call unconditional in the future
1773
1643
  return this.context.submitSummaryFn !== undefined
1774
1644
  ? this.context.submitSummaryFn(contents)
@@ -1823,18 +1693,41 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1823
1693
  throw new Error(`Can't rollback ${type}`);
1824
1694
  }
1825
1695
  }
1696
+ async waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryLogger) {
1697
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
1698
+ // We need to catch up to the latest summary's reference sequence number before proceeding.
1699
+ await telemetry_utils_1.PerformanceEvent.timedExecAsync(summaryLogger, {
1700
+ eventName: "WaitingForSeq",
1701
+ lastSequenceNumber: this.deltaManager.lastSequenceNumber,
1702
+ targetSequenceNumber: latestSnapshotRefSeq,
1703
+ lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
1704
+ }, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
1705
+ }
1706
+ }
1826
1707
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1827
- async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1708
+ async refreshLatestSummaryAck(options) {
1709
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
1828
1710
  const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
1829
1711
  // The call to fetch the snapshot is very expensive and not always needed.
1830
1712
  // It should only be done by the summarizerNode, if required.
1713
+ // When fetching from storage we will always get the latest version and do not use the ackHandle.
1831
1714
  const snapshotTreeFetcher = async () => {
1832
- const fetchResult = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
1715
+ const fetchResult = await this.fetchSnapshotFromStorage(null, summaryLogger, {
1833
1716
  eventName: "RefreshLatestSummaryGetSnapshot",
1834
1717
  ackHandle,
1835
1718
  summaryRefSeq,
1836
- fetchLatest: false,
1719
+ fetchLatest: true,
1720
+ });
1721
+ const latestSnapshotRefSeq = await (0, runtime_utils_1.seqFromTree)(fetchResult.snapshotTree, readAndParseBlob);
1722
+ summaryLogger.sendTelemetryEvent({
1723
+ eventName: "LatestSummaryRetrieved",
1724
+ ackHandle,
1725
+ lastSequenceNumber: latestSnapshotRefSeq,
1726
+ targetSequenceNumber: summaryRefSeq,
1837
1727
  });
1728
+ // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
1729
+ // wait for the delta manager to catch up before refreshing the latest Summary.
1730
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryLogger);
1838
1731
  return fetchResult.snapshotTree;
1839
1732
  };
1840
1733
  const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, snapshotTreeFetcher, readAndParseBlob, summaryLogger);
@@ -1879,7 +1772,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1879
1772
  this.baseSnapshotBlobs = serializedSnapshotStorage_1.SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
1880
1773
  }
1881
1774
  }
1882
- async getSnapshotBlobs() {
1775
+ async initializeBaseSnapshotBlobs() {
1883
1776
  var _a;
1884
1777
  if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
1885
1778
  this.attachState !== container_definitions_1.AttachState.Attached || this.context.pendingLocalState) {
@@ -1897,6 +1790,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1897
1790
  // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
1898
1791
  // to close current batch.
1899
1792
  this.flush();
1793
+ if (this._orderSequentiallyCalls !== 0) {
1794
+ throw new container_utils_1.UsageError("can't get state during orderSequentially");
1795
+ }
1900
1796
  const previousPendingState = this.context.pendingLocalState;
1901
1797
  if (previousPendingState) {
1902
1798
  return {
@@ -1964,6 +1860,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1964
1860
  throw new container_utils_1.UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
1965
1861
  }
1966
1862
  }
1863
+ if (configuration.minIdleTime > configuration.maxIdleTime) {
1864
+ throw new container_utils_1.UsageError(`"minIdleTime" [${configuration.minIdleTime}] cannot be greater than "maxIdleTime" [${configuration.maxIdleTime}]`);
1865
+ }
1967
1866
  }
1968
1867
  }
1969
1868
  exports.ContainerRuntime = ContainerRuntime;
@@ -1974,12 +1873,18 @@ exports.ContainerRuntime = ContainerRuntime;
1974
1873
  const waitForSeq = async (deltaManager, targetSeq) => new Promise((resolve, reject) => {
1975
1874
  // TODO: remove cast to any when actual event is determined
1976
1875
  deltaManager.on("closed", reject);
1977
- const handleOp = (message) => {
1978
- if (message.sequenceNumber >= targetSeq) {
1979
- resolve();
1980
- deltaManager.off("op", handleOp);
1981
- }
1982
- };
1983
- deltaManager.on("op", handleOp);
1876
+ // If we already reached target sequence number, simply resolve the promise.
1877
+ if (deltaManager.lastSequenceNumber >= targetSeq) {
1878
+ resolve();
1879
+ }
1880
+ else {
1881
+ const handleOp = (message) => {
1882
+ if (message.sequenceNumber >= targetSeq) {
1883
+ resolve();
1884
+ deltaManager.off("op", handleOp);
1885
+ }
1886
+ };
1887
+ deltaManager.on("op", handleOp);
1888
+ }
1984
1889
  });
1985
1890
  //# sourceMappingURL=containerRuntime.js.map