@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
@@ -15,7 +15,6 @@ import { Summarizer } from "./summarizer";
15
15
  import { SummaryManager } from "./summaryManager";
16
16
  import { ReportOpPerfTelemetry, } from "./connectionTelemetry";
17
17
  import { PendingStateManager, } from "./pendingStateManager";
18
- import { BatchManager } from "./batchManager";
19
18
  import { pkgVersion } from "./packageVersion";
20
19
  import { BlobManager } from "./blobManager";
21
20
  import { DataStores, getSummaryForDatastores } from "./dataStores";
@@ -25,11 +24,13 @@ import { OrderedClientCollection, OrderedClientElection } from "./orderedClientE
25
24
  import { SummarizerClientElection, summarizerClientType } from "./summarizerClientElection";
26
25
  import { formExponentialFn, Throttler } from "./throttler";
27
26
  import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
28
- import { GarbageCollector, GCNodeType, gcTreeKey, } from "./garbageCollection";
27
+ import { GarbageCollector, GCNodeType, } from "./garbageCollection";
28
+ import { gcTreeKey, } from "./garbageCollectionConstants";
29
29
  import { channelToDataStore, isDataStoreAliasMessage, } from "./dataStore";
30
30
  import { BindBatchTracker } from "./batchTracker";
31
31
  import { SerializedSnapshotStorage } from "./serializedSnapshotStorage";
32
32
  import { ScheduleManager } from "./scheduleManager";
33
+ import { OpCompressor, OpDecompressor, Outbox, OpSplitter, RemoteMessageProcessor, } from "./opLifecycle";
33
34
  export var ContainerMessageType;
34
35
  (function (ContainerMessageType) {
35
36
  // An op to be delivered to store
@@ -58,6 +59,7 @@ export const DefaultSummaryConfiguration = {
58
59
  summarizerClientElection: false,
59
60
  nonRuntimeOpWeight: 0.1,
60
61
  runtimeOpWeight: 1.0,
62
+ nonRuntimeHeuristicThreshold: 20,
61
63
  };
62
64
  /**
63
65
  * Accepted header keys for requests coming to the runtime.
@@ -74,8 +76,20 @@ export var RuntimeHeaders;
74
76
  /** True if the request is coming from an IFluidHandle. */
75
77
  RuntimeHeaders["viaHandle"] = "viaHandle";
76
78
  })(RuntimeHeaders || (RuntimeHeaders = {}));
79
+ /**
80
+ * Available compression algorithms for op compression.
81
+ */
82
+ export var CompressionAlgorithms;
83
+ (function (CompressionAlgorithms) {
84
+ CompressionAlgorithms["lz4"] = "lz4";
85
+ })(CompressionAlgorithms || (CompressionAlgorithms = {}));
77
86
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
78
87
  const defaultFlushMode = FlushMode.TurnBased;
88
+ // The actual limit is 1Mb (socket.io and Kafka limits)
89
+ // We can't estimate it fully, as we
90
+ // - do not know what properties relay service will add
91
+ // - we do not stringify final op, thus we do not know how much escaping will be added.
92
+ const defaultMaxBatchSizeInBytes = 950 * 1024;
79
93
  /**
80
94
  * @deprecated - use ContainerRuntimeMessage instead
81
95
  */
@@ -93,42 +107,7 @@ export var RuntimeMessage;
93
107
  * @deprecated - please use version in driver-utils
94
108
  */
95
109
  export function isRuntimeMessage(message) {
96
- if (Object.values(RuntimeMessage).includes(message.type)) {
97
- return true;
98
- }
99
- return false;
100
- }
101
- /**
102
- * Unpacks runtime messages
103
- *
104
- * @remarks This API makes no promises regarding backward-compatability. This is internal API.
105
- * @param message - message (as it observed in storage / service)
106
- * @returns unpacked runtime message
107
- *
108
- * @internal
109
- */
110
- export function unpackRuntimeMessage(message) {
111
- if (message.type === MessageType.Operation) {
112
- // legacy op format?
113
- if (message.contents.address !== undefined && message.contents.type === undefined) {
114
- message.type = ContainerMessageType.FluidDataStoreOp;
115
- }
116
- else {
117
- // new format
118
- const innerContents = message.contents;
119
- assert(innerContents.type !== undefined, 0x121 /* "Undefined inner contents type!" */);
120
- message.type = innerContents.type;
121
- message.contents = innerContents.contents;
122
- }
123
- return true;
124
- }
125
- else {
126
- // Legacy format, but it's already "unpacked",
127
- // i.e. message.type is actually ContainerMessageType.
128
- // Or it's non-runtime message.
129
- // Nothing to do in such case.
130
- return false;
131
- }
110
+ return Object.values(RuntimeMessage).includes(message.type);
132
111
  }
133
112
  /**
134
113
  * Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
@@ -155,8 +134,11 @@ export function getDeviceSpec() {
155
134
  * It will define the store level mappings.
156
135
  */
