@fluidframework/container-runtime 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.6.4.0.191258

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 (561) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/README.md +4 -3
  3. package/dist/batchTracker.d.ts +3 -2
  4. package/dist/batchTracker.d.ts.map +1 -1
  5. package/dist/batchTracker.js +6 -5
  6. package/dist/batchTracker.js.map +1 -1
  7. package/dist/blobManager.d.ts +15 -18
  8. package/dist/blobManager.d.ts.map +1 -1
  9. package/dist/blobManager.js +212 -171
  10. package/dist/blobManager.js.map +1 -1
  11. package/dist/connectionTelemetry.d.ts.map +1 -1
  12. package/dist/connectionTelemetry.js +33 -17
  13. package/dist/connectionTelemetry.js.map +1 -1
  14. package/dist/containerRuntime.d.ts +172 -35
  15. package/dist/containerRuntime.d.ts.map +1 -1
  16. package/dist/containerRuntime.js +722 -425
  17. package/dist/containerRuntime.js.map +1 -1
  18. package/dist/dataStore.d.ts.map +1 -1
  19. package/dist/dataStore.js +15 -7
  20. package/dist/dataStore.js.map +1 -1
  21. package/dist/dataStoreContext.d.ts +4 -4
  22. package/dist/dataStoreContext.d.ts.map +1 -1
  23. package/dist/dataStoreContext.js +87 -90
  24. package/dist/dataStoreContext.js.map +1 -1
  25. package/dist/dataStoreContexts.d.ts +1 -1
  26. package/dist/dataStoreContexts.d.ts.map +1 -1
  27. package/dist/dataStoreContexts.js +10 -10
  28. package/dist/dataStoreContexts.js.map +1 -1
  29. package/dist/dataStoreRegistry.js +2 -2
  30. package/dist/dataStoreRegistry.js.map +1 -1
  31. package/dist/dataStores.d.ts +23 -7
  32. package/dist/dataStores.d.ts.map +1 -1
  33. package/dist/dataStores.js +125 -82
  34. package/dist/dataStores.js.map +1 -1
  35. package/dist/deltaManagerProxyBase.d.ts +35 -0
  36. package/dist/deltaManagerProxyBase.d.ts.map +1 -0
  37. package/dist/deltaManagerProxyBase.js +77 -0
  38. package/dist/deltaManagerProxyBase.js.map +1 -0
  39. package/dist/deltaManagerSummarizerProxy.d.ts +1 -1
  40. package/dist/deltaManagerSummarizerProxy.d.ts.map +1 -1
  41. package/dist/deltaManagerSummarizerProxy.js +4 -2
  42. package/dist/deltaManagerSummarizerProxy.js.map +1 -1
  43. package/dist/deltaScheduler.d.ts.map +1 -1
  44. package/dist/deltaScheduler.js +10 -10
  45. package/dist/deltaScheduler.js.map +1 -1
  46. package/dist/error.d.ts +14 -0
  47. package/dist/error.d.ts.map +1 -0
  48. package/dist/error.js +21 -0
  49. package/dist/error.js.map +1 -0
  50. package/dist/gc/garbageCollection.d.ts +10 -9
  51. package/dist/gc/garbageCollection.d.ts.map +1 -1
  52. package/dist/gc/garbageCollection.js +65 -56
  53. package/dist/gc/garbageCollection.js.map +1 -1
  54. package/dist/gc/gcConfigs.d.ts.map +1 -1
  55. package/dist/gc/gcConfigs.js +18 -14
  56. package/dist/gc/gcConfigs.js.map +1 -1
  57. package/dist/gc/gcDefinitions.d.ts +17 -5
  58. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  59. package/dist/gc/gcDefinitions.js +14 -15
  60. package/dist/gc/gcDefinitions.js.map +1 -1
  61. package/dist/gc/gcHelpers.d.ts +0 -8
  62. package/dist/gc/gcHelpers.d.ts.map +1 -1
  63. package/dist/gc/gcHelpers.js +11 -24
  64. package/dist/gc/gcHelpers.js.map +1 -1
  65. package/dist/gc/gcSummaryStateTracker.d.ts +4 -7
  66. package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
  67. package/dist/gc/gcSummaryStateTracker.js +19 -58
  68. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  69. package/dist/gc/gcTelemetry.d.ts +1 -1
  70. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  71. package/dist/gc/gcTelemetry.js +45 -35
  72. package/dist/gc/gcTelemetry.js.map +1 -1
  73. package/dist/gc/gcUnreferencedStateTracker.js +4 -4
  74. package/dist/gc/gcUnreferencedStateTracker.js.map +1 -1
  75. package/dist/gc/index.d.ts +2 -2
  76. package/dist/gc/index.d.ts.map +1 -1
  77. package/dist/gc/index.js +3 -5
  78. package/dist/gc/index.js.map +1 -1
  79. package/dist/id-compressor/appendOnlySortedMap.d.ts +8 -30
  80. package/dist/id-compressor/appendOnlySortedMap.d.ts.map +1 -1
  81. package/dist/id-compressor/appendOnlySortedMap.js +26 -68
  82. package/dist/id-compressor/appendOnlySortedMap.js.map +1 -1
  83. package/dist/id-compressor/finalSpace.d.ts +29 -0
  84. package/dist/id-compressor/finalSpace.d.ts.map +1 -0
  85. package/dist/id-compressor/finalSpace.js +62 -0
  86. package/dist/id-compressor/finalSpace.js.map +1 -0
  87. package/dist/id-compressor/idCompressor.d.ts +25 -250
  88. package/dist/id-compressor/idCompressor.d.ts.map +1 -1
  89. package/dist/id-compressor/idCompressor.js +390 -1153
  90. package/dist/id-compressor/idCompressor.js.map +1 -1
  91. package/dist/id-compressor/identifiers.d.ts +32 -0
  92. package/dist/id-compressor/identifiers.d.ts.map +1 -0
  93. package/dist/id-compressor/identifiers.js +15 -0
  94. package/dist/id-compressor/identifiers.js.map +1 -0
  95. package/dist/id-compressor/index.d.ts +5 -6
  96. package/dist/id-compressor/index.d.ts.map +1 -1
  97. package/dist/id-compressor/index.js +20 -26
  98. package/dist/id-compressor/index.js.map +1 -1
  99. package/dist/id-compressor/persistanceUtilities.d.ts +22 -0
  100. package/dist/id-compressor/persistanceUtilities.d.ts.map +1 -0
  101. package/dist/id-compressor/persistanceUtilities.js +43 -0
  102. package/dist/id-compressor/persistanceUtilities.js.map +1 -0
  103. package/dist/id-compressor/sessionSpaceNormalizer.d.ts +46 -0
  104. package/dist/id-compressor/sessionSpaceNormalizer.d.ts.map +1 -0
  105. package/dist/id-compressor/sessionSpaceNormalizer.js +80 -0
  106. package/dist/id-compressor/sessionSpaceNormalizer.js.map +1 -0
  107. package/dist/id-compressor/sessions.d.ts +115 -0
  108. package/dist/id-compressor/sessions.d.ts.map +1 -0
  109. package/dist/id-compressor/sessions.js +305 -0
  110. package/dist/id-compressor/sessions.js.map +1 -0
  111. package/dist/id-compressor/utilities.d.ts +49 -0
  112. package/dist/id-compressor/utilities.d.ts.map +1 -0
  113. package/dist/id-compressor/utilities.js +166 -0
  114. package/dist/id-compressor/utilities.js.map +1 -0
  115. package/dist/index.d.ts +3 -3
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +6 -4
  118. package/dist/index.js.map +1 -1
  119. package/dist/metadata.d.ts +18 -0
  120. package/dist/metadata.d.ts.map +1 -0
  121. package/dist/metadata.js +7 -0
  122. package/dist/metadata.js.map +1 -0
  123. package/dist/opLifecycle/batchManager.d.ts +2 -1
  124. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  125. package/dist/opLifecycle/batchManager.js +15 -7
  126. package/dist/opLifecycle/batchManager.js.map +1 -1
  127. package/dist/opLifecycle/definitions.d.ts +11 -0
  128. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  129. package/dist/opLifecycle/definitions.js.map +1 -1
  130. package/dist/opLifecycle/index.d.ts +1 -1
  131. package/dist/opLifecycle/index.d.ts.map +1 -1
  132. package/dist/opLifecycle/index.js +2 -1
  133. package/dist/opLifecycle/index.js.map +1 -1
  134. package/dist/opLifecycle/opCompressor.d.ts +2 -2
  135. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  136. package/dist/opLifecycle/opCompressor.js +12 -7
  137. package/dist/opLifecycle/opCompressor.js.map +1 -1
  138. package/dist/opLifecycle/opDecompressor.d.ts +2 -2
  139. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  140. package/dist/opLifecycle/opDecompressor.js +30 -21
  141. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  142. package/dist/opLifecycle/opGroupingManager.d.ts +1 -1
  143. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  144. package/dist/opLifecycle/opGroupingManager.js +19 -13
  145. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  146. package/dist/opLifecycle/opSplitter.d.ts +2 -2
  147. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  148. package/dist/opLifecycle/opSplitter.js +24 -19
  149. package/dist/opLifecycle/opSplitter.js.map +1 -1
  150. package/dist/opLifecycle/outbox.d.ts +39 -6
  151. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  152. package/dist/opLifecycle/outbox.js +138 -61
  153. package/dist/opLifecycle/outbox.js.map +1 -1
  154. package/dist/opLifecycle/remoteMessageProcessor.d.ts +6 -1
  155. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  156. package/dist/opLifecycle/remoteMessageProcessor.js +22 -8
  157. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  158. package/dist/opProperties.js +1 -2
  159. package/dist/opProperties.js.map +1 -1
  160. package/dist/packageVersion.d.ts +1 -1
  161. package/dist/packageVersion.js +1 -1
  162. package/dist/packageVersion.js.map +1 -1
  163. package/dist/pendingStateManager.d.ts +25 -10
  164. package/dist/pendingStateManager.d.ts.map +1 -1
  165. package/dist/pendingStateManager.js +101 -64
  166. package/dist/pendingStateManager.js.map +1 -1
  167. package/dist/scheduleManager.d.ts.map +1 -1
  168. package/dist/scheduleManager.js +43 -33
  169. package/dist/scheduleManager.js.map +1 -1
  170. package/dist/summary/index.d.ts +4 -4
  171. package/dist/summary/index.d.ts.map +1 -1
  172. package/dist/summary/index.js +3 -1
  173. package/dist/summary/index.js.map +1 -1
  174. package/dist/summary/orderedClientElection.d.ts +3 -3
  175. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  176. package/dist/summary/orderedClientElection.js +26 -27
  177. package/dist/summary/orderedClientElection.js.map +1 -1
  178. package/dist/summary/runWhileConnectedCoordinator.js +3 -3
  179. package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
  180. package/dist/summary/runningSummarizer.d.ts +31 -10
  181. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  182. package/dist/summary/runningSummarizer.js +271 -139
  183. package/dist/summary/runningSummarizer.js.map +1 -1
  184. package/dist/summary/summarizer.d.ts +8 -7
  185. package/dist/summary/summarizer.d.ts.map +1 -1
  186. package/dist/summary/summarizer.js +79 -78
  187. package/dist/summary/summarizer.js.map +1 -1
  188. package/dist/summary/summarizerClientElection.d.ts +2 -2
  189. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  190. package/dist/summary/summarizerClientElection.js +7 -11
  191. package/dist/summary/summarizerClientElection.js.map +1 -1
  192. package/dist/summary/summarizerHeuristics.js +10 -14
  193. package/dist/summary/summarizerHeuristics.js.map +1 -1
  194. package/dist/summary/summarizerNode/index.d.ts +1 -1
  195. package/dist/summary/summarizerNode/index.d.ts.map +1 -1
  196. package/dist/summary/summarizerNode/index.js.map +1 -1
  197. package/dist/summary/summarizerNode/summarizerNode.d.ts +40 -23
  198. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  199. package/dist/summary/summarizerNode/summarizerNode.js +144 -149
  200. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  201. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +25 -29
  202. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  203. package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -4
  204. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  205. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +21 -16
  206. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  207. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +74 -123
  208. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  209. package/dist/summary/summarizerTypes.d.ts +44 -24
  210. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  211. package/dist/summary/summarizerTypes.js.map +1 -1
  212. package/dist/summary/summaryCollection.d.ts +2 -2
  213. package/dist/summary/summaryCollection.d.ts.map +1 -1
  214. package/dist/summary/summaryCollection.js +16 -13
  215. package/dist/summary/summaryCollection.js.map +1 -1
  216. package/dist/summary/summaryFormat.d.ts +4 -0
  217. package/dist/summary/summaryFormat.d.ts.map +1 -1
  218. package/dist/summary/summaryFormat.js +8 -5
  219. package/dist/summary/summaryFormat.js.map +1 -1
  220. package/dist/summary/summaryGenerator.d.ts +21 -6
  221. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  222. package/dist/summary/summaryGenerator.js +117 -54
  223. package/dist/summary/summaryGenerator.js.map +1 -1
  224. package/dist/summary/summaryManager.d.ts +8 -7
  225. package/dist/summary/summaryManager.d.ts.map +1 -1
  226. package/dist/summary/summaryManager.js +38 -28
  227. package/dist/summary/summaryManager.js.map +1 -1
  228. package/lib/batchTracker.d.ts +3 -2
  229. package/lib/batchTracker.d.ts.map +1 -1
  230. package/lib/batchTracker.js +5 -4
  231. package/lib/batchTracker.js.map +1 -1
  232. package/lib/blobManager.d.ts +15 -18
  233. package/lib/blobManager.d.ts.map +1 -1
  234. package/lib/blobManager.js +187 -146
  235. package/lib/blobManager.js.map +1 -1
  236. package/lib/connectionTelemetry.d.ts.map +1 -1
  237. package/lib/connectionTelemetry.js +23 -7
  238. package/lib/connectionTelemetry.js.map +1 -1
  239. package/lib/containerRuntime.d.ts +172 -35
  240. package/lib/containerRuntime.d.ts.map +1 -1
  241. package/lib/containerRuntime.js +678 -380
  242. package/lib/containerRuntime.js.map +1 -1
  243. package/lib/dataStore.d.ts.map +1 -1
  244. package/lib/dataStore.js +13 -5
  245. package/lib/dataStore.js.map +1 -1
  246. package/lib/dataStoreContext.d.ts +4 -4
  247. package/lib/dataStoreContext.d.ts.map +1 -1
  248. package/lib/dataStoreContext.js +49 -52
  249. package/lib/dataStoreContext.js.map +1 -1
  250. package/lib/dataStoreContexts.d.ts +1 -1
  251. package/lib/dataStoreContexts.d.ts.map +1 -1
  252. package/lib/dataStoreContexts.js +3 -3
  253. package/lib/dataStoreContexts.js.map +1 -1
  254. package/lib/dataStoreRegistry.js +1 -1
  255. package/lib/dataStoreRegistry.js.map +1 -1
  256. package/lib/dataStores.d.ts +23 -7
  257. package/lib/dataStores.d.ts.map +1 -1
  258. package/lib/dataStores.js +107 -64
  259. package/lib/dataStores.js.map +1 -1
  260. package/lib/deltaManagerProxyBase.d.ts +35 -0
  261. package/lib/deltaManagerProxyBase.d.ts.map +1 -0
  262. package/lib/deltaManagerProxyBase.js +73 -0
  263. package/lib/deltaManagerProxyBase.js.map +1 -0
  264. package/lib/deltaManagerSummarizerProxy.d.ts +1 -1
  265. package/lib/deltaManagerSummarizerProxy.d.ts.map +1 -1
  266. package/lib/deltaManagerSummarizerProxy.js +3 -1
  267. package/lib/deltaManagerSummarizerProxy.js.map +1 -1
  268. package/lib/deltaScheduler.d.ts.map +1 -1
  269. package/lib/deltaScheduler.js +7 -7
  270. package/lib/deltaScheduler.js.map +1 -1
  271. package/lib/error.d.ts +14 -0
  272. package/lib/error.d.ts.map +1 -0
  273. package/lib/error.js +17 -0
  274. package/lib/error.js.map +1 -0
  275. package/lib/gc/garbageCollection.d.ts +10 -9
  276. package/lib/gc/garbageCollection.d.ts.map +1 -1
  277. package/lib/gc/garbageCollection.js +61 -52
  278. package/lib/gc/garbageCollection.js.map +1 -1
  279. package/lib/gc/gcConfigs.d.ts.map +1 -1
  280. package/lib/gc/gcConfigs.js +16 -12
  281. package/lib/gc/gcConfigs.js.map +1 -1
  282. package/lib/gc/gcDefinitions.d.ts +17 -5
  283. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  284. package/lib/gc/gcDefinitions.js +13 -14
  285. package/lib/gc/gcDefinitions.js.map +1 -1
  286. package/lib/gc/gcHelpers.d.ts +0 -8
  287. package/lib/gc/gcHelpers.d.ts.map +1 -1
  288. package/lib/gc/gcHelpers.js +5 -17
  289. package/lib/gc/gcHelpers.js.map +1 -1
  290. package/lib/gc/gcSummaryStateTracker.d.ts +4 -7
  291. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  292. package/lib/gc/gcSummaryStateTracker.js +20 -59
  293. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  294. package/lib/gc/gcTelemetry.d.ts +1 -1
  295. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  296. package/lib/gc/gcTelemetry.js +46 -36
  297. package/lib/gc/gcTelemetry.js.map +1 -1
  298. package/lib/gc/gcUnreferencedStateTracker.js +1 -1
  299. package/lib/gc/gcUnreferencedStateTracker.js.map +1 -1
  300. package/lib/gc/index.d.ts +2 -2
  301. package/lib/gc/index.d.ts.map +1 -1
  302. package/lib/gc/index.js +2 -2
  303. package/lib/gc/index.js.map +1 -1
  304. package/lib/id-compressor/appendOnlySortedMap.d.ts +8 -30
  305. package/lib/id-compressor/appendOnlySortedMap.d.ts.map +1 -1
  306. package/lib/id-compressor/appendOnlySortedMap.js +25 -66
  307. package/lib/id-compressor/appendOnlySortedMap.js.map +1 -1
  308. package/lib/id-compressor/finalSpace.d.ts +29 -0
  309. package/lib/id-compressor/finalSpace.d.ts.map +1 -0
  310. package/lib/id-compressor/finalSpace.js +58 -0
  311. package/lib/id-compressor/finalSpace.js.map +1 -0
  312. package/lib/id-compressor/idCompressor.d.ts +25 -250
  313. package/lib/id-compressor/idCompressor.d.ts.map +1 -1
  314. package/lib/id-compressor/idCompressor.js +385 -1142
  315. package/lib/id-compressor/idCompressor.js.map +1 -1
  316. package/lib/id-compressor/identifiers.d.ts +32 -0
  317. package/lib/id-compressor/identifiers.d.ts.map +1 -0
  318. package/lib/id-compressor/identifiers.js +11 -0
  319. package/lib/id-compressor/identifiers.js.map +1 -0
  320. package/lib/id-compressor/index.d.ts +5 -6
  321. package/lib/id-compressor/index.d.ts.map +1 -1
  322. package/lib/id-compressor/index.js +5 -6
  323. package/lib/id-compressor/index.js.map +1 -1
  324. package/lib/id-compressor/persistanceUtilities.d.ts +22 -0
  325. package/lib/id-compressor/persistanceUtilities.d.ts.map +1 -0
  326. package/lib/id-compressor/persistanceUtilities.js +34 -0
  327. package/lib/id-compressor/persistanceUtilities.js.map +1 -0
  328. package/lib/id-compressor/sessionSpaceNormalizer.d.ts +46 -0
  329. package/lib/id-compressor/sessionSpaceNormalizer.d.ts.map +1 -0
  330. package/lib/id-compressor/sessionSpaceNormalizer.js +76 -0
  331. package/lib/id-compressor/sessionSpaceNormalizer.js.map +1 -0
  332. package/lib/id-compressor/sessions.d.ts +115 -0
  333. package/lib/id-compressor/sessions.d.ts.map +1 -0
  334. package/lib/id-compressor/sessions.js +290 -0
  335. package/lib/id-compressor/sessions.js.map +1 -0
  336. package/lib/id-compressor/utilities.d.ts +49 -0
  337. package/lib/id-compressor/utilities.d.ts.map +1 -0
  338. package/lib/id-compressor/utilities.js +148 -0
  339. package/lib/id-compressor/utilities.js.map +1 -0
  340. package/lib/index.d.ts +3 -3
  341. package/lib/index.d.ts.map +1 -1
  342. package/lib/index.js +2 -2
  343. package/lib/index.js.map +1 -1
  344. package/lib/metadata.d.ts +18 -0
  345. package/lib/metadata.d.ts.map +1 -0
  346. package/lib/metadata.js +6 -0
  347. package/lib/metadata.js.map +1 -0
  348. package/lib/opLifecycle/batchManager.d.ts +2 -1
  349. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  350. package/lib/opLifecycle/batchManager.js +15 -7
  351. package/lib/opLifecycle/batchManager.js.map +1 -1
  352. package/lib/opLifecycle/definitions.d.ts +11 -0
  353. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  354. package/lib/opLifecycle/definitions.js.map +1 -1
  355. package/lib/opLifecycle/index.d.ts +1 -1
  356. package/lib/opLifecycle/index.d.ts.map +1 -1
  357. package/lib/opLifecycle/index.js +1 -1
  358. package/lib/opLifecycle/index.js.map +1 -1
  359. package/lib/opLifecycle/opCompressor.d.ts +2 -2
  360. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  361. package/lib/opLifecycle/opCompressor.js +10 -5
  362. package/lib/opLifecycle/opCompressor.js.map +1 -1
  363. package/lib/opLifecycle/opDecompressor.d.ts +2 -2
  364. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  365. package/lib/opLifecycle/opDecompressor.js +22 -13
  366. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  367. package/lib/opLifecycle/opGroupingManager.d.ts +1 -1
  368. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  369. package/lib/opLifecycle/opGroupingManager.js +17 -11
  370. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  371. package/lib/opLifecycle/opSplitter.d.ts +2 -2
  372. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  373. package/lib/opLifecycle/opSplitter.js +15 -10
  374. package/lib/opLifecycle/opSplitter.js.map +1 -1
  375. package/lib/opLifecycle/outbox.d.ts +39 -6
  376. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  377. package/lib/opLifecycle/outbox.js +132 -56
  378. package/lib/opLifecycle/outbox.js.map +1 -1
  379. package/lib/opLifecycle/remoteMessageProcessor.d.ts +6 -1
  380. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  381. package/lib/opLifecycle/remoteMessageProcessor.js +23 -9
  382. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  383. package/lib/opProperties.js +1 -2
  384. package/lib/opProperties.js.map +1 -1
  385. package/lib/packageVersion.d.ts +1 -1
  386. package/lib/packageVersion.js +1 -1
  387. package/lib/packageVersion.js.map +1 -1
  388. package/lib/pendingStateManager.d.ts +25 -10
  389. package/lib/pendingStateManager.d.ts.map +1 -1
  390. package/lib/pendingStateManager.js +90 -53
  391. package/lib/pendingStateManager.js.map +1 -1
  392. package/lib/scheduleManager.d.ts.map +1 -1
  393. package/lib/scheduleManager.js +25 -15
  394. package/lib/scheduleManager.js.map +1 -1
  395. package/lib/summary/index.d.ts +4 -4
  396. package/lib/summary/index.d.ts.map +1 -1
  397. package/lib/summary/index.js +2 -2
  398. package/lib/summary/index.js.map +1 -1
  399. package/lib/summary/orderedClientElection.d.ts +3 -3
  400. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  401. package/lib/summary/orderedClientElection.js +21 -22
  402. package/lib/summary/orderedClientElection.js.map +1 -1
  403. package/lib/summary/runWhileConnectedCoordinator.js +1 -1
  404. package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
  405. package/lib/summary/runningSummarizer.d.ts +31 -10
  406. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  407. package/lib/summary/runningSummarizer.js +265 -133
  408. package/lib/summary/runningSummarizer.js.map +1 -1
  409. package/lib/summary/summarizer.d.ts +8 -7
  410. package/lib/summary/summarizer.d.ts.map +1 -1
  411. package/lib/summary/summarizer.js +75 -74
  412. package/lib/summary/summarizer.js.map +1 -1
  413. package/lib/summary/summarizerClientElection.d.ts +2 -2
  414. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  415. package/lib/summary/summarizerClientElection.js +6 -10
  416. package/lib/summary/summarizerClientElection.js.map +1 -1
  417. package/lib/summary/summarizerHeuristics.js +9 -13
  418. package/lib/summary/summarizerHeuristics.js.map +1 -1
  419. package/lib/summary/summarizerNode/index.d.ts +1 -1
  420. package/lib/summary/summarizerNode/index.d.ts.map +1 -1
  421. package/lib/summary/summarizerNode/index.js.map +1 -1
  422. package/lib/summary/summarizerNode/summarizerNode.d.ts +40 -23
  423. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  424. package/lib/summary/summarizerNode/summarizerNode.js +132 -137
  425. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  426. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +25 -29
  427. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  428. package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -4
  429. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  430. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +21 -16
  431. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  432. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +70 -119
  433. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  434. package/lib/summary/summarizerTypes.d.ts +44 -24
  435. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  436. package/lib/summary/summarizerTypes.js.map +1 -1
  437. package/lib/summary/summaryCollection.d.ts +2 -2
  438. package/lib/summary/summaryCollection.d.ts.map +1 -1
  439. package/lib/summary/summaryCollection.js +9 -6
  440. package/lib/summary/summaryCollection.js.map +1 -1
  441. package/lib/summary/summaryFormat.d.ts +4 -0
  442. package/lib/summary/summaryFormat.d.ts.map +1 -1
  443. package/lib/summary/summaryFormat.js +7 -4
  444. package/lib/summary/summaryFormat.js.map +1 -1
  445. package/lib/summary/summaryGenerator.d.ts +21 -6
  446. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  447. package/lib/summary/summaryGenerator.js +109 -47
  448. package/lib/summary/summaryGenerator.js.map +1 -1
  449. package/lib/summary/summaryManager.d.ts +8 -7
  450. package/lib/summary/summaryManager.d.ts.map +1 -1
  451. package/lib/summary/summaryManager.js +35 -25
  452. package/lib/summary/summaryManager.js.map +1 -1
  453. package/package.json +30 -32
  454. package/src/batchTracker.ts +7 -5
  455. package/src/blobManager.ts +235 -172
  456. package/src/connectionTelemetry.ts +19 -5
  457. package/src/containerRuntime.ts +853 -431
  458. package/src/dataStore.ts +12 -4
  459. package/src/dataStoreContext.ts +49 -46
  460. package/src/dataStoreContexts.ts +4 -4
  461. package/src/dataStoreRegistry.ts +1 -1
  462. package/src/dataStores.ts +119 -80
  463. package/src/deltaManagerProxyBase.ts +111 -0
  464. package/src/deltaManagerSummarizerProxy.ts +4 -1
  465. package/src/deltaScheduler.ts +7 -11
  466. package/src/error.ts +18 -0
  467. package/src/gc/garbageCollection.md +53 -5
  468. package/src/gc/garbageCollection.ts +58 -51
  469. package/src/gc/gcConfigs.ts +4 -2
  470. package/src/gc/gcDefinitions.ts +17 -21
  471. package/src/gc/gcEarlyAdoption.md +145 -0
  472. package/src/gc/gcHelpers.ts +1 -12
  473. package/src/gc/gcSummaryStateTracker.ts +19 -65
  474. package/src/gc/gcTelemetry.ts +15 -13
  475. package/src/gc/gcUnreferencedStateTracker.ts +1 -1
  476. package/src/gc/index.ts +2 -4
  477. package/src/id-compressor/appendOnlySortedMap.ts +26 -87
  478. package/src/id-compressor/finalSpace.ts +67 -0
  479. package/src/id-compressor/idCompressor.ts +458 -1682
  480. package/src/id-compressor/identifiers.ts +42 -0
  481. package/src/id-compressor/index.ts +11 -20
  482. package/src/id-compressor/persistanceUtilities.ts +58 -0
  483. package/src/id-compressor/sessionSpaceNormalizer.ts +83 -0
  484. package/src/id-compressor/sessions.ts +405 -0
  485. package/src/id-compressor/utilities.ts +187 -0
  486. package/src/index.ts +9 -2
  487. package/src/metadata.ts +19 -0
  488. package/src/opLifecycle/README.md +20 -0
  489. package/src/opLifecycle/batchManager.ts +9 -1
  490. package/src/opLifecycle/definitions.ts +11 -0
  491. package/src/opLifecycle/index.ts +1 -1
  492. package/src/opLifecycle/opCompressor.ts +6 -5
  493. package/src/opLifecycle/opDecompressor.ts +47 -17
  494. package/src/opLifecycle/opGroupingManager.ts +18 -8
  495. package/src/opLifecycle/opSplitter.ts +10 -7
  496. package/src/opLifecycle/outbox.ts +177 -72
  497. package/src/opLifecycle/remoteMessageProcessor.ts +32 -9
  498. package/src/packageVersion.ts +1 -1
  499. package/src/pendingStateManager.ts +123 -78
  500. package/src/scheduleManager.ts +22 -11
  501. package/src/summary/index.ts +7 -4
  502. package/src/summary/orderedClientElection.ts +10 -6
  503. package/src/summary/runWhileConnectedCoordinator.ts +1 -1
  504. package/src/summary/runningSummarizer.ts +291 -163
  505. package/src/summary/summarizer.ts +27 -16
  506. package/src/summary/summarizerClientElection.ts +2 -2
  507. package/src/summary/summarizerHeuristics.ts +1 -1
  508. package/src/summary/summarizerNode/index.ts +2 -2
  509. package/src/summary/summarizerNode/summarizerNode.ts +142 -184
  510. package/src/summary/summarizerNode/summarizerNodeUtils.ts +27 -35
  511. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +72 -148
  512. package/src/summary/summarizerTypes.ts +49 -24
  513. package/src/summary/summaryCollection.ts +9 -4
  514. package/src/summary/summaryFormat.ts +9 -2
  515. package/src/summary/summaryGenerator.ts +72 -49
  516. package/src/summary/summaryManager.ts +44 -16
  517. package/dist/id-compressor/idRange.d.ts +0 -11
  518. package/dist/id-compressor/idRange.d.ts.map +0 -1
  519. package/dist/id-compressor/idRange.js +0 -29
  520. package/dist/id-compressor/idRange.js.map +0 -1
  521. package/dist/id-compressor/numericUuid.d.ts +0 -59
  522. package/dist/id-compressor/numericUuid.d.ts.map +0 -1
  523. package/dist/id-compressor/numericUuid.js +0 -325
  524. package/dist/id-compressor/numericUuid.js.map +0 -1
  525. package/dist/id-compressor/sessionIdNormalizer.d.ts +0 -138
  526. package/dist/id-compressor/sessionIdNormalizer.d.ts.map +0 -1
  527. package/dist/id-compressor/sessionIdNormalizer.js +0 -488
  528. package/dist/id-compressor/sessionIdNormalizer.js.map +0 -1
  529. package/dist/id-compressor/utils.d.ts +0 -57
  530. package/dist/id-compressor/utils.d.ts.map +0 -1
  531. package/dist/id-compressor/utils.js +0 -90
  532. package/dist/id-compressor/utils.js.map +0 -1
  533. package/dist/id-compressor/uuidUtilities.d.ts +0 -30
  534. package/dist/id-compressor/uuidUtilities.d.ts.map +0 -1
  535. package/dist/id-compressor/uuidUtilities.js +0 -106
  536. package/dist/id-compressor/uuidUtilities.js.map +0 -1
  537. package/lib/id-compressor/idRange.d.ts +0 -11
  538. package/lib/id-compressor/idRange.d.ts.map +0 -1
  539. package/lib/id-compressor/idRange.js +0 -25
  540. package/lib/id-compressor/idRange.js.map +0 -1
  541. package/lib/id-compressor/numericUuid.d.ts +0 -59
  542. package/lib/id-compressor/numericUuid.d.ts.map +0 -1
  543. package/lib/id-compressor/numericUuid.js +0 -315
  544. package/lib/id-compressor/numericUuid.js.map +0 -1
  545. package/lib/id-compressor/sessionIdNormalizer.d.ts +0 -138
  546. package/lib/id-compressor/sessionIdNormalizer.d.ts.map +0 -1
  547. package/lib/id-compressor/sessionIdNormalizer.js +0 -484
  548. package/lib/id-compressor/sessionIdNormalizer.js.map +0 -1
  549. package/lib/id-compressor/utils.d.ts +0 -57
  550. package/lib/id-compressor/utils.d.ts.map +0 -1
  551. package/lib/id-compressor/utils.js +0 -79
  552. package/lib/id-compressor/utils.js.map +0 -1
  553. package/lib/id-compressor/uuidUtilities.d.ts +0 -30
  554. package/lib/id-compressor/uuidUtilities.d.ts.map +0 -1
  555. package/lib/id-compressor/uuidUtilities.js +0 -98
  556. package/lib/id-compressor/uuidUtilities.js.map +0 -1
  557. package/src/id-compressor/idRange.ts +0 -35
  558. package/src/id-compressor/numericUuid.ts +0 -383
  559. package/src/id-compressor/sessionIdNormalizer.ts +0 -609
  560. package/src/id-compressor/utils.ts +0 -114
  561. package/src/id-compressor/uuidUtilities.ts +0 -123