157
136
  export class ContainerRuntime extends TypedEventEmitter {
137
+ /**
138
+ * @internal
139
+ */
158
140
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
159
- var _a, _b, _c, _d;
141
+ var _a, _b, _c, _d, _e, _f;
160
142
  if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
161
143
  super();
162
144
  this.context = context;
@@ -169,7 +151,7 @@ export class ContainerRuntime extends TypedEventEmitter {
169
151
  this.summaryConfiguration = summaryConfiguration;
170
152
  this.defaultMaxConsecutiveReconnects = 7;
171
153
  this._orderSequentiallyCalls = 0;
172
- this.flushTrigger = false;
154
+ this.flushMicroTaskExists = false;
173
155
  this.savedOps = [];
174
156
  this.consecutiveReconnects = 0;
175
157
  this._disposed = false;
@@ -181,12 +163,6 @@ export class ContainerRuntime extends TypedEventEmitter {
181
163
  signalTimestamp: 0,
182
164
  trackingSignalSequenceNumber: undefined,
183
165
  };
184
- // Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
185
- // but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
186
- // So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
187
- // payloads. That number represents final (compressed) bits (once compression is implemented).
188
- this.pendingAttachBatch = new BatchManager(64 * 1024);
189
- this.pendingBatch = new BatchManager();
190
166
  this.summarizeOnDemand = (...args) => {
191
167
  if (this.clientDetails.type === summarizerClientType) {
192
168
  return this.summarizer.summarizeOnDemand(...args);
@@ -215,9 +191,29 @@ export class ContainerRuntime extends TypedEventEmitter {
215
191
  throw new UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
216
192
  }
217
193
  };
194
+ let loadSummaryNumber;
195
+ // Get the container creation metadata. For new container, we initialize these. For existing containers,
196
+ // get the values from the metadata blob.
197
+ if (existing) {
198
+ this.createContainerMetadata = {
199
+ createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
200
+ createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
201
+ };
202
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
203
+ // the count is reset to 0.
204
+ loadSummaryNumber = (_b = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _b !== void 0 ? _b : 0;
205
+ }
206
+ else {
207
+ this.createContainerMetadata = {
208
+ createContainerRuntimeVersion: pkgVersion,
209
+ createContainerTimestamp: Date.now(),
210
+ };
211
+ loadSummaryNumber = 0;
212
+ }
213
+ this.nextSummaryNumber = loadSummaryNumber + 1;
218
214
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
219
215
  this._connected = this.context.connected;
220
- this.chunkMap = new Map(chunks);
216
+ this.remoteMessageProcessor = new RemoteMessageProcessor(new OpSplitter(chunks), new OpDecompressor());
221
217
  this.handleContext = new ContainerFluidHandleContext("", this);
222
218
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
223
219
  if (this.summaryConfiguration.state === "enabled") {
@@ -229,10 +225,17 @@ export class ContainerRuntime extends TypedEventEmitter {
229
225
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
230
226
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
231
227
  this.maxConsecutiveReconnects =
232
- (_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
228
+ (_c = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _c !== void 0 ? _c : this.defaultMaxConsecutiveReconnects;
233
229
  this._flushMode = runtimeOptions.flushMode;
234
230
  const pendingRuntimeState = context.pendingLocalState;
235
- const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
231
+ const baseSnapshot = (_d = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _d !== void 0 ? _d : context.baseSnapshot;
232
+ const maxSnapshotCacheDurationMs = (_f = (_e = this._storage) === null || _e === void 0 ? void 0 : _e.policies) === null || _f === void 0 ? void 0 : _f.maximumCacheDurationMs;
233
+ if (maxSnapshotCacheDurationMs !== undefined && maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
234
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
235
+ // which dictates the value is either undefined or exactly 5 days in ms.
236
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
237
+ throw new UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
238
+ }
236
239
  this.garbageCollector = GarbageCollector.create({
237
240
  runtime: this,
238
241
  gcOptions: this.runtimeOptions.gcOptions,
@@ -240,6 +243,7 @@ export class ContainerRuntime extends TypedEventEmitter {
240
243
  baseLogger: this.mc.logger,
241
244
  existing,
242
245
  metadata,
246
+ createContainerMetadata: this.createContainerMetadata,
243
247
  isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
244
248
  getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
245
249
  getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
@@ -265,28 +269,37 @@ export class ContainerRuntime extends TypedEventEmitter {
265
269
  gcDisabled: !this.garbageCollector.shouldRunGC,
266
270
  });
267
271
  if (baseSnapshot) {
268
- this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
272
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
269
273
  }
270
- this.dataStores = new DataStores(getSummaryForDatastores(baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getBaseGCDetails(), (path, timestampMs, packagePath) => this.garbageCollector.nodeUpdated(path, "Changed", timestampMs, packagePath), new Map(dataStoreAliasMap), this.garbageCollector.writeDataAtRoot);
274
+ this.dataStores = new DataStores(getSummaryForDatastores(baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getBaseGCDetails(), (path, timestampMs, packagePath) => this.garbageCollector.nodeUpdated(path, "Changed", timestampMs, packagePath), new Map(dataStoreAliasMap));
271
275
  this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
272
276
  if (!this.disposed) {
273
277
  this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
274
278
  }
275
279
  }, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
276
280
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, () => this.clientId, ChildLogger.create(this.logger, "ScheduleManager"));
277
- this.deltaSender = this.deltaManager;
278
281
  this.pendingStateManager = new PendingStateManager({
279
282
  applyStashedOp: this.applyStashedOp.bind(this),
280
283
  clientId: () => this.clientId,
281
284
  close: this.closeFn,
282
285
  connected: () => this.connected,
283
286
  flush: this.flush.bind(this),
284
- flushMode: () => this.flushMode,
285
287
  reSubmit: this.reSubmit.bind(this),
286
- setFlushMode: (mode) => this.setFlushMode(mode),
287
- }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
288
+ rollback: this.rollback.bind(this),
289
+ orderSequentially: this.orderSequentially.bind(this),
290
+ }, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
291
+ this.outbox = new Outbox({
292
+ shouldSend: () => this.canSendOps(),
293
+ pendingStateManager: this.pendingStateManager,
294
+ containerContext: this.context,
295
+ compressor: new OpCompressor(this.mc.logger),
296
+ config: {
297
+ compressionOptions: runtimeOptions.compressionOptions,
298
+ maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
299
+ },
300
+ });
288
301
  this.context.quorum.on("removeMember", (clientId) => {
289
- this.clearPartialChunks(clientId);
302
+ this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
290
303
  });
291
304
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
292
305
  this.dirtyContainer = this.context.attachState !== AttachState.Attached
@@ -308,7 +321,7 @@ export class ContainerRuntime extends TypedEventEmitter {
308
321
  // if summaries are enabled and we are not the summarizer client.
309
322
  const defaultAction = () => {
310
323
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
311
- this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
324
+ this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
312
325
  // unregister default to no log on every op after falling behind
313
326
  // and register summary ack handler to re-register this handler
314
327
  // after successful summary
@@ -354,26 +367,6 @@ export class ContainerRuntime extends TypedEventEmitter {
354
367
  });
355
368
  // logging hardware telemetry
356
369
  logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
357
- let loadSummaryNumber;
358
- // Get the container creation metadata. For new container, we initialize these. For existing containers,
359
- // get the values from the metadata blob.
360
- if (existing) {
361
- this.createContainerMetadata = {
362
- createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
363
- createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
364
- };
365
- // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
366
- // the count is reset to 0.
367
- loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
368
- }
369
- else {
370
- this.createContainerMetadata = {
371
- createContainerRuntimeVersion: pkgVersion,
372
- createContainerTimestamp: Date.now(),
373
- };
374
- loadSummaryNumber = 0;
375
- }
376
- this.nextSummaryNumber = loadSummaryNumber + 1;
377
370
  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 }));
378
371
  ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
379
372
  BindBatchTracker(this, this.logger);
@@ -387,8 +380,10 @@ export class ContainerRuntime extends TypedEventEmitter {
387
380
  * @param requestHandler - Request handlers for the container runtime
388
381
  * @param runtimeOptions - Additional options to be passed to the runtime
389
382
  * @param existing - (optional) When loading from an existing snapshot. Precedes context.existing if provided
383
+ * @param containerRuntimeCtor - (optional) Constructor to use to create the ContainerRuntime instance. This
384
+ * allows mixin classes to leverage this method to define their own async initializer.
390
385
  */
391
- static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing) {
386
+ static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing, containerRuntimeCtor = ContainerRuntime) {
392
387
  var _a, _b, _c;
393
388
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
394
389
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
@@ -399,7 +394,10 @@ export class ContainerRuntime extends TypedEventEmitter {
399
394
  runtimeVersion: pkgVersion,
400
395
  },
401
396
  });
402
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
397
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {
398
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
399
+ compressionAlgorithm: CompressionAlgorithms.lz4
400
+ }, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, } = runtimeOptions;
403
401
  const pendingRuntimeState = context.pendingLocalState;
404
402
  const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
405
403
  const storage = !pendingRuntimeState ?
@@ -448,19 +446,22 @@ export class ContainerRuntime extends TypedEventEmitter {
448
446
  }
449
447
  }
450
448
  }
451
- const runtime = new ContainerRuntime(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
449
+ const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
452
450
  summaryOptions,
453
451
  gcOptions,
454
452
  loadSequenceNumberVerification,
455
453
  flushMode,
456
454
  enableOfflineLoad,
455
+ compressionOptions,
456
+ maxBatchSizeInBytes,
457
457
  }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
458
458
  if (pendingRuntimeState) {
459
459
  await runtime.processSavedOps(pendingRuntimeState);
460
460
  // delete these once runtime has seen them to save space
461
461
  pendingRuntimeState.savedOps = [];
462
462
  }
463
- await runtime.getSnapshotBlobs();
463
+ // Initialize the base state of the runtime before it's returned.
464
+ await runtime.initializeBaseState();
464
465
  return runtime;
465
466
  }
466
467
  get options() {
@@ -509,9 +510,6 @@ export class ContainerRuntime extends TypedEventEmitter {
509
510
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
510
511
  }
511
512
  get disposed() { return this._disposed; }
512
- get emptyBatch() {
513
- return this.pendingBatch.empty && this.pendingAttachBatch.empty;
514
- }
515
513
  get summarizer() {
516
514
  assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
517
515
  return this._summarizer;
@@ -567,6 +565,13 @@ export class ContainerRuntime extends TypedEventEmitter {
567
565
  ? this.summaryConfiguration.initialSummarizerDelayMs
568
566
  : 0;
569
567
  }
568
+ /**
569
+ * Initializes the state from the base snapshot this container runtime loaded from.
570
+ */
571
+ async initializeBaseState() {
572
+ await this.initializeBaseSnapshotBlobs();
573
+ await this.garbageCollector.initializeBaseState();
574
+ }
570
575
  dispose(error) {
571
576
  var _a;
572
577
  if (this._disposed) {
@@ -665,13 +670,16 @@ export class ContainerRuntime extends TypedEventEmitter {
665
670
  return (_a = this.dataStores.aliases.get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
666
671
  }
667
672
  async getDataStoreFromRequest(id, request) {
668
- var _a, _b, _c;
673
+ var _a, _b, _c, _d, _e;
669
674
  const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
670
675
  ? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
671
676
  : true;
677
+ const viaHandle = typeof ((_c = request.headers) === null || _c === void 0 ? void 0 : _c[RuntimeHeaders.viaHandle]) === "boolean"
678
+ ? (_d = request.headers) === null || _d === void 0 ? void 0 : _d[RuntimeHeaders.viaHandle]
679
+ : false;
672
680
  await this.dataStores.waitIfPendingAlias(id);
673
681
  const internalId = this.internalId(id);
674
- const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
682
+ const dataStoreContext = await this.dataStores.getDataStore(internalId, wait, viaHandle);
675
683
  /**
676
684
  * If GC should run and this an external app request with "externalRequest" header, we need to return
677
685
  * an error if the data store being requested is marked as unreferenced as per the data store's base
@@ -680,7 +688,7 @@ export class ContainerRuntime extends TypedEventEmitter {
680
688
  * This is a workaround to handle scenarios where a data store shared with an external app is deleted
681
689
  * and marked as unreferenced by GC. Returning an error will fail to load the data store for the app.
682
690
  */
683
- if (((_c = request.headers) === null || _c === void 0 ? void 0 : _c[RuntimeHeaders.externalRequest]) && this.garbageCollector.shouldRunGC) {
691
+ if (((_e = request.headers) === null || _e === void 0 ? void 0 : _e[RuntimeHeaders.externalRequest]) && this.garbageCollector.shouldRunGC) {
684
692
  // The data store is referenced if used routes in the base summary has a route to self.
685
693
  // Older documents may not have used routes in the summary. They are considered referenced.
686
694
  const usedRoutes = (await dataStoreContext.getBaseGCDetails()).usedRoutes;
@@ -709,8 +717,8 @@ export class ContainerRuntime extends TypedEventEmitter {
709
717
  addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
710
718
  var _a;
711
719
  this.addMetadataToSummary(summaryTree);
712
- if (this.chunkMap.size > 0) {
713
- const content = JSON.stringify([...this.chunkMap]);
720
+ if (this.remoteMessageProcessor.partialMessages.size > 0) {
721
+ const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
714
722
  addBlobToSummary(summaryTree, chunksBlobName, content);
715
723
  }
716
724
  const dataStoreAliases = this.dataStores.aliases;
@@ -727,11 +735,9 @@ export class ContainerRuntime extends TypedEventEmitter {
727
735
  if (Object.keys(blobManagerSummary.summary.tree).length > 0) {
728
736
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
729
737
  }
730
- if (this.garbageCollector.writeDataAtRoot) {
731
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
732
- if (gcSummary !== undefined) {
733
- addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
734
- }
738
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
739
+ if (gcSummary !== undefined) {
740
+ addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
735
741
  }
736
742
  }
737
743
  // Track how many times the container tries to reconnect with pending messages.
@@ -876,31 +882,21 @@ export class ContainerRuntime extends TypedEventEmitter {
876
882
  process(messageArg, local) {
877
883
  var _a;
878
884
  this.verifyNotClosed();
879
- // Do shallow copy of message, as methods below will modify it.
880
- // There might be multiple container instances receiving same message
881
- // We do not need to make deep copy, as each layer will just replace message.content itself,
882
- // but would not modify contents details
883
- let message = Object.assign({}, messageArg);
884
- // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
885
- // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
886
- // Old ops may contain empty string (I assume noops).
887
- if (typeof message.contents === "string" && message.contents !== "") {
888
- message.contents = JSON.parse(message.contents);
889
- }
890
- // Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
891
- // This format was not shipped to production workflows.
892
- const runtimeMessage = unpackRuntimeMessage(message);
893
885
  if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
894
886
  this.savedOps.push(messageArg);
895
887
  }
888
+ // Whether or not the message is actually a runtime message.
889
+ // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
890
+ // or something different, like a system message.
891
+ const runtimeMessage = messageArg.type === MessageType.Operation;
892
+ // Do shallow copy of message, as the processing flow will modify it.
893
+ const messageCopy = Object.assign({}, messageArg);
894
+ const message = this.remoteMessageProcessor.process(messageCopy);
896
895
  // Surround the actual processing of the operation with messages to the schedule manager indicating
897
896
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
898
897
  // messages once a batch has been fully processed.
899
898
  this.scheduleManager.beforeOpProcessing(message);
900
899
  try {
901
- // Chunk processing must come first given that we will transform the message to the unchunked version
902
- // once all pieces are available
903
- message = this.processRemoteChunkedMessage(message);
904
900
  let localOpMetadata;
905
901
  if (local && runtimeMessage) {
906
902
  localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
@@ -1004,112 +1000,31 @@ export class ContainerRuntime extends TypedEventEmitter {
1004
1000
  async getRootDataStoreChannel(id, wait = true) {
1005
1001
  await this.dataStores.waitIfPendingAlias(id);
1006
1002
  const internalId = this.internalId(id);
1007
- const context = await this.dataStores.getDataStore(internalId, wait);
1003
+ const context = await this.dataStores.getDataStore(internalId, wait, false /* viaHandle */);
1008
1004
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
1009
1005
  return context.realize();
1010
1006
  }
1011
- setFlushMode(mode) {
1012
- if (mode === this._flushMode) {
1013
- return;
1014
- }
1015
- this.mc.logger.sendTelemetryEvent({
1016
- eventName: "FlushMode Updated",
1017
- old: this._flushMode,
1018
- new: mode,
1019
- });
1020
- // Flush any pending batches if switching to immediate
1021
- if (mode === FlushMode.Immediate) {
1022
- this.flush();
1023
- }
1024
- this._flushMode = mode;
1025
- // Let the PendingStateManager know that FlushMode has been updated.
1026
- this.pendingStateManager.onFlushModeUpdated(mode);
1027
- }
1007
+ /**
1008
+ * Flush the pending ops manually.
1009
+ * This method is expected to be called at the end of a batch.
1010
+ */
1028
1011
  flush() {
1029
1012
  assert(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
1030
- this.flushBatch(this.pendingAttachBatch.popBatch());
1031
- this.flushBatch(this.pendingBatch.popBatch());
1032
- assert(this.emptyBatch, 0x3cf /* reentrancy */);
1033
- }
1034
- flushBatch(batch) {
1035
- const length = batch.length;
1036
- if (length > 1) {
1037
- batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
1038
- batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
1039
- // This assert fires for the following reason (there might be more cases like that):
1040
- // AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
1041
- // i.e. in the middle of op processing!
1042
- // Sending ops while processing ops is not good idea - it's not defined when
1043
- // referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
1044
- // If we send ops in response to processing multiple ops, then we for sure hit this assert!
1045
- // Tracked via ADO #1834
1046
- // assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
1047
- // "Batch should be generated synchronously, without processing ops in the middle!");
1048
- }
1049
- let clientSequenceNumber = -1;
1050
- // Did we disconnect in the middle of turn-based batch?
1051
- // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
1052
- if (this.canSendOps()) {
1053
- if (this.context.submitBatchFn !== undefined) {
1054
- const batchToSend = [];
1055
- for (const message of batch) {
1056
- batchToSend.push({ contents: message.contents, metadata: message.metadata });
1057
- }
1058
- // returns clientSequenceNumber of last message in a batch
1059
- clientSequenceNumber = this.context.submitBatchFn(batchToSend);
1060
- }
1061
- else {
1062
- // Legacy path - supporting old loader versions. Can be removed only when LTS moves above
1063
- // version that has support for batches (submitBatchFn)
1064
- for (const message of batch) {
1065
- clientSequenceNumber = this.context.submitFn(MessageType.Operation, message.deserializedContent, true, // batch
1066
- message.metadata);
1067
- }
1068
- this.deltaSender.flush();
1069
- }
1070
- // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
1071
- clientSequenceNumber -= batch.length - 1;
1072
- assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
1073
- }
1074
- // Let the PendingStateManager know that a message was submitted.
1075
- // In future, need to shift toward keeping batch as a whole!
1076
- for (const message of batch) {
1077
- this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
1078
- clientSequenceNumber++;
1079
- }
1080
- this.pendingStateManager.onFlush();
1013
+ this.outbox.flush();
1014
+ assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1081
1015
  }
1082
1016
  orderSequentially(callback) {
1083
- // If flush mode is already TurnBased we are either
1084
- // nested in another orderSequentially, or
1085
- // the app is flushing manually, in which
1086
- // case this invocation doesn't own
1087
- // flushing.
1088
- if (this.flushMode === FlushMode.TurnBased) {
1089
- this.trackOrderSequentiallyCalls(callback);
1090
- return;
1091
- }
1092
- const savedFlushMode = this.flushMode;
1093
- this.setFlushMode(FlushMode.TurnBased);
1094
- try {
1095
- this.trackOrderSequentiallyCalls(callback);
1096
- this.flush();
1097
- }
1098
- finally {
1099
- this.setFlushMode(savedFlushMode);
1100
- }
1101
- }
1102
- trackOrderSequentiallyCalls(callback) {
1103
1017
  let checkpoint;
1018
+ let result;
1104
1019
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1105
1020
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1106
1021
  // 1. It would not help, as we flush attach ops as they become available.
1107
1022
  // 2. There is no way to undo process of data store creation.
1108
- checkpoint = this.pendingBatch.checkpoint();
1023
+ checkpoint = this.outbox.checkpoint().mainBatch;
1109
1024
  }
1110
1025
  try {
1111
1026
  this._orderSequentiallyCalls++;
1112
- callback();
1027
+ result = callback();
1113
1028
  }
1114
1029
  catch (error) {
1115
1030
  if (checkpoint) {
@@ -1134,6 +1049,10 @@ export class ContainerRuntime extends TypedEventEmitter {
1134
1049
  finally {
1135
1050
  this._orderSequentiallyCalls--;
1136
1051
  }
1052
+ if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1053
+ this.flush();
1054
+ }
1055
+ return result;
1137
1056
  }
1138
1057
  async createDataStore(pkg) {
1139
1058
  const internalId = uuid();
@@ -1160,6 +1079,12 @@ export class ContainerRuntime extends TypedEventEmitter {
1160
1079
  canSendOps() {
1161
1080
  return this.connected && !this.deltaManager.readOnlyInfo.readonly;
1162
1081
  }
1082
+ /**
1083
+ * Are we in the middle of batching ops together?
1084
+ */
1085
+ currentlyBatching() {
1086
+ return this.flushMode === FlushMode.TurnBased || this._orderSequentiallyCalls !== 0;
1087
+ }
1163
1088
  getQuorum() {
1164
1089
  return this.context.quorum;
1165
1090
  }
@@ -1310,28 +1235,33 @@ export class ContainerRuntime extends TypedEventEmitter {
1310
1235
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
1311
1236
  * After GC has run, called to notify this container's nodes of routes that are used in it.
1312
1237
  * @param usedRoutes - The routes that are used in all nodes in this Container.
1313
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
1314
- * unreferenced as part of this GC run, this should be used to update the time when it happens.
1315
1238
  */
1316
- updateUsedRoutes(usedRoutes, gcTimestamp) {
1239
+ updateUsedRoutes(usedRoutes) {
1317
1240
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
1318
1241
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1319
1242
  // always referenced, so the used routes is only self-route (empty string).
1320
1243
  this.summarizerNode.updateUsedRoutes([""]);
1244
+ const blobManagerUsedRoutes = [];
1321
1245
  const dataStoreUsedRoutes = [];
1322
1246
  for (const route of usedRoutes) {
1323
- if (route.split("/")[1] !== BlobManager.basePath) {
1247
+ if (this.isBlobPath(route)) {
1248
+ blobManagerUsedRoutes.push(route);
1249
+ }
1250
+ else {
1324
1251
  dataStoreUsedRoutes.push(route);
1325
1252
  }
1326
1253
  }
1327
- return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
1254
+ this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
1255
+ this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
1328
1256
  }
1329
1257
  /**
1330
- * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
1331
- * scenarios with accessing deleted content.
1332
- * @param unusedRoutes - The routes that are unused in all data stores in this Container.
1258
+ * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
1259
+ * tombstones.
1260
+ * @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
1261
+ * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
1262
+ * are deleted.
1333
1263
  */
1334
- deleteUnusedRoutes(unusedRoutes) {
1264
+ updateUnusedRoutes(unusedRoutes, tombstone) {
1335
1265
  const blobManagerUnusedRoutes = [];
1336
1266
  const dataStoreUnusedRoutes = [];
1337
1267
  for (const route of unusedRoutes) {
@@ -1342,8 +1272,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1342
1272
  dataStoreUnusedRoutes.push(route);
1343
1273
  }
1344
1274
  }
1345
- this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
1346
- this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
1275
+ this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
1276
+ this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
1347
1277
  }
1348
1278
  /**
1349
1279
  * Returns a server generated referenced timestamp to be used to track unreferenced nodes by GC.
@@ -1423,21 +1353,14 @@ export class ContainerRuntime extends TypedEventEmitter {
1423
1353
  const summaryNumberLogger = ChildLogger.create(summaryLogger, undefined, {
1424
1354
  all: { summaryNumber },
1425
1355
  });
1426
- assert(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1356
+ assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
1427
1357
  let latestSnapshotVersionId;
1428
1358
  if (refreshLatestAck) {
1429
1359
  const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
1430
1360
  const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
1431
1361
  latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
1432
- if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
1433
- // We need to catch up to the latest summary's reference sequence number before pausing.
1434
- await PerformanceEvent.timedExecAsync(summaryNumberLogger, {
1435
- eventName: "WaitingForSeq",
1436
- lastSequenceNumber: this.deltaManager.lastSequenceNumber,
1437
- targetSequenceNumber: latestSnapshotRefSeq,
1438
- lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
1439
- }, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
1440
- }
1362
+ // We might need to catch up to the latest summary's reference sequence number before pausing.
1363
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryNumberLogger);
1441
1364
  }
1442
1365
  try {
1443
1366
  await this.deltaManager.inbound.pause();
@@ -1603,40 +1526,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1603
1526
  this.deltaManager.inbound.resume();
1604
1527
  }
1605
1528
  }
1606
- processRemoteChunkedMessage(message) {
1607
- if (message.type !== ContainerMessageType.ChunkedOp) {
1608
- return message;
1609
- }
1610
- const clientId = message.clientId;
1611
- const chunkedContent = message.contents;
1612
- this.addChunk(clientId, chunkedContent);
1613
- if (chunkedContent.chunkId === chunkedContent.totalChunks) {
1614
- const newMessage = Object.assign({}, message);
1615
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1616
- const serializedContent = this.chunkMap.get(clientId).join("");
1617
- newMessage.contents = JSON.parse(serializedContent);
1618
- newMessage.type = chunkedContent.originalType;
1619
- this.clearPartialChunks(clientId);
1620
- return newMessage;
1621
- }
1622
- return message;
1623
- }
1624
- addChunk(clientId, chunkedContent) {
1625
- let map = this.chunkMap.get(clientId);
1626
- if (map === undefined) {
1627
- map = [];
1628
- this.chunkMap.set(clientId, map);
1629
- }
1630
- assert(chunkedContent.chunkId === map.length + 1, 0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
1631
- map.push(chunkedContent.contents);
1632
- }
1633
- clearPartialChunks(clientId) {
1634
- if (this.chunkMap.has(clientId)) {
1635
- this.chunkMap.delete(clientId);
1636
- }
1637
- }
1638
1529
  hasPendingMessages() {
1639
- return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
1530
+ return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
1640
1531
  }
1641
1532
  updateDocumentDirtyState(dirty) {
1642
1533
  if (this.attachState !== AttachState.Attached) {
@@ -1680,7 +1571,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1680
1571
  const deserializedContent = { type, contents };
1681
1572
  const serializedContent = JSON.stringify(deserializedContent);
1682
1573
  if (this.deltaManager.readOnlyInfo.readonly) {
1683
- this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
1574
+ this.logger.sendTelemetryEvent({ eventName: "SubmitOpInReadonly", connected: this.connected });
1684
1575
  }
1685
1576
  const message = {
1686
1577
  contents: serializedContent,
@@ -1710,44 +1601,24 @@ export class ContainerRuntime extends TypedEventEmitter {
1710
1601
  // issue than sending.
1711
1602
  // Please note that this does not change file format, so it can be disabled in the future if this
1712
1603
  // optimization no longer makes sense (for example, batch compression may make it less appealing).
1713
- if (type === ContainerMessageType.Attach &&
1604
+ if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
1714
1605
  this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
1715
- if (!this.pendingAttachBatch.push(message)) {
1716
- // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
1717
- // when queue is not empty.
1718
- // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
1719
- this.flushBatch(this.pendingAttachBatch.popBatch());
1720
- if (!this.pendingAttachBatch.push(message)) {
1721
- throw new GenericError("BatchTooLarge",
1722
- /* error */ undefined, {
1723
- opSize: message.contents.length,
1724
- count: this.pendingAttachBatch.length,
1725
- limit: this.pendingAttachBatch.limit,
1726
- });
1727
- }
1728
- }
1606
+ this.outbox.submitAttach(message);
1729
1607
  }
1730
1608
  else {
1731
- if (!this.pendingBatch.push(message)) {
1732
- throw new GenericError("BatchTooLarge",
1733
- /* error */ undefined, {
1734
- opSize: message.contents.length,
1735
- count: this.pendingBatch.length,
1736
- limit: this.pendingBatch.limit,
1737
- });
1738
- }
1609
+ this.outbox.submit(message);
1739
1610
  }
1740
- if (this._flushMode !== FlushMode.TurnBased) {
1611
+ if (!this.currentlyBatching()) {
1741
1612
  this.flush();
1742
1613
  }
1743
- else if (!this.flushTrigger) {
1744
- this.flushTrigger = true;
1614
+ else if (!this.flushMicroTaskExists) {
1615
+ this.flushMicroTaskExists = true;
1745
1616
  // Queue a microtask to detect the end of the turn and force a flush.
1746
1617
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1747
1618
  Promise.resolve().then(() => {
1748
- this.flushTrigger = false;
1619
+ this.flushMicroTaskExists = false;
1749
1620
  this.flush();
1750
- });
1621
+ }).catch((error) => { this.closeFn(error); });
1751
1622
  }
1752
1623
  }
1753
1624
  catch (error) {
@@ -1762,7 +1633,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1762
1633
  this.verifyNotClosed();
1763
1634
  assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
1764
1635
  // System message should not be sent in the middle of the batch.
1765
- assert(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
1636
+ assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
1766
1637
  // back-compat: ADO #1385: Make this call unconditional in the future
1767
1638
  return this.context.submitSummaryFn !== undefined
1768
1639
  ? this.context.submitSummaryFn(contents)
@@ -1817,18 +1688,41 @@ export class ContainerRuntime extends TypedEventEmitter {
1817
1688
  throw new Error(`Can't rollback ${type}`);
1818
1689
  }
1819
1690
  }
1691
+ async waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryLogger) {
1692
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
1693
+ // We need to catch up to the latest summary's reference sequence number before proceeding.
1694
+ await PerformanceEvent.timedExecAsync(summaryLogger, {
1695
+ eventName: "WaitingForSeq",
1696
+ lastSequenceNumber: this.deltaManager.lastSequenceNumber,
1697
+ targetSequenceNumber: latestSnapshotRefSeq,
1698
+ lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
1699
+ }, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
1700
+ }
1701
+ }
1820
1702
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1821
- async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1703
+ async refreshLatestSummaryAck(options) {
1704
+ const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
1822
1705
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
1823
1706
  // The call to fetch the snapshot is very expensive and not always needed.
1824
1707
  // It should only be done by the summarizerNode, if required.
1708
+ // When fetching from storage we will always get the latest version and do not use the ackHandle.
1825
1709
  const snapshotTreeFetcher = async () => {
1826
- const fetchResult = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
1710
+ const fetchResult = await this.fetchSnapshotFromStorage(null, summaryLogger, {
1827
1711
  eventName: "RefreshLatestSummaryGetSnapshot",
1828
1712
  ackHandle,
1829
1713
  summaryRefSeq,
1830
- fetchLatest: false,
1714
+ fetchLatest: true,
1715
+ });
1716
+ const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
1717
+ summaryLogger.sendTelemetryEvent({
1718
+ eventName: "LatestSummaryRetrieved",
1719
+ ackHandle,
1720
+ lastSequenceNumber: latestSnapshotRefSeq,
1721
+ targetSequenceNumber: summaryRefSeq,
1831
1722
  });
1723
+ // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
1724
+ // wait for the delta manager to catch up before refreshing the latest Summary.
1725
+ await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryLogger);
1832
1726
  return fetchResult.snapshotTree;
1833
1727
  };
1834
1728
  const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, snapshotTreeFetcher, readAndParseBlob, summaryLogger);
@@ -1873,7 +1767,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1873
1767
  this.baseSnapshotBlobs = SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
1874
1768
  }
1875
1769
  }
1876
- async getSnapshotBlobs() {
1770
+ async initializeBaseSnapshotBlobs() {
1877
1771
  var _a;
1878
1772
  if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
1879
1773
  this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
@@ -1891,6 +1785,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1891
1785
  // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
1892
1786
  // to close current batch.
1893
1787
  this.flush();
1788
+ if (this._orderSequentiallyCalls !== 0) {
1789
+ throw new UsageError("can't get state during orderSequentially");
1790
+ }
1894
1791
  const previousPendingState = this.context.pendingLocalState;
1895
1792
  if (previousPendingState) {
1896
1793
  return {
@@ -1958,6 +1855,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1958
1855
  throw new UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
1959
1856
  }
1960
1857
  }
1858
+ if (configuration.minIdleTime > configuration.maxIdleTime) {
1859
+ throw new UsageError(`"minIdleTime" [${configuration.minIdleTime}] cannot be greater than "maxIdleTime" [${configuration.maxIdleTime}]`);
1860
+ }
1961
1861
  }
1962
1862
  }
1963
1863
  /**
@@ -1967,12 +1867,18 @@ export class ContainerRuntime extends TypedEventEmitter {
1967
1867
  const waitForSeq = async (deltaManager, targetSeq) => new Promise((resolve, reject) => {
1968
1868
  // TODO: remove cast to any when actual event is determined
1969
1869
  deltaManager.on("closed", reject);
1970
- const handleOp = (message) => {
1971
- if (message.sequenceNumber >= targetSeq) {
1972
- resolve();
1973
- deltaManager.off("op", handleOp);
1974
- }
1975
- };
1976
- deltaManager.on("op", handleOp);
1870
+ // If we already reached target sequence number, simply resolve the promise.
1871
+ if (deltaManager.lastSequenceNumber >= targetSeq) {
1872
+ resolve();
1873
+ }
1874
+ else {
1875
+ const handleOp = (message) => {
1876
+ if (message.sequenceNumber >= targetSeq) {
1877
+ resolve();
1878
+ deltaManager.off("op", handleOp);
1879
+ }
1880
+ };
1881
+ deltaManager.on("op", handleOp);
1882
+ }
1977
1883
  });
1978
1884
  //# sourceMappingURL=containerRuntime.js.map