@@ -2,8 +2,9 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { ITelemetryBaseLogger, ITelemetryGenericEvent } from "@fluidframework/common-definitions";
6
5
  import {
6
+ ITelemetryBaseLogger,
7
+ ITelemetryGenericEvent,
7
8
  FluidObject,
8
9
  IFluidHandle,
9
10
  IFluidHandleContext,
@@ -13,6 +14,7 @@ import {
13
14
  } from "@fluidframework/core-interfaces";
14
15
  import {
15
16
  IAudience,
17
+ IBatchMessage,
16
18
  IContainerContext,
17
19
  IDeltaManager,
18
20
  IRuntime,
@@ -25,23 +27,22 @@ import {
25
27
  IContainerRuntime,
26
28
  IContainerRuntimeEvents,
27
29
  } from "@fluidframework/container-runtime-definitions";
30
+ import { assert, delay, LazyPromise } from "@fluidframework/core-utils";
31
+ import { Trace, TypedEventEmitter } from "@fluid-internal/client-utils";
28
32
  import {
29
- assert,
30
- delay,
31
- LazyPromise,
32
- Trace,
33
- TypedEventEmitter,
34
- unreachableCase,
35
- } from "@fluidframework/common-utils";
36
- import {
37
- ChildLogger,
33
+ createChildLogger,
34
+ createChildMonitoringContext,
35
+ DataCorruptionError,
36
+ DataProcessingError,
37
+ GenericError,
38
38
  raiseConnectedEvent,
39
39
  PerformanceEvent,
40
+ // eslint-disable-next-line import/no-deprecated
40
41
  TaggedLoggerAdapter,
41
42
  MonitoringContext,
42
- loggerToMonitoringContext,
43
43
  wrapError,
44
44
  ITelemetryLoggerExt,
45
+ UsageError,
45
46
  } from "@fluidframework/telemetry-utils";
46
47
  import {
47
48
  DriverHeader,
@@ -50,12 +51,6 @@ import {
50
51
  ISummaryContext,
51
52
  } from "@fluidframework/driver-definitions";
52
53
  import { readAndParse } from "@fluidframework/driver-utils";
53
- import {
54
- DataCorruptionError,
55
- DataProcessingError,
56
- GenericError,
57
- UsageError,
58
- } from "@fluidframework/container-utils";
59
54
  import {
60
55
  IClientDetails,
61
56
  IDocumentMessage,
@@ -113,7 +108,11 @@ import { v4 as uuid } from "uuid";
113
108
  import { ContainerFluidHandleContext } from "./containerHandleContext";
114
109
  import { FluidDataStoreRegistry } from "./dataStoreRegistry";
115
110
  import { ReportOpPerfTelemetry, IPerfSignalReport } from "./connectionTelemetry";
116
- import { IPendingLocalState, PendingStateManager } from "./pendingStateManager";
111
+ import {
112
+ IPendingBatchMessage,
113
+ IPendingLocalState,
114
+ PendingStateManager,
115
+ } from "./pendingStateManager";
117
116
  import { pkgVersion } from "./packageVersion";
118
117
  import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
119
118
  import { DataStores, getSummaryForDatastores } from "./dataStores";
@@ -127,7 +126,6 @@ import {
127
126
  IContainerRuntimeMetadata,
128
127
  ICreateContainerMetadata,
129
128
  idCompressorBlobName,
130
- IFetchSnapshotResult,
131
129
  IRootSummarizerNodeWithGC,
132
130
  ISummaryMetadataMessage,
133
131
  metadataBlobName,
@@ -149,6 +147,14 @@ import {
149
147
  ISummarizerRuntime,
150
148
  IRefreshSummaryAckOptions,
151
149
  RunWhileConnectedCoordinator,
150
+ IGenerateSummaryTreeResult,
151
+ RetriableSummaryError,
152
+ IOnDemandSummarizeOptions,
153
+ ISummarizeResults,
154
+ IEnqueueSummarizeOptions,
155
+ EnqueueSummarizeResult,
156
+ ISummarizerEvents,
157
+ IBaseSummarizeResult,
152
158
  } from "./summary";
153
159
  import { formExponentialFn, Throttler } from "./throttler";
154
160
  import {
@@ -166,6 +172,7 @@ import { BindBatchTracker } from "./batchTracker";
166
172
  import { ScheduleManager } from "./scheduleManager";
167
173
  import {
168
174
  BatchMessage,
175
+ IBatch,
169
176
  IBatchCheckpoint,
170
177
  OpCompressor,
171
178
  OpDecompressor,
@@ -173,8 +180,10 @@ import {
173
180
  OpSplitter,
174
181
  RemoteMessageProcessor,
175
182
  OpGroupingManager,
183
+ getLongStack,
176
184
  } from "./opLifecycle";
177
185
  import { DeltaManagerSummarizerProxy } from "./deltaManagerSummarizerProxy";
186
+ import { IBatchMetadata } from "./metadata";
178
187
 
179
188
  export enum ContainerMessageType {
180
189
  // An op to be delivered to store
@@ -203,11 +212,64 @@ export enum ContainerMessageType {
203
212
  IdAllocation = "idAllocation",
204
213
  }
205
214
 
215
+ /**
216
+ * How should an older client handle an unrecognized remote op type?
217
+ *
218
+ * @internal
219
+ */
220
+ export type CompatModeBehavior =
221
+ /** Ignore the op. It won't be persisted if this client summarizes */
222
+ | "Ignore"
223
+ /** Fail processing immediately. (The container will close) */
224
+ | "FailToProcess";
225
+
226
+ /**
227
+ * All the info an older client would need to know how to handle an unrecognized remote op type
228
+ *
229
+ * @internal
230
+ */
231
+ export interface IContainerRuntimeMessageCompatDetails {
232
+ /** How should an older client handle an unrecognized remote op type? */
233
+ behavior: CompatModeBehavior;
234
+ }
235
+
236
+ /**
237
+ * Utility to implement compat behaviors given an unknown message type
238
+ * The parameters are typed to support compile-time enforcement of handling all known types/behaviors
239
+ *
240
+ * @param _unknownContainerRuntimeMessageType - Typed as never, to ensure all known types have been
241
+ * handled before calling this function (e.g. in a switch statement).
242
+ * @param compatBehavior - Typed redundantly with CompatModeBehavior to ensure handling is added when updating that type
243
+ */
244
+ function compatBehaviorAllowsMessageType(
245
+ _unknownContainerRuntimeMessageType: never,
246
+ compatBehavior: "Ignore" | "FailToProcess" | undefined,
247
+ ): boolean {
248
+ // undefined defaults to same behavior as "FailToProcess"
249
+ return compatBehavior === "Ignore";
250
+ }
251
+
252
+ /**
253
+ * The unpacked runtime message / details to be handled or dispatched by the ContainerRuntime
254
+ *
255
+ * IMPORTANT: when creating one to be serialized, set the properties in the order they appear here.
256
+ * This way stringified values can be compared.
257
+ */
206
258
  export interface ContainerRuntimeMessage {
207
- contents: any;
259
+ /** Type of the op, within the ContainerRuntime's domain */
208
260
  type: ContainerMessageType;
261
+ /** Domain-specific contents, interpreted according to the type */
262
+ contents: any;
263
+ /** Info describing how to handle this op in case the type is unrecognized (default: fail to process) */
264
+ compatDetails?: IContainerRuntimeMessageCompatDetails;
209
265
  }
210
266
 
267
+ /**
268
+ * An unpacked ISequencedDocumentMessage with the inner ContainerRuntimeMessage type/contents/etc
269
+ * promoted up to the outer object
270
+ */
271
+ export type SequencedContainerRuntimeMessage = ISequencedDocumentMessage & ContainerRuntimeMessage;
272
+
211
273
  export interface ISummaryBaseConfiguration {
212
274
  /**
213
275
  * Delay before first attempt to spawn summarizing container.
@@ -434,14 +496,6 @@ export interface IContainerRuntimeOptions {
434
496
  readonly enableGroupedBatching?: boolean;
435
497
  }
436
498
 
437
- /**
438
- * The summary tree returned by the root node. It adds state relevant to the root of the tree.
439
- */
440
- export interface IRootSummaryTreeWithStats extends ISummaryTreeWithStats {
441
- /** The garbage collection stats if GC ran, undefined otherwise. */
442
- gcStats?: IGCStats;
443
- }
444
-
445
499
  /**
446
500
  * Accepted header keys for requests coming to the runtime.
447
501
  */
@@ -454,9 +508,13 @@ export enum RuntimeHeaders {
454
508
 
455
509
  /** True if a tombstoned object should be returned without erroring */
456
510
  export const AllowTombstoneRequestHeaderKey = "allowTombstone"; // Belongs in the enum above, but avoiding the breaking change
511
+ /** [IRRELEVANT IF throwOnInactiveLoad OPTION NOT SET] True if an inactive object should be returned without erroring */
512
+ export const AllowInactiveRequestHeaderKey = "allowInactive"; // Belongs in the enum above, but avoiding the breaking change
457
513
 
458
514
  /** Tombstone error responses will have this header set to true */
459
515
  export const TombstoneResponseHeaderKey = "isTombstoned";
516
+ /** Inactive error responses will have this header set to true */
517
+ export const InactiveResponseHeaderKey = "isInactive";
460
518
 
461
519
  /**
462
520
  * The full set of parsed header data that may be found on Runtime requests
@@ -497,7 +555,7 @@ interface OldContainerContextWithLogger extends Omit<IContainerContext, "taggedL
497
555
  * instantiated runtime in a new instance of the container, so it can load to the
498
556
  * same state
499
557
  */
500
- interface IPendingRuntimeState {
558
+ export interface IPendingRuntimeState {
501
559
  /**
502
560
  * Pending ops from PendingStateManager
503
561
  */
@@ -526,12 +584,17 @@ const defaultCompressionConfig = {
526
584
 
527
585
  const defaultChunkSizeInBytes = 204800;
528
586
 
587
+ /** The default time to wait for pending ops to be processed during summarization */
588
+ export const defaultPendingOpsWaitTimeoutMs = 1000;
589
+ /** The default time to delay a summarization retry attempt when there are pending ops */
590
+ export const defaultPendingOpsRetryDelayMs = 1000;
591
+
529
592
  /**
530
593
  * Instead of refreshing from latest because we do not have 100% confidence in the state
531
594
  * of the current system, we should close the summarizer and let it recover.
532
595
  * This delay's goal is to prevent tight restart loops
533
596
  */
534
- const defaultCloseSummarizerDelayMs = 10000; // 10 seconds
597
+ const defaultCloseSummarizerDelayMs = 5000; // 5 seconds
535
598
 
536
599
  /**
537
600
  * @deprecated - use ContainerRuntimeMessage instead
@@ -573,14 +636,41 @@ export function getDeviceSpec() {
573
636
  return {};
574
637
  }
575
638
 
639
+ /**
640
+ * Older loader doesn't have a submitBatchFn member, this is the older way of submitting a batch.
641
+ * Rather than exposing the submitFn (now deprecated) and IDeltaManager (dangerous to hand out) to the Outbox,
642
+ * we can provide a partially-applied function to keep those items private to the ContainerRuntime.
643
+ */
644
+ export const makeLegacySendBatchFn =
645
+ (
646
+ submitFn: (type: MessageType, contents: any, batch: boolean, appData?: any) => number,
647
+ deltaManager: Pick<IDeltaManager<unknown, unknown>, "flush">,
648
+ ) =>
649
+ (batch: IBatch) => {
650
+ for (const message of batch.content) {
651
+ submitFn(
652
+ MessageType.Operation,
653
+ // For back-compat (submitFn only works on deserialized content)
654
+ message.contents === undefined ? undefined : JSON.parse(message.contents),
655
+ true, // batch
656
+ message.metadata,
657
+ );
658
+ }
659
+
660
+ deltaManager.flush();
661
+ };
662
+
576
663
  /**
577
664
  * Represents the runtime of the container. Contains helper functions/state of the container.
578
665
  * It will define the store level mappings.
579
666
  */
580
667
  export class ContainerRuntime
581
- extends TypedEventEmitter<IContainerRuntimeEvents>
668
+ extends TypedEventEmitter<IContainerRuntimeEvents & ISummarizerEvents>
582
669
  implements IContainerRuntime, IRuntime, ISummarizerRuntime, ISummarizerInternalsProvider
583
670
  {
671
+ /**
672
+ * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
673
+ */
584
674
  public get IFluidRouter() {
585
675
  return this;
586
676
  }
@@ -639,10 +729,10 @@ export class ContainerRuntime
639
729
  context: IContainerContext;
640
730
  registryEntries: NamedFluidDataStoreRegistryEntries;
641
731
  existing: boolean;
642
- requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
643
732
  runtimeOptions?: IContainerRuntimeOptions;
644
733
  containerScope?: FluidObject;
645
734
  containerRuntimeCtor?: typeof ContainerRuntime;
735
+ requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
646
736
  initializeEntryPoint?: (containerRuntime: IContainerRuntime) => Promise<FluidObject>;
647
737
  }): Promise<ContainerRuntime> {
648
738
  const {
@@ -653,18 +743,32 @@ export class ContainerRuntime
653
743
  runtimeOptions = {},
654
744
  containerScope = {},
655
745
  containerRuntimeCtor = ContainerRuntime,
656
- initializeEntryPoint,
657
746
  } = params;
658
747
 
748
+ const initializeEntryPoint =
749
+ params.initializeEntryPoint ??
750
+ (async (containerRuntime: IContainerRuntime) => ({
751
+ get IFluidRouter() {
752
+ return this;
753
+ },
754
+ async request(req) {
755
+ return containerRuntime.request(req);
756
+ },
757
+ }));
758
+
659
759
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
660
760
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
661
761
  const backCompatContext: IContainerContext | OldContainerContextWithLogger = context;
662
762
  const passLogger =
663
763
  backCompatContext.taggedLogger ??
764
+ // eslint-disable-next-line import/no-deprecated
664
765
  new TaggedLoggerAdapter((backCompatContext as OldContainerContextWithLogger).logger);
665
- const logger = ChildLogger.create(passLogger, undefined, {
666
- all: {
667
- runtimeVersion: pkgVersion,
766
+ const logger = createChildLogger({
767
+ logger: passLogger,
768
+ properties: {
769
+ all: {
770
+ runtimeVersion: pkgVersion,
771
+ },
668
772
  },
669
773
  });
670
774
 
@@ -705,8 +809,6 @@ export class ContainerRuntime
705
809
  tryFetchBlob<SerializedIdCompressorWithNoSession>(idCompressorBlobName),
706
810
  ]);
707
811
 
708
- const loadExisting = existing === true || context.existing === true;
709
-
710
812
  // read snapshot blobs needed for BlobManager to load
711
813
  const blobManagerSnapshot = await BlobManager.load(
712
814
  context.baseSnapshot?.trees[blobsTreeName],
@@ -741,9 +843,7 @@ export class ContainerRuntime
741
843
  if (loadSequenceNumberVerification === "log") {
742
844
  logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
743
845
  } else {
744
- // Call both close and dispose as closeFn implementation will no longer dispose runtime in future
745
846
  context.closeFn(error);
746
- context.disposeFn?.(error);
747
847
  }
748
848
  }
749
849
  }
@@ -756,7 +856,7 @@ export class ContainerRuntime
756
856
  idCompressor =
757
857
  serializedIdCompressor !== undefined
758
858
  ? IdCompressor.deserialize(serializedIdCompressor, createSessionId())
759
- : new IdCompressor(createSessionId(), logger);
859
+ : IdCompressor.create(logger);
760
860
  }
761
861
 
762
862
  const runtime = new containerRuntimeCtor(
@@ -780,7 +880,7 @@ export class ContainerRuntime
780
880
  },
781
881
  containerScope,
782
882
  logger,
783
- loadExisting,
883
+ existing,
784
884
  blobManagerSnapshot,
785
885
  context.storage,
786
886
  idCompressor,
@@ -789,6 +889,7 @@ export class ContainerRuntime
789
889
  initializeEntryPoint,
790
890
  );
791
891
 
892
+ await runtime.blobManager.processStashedChanges();
792
893
  // It's possible to have ops with a reference sequence number of 0. Op sequence numbers start
793
894
  // at 1, so we won't see a replayed saved op with a sequence number of 0.
794
895
  await runtime.pendingStateManager.applyStashedOpsAt(0);
@@ -799,44 +900,49 @@ export class ContainerRuntime
799
900
  return runtime;
800
901
  }
801
902
 
802
- public get options(): ILoaderOptions {
803
- return this.context.options;
804
- }
903
+ public readonly options: ILoaderOptions;
805
904
 
905
+ private readonly _getClientId: () => string | undefined;
806
906
  public get clientId(): string | undefined {
807
- return this.context.clientId;
907
+ return this._getClientId();
808
908
  }
809
909
 
810
- public get clientDetails(): IClientDetails {
811
- return this.context.clientDetails;
812
- }
910
+ public readonly clientDetails: IClientDetails;
813
911
 
814
912
  public get storage(): IDocumentStorageService {
815
913
  return this._storage;
816
914
  }
817
915
 
818
- public get reSubmitFn(): (
819
- type: ContainerMessageType,
820
- content: any,
821
- localOpMetadata: unknown,
822
- opMetadata: Record<string, unknown> | undefined,
823
- ) => void {
824
- // eslint-disable-next-line @typescript-eslint/unbound-method
825
- return this.reSubmitCore;
916
+ /** @deprecated - The functionality is no longer exposed publicly */
917
+ public get reSubmitFn() {
918
+ return (
919
+ type: ContainerMessageType,
920
+ contents: any,
921
+ localOpMetadata: unknown,
922
+ opMetadata: Record<string, unknown> | undefined,
923
+ ) => this.reSubmitCore({ type, contents }, localOpMetadata, opMetadata);
924
+ // Note: compatDetails is not included in this deprecated API
826
925
  }
827
926
 
828
- public get disposeFn(): (error?: ICriticalContainerError) => void {
829
- // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
830
- return this.context.disposeFn ?? this.context.closeFn;
831
- }
832
-
833
- public get closeFn(): (error?: ICriticalContainerError) => void {
834
- // Also call disposeFn to retain functionality of runtime being disposed on close
835
- return (error?: ICriticalContainerError) => {
836
- this.context.closeFn(error);
837
- this.context.disposeFn?.(error);
838
- };
839
- }
927
+ private readonly submitFn: (
928
+ type: MessageType,
929
+ contents: any,
930
+ batch: boolean,
931
+ appData?: any,
932
+ ) => number;
933
+ /**
934
+ * Although current IContainerContext guarantees submitBatchFn, it is not available on older loaders.
935
+ */
936
+ private readonly submitBatchFn:
937
+ | ((batch: IBatchMessage[], referenceSequenceNumber?: number) => number)
938
+ | undefined;
939
+ private readonly submitSummaryFn: (
940
+ summaryOp: ISummaryContent,
941
+ referenceSequenceNumber?: number,
942
+ ) => number;
943
+ private readonly submitSignalFn: (contents: any) => void;
944
+ public readonly disposeFn: (error?: ICriticalContainerError) => void;
945
+ public readonly closeFn: (error?: ICriticalContainerError) => void;
840
946
 
841
947
  public get flushMode(): FlushMode {
842
948
  return this._flushMode;
@@ -850,8 +956,9 @@ export class ContainerRuntime
850
956
  return this.registry;
851
957
  }
852
958
 
959
+ private readonly _getAttachState: () => AttachState;
853
960
  public get attachState(): AttachState {
854
- return this.context.attachState;
961
+ return this._getAttachState();
855
962
  }
856
963
 
857
964
  public idCompressor: (IIdCompressor & IIdCompressorCore) | undefined;
@@ -950,11 +1057,16 @@ export class ContainerRuntime
950
1057
  private emitDirtyDocumentEvent = true;
951
1058
  private readonly enableOpReentryCheck: boolean;
952
1059
  private readonly disableAttachReorder: boolean | undefined;
953
- private readonly summaryStateUpdateMethod: string | undefined;
954
1060
  private readonly closeSummarizerDelayMs: number;
1061
+ /**
1062
+ * If true, summary generated is validate before uploading it to the server. With single commit summaries,
1063
+ * summaries will be accepted once uploaded, so they should be validated before upload. However, this can
1064
+ * currently be controlled via a feature flag as its a new functionality.
1065
+ */
1066
+ private readonly validateSummaryBeforeUpload: boolean;
955
1067
 
956
1068
  private readonly defaultTelemetrySignalSampleCount = 100;
957
- private _perfSignalData: IPerfSignalReport = {
1069
+ private readonly _perfSignalData: IPerfSignalReport = {
958
1070
  signalsLost: 0,
959
1071
  signalSequenceNumber: 0,
960
1072
  signalTimestamp: 0,
@@ -1036,11 +1148,21 @@ export class ContainerRuntime
1036
1148
  */
1037
1149
  private readonly idCompressorEnabled: boolean;
1038
1150
 
1151
+ /**
1152
+ * Whether this client is the summarizer client itself (type is summarizerClientType)
1153
+ */
1154
+ private readonly isSummarizerClient: boolean;
1155
+
1156
+ /**
1157
+ * The id of the version used to initially load this runtime, or undefined if it's newly created.
1158
+ */
1159
+ private readonly loadedFromVersionId: string | undefined;
1160
+
1039
1161
  /**
1040
1162
  * @internal
1041
1163
  */
1042
1164
  protected constructor(
1043
- private readonly context: IContainerContext,
1165
+ context: IContainerContext,
1044
1166
  private readonly registry: IFluidDataStoreRegistry,
1045
1167
  metadata: IContainerRuntimeMetadata | undefined,
1046
1168
  electedSummarizerData: ISerializedElection | undefined,
@@ -1067,10 +1189,64 @@ export class ContainerRuntime
1067
1189
  ) {
1068
1190
  super();
1069
1191
 
1070
- this.innerDeltaManager = context.deltaManager;
1071
- this.deltaManager = new DeltaManagerSummarizerProxy(context.deltaManager);
1192
+ const {
1193
+ options,
1194
+ clientDetails,
1195
+ connected,
1196
+ baseSnapshot,
1197
+ submitFn,
1198
+ submitBatchFn,
1199
+ submitSummaryFn,
1200
+ submitSignalFn,
1201
+ disposeFn,
1202
+ closeFn,
1203
+ deltaManager,
1204
+ quorum,
1205
+ audience,
1206
+ loader,
1207
+ pendingLocalState,
1208
+ supportedFeatures,
1209
+ } = context;
1210
+
1211
+ this.innerDeltaManager = deltaManager;
1212
+ this.deltaManager = new DeltaManagerSummarizerProxy(this.innerDeltaManager);
1213
+
1214
+ // Here we could wrap/intercept on these functions to block/modify outgoing messages if needed.
1215
+ // This makes ContainerRuntime the final gatekeeper for outgoing messages.
1216
+ this.submitFn = submitFn;
1217
+ this.submitBatchFn = submitBatchFn;
1218
+ this.submitSummaryFn = submitSummaryFn;
1219
+ this.submitSignalFn = submitSignalFn;
1220
+
1221
+ this.options = options;
1222
+ this.clientDetails = clientDetails;
1223
+ this.isSummarizerClient = this.clientDetails.type === summarizerClientType;
1224
+ this.loadedFromVersionId = context.getLoadedFromVersion()?.id;
1225
+ this._getClientId = () => context.clientId;
1226
+ this._getAttachState = () => context.attachState;
1227
+ this.getAbsoluteUrl = async (relativeUrl: string) => {
1228
+ if (context.getAbsoluteUrl === undefined) {
1229
+ throw new Error("Driver does not implement getAbsoluteUrl");
1230
+ }
1231
+ if (this.attachState !== AttachState.Attached) {
1232
+ return undefined;
1233
+ }
1234
+ return context.getAbsoluteUrl(relativeUrl);
1235
+ };
1236
+ // TODO: Consider that the Container could just listen to these events itself, or even more appropriately maybe the
1237
+ // customer should observe dirty state on the runtime (the owner of dirty state) directly, rather than on the IContainer.
1238
+ this.on("dirty", () => context.updateDirtyContainerState(true));
1239
+ this.on("saved", () => context.updateDirtyContainerState(false));
1240
+
1241
+ // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
1242
+ this.disposeFn = disposeFn ?? closeFn;
1243
+ // In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
1244
+ this.closeFn = this.isSummarizerClient ? this.disposeFn : closeFn;
1072
1245
 
1073
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
1246
+ this.mc = createChildMonitoringContext({
1247
+ logger: this.logger,
1248
+ namespace: "ContainerRuntime",
1249
+ });
1074
1250
 
1075
1251
  let loadSummaryNumber: number;
1076
1252
  // Get the container creation metadata. For new container, we initialize these. For existing containers,
@@ -1102,7 +1278,9 @@ export class ContainerRuntime
1102
1278
 
1103
1279
  this.messageAtLastSummary = metadata?.message;
1104
1280
 
1105
- this._connected = this.context.connected;
1281
+ // Note that we only need to pull the *initial* connected state from the context.
1282
+ // Later updates come through calls to setConnectionState.
1283
+ this._connected = connected;
1106
1284
 
1107
1285
  this.gcTombstoneEnforcementAllowed = shouldAllowGcTombstoneEnforcement(
1108
1286
  metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
@@ -1131,7 +1309,7 @@ export class ContainerRuntime
1131
1309
 
1132
1310
  const opSplitter = new OpSplitter(
1133
1311
  chunks,
1134
- this.context.submitBatchFn,
1312
+ this.submitBatchFn,
1135
1313
  disableChunking === true ? Number.POSITIVE_INFINITY : runtimeOptions.chunkSizeInBytes,
1136
1314
  runtimeOptions.maxBatchSizeInBytes,
1137
1315
  this.mc.logger,
@@ -1172,7 +1350,7 @@ export class ContainerRuntime
1172
1350
 
1173
1351
  if (
1174
1352
  runtimeOptions.flushMode === (FlushModeExperimental.Async as unknown as FlushMode) &&
1175
- context.supportedFeatures?.get("referenceSequenceNumbers") !== true
1353
+ supportedFeatures?.get("referenceSequenceNumbers") !== true
1176
1354
  ) {
1177
1355
  // The loader does not support reference sequence numbers, falling back on FlushMode.TurnBased
1178
1356
  this.mc.logger.sendErrorEvent({ eventName: "FlushModeFallback" });
@@ -1181,7 +1359,7 @@ export class ContainerRuntime
1181
1359
  this._flushMode = runtimeOptions.flushMode;
1182
1360
  }
1183
1361
 
1184
- const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
1362
+ const pendingRuntimeState = pendingLocalState as IPendingRuntimeState | undefined;
1185
1363
 
1186
1364
  const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
1187
1365
  if (
@@ -1197,16 +1375,15 @@ export class ContainerRuntime
1197
1375
  this.garbageCollector = GarbageCollector.create({
1198
1376
  runtime: this,
1199
1377
  gcOptions: this.runtimeOptions.gcOptions,
1200
- baseSnapshot: context.baseSnapshot,
1378
+ baseSnapshot,
1201
1379
  baseLogger: this.mc.logger,
1202
1380
  existing,
1203
1381
  metadata,
1204
1382
  createContainerMetadata: this.createContainerMetadata,
1205
- isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
1383
+ isSummarizerClient: this.isSummarizerClient,
1206
1384
  getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
1207
1385
  getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
1208
1386
  readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
1209
- getContainerDiagnosticId: () => this.context.id,
1210
1387
  // GC runs in summarizer client and needs access to the real (non-proxy) active information. The proxy
1211
1388
  // delta manager would always return false for summarizer client.
1212
1389
  activeConnection: () => this.innerDeltaManager.active,
@@ -1214,14 +1391,14 @@ export class ContainerRuntime
1214
1391
 
1215
1392
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
1216
1393
  this.summarizerNode = createRootSummarizerNodeWithGC(
1217
- ChildLogger.create(this.logger, "SummarizerNode"),
1394
+ createChildLogger({ logger: this.logger, namespace: "SummarizerNode" }),
1218
1395
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
1219
1396
  async (fullTree: boolean, trackState: boolean, telemetryContext?: ITelemetryContext) =>
1220
1397
  this.summarizeInternal(fullTree, trackState, telemetryContext),
1221
1398
  // Latest change sequence number, no changes since summary applied yet
1222
1399
  loadedFromSequenceNumber,
1223
1400
  // Summary reference sequence number, undefined if no summary yet
1224
- context.baseSnapshot ? loadedFromSequenceNumber : undefined,
1401
+ baseSnapshot !== undefined ? loadedFromSequenceNumber : undefined,
1225
1402
  {
1226
1403
  // Must set to false to prevent sending summary handle which would be pointing to
1227
1404
  // a summary with an older protocol state.
@@ -1238,14 +1415,14 @@ export class ContainerRuntime
1238
1415
  async () => this.garbageCollector.getBaseGCDetails(),
1239
1416
  );
1240
1417
 
1241
- if (context.baseSnapshot) {
1242
- this.summarizerNode.updateBaseSummaryState(context.baseSnapshot);
1418
+ if (baseSnapshot) {
1419
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
1243
1420
  }
1244
1421
 
1245
1422
  this.dataStores = new DataStores(
1246
- getSummaryForDatastores(context.baseSnapshot, metadata),
1423
+ getSummaryForDatastores(baseSnapshot, metadata),
1247
1424
  this,
1248
- (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg),
1425
+ (attachMsg) => this.submit({ type: ContainerMessageType.Attach, contents: attachMsg }),
1249
1426
  (id: string, createParam: CreateChildSummarizerNodeParam) =>
1250
1427
  (
1251
1428
  summarizeInternal: SummarizeInternalFn,
@@ -1272,10 +1449,14 @@ export class ContainerRuntime
1272
1449
  () => this.storage,
1273
1450
  (localId: string, blobId?: string) => {
1274
1451
  if (!this.disposed) {
1275
- this.submit(ContainerMessageType.BlobAttach, undefined, undefined, {
1276
- localId,
1277
- blobId,
1278
- });
1452
+ this.submit(
1453
+ { type: ContainerMessageType.BlobAttach, contents: undefined },
1454
+ undefined,
1455
+ {
1456
+ localId,
1457
+ blobId,
1458
+ },
1459
+ );
1279
1460
  }
1280
1461
  },
1281
1462
  (blobPath: string) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"),
@@ -1286,10 +1467,10 @@ export class ContainerRuntime
1286
1467
  );
1287
1468
 
1288
1469
  this.scheduleManager = new ScheduleManager(
1289
- context.deltaManager,
1470
+ this.innerDeltaManager,
1290
1471
  this,
1291
1472
  () => this.clientId,
1292
- ChildLogger.create(this.logger, "ScheduleManager"),
1473
+ createChildLogger({ logger: this.logger, namespace: "ScheduleManager" }),
1293
1474
  );
1294
1475
 
1295
1476
  this.pendingStateManager = new PendingStateManager(
@@ -1299,9 +1480,11 @@ export class ContainerRuntime
1299
1480
  close: this.closeFn,
1300
1481
  connected: () => this.connected,
1301
1482
  reSubmit: this.reSubmit.bind(this),
1302
- orderSequentially: this.orderSequentially.bind(this),
1483
+ reSubmitBatch: this.reSubmitBatch.bind(this),
1484
+ isActiveConnection: () => this.innerDeltaManager.active,
1303
1485
  },
1304
1486
  pendingRuntimeState?.pending,
1487
+ this.logger,
1305
1488
  );
1306
1489
 
1307
1490
  const disableCompression = this.mc.config.getBoolean(
@@ -1318,16 +1501,21 @@ export class ContainerRuntime
1318
1501
  const disablePartialFlush = this.mc.config.getBoolean(
1319
1502
  "Fluid.ContainerRuntime.DisablePartialFlush",
1320
1503
  );
1504
+
1505
+ const legacySendBatchFn = makeLegacySendBatchFn(this.submitFn, this.innerDeltaManager);
1506
+
1321
1507
  this.outbox = new Outbox({
1322
1508
  shouldSend: () => this.canSendOps(),
1323
1509
  pendingStateManager: this.pendingStateManager,
1324
- containerContext: this.context,
1510
+ submitBatchFn: this.submitBatchFn,
1511
+ legacySendBatchFn,
1325
1512
  compressor: new OpCompressor(this.mc.logger),
1326
1513
  splitter: opSplitter,
1327
1514
  config: {
1328
1515
  compressionOptions,
1329
1516
  maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
1330
1517
  disablePartialFlush: disablePartialFlush === true,
1518
+ enableGroupedBatching: this.groupedBatchingEnabled,
1331
1519
  },
1332
1520
  logger: this.mc.logger,
1333
1521
  groupingManager: opGroupingManager,
@@ -1335,40 +1523,48 @@ export class ContainerRuntime
1335
1523
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
1336
1524
  clientSequenceNumber: this._processedClientSequenceNumber,
1337
1525
  }),
1526
+ reSubmit: this.reSubmit.bind(this),
1527
+ opReentrancy: () => this.ensureNoDataModelChangesCalls > 0,
1528
+ closeContainer: this.closeFn,
1338
1529
  });
1339
1530
 
1340
- this.context.quorum.on("removeMember", (clientId: string) => {
1531
+ this._quorum = quorum;
1532
+ this._quorum.on("removeMember", (clientId: string) => {
1341
1533
  this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
1342
1534
  });
1343
1535
 
1344
- this.summaryStateUpdateMethod = this.mc.config.getString(
1345
- "Fluid.ContainerRuntime.Test.SummaryStateUpdateMethod",
1346
- );
1536
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1537
+ this._audience = audience!;
1538
+
1347
1539
  const closeSummarizerDelayOverride = this.mc.config.getNumber(
1348
1540
  "Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs",
1349
1541
  );
1350
1542
  this.closeSummarizerDelayMs = closeSummarizerDelayOverride ?? defaultCloseSummarizerDelayMs;
1543
+ this.validateSummaryBeforeUpload =
1544
+ this.mc.config.getBoolean("Fluid.Summarizer.ValidateSummaryBeforeUpload") ?? false;
1351
1545
 
1352
1546
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
1353
1547
 
1354
1548
  this.dirtyContainer =
1355
- this.context.attachState !== AttachState.Attached ||
1356
- this.pendingStateManager.hasPendingMessages();
1357
- this.context.updateDirtyContainerState(this.dirtyContainer);
1549
+ this.attachState !== AttachState.Attached || this.hasPendingMessages();
1550
+ context.updateDirtyContainerState(this.dirtyContainer);
1358
1551
 
1359
1552
  if (this.summariesDisabled) {
1360
1553
  this.mc.logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
1361
1554
  } else {
1362
- const orderedClientLogger = ChildLogger.create(this.logger, "OrderedClientElection");
1555
+ const orderedClientLogger = createChildLogger({
1556
+ logger: this.logger,
1557
+ namespace: "OrderedClientElection",
1558
+ });
1363
1559
  const orderedClientCollection = new OrderedClientCollection(
1364
1560
  orderedClientLogger,
1365
- this.context.deltaManager,
1366
- this.context.quorum,
1561
+ this.innerDeltaManager,
1562
+ this._quorum,
1367
1563
  );
1368
1564
  const orderedClientElectionForSummarizer = new OrderedClientElection(
1369
1565
  orderedClientLogger,
1370
1566
  orderedClientCollection,
1371
- electedSummarizerData ?? this.context.deltaManager.lastSequenceNumber,
1567
+ electedSummarizerData ?? this.innerDeltaManager.lastSequenceNumber,
1372
1568
  SummarizerClientElection.isClientEligible,
1373
1569
  );
1374
1570
 
@@ -1379,7 +1575,7 @@ export class ContainerRuntime
1379
1575
  this.maxOpsSinceLastSummary,
1380
1576
  );
1381
1577
 
1382
- if (this.context.clientDetails.type === summarizerClientType) {
1578
+ if (this.isSummarizerClient) {
1383
1579
  this._summarizer = new Summarizer(
1384
1580
  this /* ISummarizerRuntime */,
1385
1581
  () => this.summaryConfiguration,
@@ -1394,19 +1590,19 @@ export class ContainerRuntime
1394
1590
  () => this.innerDeltaManager.active,
1395
1591
  ),
1396
1592
  );
1397
- } else if (
1398
- SummarizerClientElection.clientDetailsPermitElection(this.context.clientDetails)
1399
- ) {
1593
+ } else if (SummarizerClientElection.clientDetailsPermitElection(this.clientDetails)) {
1400
1594
  // Only create a SummaryManager and SummarizerClientElection
1401
1595
  // if summaries are enabled and we are not the summarizer client.
1402
1596
  const defaultAction = () => {
1403
1597
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
1404
- this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
1598
+ this.mc.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
1405
1599
  // unregister default to no log on every op after falling behind
1406
1600
  // and register summary ack handler to re-register this handler
1407
1601
  // after successful summary
1408
1602
  this.summaryCollection.once(MessageType.SummaryAck, () => {
1409
- this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:CaughtUp" });
1603
+ this.mc.logger.sendTelemetryEvent({
1604
+ eventName: "SummaryStatus:CaughtUp",
1605
+ });
1410
1606
  // we've caught up, so re-register the default action to monitor for
1411
1607
  // falling behind, and unregister ourself
1412
1608
  this.summaryCollection.on("default", defaultAction);
@@ -1423,7 +1619,7 @@ export class ContainerRuntime
1423
1619
  this, // IConnectedState
1424
1620
  this.summaryCollection,
1425
1621
  this.logger,
1426
- this.formRequestSummarizerFn(this.context.loader),
1622
+ this.formRequestSummarizerFn(loader),
1427
1623
  new Throttler(
1428
1624
  60 * 1000, // 60 sec delay window
1429
1625
  30 * 1000, // 30 sec max delay
@@ -1435,6 +1631,9 @@ export class ContainerRuntime
1435
1631
  },
1436
1632
  this.heuristicsDisabled,
1437
1633
  );
1634
+ this.summaryManager.on("summarize", (eventProps) => {
1635
+ this.emit("summarize", eventProps);
1636
+ });
1438
1637
  this.summaryManager.start();
1439
1638
  }
1440
1639
  }
@@ -1474,7 +1673,7 @@ export class ContainerRuntime
1474
1673
  ...getDeviceSpec(),
1475
1674
  });
1476
1675
 
1477
- this.logger.sendTelemetryEvent({
1676
+ this.mc.logger.sendTelemetryEvent({
1478
1677
  eventName: "ContainerLoadStats",
1479
1678
  ...this.createContainerMetadata,
1480
1679
  ...this.dataStores.containerLoadStats,
@@ -1490,18 +1689,17 @@ export class ContainerRuntime
1490
1689
  disableAttachReorder: this.disableAttachReorder,
1491
1690
  disablePartialFlush,
1492
1691
  idCompressorEnabled: this.idCompressorEnabled,
1493
- summaryStateUpdateMethod: this.summaryStateUpdateMethod,
1494
1692
  closeSummarizerDelayOverride,
1495
1693
  }),
1496
1694
  telemetryDocumentId: this.telemetryDocumentId,
1497
1695
  groupedBatchingEnabled: this.groupedBatchingEnabled,
1498
1696
  });
1499
1697
 
1500
- ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
1698
+ ReportOpPerfTelemetry(this.clientId, this.deltaManager, this.logger);
1501
1699
  BindBatchTracker(this, this.logger);
1502
1700
 
1503
1701
  this.entryPoint = new LazyPromise(async () => {
1504
- if (this.context.clientDetails.type === summarizerClientType) {
1702
+ if (this.isSummarizerClient) {
1505
1703
  assert(
1506
1704
  this._summarizer !== undefined,
1507
1705
  0x5bf /* Summarizer object is undefined in a summarizer client */,
@@ -1525,7 +1723,7 @@ export class ContainerRuntime
1525
1723
  }
1526
1724
  this._disposed = true;
1527
1725
 
1528
- this.logger.sendTelemetryEvent(
1726
+ this.mc.logger.sendTelemetryEvent(
1529
1727
  {
1530
1728
  eventName: "ContainerRuntimeDisposed",
1531
1729
  isDirty: this.isDirty,
@@ -1549,6 +1747,7 @@ export class ContainerRuntime
1549
1747
  /**
1550
1748
  * Notifies this object about the request made to the container.
1551
1749
  * @param request - Request made to the handler.
1750
+ * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
1552
1751
  */
1553
1752
  public async request(request: IRequest): Promise<IResponse> {
1554
1753
  try {
@@ -1606,7 +1805,7 @@ export class ContainerRuntime
1606
1805
  subRequest.url.startsWith("/"),
1607
1806
  0x126 /* "Expected createSubRequest url to include a leading slash" */,
1608
1807
  );
1609
- return dataStore.IFluidRouter.request(subRequest);
1808
+ return dataStore.request(subRequest);
1610
1809
  }
1611
1810
 
1612
1811
  return create404Response(request);
@@ -1654,6 +1853,7 @@ export class ContainerRuntime
1654
1853
  dataStoreContext.packagePath,
1655
1854
  request?.headers,
1656
1855
  );
1856
+
1657
1857
  return dataStoreChannel;
1658
1858
  }
1659
1859
 
@@ -1748,7 +1948,7 @@ export class ContainerRuntime
1748
1948
  this.mc.logger.sendTelemetryEvent({
1749
1949
  eventName: "ReconnectsWithNoProgress",
1750
1950
  attempts: this.consecutiveReconnects,
1751
- pendingMessages: this.pendingStateManager.pendingMessagesCount,
1951
+ pendingMessages: this.pendingMessagesCount,
1752
1952
  });
1753
1953
  }
1754
1954
 
@@ -1815,19 +2015,17 @@ export class ContainerRuntime
1815
2015
  * Parse an op's type and actual content from given serialized content
1816
2016
  * ! Note: this format needs to be in-line with what is set in the "ContainerRuntime.submit(...)" method
1817
2017
  */
1818
- private parseOpContent(serializedContent?: string): {
1819
- type: ContainerMessageType;
1820
- contents: unknown;
1821
- } {
2018
+ private parseOpContent(serializedContent?: string): ContainerRuntimeMessage {
1822
2019
  assert(serializedContent !== undefined, 0x6d5 /* content must be defined */);
1823
- const parsed = JSON.parse(serializedContent);
1824
- assert(parsed.type !== undefined, 0x6d6 /* incorrect op content format */);
1825
- return { type: parsed.type as ContainerMessageType, contents: parsed.contents };
2020
+ const { type, contents, compatDetails }: ContainerRuntimeMessage =
2021
+ JSON.parse(serializedContent);
2022
+ assert(type !== undefined, 0x6d6 /* incorrect op content format */);
2023
+ return { type, contents, compatDetails };
1826
2024
  }
1827
2025
 
1828
2026
  private async applyStashedOp(op: string): Promise<unknown> {
1829
2027
  // Need to parse from string for back-compat
1830
- const { type, contents } = this.parseOpContent(op);
2028
+ const { type, contents, compatDetails } = this.parseOpContent(op);
1831
2029
  switch (type) {
1832
2030
  case ContainerMessageType.FluidDataStoreOp:
1833
2031
  return this.dataStores.applyStashedOp(contents as IEnvelope);
@@ -1846,8 +2044,27 @@ export class ContainerRuntime
1846
2044
  throw new Error("chunkedOp not expected here");
1847
2045
  case ContainerMessageType.Rejoin:
1848
2046
  throw new Error("rejoin not expected here");
1849
- default:
1850
- unreachableCase(type, `Unknown ContainerMessageType: ${type}`);
2047
+ default: {
2048
+ // This should be extremely rare for stashed ops.
2049
+ // It would require a newer runtime stashing ops and then an older one applying them,
2050
+ // e.g. if an app rolled back its container version
2051
+ const compatBehavior = compatDetails?.behavior;
2052
+ if (!compatBehaviorAllowsMessageType(type, compatBehavior)) {
2053
+ const error = DataProcessingError.create(
2054
+ "Stashed runtime message of unknown type",
2055
+ "applyStashedOp",
2056
+ undefined /* sequencedMessage */,
2057
+ {
2058
+ messageDetails: JSON.stringify({
2059
+ type,
2060
+ compatBehavior,
2061
+ }),
2062
+ },
2063
+ );
2064
+ this.closeFn(error);
2065
+ throw error;
2066
+ }
2067
+ }
1851
2068
  }
1852
2069
  }
1853
2070
 
@@ -1861,32 +2078,6 @@ export class ContainerRuntime
1861
2078
  return;
1862
2079
  }
1863
2080
 
1864
- // If attachment blobs were added while disconnected, we need to delay
1865
- // propagation of the "connected" event until we have uploaded them to
1866
- // ensure we don't submit ops referencing a blob that has not been uploaded
1867
- // Note that the inner (non-proxy) delta manager is needed here to get the readonly information.
1868
- const connecting =
1869
- connected && !this._connected && !this.innerDeltaManager.readOnlyInfo.readonly;
1870
- if (connecting && this.blobManager.hasPendingOfflineUploads) {
1871
- assert(
1872
- !this.delayConnectClientId,
1873
- 0x392 /* Connect event delay must be canceled before subsequent connect event */,
1874
- );
1875
- assert(!!clientId, 0x393 /* Must have clientId when connecting */);
1876
- this.delayConnectClientId = clientId;
1877
- this.blobManager.onConnected().then(
1878
- () => {
1879
- // make sure we didn't reconnect before the promise resolved
1880
- if (this.delayConnectClientId === clientId && !this.disposed) {
1881
- this.delayConnectClientId = undefined;
1882
- this.setConnectionStateCore(connected, clientId);
1883
- }
1884
- },
1885
- (error) => this.closeFn(error),
1886
- );
1887
- return;
1888
- }
1889
-
1890
2081
  this.setConnectionStateCore(connected, clientId);
1891
2082
  }
1892
2083
 
@@ -1900,6 +2091,14 @@ export class ContainerRuntime
1900
2091
  // There might be no change of state due to Container calling this API after loading runtime.
1901
2092
  const changeOfState = this._connected !== connected;
1902
2093
  const reconnection = changeOfState && !connected;
2094
+
2095
+ // We need to flush the ops currently collected by Outbox to preserve original order.
2096
+ // This flush NEEDS to happen before we set the ContainerRuntime to "connected".
2097
+ // We want these ops to get to the PendingStateManager without sending to service and have them return to the Outbox upon calling "replayPendingStates".
2098
+ if (changeOfState && connected) {
2099
+ this.flush();
2100
+ }
2101
+
1903
2102
  this._connected = connected;
1904
2103
 
1905
2104
  if (!connected) {
@@ -1926,7 +2125,7 @@ export class ContainerRuntime
1926
2125
  {
1927
2126
  dataLoss: 1,
1928
2127
  attempts: this.consecutiveReconnects,
1929
- pendingMessages: this.pendingStateManager.pendingMessagesCount,
2128
+ pendingMessages: this.pendingMessagesCount,
1930
2129
  },
1931
2130
  ),
1932
2131
  );
@@ -1951,24 +2150,30 @@ export class ContainerRuntime
1951
2150
  public process(messageArg: ISequencedDocumentMessage, local: boolean) {
1952
2151
  this.verifyNotClosed();
1953
2152
 
1954
- // Whether or not the message is actually a runtime message.
2153
+ // Whether or not the message appears to be a runtime message from an up-to-date client.
1955
2154
  // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
1956
2155
  // or something different, like a system message.
1957
- const runtimeMessage = messageArg.type === MessageType.Operation;
2156
+ const modernRuntimeMessage = messageArg.type === MessageType.Operation;
1958
2157
 
1959
2158
  // Do shallow copy of message, as the processing flow will modify it.
1960
2159
  const messageCopy = { ...messageArg };
1961
2160
  for (const message of this.remoteMessageProcessor.process(messageCopy)) {
1962
- this.processCore(message, local, runtimeMessage);
2161
+ this.processCore(message, local, modernRuntimeMessage);
1963
2162
  }
1964
2163
  }
1965
2164
 
1966
2165
  private _processedClientSequenceNumber: number | undefined;
1967
2166
 
2167
+ /**
2168
+ * Direct the message to the correct subsystem for processing, and implement other side effects
2169
+ * @param message - The unpacked message. Likely a ContainerRuntimeMessage, but could also be a system op
2170
+ * @param local - Did this client send the op?
2171
+ * @param modernRuntimeMessage - Does this appear like a current ContainerRuntimeMessage?
2172
+ */
1968
2173
  private processCore(
1969
- message: ISequencedDocumentMessage,
2174
+ message: ISequencedDocumentMessage | SequencedContainerRuntimeMessage,
1970
2175
  local: boolean,
1971
- runtimeMessage: boolean,
2176
+ modernRuntimeMessage: boolean,
1972
2177
  ) {
1973
2178
  // Surround the actual processing of the operation with messages to the schedule manager indicating
1974
2179
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
@@ -1979,8 +2184,10 @@ export class ContainerRuntime
1979
2184
 
1980
2185
  try {
1981
2186
  let localOpMetadata: unknown;
1982
- if (local && runtimeMessage && message.type !== ContainerMessageType.ChunkedOp) {
1983
- localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
2187
+ if (local && modernRuntimeMessage && message.type !== ContainerMessageType.ChunkedOp) {
2188
+ localOpMetadata = this.pendingStateManager.processPendingLocalMessage(
2189
+ message as SequencedContainerRuntimeMessage,
2190
+ );
1984
2191
  }
1985
2192
 
1986
2193
  // If there are no more pending messages after processing a local message,
@@ -1989,53 +2196,14 @@ export class ContainerRuntime
1989
2196
  this.updateDocumentDirtyState(false);
1990
2197
  }
1991
2198
 
1992
- const type = message.type as ContainerMessageType;
1993
- switch (type) {
1994
- case ContainerMessageType.Attach:
1995
- this.dataStores.processAttachMessage(message, local);
1996
- break;
1997
- case ContainerMessageType.Alias:
1998
- this.processAliasMessage(message, localOpMetadata, local);
1999
- break;
2000
- case ContainerMessageType.FluidDataStoreOp:
2001
- this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
2002
- break;
2003
- case ContainerMessageType.BlobAttach:
2004
- this.blobManager.processBlobAttachOp(message, local);
2005
- break;
2006
- case ContainerMessageType.IdAllocation:
2007
- assert(
2008
- this.idCompressor !== undefined,
2009
- 0x67c /* IdCompressor should be defined if enabled */,
2010
- );
2011
- this.idCompressor.finalizeCreationRange(message.contents as IdCreationRange);
2012
- break;
2013
- case ContainerMessageType.ChunkedOp:
2014
- case ContainerMessageType.Rejoin:
2015
- break;
2016
- default:
2017
- if (runtimeMessage) {
2018
- const error = DataProcessingError.create(
2019
- // Former assert 0x3ce
2020
- "Runtime message of unknown type",
2021
- "OpProcessing",
2022
- message,
2023
- {
2024
- local,
2025
- type: message.type,
2026
- contentType: typeof message.contents,
2027
- batch: message.metadata?.batch,
2028
- compression: message.compression,
2029
- },
2030
- );
2031
- this.closeFn(error);
2032
- throw error;
2033
- }
2034
- }
2199
+ this.validateAndProcessRuntimeMessage(
2200
+ message,
2201
+ localOpMetadata,
2202
+ local,
2203
+ modernRuntimeMessage,
2204
+ );
2035
2205
 
2036
- if (runtimeMessage || this.groupedBatchingEnabled) {
2037
- this.emit("op", message, runtimeMessage);
2038
- }
2206
+ this.emit("op", message, modernRuntimeMessage);
2039
2207
 
2040
2208
  this.scheduleManager.afterOpProcessing(undefined, message);
2041
2209
 
@@ -2050,13 +2218,74 @@ export class ContainerRuntime
2050
2218
  throw e;
2051
2219
  }
2052
2220
  }
2053
-
2054
- private processAliasMessage(
2221
+ /**
2222
+ * Assuming the given message is also a ContainerRuntimeMessage,
2223
+ * checks its type and dispatches the message to the appropriate handler in the runtime.
2224
+ * Throws a DataProcessingError if the message doesn't conform to the ContainerRuntimeMessage type.
2225
+ */
2226
+ private validateAndProcessRuntimeMessage(
2055
2227
  message: ISequencedDocumentMessage,
2056
2228
  localOpMetadata: unknown,
2057
2229
  local: boolean,
2058
- ) {
2059
- this.dataStores.processAliasMessage(message, localOpMetadata, local);
2230
+ expectRuntimeMessageType: boolean,
2231
+ ): asserts message is SequencedContainerRuntimeMessage {
2232
+ // Optimistically extract ContainerRuntimeMessage-specific props from the message
2233
+ const { type: maybeContainerMessageType, compatDetails } =
2234
+ message as ContainerRuntimeMessage;
2235
+
2236
+ switch (maybeContainerMessageType) {
2237
+ case ContainerMessageType.Attach:
2238
+ this.dataStores.processAttachMessage(message, local);
2239
+ break;
2240
+ case ContainerMessageType.Alias:
2241
+ this.dataStores.processAliasMessage(message, localOpMetadata, local);
2242
+ break;
2243
+ case ContainerMessageType.FluidDataStoreOp:
2244
+ this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
2245
+ break;
2246
+ case ContainerMessageType.BlobAttach:
2247
+ this.blobManager.processBlobAttachOp(message, local);
2248
+ break;
2249
+ case ContainerMessageType.IdAllocation:
2250
+ assert(
2251
+ this.idCompressor !== undefined,
2252
+ 0x67c /* IdCompressor should be defined if enabled */,
2253
+ );
2254
+ this.idCompressor.finalizeCreationRange(message.contents as IdCreationRange);
2255
+ break;
2256
+ case ContainerMessageType.ChunkedOp:
2257
+ case ContainerMessageType.Rejoin:
2258
+ break;
2259
+ default: {
2260
+ // If we didn't necessarily expect a runtime message type, then no worries - just return
2261
+ // e.g. this case applies to system ops, or legacy ops that would have fallen into the above cases anyway.
2262
+ if (!expectRuntimeMessageType) {
2263
+ return;
2264
+ }
2265
+
2266
+ const compatBehavior = compatDetails?.behavior;
2267
+ if (!compatBehaviorAllowsMessageType(maybeContainerMessageType, compatBehavior)) {
2268
+ const error = DataProcessingError.create(
2269
+ // Former assert 0x3ce
2270
+ "Runtime message of unknown type",
2271
+ "OpProcessing",
2272
+ message,
2273
+ {
2274
+ local,
2275
+ messageDetails: JSON.stringify({
2276
+ type: message.type,
2277
+ contentType: typeof message.contents,
2278
+ compatBehavior,
2279
+ batch: (message.metadata as IBatchMetadata | undefined)?.batch,
2280
+ compression: message.compression,
2281
+ }),
2282
+ },
2283
+ );
2284
+ this.closeFn(error);
2285
+ throw error;
2286
+ }
2287
+ }
2288
+ }
2060
2289
  }
2061
2290
 
2062
2291
  /**
@@ -2065,7 +2294,7 @@ export class ContainerRuntime
2065
2294
  */
2066
2295
  private sendSignalTelemetryEvent(clientSignalSequenceNumber: number) {
2067
2296
  const duration = Date.now() - this._perfSignalData.signalTimestamp;
2068
- this.logger.sendPerformanceEvent({
2297
+ this.mc.logger.sendPerformanceEvent({
2069
2298
  eventName: "SignalLatency",
2070
2299
  duration,
2071
2300
  signalsLost: this._perfSignalData.signalsLost,
@@ -2093,7 +2322,7 @@ export class ContainerRuntime
2093
2322
  ) {
2094
2323
  this._perfSignalData.signalsLost++;
2095
2324
  this._perfSignalData.trackingSignalSequenceNumber = undefined;
2096
- this.logger.sendErrorEvent({
2325
+ this.mc.logger.sendErrorEvent({
2097
2326
  eventName: "SignalLost",
2098
2327
  type: envelope.contents.type,
2099
2328
  signalsLost: this._perfSignalData.signalsLost,
@@ -2121,6 +2350,12 @@ export class ContainerRuntime
2121
2350
  this.dataStores.processSignal(envelope.address, transformed, local);
2122
2351
  }
2123
2352
 
2353
+ /**
2354
+ * Returns the runtime of the data store.
2355
+ * @param id - Id supplied during creating the data store.
2356
+ * @param wait - True if you want to wait for it.
2357
+ * @deprecated - Use getAliasedDataStoreEntryPoint instead to get an aliased data store's entry point.
2358
+ */
2124
2359
  public async getRootDataStore(id: string, wait = true): Promise<IFluidRouter> {
2125
2360
  return this.getRootDataStoreChannel(id, wait);
2126
2361
  }
@@ -2196,15 +2431,30 @@ export class ContainerRuntime
2196
2431
  return result;
2197
2432
  }
2198
2433
 
2199
- public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
2200
- const internalId = uuid();
2201
- return channelToDataStore(
2202
- await this._createDataStore(pkg, internalId),
2203
- internalId,
2204
- this,
2205
- this.dataStores,
2206
- this.mc.logger,
2207
- );
2434
+ /**
2435
+ * Returns the aliased data store's entryPoint, given the alias.
2436
+ * @param alias - The alias for the data store.
2437
+ * @returns The data store's entry point ({@link @fluidframework/core-interfaces#IFluidHandle}) if it exists and is aliased.
2438
+ * Returns undefined if no data store has been assigned the given alias.
2439
+ */
2440
+ public async getAliasedDataStoreEntryPoint(
2441
+ alias: string,
2442
+ ): Promise<IFluidHandle<FluidObject> | undefined> {
2443
+ await this.dataStores.waitIfPendingAlias(alias);
2444
+ const internalId = this.internalId(alias);
2445
+ const context = await this.dataStores.getDataStoreIfAvailable(internalId, { wait: false });
2446
+ // If the data store is not available or not an alias, return undefined.
2447
+ if (context === undefined || !(await context.isRoot())) {
2448
+ return undefined;
2449
+ }
2450
+
2451
+ const channel = await context.realize();
2452
+ if (channel.entryPoint === undefined) {
2453
+ throw new UsageError(
2454
+ "entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint",
2455
+ );
2456
+ }
2457
+ return channel.entryPoint;
2208
2458
  }
2209
2459
 
2210
2460
  public createDetachedRootDataStore(
@@ -2221,25 +2471,37 @@ export class ContainerRuntime
2221
2471
  return this.dataStores.createDetachedDataStoreCore(pkg, false);
2222
2472
  }
2223
2473
 
2474
+ public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
2475
+ const id = uuid();
2476
+ return channelToDataStore(
2477
+ await this.dataStores
2478
+ ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id)
2479
+ .realize(),
2480
+ id,
2481
+ this,
2482
+ this.dataStores,
2483
+ this.mc.logger,
2484
+ );
2485
+ }
2486
+
2487
+ /**
2488
+ * @deprecated 0.16 Issue #1537, #3631
2489
+ * @internal
2490
+ */
2224
2491
  public async _createDataStoreWithProps(
2225
2492
  pkg: string | string[],
2226
2493
  props?: any,
2227
2494
  id = uuid(),
2228
2495
  ): Promise<IDataStore> {
2229
- const fluidDataStore = await this.dataStores
2230
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
2231
- .realize();
2232
- return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
2233
- }
2234
-
2235
- private async _createDataStore(
2236
- pkg: string | string[],
2237
- id = uuid(),
2238
- props?: any,
2239
- ): Promise<IFluidDataStoreChannel> {
2240
- return this.dataStores
2241
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
2242
- .realize();
2496
+ return channelToDataStore(
2497
+ await this.dataStores
2498
+ ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
2499
+ .realize(),
2500
+ id,
2501
+ this,
2502
+ this.dataStores,
2503
+ this.mc.logger,
2504
+ );
2243
2505
  }
2244
2506
 
2245
2507
  private canSendOps() {
@@ -2255,13 +2517,14 @@ export class ContainerRuntime
2255
2517
  return this.flushMode !== FlushMode.Immediate || this._orderSequentiallyCalls !== 0;
2256
2518
  }
2257
2519
 
2520
+ private readonly _quorum: IQuorumClients;
2258
2521
  public getQuorum(): IQuorumClients {
2259
- return this.context.quorum;
2522
+ return this._quorum;
2260
2523
  }
2261
2524
 
2525
+ private readonly _audience: IAudience;
2262
2526
  public getAudience(): IAudience {
2263
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2264
- return this.context.audience!;
2527
+ return this._audience;
2265
2528
  }
2266
2529
 
2267
2530
  /**
@@ -2272,7 +2535,7 @@ export class ContainerRuntime
2272
2535
  return this.dirtyContainer;
2273
2536
  }
2274
2537
 
2275
- private isContainerMessageDirtyable(type: ContainerMessageType, contents: any) {
2538
+ private isContainerMessageDirtyable({ type, contents }: ContainerRuntimeMessage) {
2276
2539
  // For legacy purposes, exclude the old built-in AgentScheduler from dirty consideration as a special-case.
2277
2540
  // Ultimately we should have no special-cases from the ContainerRuntime's perspective.
2278
2541
  if (type === ContainerMessageType.Attach) {
@@ -2321,12 +2584,12 @@ export class ContainerRuntime
2321
2584
  public submitSignal(type: string, content: any) {
2322
2585
  this.verifyNotClosed();
2323
2586
  const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content);
2324
- return this.context.submitSignalFn(envelope);
2587
+ return this.submitSignalFn(envelope);
2325
2588
  }
2326
2589
 
2327
2590
  public submitDataStoreSignal(address: string, type: string, content: any) {
2328
2591
  const envelope = this.createNewSignalEnvelope(address, type, content);
2329
- return this.context.submitSignalFn(envelope);
2592
+ return this.submitSignalFn(envelope);
2330
2593
  }
2331
2594
 
2332
2595
  public setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void {
@@ -2378,15 +2641,7 @@ export class ContainerRuntime
2378
2641
  return summarizeResult.summary;
2379
2642
  }
2380
2643
 
2381
- public async getAbsoluteUrl(relativeUrl: string): Promise<string | undefined> {
2382
- if (this.context.getAbsoluteUrl === undefined) {
2383
- throw new Error("Driver does not implement getAbsoluteUrl");
2384
- }
2385
- if (this.attachState !== AttachState.Attached) {
2386
- return undefined;
2387
- }
2388
- return this.context.getAbsoluteUrl(relativeUrl);
2389
- }
2644
+ public readonly getAbsoluteUrl: (relativeUrl: string) => Promise<string | undefined>;
2390
2645
 
2391
2646
  private async summarizeInternal(
2392
2647
  fullTree: boolean,
@@ -2427,7 +2682,7 @@ export class ContainerRuntime
2427
2682
  fullGC?: boolean;
2428
2683
  /** True to run GC sweep phase after the mark phase */
2429
2684
  runSweep?: boolean;
2430
- }): Promise<IRootSummaryTreeWithStats> {
2685
+ }): Promise<ISummaryTreeWithStats> {
2431
2686
  this.verifyNotClosed();
2432
2687
 
2433
2688
  const {
@@ -2450,9 +2705,8 @@ export class ContainerRuntime
2450
2705
  });
2451
2706
 
2452
2707
  try {
2453
- let gcStats: IGCStats | undefined;
2454
2708
  if (runGC) {
2455
- gcStats = await this.collectGarbage(
2709
+ await this.collectGarbage(
2456
2710
  { logger: summaryLogger, runSweep, fullGC },
2457
2711
  telemetryContext,
2458
2712
  );
@@ -2469,9 +2723,9 @@ export class ContainerRuntime
2469
2723
  0x12f /* "Container Runtime's summarize should always return a tree" */,
2470
2724
  );
2471
2725
 
2472
- return { stats, summary, gcStats };
2726
+ return { stats, summary };
2473
2727
  } finally {
2474
- this.logger.sendTelemetryEvent({
2728
+ this.mc.logger.sendTelemetryEvent({
2475
2729
  eventName: "SummarizeTelemetry",
2476
2730
  details: telemetryContext.serialize(),
2477
2731
  });
@@ -2543,7 +2797,7 @@ export class ContainerRuntime
2543
2797
  /**
2544
2798
  * After GC has run and identified nodes that are sweep ready, this is called to delete the sweep ready nodes.
2545
2799
  * @param sweepReadyRoutes - The routes of nodes that are sweep ready and should be deleted.
2546
- * @returns - The routes of nodes that were deleted.
2800
+ * @returns The routes of nodes that were deleted.
2547
2801
  */
2548
2802
  public deleteSweepReadyNodes(sweepReadyRoutes: string[]): string[] {
2549
2803
  const { dataStoreRoutes, blobManagerRoutes } =
@@ -2614,7 +2868,7 @@ export class ContainerRuntime
2614
2868
  /**
2615
2869
  * From a given list of routes, separate and return routes that belong to blob manager and data stores.
2616
2870
  * @param routes - A list of routes that can belong to data stores or blob manager.
2617
- * @returns - Two route lists - One that contains routes for blob manager and another one that contains routes
2871
+ * @returns Two route lists - One that contains routes for blob manager and another one that contains routes
2618
2872
  * for data stores.
2619
2873
  */
2620
2874
  private getDataStoreAndBlobManagerRoutes(routes: string[]) {
@@ -2670,12 +2924,15 @@ export class ContainerRuntime
2670
2924
  * @param options - options controlling how the summary is generated or submitted
2671
2925
  */
2672
2926
  public async submitSummary(options: ISubmitSummaryOptions): Promise<SubmitSummaryResult> {
2673
- const { fullTree = false, refreshLatestAck, summaryLogger } = options;
2927
+ const { fullTree = false, finalAttempt = false, refreshLatestAck, summaryLogger } = options;
2674
2928
  // The summary number for this summary. This will be updated during the summary process, so get it now and
2675
2929
  // use it for all events logged during this summary.
2676
2930
  const summaryNumber = this.nextSummaryNumber;
2677
- const summaryNumberLogger = ChildLogger.create(summaryLogger, undefined, {
2678
- all: { summaryNumber },
2931
+ const summaryNumberLogger = createChildLogger({
2932
+ logger: summaryLogger,
2933
+ properties: {
2934
+ all: { summaryNumber },
2935
+ },
2679
2936
  });
2680
2937
 
2681
2938
  assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
@@ -2683,7 +2940,10 @@ export class ContainerRuntime
2683
2940
  let latestSnapshotVersionId: string | undefined;
2684
2941
  if (refreshLatestAck) {
2685
2942
  const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(
2686
- ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }),
2943
+ createChildLogger({
2944
+ logger: summaryNumberLogger,
2945
+ properties: { all: { safeSummary: true } },
2946
+ }),
2687
2947
  );
2688
2948
  const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
2689
2949
  latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
@@ -2692,6 +2952,50 @@ export class ContainerRuntime
2692
2952
  await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryNumberLogger);
2693
2953
  }
2694
2954
 
2955
+ // If there are pending (unacked ops), the summary will not be eventual consistent and it may even be
2956
+ // incorrect. So, wait for the container to be saved with a timeout. If the container is not saved
2957
+ // within the timeout, check if it should be failed or can continue.
2958
+ if (this.validateSummaryBeforeUpload && this.hasPendingMessages()) {
2959
+ const countBefore = this.pendingMessagesCount;
2960
+ // The timeout for waiting for pending ops can be overridden via configurations.
2961
+ const pendingOpsTimeout =
2962
+ this.mc.config.getNumber("Fluid.Summarizer.waitForPendingOpsTimeoutMs") ??
2963
+ defaultPendingOpsWaitTimeoutMs;
2964
+ await new Promise<void>((resolve, reject) => {
2965
+ const timeoutId = setTimeout(() => resolve(), pendingOpsTimeout);
2966
+ this.once("saved", () => {
2967
+ clearTimeout(timeoutId);
2968
+ resolve();
2969
+ });
2970
+ this.once("dispose", () => {
2971
+ clearTimeout(timeoutId);
2972
+ reject(new Error("Runtime is disposed while summarizing"));
2973
+ });
2974
+ });
2975
+
2976
+ // Log that there are pending ops while summarizing. This will help us gather data on how often this
2977
+ // happens, whether we attempted to wait for these ops to be acked and what was the result.
2978
+ summaryNumberLogger.sendTelemetryEvent({
2979
+ eventName: "PendingOpsWhileSummarizing",
2980
+ saved: this.hasPendingMessages() ? false : true,
2981
+ timeout: pendingOpsTimeout,
2982
+ countBefore,
2983
+ countAfter: this.pendingMessagesCount,
2984
+ });
2985
+
2986
+ // There could still be pending ops. Check if summary should fail or continue.
2987
+ const pendingMessagesFailResult = await this.shouldFailSummaryOnPendingOps(
2988
+ summaryNumberLogger,
2989
+ this.deltaManager.lastSequenceNumber,
2990
+ this.deltaManager.minimumSequenceNumber,
2991
+ finalAttempt,
2992
+ true /* beforeSummaryGeneration */,
2993
+ );
2994
+ if (pendingMessagesFailResult !== undefined) {
2995
+ return pendingMessagesFailResult;
2996
+ }
2997
+ }
2998
+
2695
2999
  const shouldPauseInboundSignal =
2696
3000
  this.mc.config.getBoolean(
2697
3001
  "Fluid.ContainerRuntime.SubmitSummary.disableInboundSignalPause",
@@ -2762,7 +3066,7 @@ export class ContainerRuntime
2762
3066
  }
2763
3067
 
2764
3068
  const trace = Trace.start();
2765
- let summarizeResult: IRootSummaryTreeWithStats;
3069
+ let summarizeResult: ISummaryTreeWithStats;
2766
3070
  // If the GC state needs to be reset, we need to force a full tree summary and update the unreferenced
2767
3071
  // state of all the nodes.
2768
3072
  const forcedFullTree = this.garbageCollector.summaryStateNeedsReset;
@@ -2781,6 +3085,38 @@ export class ContainerRuntime
2781
3085
  error,
2782
3086
  };
2783
3087
  }
3088
+
3089
+ // If validateSummaryBeforeUpload is true, validate that the summary generated is correct before uploading.
3090
+ if (this.validateSummaryBeforeUpload) {
3091
+ // Validate that the summaries generated by summarize nodes is correct.
3092
+ const validateResult = this.summarizerNode.validateSummary();
3093
+ if (!validateResult.success) {
3094
+ const { success, ...loggingProps } = validateResult;
3095
+ const error = new RetriableSummaryError(
3096
+ validateResult.reason,
3097
+ validateResult.retryAfterSeconds,
3098
+ { ...loggingProps },
3099
+ );
3100
+ return {
3101
+ stage: "base",
3102
+ referenceSequenceNumber: summaryRefSeqNum,
3103
+ minimumSequenceNumber,
3104
+ error,
3105
+ };
3106
+ }
3107
+
3108
+ const pendingMessagesFailResult = await this.shouldFailSummaryOnPendingOps(
3109
+ summaryNumberLogger,
3110
+ summaryRefSeqNum,
3111
+ minimumSequenceNumber,
3112
+ finalAttempt,
3113
+ false /* beforeSummaryGeneration */,
3114
+ );
3115
+ if (pendingMessagesFailResult !== undefined) {
3116
+ return pendingMessagesFailResult;
3117
+ }
3118
+ }
3119
+
2784
3120
  const { summary: summaryTree, stats: partialStats } = summarizeResult;
2785
3121
 
2786
3122
  // Now that we have generated the summary, update the message at last summary to the last message processed.
@@ -2808,7 +3144,7 @@ export class ContainerRuntime
2808
3144
  summaryNumber,
2809
3145
  ...partialStats,
2810
3146
  };
2811
- const generateSummaryData = {
3147
+ const generateSummaryData: Omit<IGenerateSummaryTreeResult, "stage" | "error"> = {
2812
3148
  referenceSequenceNumber: summaryRefSeqNum,
2813
3149
  minimumSequenceNumber,
2814
3150
  summaryTree,
@@ -2840,7 +3176,7 @@ export class ContainerRuntime
2840
3176
  } else if (lastAck === undefined) {
2841
3177
  summaryContext = {
2842
3178
  proposalHandle: undefined,
2843
- ackHandle: this.context.getLoadedFromVersion()?.id,
3179
+ ackHandle: this.loadedFromVersionId,
2844
3180
  referenceSequenceNumber: summaryRefSeqNum,
2845
3181
  };
2846
3182
  } else {
@@ -2894,7 +3230,15 @@ export class ContainerRuntime
2894
3230
  submitOpDuration: trace.trace().duration,
2895
3231
  } as const;
2896
3232
 
2897
- this.summarizerNode.completeSummary(handle);
3233
+ try {
3234
+ // If validateSummaryBeforeUpload is false, the summary should be validated in this step.
3235
+ this.summarizerNode.completeSummary(
3236
+ handle,
3237
+ !this.validateSummaryBeforeUpload /* validate */,
3238
+ );
3239
+ } catch (error) {
3240
+ return { stage: "upload", ...uploadData, error };
3241
+ }
2898
3242
  return submitData;
2899
3243
  } finally {
2900
3244
  // Cleanup wip summary in case of failure
@@ -2911,8 +3255,78 @@ export class ContainerRuntime
2911
3255
  }
2912
3256
  }
2913
3257
 
3258
+ /**
3259
+ * This helper is called during summarization. If there are pending ops, it will return a failed summarize result
3260
+ * (IBaseSummarizeResult) unless this is the final summarize attempt and SkipFailingIncorrectSummary option is set.
3261
+ * @param logger - The logger to be used for sending telemetry.
3262
+ * @param referenceSequenceNumber - The reference sequence number of the summary attempt.
3263
+ * @param minimumSequenceNumber - The minimum sequence number of the summary attempt.
3264
+ * @param finalAttempt - Whether this is the final summary attempt.
3265
+ * @param beforeSummaryGeneration - Whether this is called before summary generation or after.
3266
+ * @returns failed summarize result (IBaseSummarizeResult) if summary should be failed, undefined otherwise.
3267
+ */
3268
+ private async shouldFailSummaryOnPendingOps(
3269
+ logger: ITelemetryLoggerExt,
3270
+ referenceSequenceNumber: number,
3271
+ minimumSequenceNumber: number,
3272
+ finalAttempt: boolean,
3273
+ beforeSummaryGeneration: boolean,
3274
+ ): Promise<IBaseSummarizeResult | undefined> {
3275
+ if (!this.hasPendingMessages()) {
3276
+ return;
3277
+ }
3278
+
3279
+ // If "SkipFailingIncorrectSummary" option is true, don't fail the summary in the last attempt.
3280
+ // This is a fallback to make progress in documents where there are consistently pending ops in
3281
+ // the summarizer.
3282
+ if (
3283
+ finalAttempt &&
3284
+ this.mc.config.getBoolean("Fluid.Summarizer.SkipFailingIncorrectSummary")
3285
+ ) {
3286
+ const error = DataProcessingError.create(
3287
+ "Pending ops during summarization",
3288
+ "submitSummary",
3289
+ undefined,
3290
+ { pendingMessages: this.pendingMessagesCount },
3291
+ );
3292
+ logger.sendErrorEvent(
3293
+ {
3294
+ eventName: "SkipFailingIncorrectSummary",
3295
+ referenceSequenceNumber,
3296
+ minimumSequenceNumber,
3297
+ beforeGenerate: beforeSummaryGeneration,
3298
+ },
3299
+ error,
3300
+ );
3301
+ } else {
3302
+ // The retry delay when there are pending ops can be overridden via config so that we can adjust it
3303
+ // based on telemetry while we decide on a stable number.
3304
+ const retryDelayMs =
3305
+ this.mc.config.getNumber("Fluid.Summarizer.PendingOpsRetryDelayMs") ??
3306
+ defaultPendingOpsRetryDelayMs;
3307
+ const error = new RetriableSummaryError(
3308
+ "PendingOpsWhileSummarizing",
3309
+ retryDelayMs / 1000,
3310
+ {
3311
+ count: this.pendingMessagesCount,
3312
+ beforeGenerate: beforeSummaryGeneration,
3313
+ },
3314
+ );
3315
+ return {
3316
+ stage: "base",
3317
+ referenceSequenceNumber,
3318
+ minimumSequenceNumber,
3319
+ error,
3320
+ };
3321
+ }
3322
+ }
3323
+
3324
+ private get pendingMessagesCount(): number {
3325
+ return this.pendingStateManager.pendingMessagesCount + this.outbox.messageCount;
3326
+ }
3327
+
2914
3328
  private hasPendingMessages() {
2915
- return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
3329
+ return this.pendingMessagesCount !== 0;
2916
3330
  }
2917
3331
 
2918
3332
  private updateDocumentDirtyState(dirty: boolean) {
@@ -2933,7 +3347,6 @@ export class ContainerRuntime
2933
3347
  this.dirtyContainer = dirty;
2934
3348
  if (this.emitDirtyDocumentEvent) {
2935
3349
  this.emit(dirty ? "dirty" : "saved");
2936
- this.context.updateDirtyContainerState(dirty);
2937
3350
  }
2938
3351
  }
2939
3352
 
@@ -2946,7 +3359,10 @@ export class ContainerRuntime
2946
3359
  address: id,
2947
3360
  contents,
2948
3361
  };
2949
- this.submit(ContainerMessageType.FluidDataStoreOp, envelope, localOpMetadata);
3362
+ this.submit(
3363
+ { type: ContainerMessageType.FluidDataStoreOp, contents: envelope },
3364
+ localOpMetadata,
3365
+ );
2950
3366
  }
2951
3367
 
2952
3368
  public submitDataStoreAliasOp(contents: any, localOpMetadata: unknown): void {
@@ -2955,12 +3371,15 @@ export class ContainerRuntime
2955
3371
  throw new UsageError("malformedDataStoreAliasMessage");
2956
3372
  }
2957
3373
 
2958
- this.submit(ContainerMessageType.Alias, contents, localOpMetadata);
3374
+ this.submit({ type: ContainerMessageType.Alias, contents }, localOpMetadata);
2959
3375
  }
2960
3376
 
2961
- public async uploadBlob(blob: ArrayBufferLike): Promise<IFluidHandle<ArrayBufferLike>> {
3377
+ public async uploadBlob(
3378
+ blob: ArrayBufferLike,
3379
+ signal?: AbortSignal,
3380
+ ): Promise<IFluidHandle<ArrayBufferLike>> {
2962
3381
  this.verifyNotClosed();
2963
- return this.blobManager.createBlob(blob);
3382
+ return this.blobManager.createBlob(blob, signal);
2964
3383
  }
2965
3384
 
2966
3385
  private maybeSubmitIdAllocationOp(type: ContainerMessageType) {
@@ -2974,7 +3393,7 @@ export class ContainerRuntime
2974
3393
  );
2975
3394
  idRange = this.idCompressor.takeNextCreationRange();
2976
3395
  // Don't include the idRange if there weren't any Ids allocated
2977
- idRange = idRange?.ids?.first !== undefined ? idRange : undefined;
3396
+ idRange = idRange?.ids !== undefined ? idRange : undefined;
2978
3397
  }
2979
3398
 
2980
3399
  if (idRange !== undefined) {
@@ -2998,8 +3417,7 @@ export class ContainerRuntime
2998
3417
  }
2999
3418
 
3000
3419
  private submit(
3001
- type: ContainerMessageType,
3002
- contents: any,
3420
+ containerRuntimeMessage: ContainerRuntimeMessage,
3003
3421
  localOpMetadata: unknown = undefined,
3004
3422
  metadata: Record<string, unknown> | undefined = undefined,
3005
3423
  ): void {
@@ -3012,17 +3430,18 @@ export class ContainerRuntime
3012
3430
  0x132 /* "sending ops in detached container" */,
3013
3431
  );
3014
3432
 
3015
- const serializedContent = JSON.stringify({ type, contents });
3433
+ const serializedContent = JSON.stringify(containerRuntimeMessage);
3016
3434
 
3017
3435
  // Note that the real (non-proxy) delta manager is used here to get the readonly info. This is because
3018
3436
  // container runtime's ability to submit ops depend on the actual readonly state of the delta manager.
3019
3437
  if (this.innerDeltaManager.readOnlyInfo.readonly) {
3020
- this.logger.sendTelemetryEvent({
3438
+ this.mc.logger.sendTelemetryEvent({
3021
3439
  eventName: "SubmitOpInReadonly",
3022
3440
  connected: this.connected,
3023
3441
  });
3024
3442
  }
3025
3443
 
3444
+ const type = containerRuntimeMessage.type;
3026
3445
  const message: BatchMessage = {
3027
3446
  contents: serializedContent,
3028
3447
  type,
@@ -3064,6 +3483,9 @@ export class ContainerRuntime
3064
3483
  this.disableAttachReorder !== true
3065
3484
  ) {
3066
3485
  this.outbox.submitAttach(message);
3486
+ } else if (type === ContainerMessageType.BlobAttach) {
3487
+ // BlobAttach ops must have their metadata visible and cannot be grouped (see opGroupingManager.ts)
3488
+ this.outbox.submitBlobAttach(message);
3067
3489
  } else {
3068
3490
  this.outbox.submit(message);
3069
3491
  }
@@ -3078,7 +3500,7 @@ export class ContainerRuntime
3078
3500
  throw error;
3079
3501
  }
3080
3502
 
3081
- if (this.isContainerMessageDirtyable(type, contents)) {
3503
+ if (this.isContainerMessageDirtyable(containerRuntimeMessage)) {
3082
3504
  this.updateDocumentDirtyState(true);
3083
3505
  }
3084
3506
  }
@@ -3134,9 +3556,9 @@ export class ContainerRuntime
3134
3556
  assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
3135
3557
 
3136
3558
  // back-compat: ADO #1385: Make this call unconditional in the future
3137
- return this.context.submitSummaryFn !== undefined
3138
- ? this.context.submitSummaryFn(contents, referenceSequenceNumber)
3139
- : this.context.submitFn(MessageType.Summarize, contents, false);
3559
+ return this.submitSummaryFn !== undefined
3560
+ ? this.submitSummaryFn(contents, referenceSequenceNumber)
3561
+ : this.submitFn(MessageType.Summarize, contents, false);
3140
3562
  }
3141
3563
 
3142
3564
  /**
@@ -3157,7 +3579,7 @@ export class ContainerRuntime
3157
3579
  this.mc.logger.sendTelemetryEvent(
3158
3580
  { eventName: "OpReentry" },
3159
3581
  // We need to capture the call stack in order to inspect the source of this usage pattern
3160
- new UsageError(errorMessage),
3582
+ getLongStack(() => new UsageError(errorMessage)),
3161
3583
  );
3162
3584
  this.opReentryCallsToReport--;
3163
3585
  }
@@ -3180,44 +3602,49 @@ export class ContainerRuntime
3180
3602
  }
3181
3603
  }
3182
3604
 
3183
- private reSubmit(
3184
- content: string,
3185
- localOpMetadata: unknown,
3186
- opMetadata: Record<string, unknown> | undefined,
3187
- ) {
3605
+ private reSubmitBatch(batch: IPendingBatchMessage[]) {
3606
+ this.orderSequentially(() => {
3607
+ for (const message of batch) {
3608
+ this.reSubmit(message);
3609
+ }
3610
+ });
3611
+ this.flush();
3612
+ }
3613
+
3614
+ private reSubmit(message: IPendingBatchMessage) {
3188
3615
  // Need to parse from string for back-compat
3189
- const { contents, type } = this.parseOpContent(content);
3190
- this.reSubmitCore(type, contents, localOpMetadata, opMetadata);
3616
+ const containerRuntimeMessage = this.parseOpContent(message.content);
3617
+ this.reSubmitCore(containerRuntimeMessage, message.localOpMetadata, message.opMetadata);
3191
3618
  }
3192
3619
 
3193
3620
  /**
3194
3621
  * Finds the right store and asks it to resubmit the message. This typically happens when we
3195
3622
  * reconnect and there are pending messages.
3196
- * @param content - The content of the original message.
3623
+ * @param message - The original ContainerRuntimeMessage.
3197
3624
  * @param localOpMetadata - The local metadata associated with the original message.
3198
3625
  */
3199
3626
  private reSubmitCore(
3200
- type: ContainerMessageType,
3201
- content: any,
3627
+ message: ContainerRuntimeMessage,
3202
3628
  localOpMetadata: unknown,
3203
3629
  opMetadata: Record<string, unknown> | undefined,
3204
3630
  ) {
3205
- switch (type) {
3631
+ const contents = message.contents;
3632
+ switch (message.type) {
3206
3633
  case ContainerMessageType.FluidDataStoreOp:
3207
3634
  // For Operations, call resubmitDataStoreOp which will find the right store
3208
3635
  // and trigger resubmission on it.
3209
- this.dataStores.resubmitDataStoreOp(content, localOpMetadata);
3636
+ this.dataStores.resubmitDataStoreOp(contents, localOpMetadata);
3210
3637
  break;
3211
3638
  case ContainerMessageType.Attach:
3212
3639
  case ContainerMessageType.Alias:
3213
- this.submit(type, content, localOpMetadata);
3640
+ this.submit(message, localOpMetadata);
3214
3641
  break;
3215
3642
  case ContainerMessageType.IdAllocation:
3216
3643
  // Remove the stashedState from the op if it's a stashed op
3217
- if (content.stashedState !== undefined) {
3218
- delete content.stashedState;
3644
+ if (contents.stashedState !== undefined) {
3645
+ delete contents.stashedState;
3219
3646
  }
3220
- this.submit(type, content, localOpMetadata);
3647
+ this.submit(message, localOpMetadata);
3221
3648
  break;
3222
3649
  case ContainerMessageType.ChunkedOp:
3223
3650
  throw new Error(`chunkedOp not expected here`);
@@ -3225,10 +3652,33 @@ export class ContainerRuntime
3225
3652
  this.blobManager.reSubmit(opMetadata);
3226
3653
  break;
3227
3654
  case ContainerMessageType.Rejoin:
3228
- this.submit(type, content);
3655
+ this.submit(message);
3229
3656
  break;
3230
- default:
3231
- unreachableCase(type, `Unknown ContainerMessageType: ${type}`);
3657
+ default: {
3658
+ // This case should be very rare - it would imply an op was stashed from a
3659
+ // future version of runtime code and now is being applied on an older version
3660
+ const compatBehavior = message.compatDetails?.behavior;
3661
+ if (compatBehaviorAllowsMessageType(message.type, compatBehavior)) {
3662
+ this.logger.sendTelemetryEvent({
3663
+ eventName: "resubmitUnrecognizedMessageTypeAllowed",
3664
+ messageDetails: { type: message.type, compatBehavior },
3665
+ });
3666
+ } else {
3667
+ const error = DataProcessingError.create(
3668
+ "Resubmitting runtime message of unknown type",
3669
+ "reSubmitCore",
3670
+ undefined /* sequencedMessage */,
3671
+ {
3672
+ messageDetails: JSON.stringify({
3673
+ type: message.type,
3674
+ compatBehavior,
3675
+ }),
3676
+ },
3677
+ );
3678
+ this.closeFn(error);
3679
+ throw error;
3680
+ }
3681
+ }
3232
3682
  }
3233
3683
  }
3234
3684
 
@@ -3242,6 +3692,7 @@ export class ContainerRuntime
3242
3692
  this.dataStores.rollbackDataStoreOp(contents as IEnvelope, localOpMetadata);
3243
3693
  break;
3244
3694
  default:
3695
+ // Don't check message.compatDetails because this is for rolling back a local op so the type will be known
3245
3696
  throw new Error(`Can't rollback ${type}`);
3246
3697
  }
3247
3698
  }
@@ -3269,12 +3720,22 @@ export class ContainerRuntime
3269
3720
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
3270
3721
  public async refreshLatestSummaryAck(options: IRefreshSummaryAckOptions) {
3271
3722
  const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
3723
+ // proposalHandle is always passed from RunningSummarizer.
3724
+ assert(proposalHandle !== undefined, 0x766 /* proposalHandle should be available */);
3272
3725
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
3273
- // The call to fetch the snapshot is very expensive and not always needed.
3274
- // It should only be done by the summarizerNode, if required.
3275
- // When fetching from storage we will always get the latest version and do not use the ackHandle.
3276
- const fetchLatestSnapshot: () => Promise<IFetchSnapshotResult> = async () => {
3277
- let fetchResult = await this.fetchLatestSnapshotFromStorage(
3726
+ const result = await this.summarizerNode.refreshLatestSummary(
3727
+ proposalHandle,
3728
+ summaryRefSeq,
3729
+ );
3730
+
3731
+ /**
3732
+ * When refreshing a summary ack, this check indicates a new ack of a summary that is newer than the
3733
+ * current summary that is tracked, but this summarizer runtime did not produce/track that summary. Thus
3734
+ * it needs to refresh its state. Today refresh is done by fetching the latest snapshot to update the cache
3735
+ * and then close as the current main client is likely to be re-elected as the parent summarizer again.
3736
+ */
3737
+ if (!result.isSummaryTracked && result.isSummaryNewer) {
3738
+ const fetchResult = await this.fetchSnapshotFromStorage(
3278
3739
  summaryLogger,
3279
3740
  {
3280
3741
  eventName: "RefreshLatestSummaryAckFetch",
@@ -3282,27 +3743,9 @@ export class ContainerRuntime
3282
3743
  targetSequenceNumber: summaryRefSeq,
3283
3744
  },
3284
3745
  readAndParseBlob,
3746
+ null,
3285
3747
  );
3286
3748
 
3287
- /**
3288
- * back-compat - Older loaders and drivers (pre 2.0.0-internal.1.4) don't have fetchSource as a param in the
3289
- * getVersions API. So, they will not fetch the latest snapshot from network in the previous fetch call. For
3290
- * these scenarios, fetch the snapshot corresponding to the ack handle to have the same behavior before the
3291
- * change that started fetching latest snapshot always.
3292
- */
3293
- if (fetchResult.latestSnapshotRefSeq < summaryRefSeq) {
3294
- fetchResult = await this.fetchSnapshotFromStorage(
3295
- summaryLogger,
3296
- {
3297
- eventName: "RefreshLatestSummaryAckFetchBackCompat",
3298
- ackHandle,
3299
- targetSequenceNumber: summaryRefSeq,
3300
- },
3301
- readAndParseBlob,
3302
- ackHandle,
3303
- );
3304
- }
3305
-
3306
3749
  /**
3307
3750
  * If the fetched snapshot is older than the one for which the ack was received, close the container.
3308
3751
  * This should never happen because an ack should be sent after the latest summary is updated in the server.
@@ -3324,33 +3767,16 @@ export class ContainerRuntime
3324
3767
  fetchedSnapshotRefSeq: fetchResult.latestSnapshotRefSeq,
3325
3768
  },
3326
3769
  );
3327
- this.closeFn(error);
3770
+ this.disposeFn(error);
3328
3771
  throw error;
3329
3772
  }
3330
3773
 
3331
- // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
3332
- // wait for the delta manager to catch up before refreshing the latest Summary.
3333
- await this.waitForDeltaManagerToCatchup(
3334
- fetchResult.latestSnapshotRefSeq,
3335
- summaryLogger,
3336
- );
3337
-
3338
- return {
3339
- snapshotTree: fetchResult.snapshotTree,
3340
- snapshotRefSeq: fetchResult.latestSnapshotRefSeq,
3341
- };
3342
- };
3343
-
3344
- const result = await this.summarizerNode.refreshLatestSummary(
3345
- proposalHandle,
3346
- summaryRefSeq,
3347
- fetchLatestSnapshot,
3348
- readAndParseBlob,
3349
- summaryLogger,
3350
- );
3774
+ await this.closeStaleSummarizer("RefreshLatestSummaryAckFetch");
3775
+ return;
3776
+ }
3351
3777
 
3352
3778
  // Notify the garbage collector so it can update its latest summary state.
3353
- await this.garbageCollector.refreshLatestSummary(proposalHandle, result, readAndParseBlob);
3779
+ await this.garbageCollector.refreshLatestSummary(result);
3354
3780
  }
3355
3781
 
3356
3782
  /**
@@ -3363,51 +3789,49 @@ export class ContainerRuntime
3363
3789
  summaryLogger: ITelemetryLoggerExt,
3364
3790
  ): Promise<{ latestSnapshotRefSeq: number; latestSnapshotVersionId: string | undefined }> {
3365
3791
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
3366
- const { snapshotTree, versionId, latestSnapshotRefSeq } =
3367
- await this.fetchLatestSnapshotFromStorage(
3368
- summaryLogger,
3369
- {
3370
- eventName: "RefreshLatestSummaryFromServerFetch",
3371
- },
3372
- readAndParseBlob,
3373
- );
3374
- const fetchLatestSnapshot: IFetchSnapshotResult = {
3375
- snapshotTree,
3376
- snapshotRefSeq: latestSnapshotRefSeq,
3377
- };
3378
- const result = await this.summarizerNode.refreshLatestSummary(
3379
- undefined /* proposalHandle */,
3380
- latestSnapshotRefSeq,
3381
- async () => fetchLatestSnapshot,
3382
- readAndParseBlob,
3792
+ const { versionId, latestSnapshotRefSeq } = await this.fetchSnapshotFromStorage(
3383
3793
  summaryLogger,
3384
- );
3385
-
3386
- // Notify the garbage collector so it can update its latest summary state.
3387
- await this.garbageCollector.refreshLatestSummary(
3388
- undefined /* proposalHandle */,
3389
- result,
3794
+ {
3795
+ eventName: "RefreshLatestSummaryFromServerFetch",
3796
+ },
3390
3797
  readAndParseBlob,
3798
+ null,
3391
3799
  );
3392
3800
 
3801
+ await this.closeStaleSummarizer("RefreshLatestSummaryFromServerFetch");
3802
+
3393
3803
  return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
3394
3804
  }
3395
3805
 
3396
- private async fetchLatestSnapshotFromStorage(
3397
- logger: ITelemetryLoggerExt,
3398
- event: ITelemetryGenericEvent,
3399
- readAndParseBlob: ReadAndParseBlob,
3400
- ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; latestSnapshotRefSeq: number }> {
3401
- return this.fetchSnapshotFromStorage(logger, event, readAndParseBlob, null /* latest */);
3806
+ private async closeStaleSummarizer(codePath: string): Promise<void> {
3807
+ this.mc.logger.sendTelemetryEvent(
3808
+ {
3809
+ eventName: "ClosingSummarizerOnSummaryStale",
3810
+ codePath,
3811
+ message: "Stopping fetch from storage",
3812
+ closeSummarizerDelayMs: this.closeSummarizerDelayMs,
3813
+ },
3814
+ new GenericError("Restarting summarizer instead of refreshing"),
3815
+ );
3816
+
3817
+ // Delay before restarting summarizer to prevent the summarizer from restarting too frequently.
3818
+ await delay(this.closeSummarizerDelayMs);
3819
+ this._summarizer?.stop("latestSummaryStateStale");
3820
+ this.disposeFn();
3402
3821
  }
3403
3822
 
3823
+ /**
3824
+ * Downloads snapshot from storage with the given versionId or latest if versionId is null.
3825
+ * By default, it also closes the container after downloading the snapshot. However, this may be
3826
+ * overridden via options.
3827
+ */
3404
3828
  private async fetchSnapshotFromStorage(
3405
3829
  logger: ITelemetryLoggerExt,
3406
3830
  event: ITelemetryGenericEvent,
3407
3831
  readAndParseBlob: ReadAndParseBlob,
3408
3832
  versionId: string | null,
3409
3833
  ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; latestSnapshotRefSeq: number }> {
3410
- const snapshotResults = await PerformanceEvent.timedExecAsync(
3834
+ return PerformanceEvent.timedExecAsync(
3411
3835
  logger,
3412
3836
  event,
3413
3837
  async (perfEvent: {
@@ -3453,77 +3877,75 @@ export class ContainerRuntime
3453
3877
  };
3454
3878
  },
3455
3879
  );
3456
-
3457
- // We choose to close the summarizer after the snapshot cache is updated to avoid
3458
- // situations which the main client (which is likely to be re-elected as the leader again)
3459
- // loads the summarizer from cache.
3460
- if (this.summaryStateUpdateMethod === "restart") {
3461
- const error = new GenericError("Restarting summarizer instead of refreshing");
3462
-
3463
- this.mc.logger.sendTelemetryEvent(
3464
- {
3465
- ...event,
3466
- eventName: "ClosingSummarizerOnSummaryStale",
3467
- codePath: event.eventName,
3468
- message: "Stopping fetch from storage",
3469
- versionId: versionId != null ? versionId : undefined,
3470
- closeSummarizerDelayMs: this.closeSummarizerDelayMs,
3471
- },
3472
- error,
3473
- );
3474
-
3475
- // Delay 10 seconds before restarting summarizer to prevent the summarizer from restarting too frequently.
3476
- await delay(this.closeSummarizerDelayMs);
3477
- this._summarizer?.stop("latestSummaryStateStale");
3478
- this.closeFn();
3479
- throw error;
3480
- }
3481
-
3482
- return snapshotResults;
3483
3880
  }
3484
3881
 
3485
3882
  public notifyAttaching() {} // do nothing (deprecated method)
3486
3883
 
3487
- public getPendingLocalState(): unknown {
3488
- if (this._orderSequentiallyCalls !== 0) {
3489
- throw new UsageError("can't get state during orderSequentially");
3490
- }
3491
- // Flush pending batch.
3492
- // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
3493
- // to close current batch.
3494
- this.flush();
3884
+ public async getPendingLocalState(props?: {
3885
+ notifyImminentClosure: boolean;
3886
+ }): Promise<unknown> {
3887
+ return PerformanceEvent.timedExecAsync(
3888
+ this.mc.logger,
3889
+ {
3890
+ eventName: "getPendingLocalState",
3891
+ notifyImminentClosure: props?.notifyImminentClosure,
3892
+ },
3893
+ async (event) => {
3894
+ this.verifyNotClosed();
3895
+ const waitBlobsToAttach = props?.notifyImminentClosure;
3896
+ if (this._orderSequentiallyCalls !== 0) {
3897
+ throw new UsageError("can't get state during orderSequentially");
3898
+ }
3899
+ const pendingAttachmentBlobs = await this.blobManager.getPendingBlobs(
3900
+ waitBlobsToAttach,
3901
+ );
3902
+ const pending = this.pendingStateManager.getLocalState();
3903
+ if (!pendingAttachmentBlobs && !this.hasPendingMessages()) {
3904
+ return; // no pending state to save
3905
+ }
3906
+ // Flush pending batch.
3907
+ // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
3908
+ // to close current batch.
3909
+ this.flush();
3495
3910
 
3496
- return {
3497
- pending: this.pendingStateManager.getLocalState(),
3498
- pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
3499
- };
3911
+ const pendingState: IPendingRuntimeState = {
3912
+ pending,
3913
+ pendingAttachmentBlobs,
3914
+ };
3915
+ event.end({
3916
+ attachmentBlobsSize: Object.keys(pendingAttachmentBlobs ?? {}).length,
3917
+ pendingOpsSize: pending?.pendingStates.length,
3918
+ });
3919
+ return pendingState;
3920
+ },
3921
+ );
3500
3922
  }
3501
3923
 
3502
- public readonly summarizeOnDemand: ISummarizer["summarizeOnDemand"] = (...args) => {
3503
- if (this.clientDetails.type === summarizerClientType) {
3504
- return this.summarizer.summarizeOnDemand(...args);
3924
+ public summarizeOnDemand(options: IOnDemandSummarizeOptions): ISummarizeResults {
3925
+ if (this.isSummarizerClient) {
3926
+ return this.summarizer.summarizeOnDemand(options);
3505
3927
  } else if (this.summaryManager !== undefined) {
3506
- return this.summaryManager.summarizeOnDemand(...args);
3928
+ return this.summaryManager.summarizeOnDemand(options);
3507
3929
  } else {
3508
3930
  // If we're not the summarizer, and we don't have a summaryManager, we expect that
3509
3931
  // disableSummaries is turned on. We are throwing instead of returning a failure here,
3510
3932
  // because it is a misuse of the API rather than an expected failure.
3511
3933
  throw new UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
3512
3934
  }
3513
- };
3935
+ }
3514
3936
 
3515
- public readonly enqueueSummarize: ISummarizer["enqueueSummarize"] = (...args) => {
3516
- if (this.clientDetails.type === summarizerClientType) {
3517
- return this.summarizer.enqueueSummarize(...args);
3937
+ public enqueueSummarize(options: IEnqueueSummarizeOptions): EnqueueSummarizeResult {
3938
+ if (this.isSummarizerClient) {
3939
+ return this.summarizer.enqueueSummarize(options);
3518
3940
  } else if (this.summaryManager !== undefined) {
3519
- return this.summaryManager.enqueueSummarize(...args);
3941
+ return this.summaryManager.enqueueSummarize(options);
3520
3942
  } else {
3521
3943
  // If we're not the summarizer, and we don't have a summaryManager, we expect that
3522
3944
  // generateSummaries is turned off. We are throwing instead of returning a failure here,
3523
3945
  // because it is a misuse of the API rather than an expected failure.
3524
3946
  throw new UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
3525
3947
  }
3526
- };
3948
+ }
3527
3949
 
3528
3950
  /**
3529
3951
  * * Forms a function that will request a Summarizer.