@fluidframework/container-runtime 2.0.0-dev.5.3.2.178189 → 2.0.0-dev.6.4.0.191457

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 (526) hide show
  1. package/CHANGELOG.md +123 -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 +10 -16
  8. package/dist/blobManager.d.ts.map +1 -1
  9. package/dist/blobManager.js +184 -172
  10. package/dist/blobManager.js.map +1 -1
  11. package/dist/connectionTelemetry.d.ts.map +1 -1
  12. package/dist/connectionTelemetry.js +25 -16
  13. package/dist/connectionTelemetry.js.map +1 -1
  14. package/dist/containerRuntime.d.ts +161 -35
  15. package/dist/containerRuntime.d.ts.map +1 -1
  16. package/dist/containerRuntime.js +697 -449
  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 +3 -2
  22. package/dist/dataStoreContext.d.ts.map +1 -1
  23. package/dist/dataStoreContext.js +83 -87
  24. package/dist/dataStoreContext.js.map +1 -1
  25. package/dist/dataStoreContexts.d.ts +1 -2
  26. package/dist/dataStoreContexts.d.ts.map +1 -1
  27. package/dist/dataStoreContexts.js +8 -9
  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 +22 -6
  32. package/dist/dataStores.d.ts.map +1 -1
  33. package/dist/dataStores.js +123 -81
  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 +61 -53
  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 -4
  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.map +1 -1
  70. package/dist/gc/gcTelemetry.js +45 -35
  71. package/dist/gc/gcTelemetry.js.map +1 -1
  72. package/dist/gc/gcUnreferencedStateTracker.js +4 -4
  73. package/dist/gc/gcUnreferencedStateTracker.js.map +1 -1
  74. package/dist/gc/index.d.ts +2 -2
  75. package/dist/gc/index.d.ts.map +1 -1
  76. package/dist/gc/index.js +3 -5
  77. package/dist/gc/index.js.map +1 -1
  78. package/dist/id-compressor/appendOnlySortedMap.d.ts +8 -30
  79. package/dist/id-compressor/appendOnlySortedMap.d.ts.map +1 -1
  80. package/dist/id-compressor/appendOnlySortedMap.js +26 -68
  81. package/dist/id-compressor/appendOnlySortedMap.js.map +1 -1
  82. package/dist/id-compressor/finalSpace.d.ts +29 -0
  83. package/dist/id-compressor/finalSpace.d.ts.map +1 -0
  84. package/dist/id-compressor/finalSpace.js +62 -0
  85. package/dist/id-compressor/finalSpace.js.map +1 -0
  86. package/dist/id-compressor/idCompressor.d.ts +25 -250
  87. package/dist/id-compressor/idCompressor.d.ts.map +1 -1
  88. package/dist/id-compressor/idCompressor.js +390 -1153
  89. package/dist/id-compressor/idCompressor.js.map +1 -1
  90. package/dist/id-compressor/identifiers.d.ts +32 -0
  91. package/dist/id-compressor/identifiers.d.ts.map +1 -0
  92. package/dist/id-compressor/identifiers.js +15 -0
  93. package/dist/id-compressor/identifiers.js.map +1 -0
  94. package/dist/id-compressor/index.d.ts +5 -6
  95. package/dist/id-compressor/index.d.ts.map +1 -1
  96. package/dist/id-compressor/index.js +20 -26
  97. package/dist/id-compressor/index.js.map +1 -1
  98. package/dist/id-compressor/persistanceUtilities.d.ts +22 -0
  99. package/dist/id-compressor/persistanceUtilities.d.ts.map +1 -0
  100. package/dist/id-compressor/persistanceUtilities.js +43 -0
  101. package/dist/id-compressor/persistanceUtilities.js.map +1 -0
  102. package/dist/id-compressor/sessionSpaceNormalizer.d.ts +46 -0
  103. package/dist/id-compressor/sessionSpaceNormalizer.d.ts.map +1 -0
  104. package/dist/id-compressor/sessionSpaceNormalizer.js +80 -0
  105. package/dist/id-compressor/sessionSpaceNormalizer.js.map +1 -0
  106. package/dist/id-compressor/sessions.d.ts +115 -0
  107. package/dist/id-compressor/sessions.d.ts.map +1 -0
  108. package/dist/id-compressor/sessions.js +305 -0
  109. package/dist/id-compressor/sessions.js.map +1 -0
  110. package/dist/id-compressor/utilities.d.ts +49 -0
  111. package/dist/id-compressor/utilities.d.ts.map +1 -0
  112. package/dist/id-compressor/utilities.js +166 -0
  113. package/dist/id-compressor/utilities.js.map +1 -0
  114. package/dist/index.d.ts +3 -3
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +6 -4
  117. package/dist/index.js.map +1 -1
  118. package/dist/opLifecycle/batchManager.js +10 -6
  119. package/dist/opLifecycle/batchManager.js.map +1 -1
  120. package/dist/opLifecycle/opCompressor.d.ts +2 -2
  121. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  122. package/dist/opLifecycle/opCompressor.js +12 -7
  123. package/dist/opLifecycle/opCompressor.js.map +1 -1
  124. package/dist/opLifecycle/opDecompressor.d.ts +2 -2
  125. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  126. package/dist/opLifecycle/opDecompressor.js +23 -20
  127. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  128. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  129. package/dist/opLifecycle/opGroupingManager.js +19 -9
  130. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  131. package/dist/opLifecycle/opSplitter.d.ts +2 -2
  132. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  133. package/dist/opLifecycle/opSplitter.js +22 -19
  134. package/dist/opLifecycle/opSplitter.js.map +1 -1
  135. package/dist/opLifecycle/outbox.d.ts +7 -5
  136. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  137. package/dist/opLifecycle/outbox.js +22 -31
  138. package/dist/opLifecycle/outbox.js.map +1 -1
  139. package/dist/opLifecycle/remoteMessageProcessor.d.ts +6 -1
  140. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  141. package/dist/opLifecycle/remoteMessageProcessor.js +19 -7
  142. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  143. package/dist/opProperties.js +1 -2
  144. package/dist/opProperties.js.map +1 -1
  145. package/dist/packageVersion.d.ts +1 -1
  146. package/dist/packageVersion.js +1 -1
  147. package/dist/packageVersion.js.map +1 -1
  148. package/dist/pendingStateManager.d.ts +18 -8
  149. package/dist/pendingStateManager.d.ts.map +1 -1
  150. package/dist/pendingStateManager.js +73 -51
  151. package/dist/pendingStateManager.js.map +1 -1
  152. package/dist/scheduleManager.d.ts.map +1 -1
  153. package/dist/scheduleManager.js +36 -32
  154. package/dist/scheduleManager.js.map +1 -1
  155. package/dist/summary/index.d.ts +3 -3
  156. package/dist/summary/index.d.ts.map +1 -1
  157. package/dist/summary/index.js +2 -1
  158. package/dist/summary/index.js.map +1 -1
  159. package/dist/summary/orderedClientElection.d.ts +3 -3
  160. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  161. package/dist/summary/orderedClientElection.js +26 -27
  162. package/dist/summary/orderedClientElection.js.map +1 -1
  163. package/dist/summary/runWhileConnectedCoordinator.js +3 -3
  164. package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
  165. package/dist/summary/runningSummarizer.d.ts +31 -10
  166. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  167. package/dist/summary/runningSummarizer.js +271 -139
  168. package/dist/summary/runningSummarizer.js.map +1 -1
  169. package/dist/summary/summarizer.d.ts +8 -7
  170. package/dist/summary/summarizer.d.ts.map +1 -1
  171. package/dist/summary/summarizer.js +79 -78
  172. package/dist/summary/summarizer.js.map +1 -1
  173. package/dist/summary/summarizerClientElection.d.ts +2 -2
  174. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  175. package/dist/summary/summarizerClientElection.js +7 -11
  176. package/dist/summary/summarizerClientElection.js.map +1 -1
  177. package/dist/summary/summarizerHeuristics.js +10 -14
  178. package/dist/summary/summarizerHeuristics.js.map +1 -1
  179. package/dist/summary/summarizerNode/index.d.ts +1 -1
  180. package/dist/summary/summarizerNode/index.d.ts.map +1 -1
  181. package/dist/summary/summarizerNode/index.js.map +1 -1
  182. package/dist/summary/summarizerNode/summarizerNode.d.ts +10 -19
  183. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  184. package/dist/summary/summarizerNode/summarizerNode.js +57 -130
  185. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  186. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +6 -30
  187. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  188. package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -4
  189. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  190. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +4 -14
  191. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  192. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +15 -101
  193. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  194. package/dist/summary/summarizerTypes.d.ts +38 -25
  195. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  196. package/dist/summary/summarizerTypes.js.map +1 -1
  197. package/dist/summary/summaryCollection.d.ts +2 -3
  198. package/dist/summary/summaryCollection.d.ts.map +1 -1
  199. package/dist/summary/summaryCollection.js +12 -13
  200. package/dist/summary/summaryCollection.js.map +1 -1
  201. package/dist/summary/summaryFormat.d.ts +3 -0
  202. package/dist/summary/summaryFormat.d.ts.map +1 -1
  203. package/dist/summary/summaryFormat.js +6 -4
  204. package/dist/summary/summaryFormat.js.map +1 -1
  205. package/dist/summary/summaryGenerator.d.ts +10 -4
  206. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  207. package/dist/summary/summaryGenerator.js +106 -57
  208. package/dist/summary/summaryGenerator.js.map +1 -1
  209. package/dist/summary/summaryManager.d.ts +8 -8
  210. package/dist/summary/summaryManager.d.ts.map +1 -1
  211. package/dist/summary/summaryManager.js +38 -28
  212. package/dist/summary/summaryManager.js.map +1 -1
  213. package/lib/batchTracker.d.ts +3 -2
  214. package/lib/batchTracker.d.ts.map +1 -1
  215. package/lib/batchTracker.js +5 -4
  216. package/lib/batchTracker.js.map +1 -1
  217. package/lib/blobManager.d.ts +10 -16
  218. package/lib/blobManager.d.ts.map +1 -1
  219. package/lib/blobManager.js +159 -147
  220. package/lib/blobManager.js.map +1 -1
  221. package/lib/connectionTelemetry.d.ts.map +1 -1
  222. package/lib/connectionTelemetry.js +15 -6
  223. package/lib/connectionTelemetry.js.map +1 -1
  224. package/lib/containerRuntime.d.ts +161 -35
  225. package/lib/containerRuntime.d.ts.map +1 -1
  226. package/lib/containerRuntime.js +651 -402
  227. package/lib/containerRuntime.js.map +1 -1
  228. package/lib/dataStore.d.ts.map +1 -1
  229. package/lib/dataStore.js +13 -5
  230. package/lib/dataStore.js.map +1 -1
  231. package/lib/dataStoreContext.d.ts +3 -2
  232. package/lib/dataStoreContext.d.ts.map +1 -1
  233. package/lib/dataStoreContext.js +47 -51
  234. package/lib/dataStoreContext.js.map +1 -1
  235. package/lib/dataStoreContexts.d.ts +1 -2
  236. package/lib/dataStoreContexts.d.ts.map +1 -1
  237. package/lib/dataStoreContexts.js +3 -4
  238. package/lib/dataStoreContexts.js.map +1 -1
  239. package/lib/dataStoreRegistry.js +1 -1
  240. package/lib/dataStoreRegistry.js.map +1 -1
  241. package/lib/dataStores.d.ts +22 -6
  242. package/lib/dataStores.d.ts.map +1 -1
  243. package/lib/dataStores.js +107 -65
  244. package/lib/dataStores.js.map +1 -1
  245. package/lib/deltaManagerProxyBase.d.ts +35 -0
  246. package/lib/deltaManagerProxyBase.d.ts.map +1 -0
  247. package/lib/deltaManagerProxyBase.js +73 -0
  248. package/lib/deltaManagerProxyBase.js.map +1 -0
  249. package/lib/deltaManagerSummarizerProxy.d.ts +1 -1
  250. package/lib/deltaManagerSummarizerProxy.d.ts.map +1 -1
  251. package/lib/deltaManagerSummarizerProxy.js +3 -1
  252. package/lib/deltaManagerSummarizerProxy.js.map +1 -1
  253. package/lib/deltaScheduler.d.ts.map +1 -1
  254. package/lib/deltaScheduler.js +7 -7
  255. package/lib/deltaScheduler.js.map +1 -1
  256. package/lib/error.d.ts +14 -0
  257. package/lib/error.d.ts.map +1 -0
  258. package/lib/error.js +17 -0
  259. package/lib/error.js.map +1 -0
  260. package/lib/gc/garbageCollection.d.ts +10 -9
  261. package/lib/gc/garbageCollection.d.ts.map +1 -1
  262. package/lib/gc/garbageCollection.js +61 -53
  263. package/lib/gc/garbageCollection.js.map +1 -1
  264. package/lib/gc/gcConfigs.d.ts.map +1 -1
  265. package/lib/gc/gcConfigs.js +16 -12
  266. package/lib/gc/gcConfigs.js.map +1 -1
  267. package/lib/gc/gcDefinitions.d.ts +17 -4
  268. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  269. package/lib/gc/gcDefinitions.js +13 -14
  270. package/lib/gc/gcDefinitions.js.map +1 -1
  271. package/lib/gc/gcHelpers.d.ts +0 -8
  272. package/lib/gc/gcHelpers.d.ts.map +1 -1
  273. package/lib/gc/gcHelpers.js +5 -17
  274. package/lib/gc/gcHelpers.js.map +1 -1
  275. package/lib/gc/gcSummaryStateTracker.d.ts +4 -7
  276. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  277. package/lib/gc/gcSummaryStateTracker.js +20 -59
  278. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  279. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  280. package/lib/gc/gcTelemetry.js +46 -36
  281. package/lib/gc/gcTelemetry.js.map +1 -1
  282. package/lib/gc/gcUnreferencedStateTracker.js +1 -1
  283. package/lib/gc/gcUnreferencedStateTracker.js.map +1 -1
  284. package/lib/gc/index.d.ts +2 -2
  285. package/lib/gc/index.d.ts.map +1 -1
  286. package/lib/gc/index.js +2 -2
  287. package/lib/gc/index.js.map +1 -1
  288. package/lib/id-compressor/appendOnlySortedMap.d.ts +8 -30
  289. package/lib/id-compressor/appendOnlySortedMap.d.ts.map +1 -1
  290. package/lib/id-compressor/appendOnlySortedMap.js +25 -66
  291. package/lib/id-compressor/appendOnlySortedMap.js.map +1 -1
  292. package/lib/id-compressor/finalSpace.d.ts +29 -0
  293. package/lib/id-compressor/finalSpace.d.ts.map +1 -0
  294. package/lib/id-compressor/finalSpace.js +58 -0
  295. package/lib/id-compressor/finalSpace.js.map +1 -0
  296. package/lib/id-compressor/idCompressor.d.ts +25 -250
  297. package/lib/id-compressor/idCompressor.d.ts.map +1 -1
  298. package/lib/id-compressor/idCompressor.js +385 -1142
  299. package/lib/id-compressor/idCompressor.js.map +1 -1
  300. package/lib/id-compressor/identifiers.d.ts +32 -0
  301. package/lib/id-compressor/identifiers.d.ts.map +1 -0
  302. package/lib/id-compressor/identifiers.js +11 -0
  303. package/lib/id-compressor/identifiers.js.map +1 -0
  304. package/lib/id-compressor/index.d.ts +5 -6
  305. package/lib/id-compressor/index.d.ts.map +1 -1
  306. package/lib/id-compressor/index.js +5 -6
  307. package/lib/id-compressor/index.js.map +1 -1
  308. package/lib/id-compressor/persistanceUtilities.d.ts +22 -0
  309. package/lib/id-compressor/persistanceUtilities.d.ts.map +1 -0
  310. package/lib/id-compressor/persistanceUtilities.js +34 -0
  311. package/lib/id-compressor/persistanceUtilities.js.map +1 -0
  312. package/lib/id-compressor/sessionSpaceNormalizer.d.ts +46 -0
  313. package/lib/id-compressor/sessionSpaceNormalizer.d.ts.map +1 -0
  314. package/lib/id-compressor/sessionSpaceNormalizer.js +76 -0
  315. package/lib/id-compressor/sessionSpaceNormalizer.js.map +1 -0
  316. package/lib/id-compressor/sessions.d.ts +115 -0
  317. package/lib/id-compressor/sessions.d.ts.map +1 -0
  318. package/lib/id-compressor/sessions.js +290 -0
  319. package/lib/id-compressor/sessions.js.map +1 -0
  320. package/lib/id-compressor/utilities.d.ts +49 -0
  321. package/lib/id-compressor/utilities.d.ts.map +1 -0
  322. package/lib/id-compressor/utilities.js +148 -0
  323. package/lib/id-compressor/utilities.js.map +1 -0
  324. package/lib/index.d.ts +3 -3
  325. package/lib/index.d.ts.map +1 -1
  326. package/lib/index.js +2 -2
  327. package/lib/index.js.map +1 -1
  328. package/lib/opLifecycle/batchManager.js +10 -6
  329. package/lib/opLifecycle/batchManager.js.map +1 -1
  330. package/lib/opLifecycle/opCompressor.d.ts +2 -2
  331. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  332. package/lib/opLifecycle/opCompressor.js +10 -5
  333. package/lib/opLifecycle/opCompressor.js.map +1 -1
  334. package/lib/opLifecycle/opDecompressor.d.ts +2 -2
  335. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  336. package/lib/opLifecycle/opDecompressor.js +15 -12
  337. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  338. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  339. package/lib/opLifecycle/opGroupingManager.js +17 -7
  340. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  341. package/lib/opLifecycle/opSplitter.d.ts +2 -2
  342. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  343. package/lib/opLifecycle/opSplitter.js +13 -10
  344. package/lib/opLifecycle/opSplitter.js.map +1 -1
  345. package/lib/opLifecycle/outbox.d.ts +7 -5
  346. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  347. package/lib/opLifecycle/outbox.js +13 -22
  348. package/lib/opLifecycle/outbox.js.map +1 -1
  349. package/lib/opLifecycle/remoteMessageProcessor.d.ts +6 -1
  350. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  351. package/lib/opLifecycle/remoteMessageProcessor.js +20 -8
  352. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  353. package/lib/opProperties.js +1 -2
  354. package/lib/opProperties.js.map +1 -1
  355. package/lib/packageVersion.d.ts +1 -1
  356. package/lib/packageVersion.js +1 -1
  357. package/lib/packageVersion.js.map +1 -1
  358. package/lib/pendingStateManager.d.ts +18 -8
  359. package/lib/pendingStateManager.d.ts.map +1 -1
  360. package/lib/pendingStateManager.js +62 -40
  361. package/lib/pendingStateManager.js.map +1 -1
  362. package/lib/scheduleManager.d.ts.map +1 -1
  363. package/lib/scheduleManager.js +18 -14
  364. package/lib/scheduleManager.js.map +1 -1
  365. package/lib/summary/index.d.ts +3 -3
  366. package/lib/summary/index.d.ts.map +1 -1
  367. package/lib/summary/index.js +1 -1
  368. package/lib/summary/index.js.map +1 -1
  369. package/lib/summary/orderedClientElection.d.ts +3 -3
  370. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  371. package/lib/summary/orderedClientElection.js +21 -22
  372. package/lib/summary/orderedClientElection.js.map +1 -1
  373. package/lib/summary/runWhileConnectedCoordinator.js +1 -1
  374. package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
  375. package/lib/summary/runningSummarizer.d.ts +31 -10
  376. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  377. package/lib/summary/runningSummarizer.js +265 -133
  378. package/lib/summary/runningSummarizer.js.map +1 -1
  379. package/lib/summary/summarizer.d.ts +8 -7
  380. package/lib/summary/summarizer.d.ts.map +1 -1
  381. package/lib/summary/summarizer.js +75 -74
  382. package/lib/summary/summarizer.js.map +1 -1
  383. package/lib/summary/summarizerClientElection.d.ts +2 -2
  384. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  385. package/lib/summary/summarizerClientElection.js +6 -10
  386. package/lib/summary/summarizerClientElection.js.map +1 -1
  387. package/lib/summary/summarizerHeuristics.js +9 -13
  388. package/lib/summary/summarizerHeuristics.js.map +1 -1
  389. package/lib/summary/summarizerNode/index.d.ts +1 -1
  390. package/lib/summary/summarizerNode/index.d.ts.map +1 -1
  391. package/lib/summary/summarizerNode/index.js.map +1 -1
  392. package/lib/summary/summarizerNode/summarizerNode.d.ts +10 -19
  393. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  394. package/lib/summary/summarizerNode/summarizerNode.js +42 -115
  395. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  396. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +6 -30
  397. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  398. package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -4
  399. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  400. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +4 -14
  401. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  402. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +14 -100
  403. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  404. package/lib/summary/summarizerTypes.d.ts +38 -25
  405. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  406. package/lib/summary/summarizerTypes.js.map +1 -1
  407. package/lib/summary/summaryCollection.d.ts +2 -3
  408. package/lib/summary/summaryCollection.d.ts.map +1 -1
  409. package/lib/summary/summaryCollection.js +5 -6
  410. package/lib/summary/summaryCollection.js.map +1 -1
  411. package/lib/summary/summaryFormat.d.ts +3 -0
  412. package/lib/summary/summaryFormat.d.ts.map +1 -1
  413. package/lib/summary/summaryFormat.js +5 -3
  414. package/lib/summary/summaryFormat.js.map +1 -1
  415. package/lib/summary/summaryGenerator.d.ts +10 -4
  416. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  417. package/lib/summary/summaryGenerator.js +100 -51
  418. package/lib/summary/summaryGenerator.js.map +1 -1
  419. package/lib/summary/summaryManager.d.ts +8 -8
  420. package/lib/summary/summaryManager.d.ts.map +1 -1
  421. package/lib/summary/summaryManager.js +35 -25
  422. package/lib/summary/summaryManager.js.map +1 -1
  423. package/package.json +28 -31
  424. package/src/batchTracker.ts +7 -5
  425. package/src/blobManager.ts +188 -166
  426. package/src/connectionTelemetry.ts +9 -4
  427. package/src/containerRuntime.ts +806 -441
  428. package/src/dataStore.ts +12 -4
  429. package/src/dataStoreContext.ts +39 -43
  430. package/src/dataStoreContexts.ts +4 -6
  431. package/src/dataStoreRegistry.ts +1 -1
  432. package/src/dataStores.ts +114 -80
  433. package/src/deltaManagerProxyBase.ts +111 -0
  434. package/src/deltaManagerSummarizerProxy.ts +4 -1
  435. package/src/deltaScheduler.ts +7 -11
  436. package/src/error.ts +18 -0
  437. package/src/gc/garbageCollection.md +53 -5
  438. package/src/gc/garbageCollection.ts +58 -52
  439. package/src/gc/gcConfigs.ts +4 -2
  440. package/src/gc/gcDefinitions.ts +17 -20
  441. package/src/gc/gcEarlyAdoption.md +145 -0
  442. package/src/gc/gcHelpers.ts +1 -12
  443. package/src/gc/gcSummaryStateTracker.ts +19 -65
  444. package/src/gc/gcTelemetry.ts +14 -12
  445. package/src/gc/gcUnreferencedStateTracker.ts +1 -1
  446. package/src/gc/index.ts +2 -4
  447. package/src/id-compressor/appendOnlySortedMap.ts +26 -87
  448. package/src/id-compressor/finalSpace.ts +67 -0
  449. package/src/id-compressor/idCompressor.ts +458 -1682
  450. package/src/id-compressor/identifiers.ts +42 -0
  451. package/src/id-compressor/index.ts +11 -20
  452. package/src/id-compressor/persistanceUtilities.ts +58 -0
  453. package/src/id-compressor/sessionSpaceNormalizer.ts +83 -0
  454. package/src/id-compressor/sessions.ts +405 -0
  455. package/src/id-compressor/utilities.ts +187 -0
  456. package/src/index.ts +7 -2
  457. package/src/opLifecycle/opCompressor.ts +6 -5
  458. package/src/opLifecycle/opDecompressor.ts +6 -4
  459. package/src/opLifecycle/opGroupingManager.ts +9 -6
  460. package/src/opLifecycle/opSplitter.ts +7 -6
  461. package/src/opLifecycle/outbox.ts +23 -32
  462. package/src/opLifecycle/remoteMessageProcessor.ts +27 -8
  463. package/src/packageVersion.ts +1 -1
  464. package/src/pendingStateManager.ts +72 -42
  465. package/src/scheduleManager.ts +7 -5
  466. package/src/summary/index.ts +4 -3
  467. package/src/summary/orderedClientElection.ts +10 -6
  468. package/src/summary/runWhileConnectedCoordinator.ts +1 -1
  469. package/src/summary/runningSummarizer.ts +291 -163
  470. package/src/summary/summarizer.ts +27 -16
  471. package/src/summary/summarizerClientElection.ts +2 -2
  472. package/src/summary/summarizerHeuristics.ts +1 -1
  473. package/src/summary/summarizerNode/index.ts +1 -2
  474. package/src/summary/summarizerNode/summarizerNode.ts +36 -160
  475. package/src/summary/summarizerNode/summarizerNodeUtils.ts +7 -38
  476. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +11 -130
  477. package/src/summary/summarizerTypes.ts +40 -25
  478. package/src/summary/summaryCollection.ts +3 -3
  479. package/src/summary/summaryFormat.ts +4 -1
  480. package/src/summary/summaryGenerator.ts +52 -52
  481. package/src/summary/summaryManager.ts +44 -17
  482. package/dist/id-compressor/idRange.d.ts +0 -11
  483. package/dist/id-compressor/idRange.d.ts.map +0 -1
  484. package/dist/id-compressor/idRange.js +0 -29
  485. package/dist/id-compressor/idRange.js.map +0 -1
  486. package/dist/id-compressor/numericUuid.d.ts +0 -59
  487. package/dist/id-compressor/numericUuid.d.ts.map +0 -1
  488. package/dist/id-compressor/numericUuid.js +0 -325
  489. package/dist/id-compressor/numericUuid.js.map +0 -1
  490. package/dist/id-compressor/sessionIdNormalizer.d.ts +0 -138
  491. package/dist/id-compressor/sessionIdNormalizer.d.ts.map +0 -1
  492. package/dist/id-compressor/sessionIdNormalizer.js +0 -488
  493. package/dist/id-compressor/sessionIdNormalizer.js.map +0 -1
  494. package/dist/id-compressor/utils.d.ts +0 -57
  495. package/dist/id-compressor/utils.d.ts.map +0 -1
  496. package/dist/id-compressor/utils.js +0 -90
  497. package/dist/id-compressor/utils.js.map +0 -1
  498. package/dist/id-compressor/uuidUtilities.d.ts +0 -28
  499. package/dist/id-compressor/uuidUtilities.d.ts.map +0 -1
  500. package/dist/id-compressor/uuidUtilities.js +0 -104
  501. package/dist/id-compressor/uuidUtilities.js.map +0 -1
  502. package/lib/id-compressor/idRange.d.ts +0 -11
  503. package/lib/id-compressor/idRange.d.ts.map +0 -1
  504. package/lib/id-compressor/idRange.js +0 -25
  505. package/lib/id-compressor/idRange.js.map +0 -1
  506. package/lib/id-compressor/numericUuid.d.ts +0 -59
  507. package/lib/id-compressor/numericUuid.d.ts.map +0 -1
  508. package/lib/id-compressor/numericUuid.js +0 -315
  509. package/lib/id-compressor/numericUuid.js.map +0 -1
  510. package/lib/id-compressor/sessionIdNormalizer.d.ts +0 -138
  511. package/lib/id-compressor/sessionIdNormalizer.d.ts.map +0 -1
  512. package/lib/id-compressor/sessionIdNormalizer.js +0 -484
  513. package/lib/id-compressor/sessionIdNormalizer.js.map +0 -1
  514. package/lib/id-compressor/utils.d.ts +0 -57
  515. package/lib/id-compressor/utils.d.ts.map +0 -1
  516. package/lib/id-compressor/utils.js +0 -79
  517. package/lib/id-compressor/utils.js.map +0 -1
  518. package/lib/id-compressor/uuidUtilities.d.ts +0 -28
  519. package/lib/id-compressor/uuidUtilities.d.ts.map +0 -1
  520. package/lib/id-compressor/uuidUtilities.js +0 -96
  521. package/lib/id-compressor/uuidUtilities.js.map +0 -1
  522. package/src/id-compressor/idRange.ts +0 -35
  523. package/src/id-compressor/numericUuid.ts +0 -383
  524. package/src/id-compressor/sessionIdNormalizer.ts +0 -609
  525. package/src/id-compressor/utils.ts +0 -114
  526. package/src/id-compressor/uuidUtilities.ts +0 -120
@@ -14,6 +14,7 @@ import {
14
14
  } from "@fluidframework/core-interfaces";
15
15
  import {
16
16
  IAudience,
17
+ IBatchMessage,
17
18
  IContainerContext,
18
19
  IDeltaManager,
19
20
  IRuntime,
@@ -26,23 +27,22 @@ import {
26
27
  IContainerRuntime,
27
28
  IContainerRuntimeEvents,
28
29
  } from "@fluidframework/container-runtime-definitions";
30
+ import { assert, delay, LazyPromise } from "@fluidframework/core-utils";
31
+ import { Trace, TypedEventEmitter } from "@fluid-internal/client-utils";
29
32
  import {
30
- assert,
31
- delay,
32
- Trace,
33
- TypedEventEmitter,
34
- unreachableCase,
35
- } from "@fluidframework/common-utils";
36
- import { LazyPromise } from "@fluidframework/core-utils";
37
- import {
38
- ChildLogger,
33
+ createChildLogger,
34
+ createChildMonitoringContext,
35
+ DataCorruptionError,
36
+ DataProcessingError,
37
+ GenericError,
39
38
  raiseConnectedEvent,
40
39
  PerformanceEvent,
40
+ // eslint-disable-next-line import/no-deprecated
41
41
  TaggedLoggerAdapter,
42
42
  MonitoringContext,
43
- loggerToMonitoringContext,
44
43
  wrapError,
45
44
  ITelemetryLoggerExt,
45
+ UsageError,
46
46
  } from "@fluidframework/telemetry-utils";
47
47
  import {
48
48
  DriverHeader,
@@ -51,12 +51,6 @@ import {
51
51
  ISummaryContext,
52
52
  } from "@fluidframework/driver-definitions";
53
53
  import { readAndParse } from "@fluidframework/driver-utils";
54
- import {
55
- DataCorruptionError,
56
- DataProcessingError,
57
- GenericError,
58
- UsageError,
59
- } from "@fluidframework/container-utils";
60
54
  import {
61
55
  IClientDetails,
62
56
  IDocumentMessage,
@@ -132,7 +126,6 @@ import {
132
126
  IContainerRuntimeMetadata,
133
127
  ICreateContainerMetadata,
134
128
  idCompressorBlobName,
135
- IFetchSnapshotResult,
136
129
  IRootSummarizerNodeWithGC,
137
130
  ISummaryMetadataMessage,
138
131
  metadataBlobName,
@@ -156,6 +149,12 @@ import {
156
149
  RunWhileConnectedCoordinator,
157
150
  IGenerateSummaryTreeResult,
158
151
  RetriableSummaryError,
152
+ IOnDemandSummarizeOptions,
153
+ ISummarizeResults,
154
+ IEnqueueSummarizeOptions,
155
+ EnqueueSummarizeResult,
156
+ ISummarizerEvents,
157
+ IBaseSummarizeResult,
159
158
  } from "./summary";
160
159
  import { formExponentialFn, Throttler } from "./throttler";
161
160
  import {
@@ -173,6 +172,7 @@ import { BindBatchTracker } from "./batchTracker";
173
172
  import { ScheduleManager } from "./scheduleManager";
174
173
  import {
175
174
  BatchMessage,
175
+ IBatch,
176
176
  IBatchCheckpoint,
177
177
  OpCompressor,
178
178
  OpDecompressor,
@@ -212,11 +212,64 @@ export enum ContainerMessageType {
212
212
  IdAllocation = "idAllocation",
213
213
  }
214
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
+ */
215
258
  export interface ContainerRuntimeMessage {
216
- contents: any;
259
+ /** Type of the op, within the ContainerRuntime's domain */
217
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;
218
265
  }
219
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
+
220
273
  export interface ISummaryBaseConfiguration {
221
274
  /**
222
275
  * Delay before first attempt to spawn summarizing container.
@@ -443,14 +496,6 @@ export interface IContainerRuntimeOptions {
443
496
  readonly enableGroupedBatching?: boolean;
444
497
  }
445
498
 
446
- /**
447
- * The summary tree returned by the root node. It adds state relevant to the root of the tree.
448
- */
449
- export interface IRootSummaryTreeWithStats extends ISummaryTreeWithStats {
450
- /** The garbage collection stats if GC ran, undefined otherwise. */
451
- gcStats?: IGCStats;
452
- }
453
-
454
499
  /**
455
500
  * Accepted header keys for requests coming to the runtime.
456
501
  */
@@ -463,9 +508,13 @@ export enum RuntimeHeaders {
463
508
 
464
509
  /** True if a tombstoned object should be returned without erroring */
465
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
466
513
 
467
514
  /** Tombstone error responses will have this header set to true */
468
515
  export const TombstoneResponseHeaderKey = "isTombstoned";
516
+ /** Inactive error responses will have this header set to true */
517
+ export const InactiveResponseHeaderKey = "isInactive";
469
518
 
470
519
  /**
471
520
  * The full set of parsed header data that may be found on Runtime requests
@@ -506,7 +555,7 @@ interface OldContainerContextWithLogger extends Omit<IContainerContext, "taggedL
506
555
  * instantiated runtime in a new instance of the container, so it can load to the
507
556
  * same state
508
557
  */
509
- interface IPendingRuntimeState {
558
+ export interface IPendingRuntimeState {
510
559
  /**
511
560
  * Pending ops from PendingStateManager
512
561
  */
@@ -535,12 +584,17 @@ const defaultCompressionConfig = {
535
584
 
536
585
  const defaultChunkSizeInBytes = 204800;
537
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
+
538
592
  /**
539
593
  * Instead of refreshing from latest because we do not have 100% confidence in the state
540
594
  * of the current system, we should close the summarizer and let it recover.
541
595
  * This delay's goal is to prevent tight restart loops
542
596
  */
543
- const defaultCloseSummarizerDelayMs = 10000; // 10 seconds
597
+ const defaultCloseSummarizerDelayMs = 5000; // 5 seconds
544
598
 
545
599
  /**
546
600
  * @deprecated - use ContainerRuntimeMessage instead
@@ -582,14 +636,41 @@ export function getDeviceSpec() {
582
636
  return {};
583
637
  }
584
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
+
585
663
  /**
586
664
  * Represents the runtime of the container. Contains helper functions/state of the container.
587
665
  * It will define the store level mappings.
588
666
  */
589
667
  export class ContainerRuntime
590
- extends TypedEventEmitter<IContainerRuntimeEvents>
668
+ extends TypedEventEmitter<IContainerRuntimeEvents & ISummarizerEvents>
591
669
  implements IContainerRuntime, IRuntime, ISummarizerRuntime, ISummarizerInternalsProvider
592
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
+ */
593
674
  public get IFluidRouter() {
594
675
  return this;
595
676
  }
@@ -648,10 +729,10 @@ export class ContainerRuntime
648
729
  context: IContainerContext;
649
730
  registryEntries: NamedFluidDataStoreRegistryEntries;
650
731
  existing: boolean;
651
- requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
652
732
  runtimeOptions?: IContainerRuntimeOptions;
653
733
  containerScope?: FluidObject;
654
734
  containerRuntimeCtor?: typeof ContainerRuntime;
735
+ requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
655
736
  initializeEntryPoint?: (containerRuntime: IContainerRuntime) => Promise<FluidObject>;
656
737
  }): Promise<ContainerRuntime> {
657
738
  const {
@@ -662,18 +743,32 @@ export class ContainerRuntime
662
743
  runtimeOptions = {},
663
744
  containerScope = {},
664
745
  containerRuntimeCtor = ContainerRuntime,
665
- initializeEntryPoint,
666
746
  } = params;
667
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
+
668
759
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
669
760
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
670
761
  const backCompatContext: IContainerContext | OldContainerContextWithLogger = context;
671
762
  const passLogger =
672
763
  backCompatContext.taggedLogger ??
764
+ // eslint-disable-next-line import/no-deprecated
673
765
  new TaggedLoggerAdapter((backCompatContext as OldContainerContextWithLogger).logger);
674
- const logger = ChildLogger.create(passLogger, undefined, {
675
- all: {
676
- runtimeVersion: pkgVersion,
766
+ const logger = createChildLogger({
767
+ logger: passLogger,
768
+ properties: {
769
+ all: {
770
+ runtimeVersion: pkgVersion,
771
+ },
677
772
  },
678
773
  });
679
774
 
@@ -714,8 +809,6 @@ export class ContainerRuntime
714
809
  tryFetchBlob<SerializedIdCompressorWithNoSession>(idCompressorBlobName),
715
810
  ]);
716
811
 
717
- const loadExisting = existing === true || context.existing === true;
718
-
719
812
  // read snapshot blobs needed for BlobManager to load
720
813
  const blobManagerSnapshot = await BlobManager.load(
721
814
  context.baseSnapshot?.trees[blobsTreeName],
@@ -750,9 +843,7 @@ export class ContainerRuntime
750
843
  if (loadSequenceNumberVerification === "log") {
751
844
  logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
752
845
  } else {
753
- // Call both close and dispose as closeFn implementation will no longer dispose runtime in future
754
846
  context.closeFn(error);
755
- context.disposeFn?.(error);
756
847
  }
757
848
  }
758
849
  }
@@ -765,7 +856,7 @@ export class ContainerRuntime
765
856
  idCompressor =
766
857
  serializedIdCompressor !== undefined
767
858
  ? IdCompressor.deserialize(serializedIdCompressor, createSessionId())
768
- : new IdCompressor(createSessionId(), logger);
859
+ : IdCompressor.create(logger);
769
860
  }
770
861
 
771
862
  const runtime = new containerRuntimeCtor(
@@ -789,7 +880,7 @@ export class ContainerRuntime
789
880
  },
790
881
  containerScope,
791
882
  logger,
792
- loadExisting,
883
+ existing,
793
884
  blobManagerSnapshot,
794
885
  context.storage,
795
886
  idCompressor,
@@ -798,6 +889,7 @@ export class ContainerRuntime
798
889
  initializeEntryPoint,
799
890
  );
800
891
 
892
+ await runtime.blobManager.processStashedChanges();
801
893
  // It's possible to have ops with a reference sequence number of 0. Op sequence numbers start
802
894
  // at 1, so we won't see a replayed saved op with a sequence number of 0.
803
895
  await runtime.pendingStateManager.applyStashedOpsAt(0);
@@ -808,49 +900,49 @@ export class ContainerRuntime
808
900
  return runtime;
809
901
  }
810
902
 
811
- public get options(): ILoaderOptions {
812
- return this.context.options;
813
- }
903
+ public readonly options: ILoaderOptions;
814
904
 
905
+ private readonly _getClientId: () => string | undefined;
815
906
  public get clientId(): string | undefined {
816
- return this.context.clientId;
907
+ return this._getClientId();
817
908
  }
818
909
 
819
- public get clientDetails(): IClientDetails {
820
- return this.context.clientDetails;
821
- }
910
+ public readonly clientDetails: IClientDetails;
822
911
 
823
912
  public get storage(): IDocumentStorageService {
824
913
  return this._storage;
825
914
  }
826
915
 
827
- public get reSubmitFn(): (
828
- type: ContainerMessageType,
829
- content: any,
830
- localOpMetadata: unknown,
831
- opMetadata: Record<string, unknown> | undefined,
832
- ) => void {
833
- // eslint-disable-next-line @typescript-eslint/unbound-method
834
- return this.reSubmitCore;
835
- }
836
-
837
- public get disposeFn(): (error?: ICriticalContainerError) => void {
838
- // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
839
- return this.context.disposeFn ?? this.context.closeFn;
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
840
925
  }
841
926
 
842
- public get closeFn(): (error?: ICriticalContainerError) => void {
843
- if (this._summarizer !== undefined) {
844
- // In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
845
- return this.disposeFn;
846
- }
847
-
848
- // Also call disposeFn to retain functionality of runtime being disposed on close
849
- return (error?: ICriticalContainerError) => {
850
- this.context.closeFn(error);
851
- this.context.disposeFn?.(error);
852
- };
853
- }
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;
854
946
 
855
947
  public get flushMode(): FlushMode {
856
948
  return this._flushMode;
@@ -864,8 +956,9 @@ export class ContainerRuntime
864
956
  return this.registry;
865
957
  }
866
958
 
959
+ private readonly _getAttachState: () => AttachState;
867
960
  public get attachState(): AttachState {
868
- return this.context.attachState;
961
+ return this._getAttachState();
869
962
  }
870
963
 
871
964
  public idCompressor: (IIdCompressor & IIdCompressorCore) | undefined;
@@ -964,7 +1057,6 @@ export class ContainerRuntime
964
1057
  private emitDirtyDocumentEvent = true;
965
1058
  private readonly enableOpReentryCheck: boolean;
966
1059
  private readonly disableAttachReorder: boolean | undefined;
967
- private readonly summaryStateUpdateMethod: string | undefined;
968
1060
  private readonly closeSummarizerDelayMs: number;
969
1061
  /**
970
1062
  * If true, summary generated is validate before uploading it to the server. With single commit summaries,
@@ -974,7 +1066,7 @@ export class ContainerRuntime
974
1066
  private readonly validateSummaryBeforeUpload: boolean;
975
1067
 
976
1068
  private readonly defaultTelemetrySignalSampleCount = 100;
977
- private _perfSignalData: IPerfSignalReport = {
1069
+ private readonly _perfSignalData: IPerfSignalReport = {
978
1070
  signalsLost: 0,
979
1071
  signalSequenceNumber: 0,
980
1072
  signalTimestamp: 0,
@@ -1056,11 +1148,21 @@ export class ContainerRuntime
1056
1148
  */
1057
1149
  private readonly idCompressorEnabled: boolean;
1058
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
+
1059
1161
  /**
1060
1162
  * @internal
1061
1163
  */
1062
1164
  protected constructor(
1063
- private readonly context: IContainerContext,
1165
+ context: IContainerContext,
1064
1166
  private readonly registry: IFluidDataStoreRegistry,
1065
1167
  metadata: IContainerRuntimeMetadata | undefined,
1066
1168
  electedSummarizerData: ISerializedElection | undefined,
@@ -1087,10 +1189,64 @@ export class ContainerRuntime
1087
1189
  ) {
1088
1190
  super();
1089
1191
 
1090
- this.innerDeltaManager = context.deltaManager;
1091
- 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;
1092
1245
 
1093
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
1246
+ this.mc = createChildMonitoringContext({
1247
+ logger: this.logger,
1248
+ namespace: "ContainerRuntime",
1249
+ });
1094
1250
 
1095
1251
  let loadSummaryNumber: number;
1096
1252
  // Get the container creation metadata. For new container, we initialize these. For existing containers,
@@ -1122,7 +1278,9 @@ export class ContainerRuntime
1122
1278
 
1123
1279
  this.messageAtLastSummary = metadata?.message;
1124
1280
 
1125
- 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;
1126
1284
 
1127
1285
  this.gcTombstoneEnforcementAllowed = shouldAllowGcTombstoneEnforcement(
1128
1286
  metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
@@ -1151,7 +1309,7 @@ export class ContainerRuntime
1151
1309
 
1152
1310
  const opSplitter = new OpSplitter(
1153
1311
  chunks,
1154
- this.context.submitBatchFn,
1312
+ this.submitBatchFn,
1155
1313
  disableChunking === true ? Number.POSITIVE_INFINITY : runtimeOptions.chunkSizeInBytes,
1156
1314
  runtimeOptions.maxBatchSizeInBytes,
1157
1315
  this.mc.logger,
@@ -1192,7 +1350,7 @@ export class ContainerRuntime
1192
1350
 
1193
1351
  if (
1194
1352
  runtimeOptions.flushMode === (FlushModeExperimental.Async as unknown as FlushMode) &&
1195
- context.supportedFeatures?.get("referenceSequenceNumbers") !== true
1353
+ supportedFeatures?.get("referenceSequenceNumbers") !== true
1196
1354
  ) {
1197
1355
  // The loader does not support reference sequence numbers, falling back on FlushMode.TurnBased
1198
1356
  this.mc.logger.sendErrorEvent({ eventName: "FlushModeFallback" });
@@ -1201,7 +1359,7 @@ export class ContainerRuntime
1201
1359
  this._flushMode = runtimeOptions.flushMode;
1202
1360
  }
1203
1361
 
1204
- const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
1362
+ const pendingRuntimeState = pendingLocalState as IPendingRuntimeState | undefined;
1205
1363
 
1206
1364
  const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
1207
1365
  if (
@@ -1217,12 +1375,12 @@ export class ContainerRuntime
1217
1375
  this.garbageCollector = GarbageCollector.create({
1218
1376
  runtime: this,
1219
1377
  gcOptions: this.runtimeOptions.gcOptions,
1220
- baseSnapshot: context.baseSnapshot,
1378
+ baseSnapshot,
1221
1379
  baseLogger: this.mc.logger,
1222
1380
  existing,
1223
1381
  metadata,
1224
1382
  createContainerMetadata: this.createContainerMetadata,
1225
- isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
1383
+ isSummarizerClient: this.isSummarizerClient,
1226
1384
  getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
1227
1385
  getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
1228
1386
  readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
@@ -1233,14 +1391,14 @@ export class ContainerRuntime
1233
1391
 
1234
1392
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
1235
1393
  this.summarizerNode = createRootSummarizerNodeWithGC(
1236
- ChildLogger.create(this.logger, "SummarizerNode"),
1394
+ createChildLogger({ logger: this.logger, namespace: "SummarizerNode" }),
1237
1395
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
1238
1396
  async (fullTree: boolean, trackState: boolean, telemetryContext?: ITelemetryContext) =>
1239
1397
  this.summarizeInternal(fullTree, trackState, telemetryContext),
1240
1398
  // Latest change sequence number, no changes since summary applied yet
1241
1399
  loadedFromSequenceNumber,
1242
1400
  // Summary reference sequence number, undefined if no summary yet
1243
- context.baseSnapshot ? loadedFromSequenceNumber : undefined,
1401
+ baseSnapshot !== undefined ? loadedFromSequenceNumber : undefined,
1244
1402
  {
1245
1403
  // Must set to false to prevent sending summary handle which would be pointing to
1246
1404
  // a summary with an older protocol state.
@@ -1257,14 +1415,14 @@ export class ContainerRuntime
1257
1415
  async () => this.garbageCollector.getBaseGCDetails(),
1258
1416
  );
1259
1417
 
1260
- if (context.baseSnapshot) {
1261
- this.summarizerNode.updateBaseSummaryState(context.baseSnapshot);
1418
+ if (baseSnapshot) {
1419
+ this.summarizerNode.updateBaseSummaryState(baseSnapshot);
1262
1420
  }
1263
1421
 
1264
1422
  this.dataStores = new DataStores(
1265
- getSummaryForDatastores(context.baseSnapshot, metadata),
1423
+ getSummaryForDatastores(baseSnapshot, metadata),
1266
1424
  this,
1267
- (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg),
1425
+ (attachMsg) => this.submit({ type: ContainerMessageType.Attach, contents: attachMsg }),
1268
1426
  (id: string, createParam: CreateChildSummarizerNodeParam) =>
1269
1427
  (
1270
1428
  summarizeInternal: SummarizeInternalFn,
@@ -1291,10 +1449,14 @@ export class ContainerRuntime
1291
1449
  () => this.storage,
1292
1450
  (localId: string, blobId?: string) => {
1293
1451
  if (!this.disposed) {
1294
- this.submit(ContainerMessageType.BlobAttach, undefined, undefined, {
1295
- localId,
1296
- blobId,
1297
- });
1452
+ this.submit(
1453
+ { type: ContainerMessageType.BlobAttach, contents: undefined },
1454
+ undefined,
1455
+ {
1456
+ localId,
1457
+ blobId,
1458
+ },
1459
+ );
1298
1460
  }
1299
1461
  },
1300
1462
  (blobPath: string) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"),
@@ -1305,10 +1467,10 @@ export class ContainerRuntime
1305
1467
  );
1306
1468
 
1307
1469
  this.scheduleManager = new ScheduleManager(
1308
- context.deltaManager,
1470
+ this.innerDeltaManager,
1309
1471
  this,
1310
1472
  () => this.clientId,
1311
- ChildLogger.create(this.logger, "ScheduleManager"),
1473
+ createChildLogger({ logger: this.logger, namespace: "ScheduleManager" }),
1312
1474
  );
1313
1475
 
1314
1476
  this.pendingStateManager = new PendingStateManager(
@@ -1319,8 +1481,10 @@ export class ContainerRuntime
1319
1481
  connected: () => this.connected,
1320
1482
  reSubmit: this.reSubmit.bind(this),
1321
1483
  reSubmitBatch: this.reSubmitBatch.bind(this),
1484
+ isActiveConnection: () => this.innerDeltaManager.active,
1322
1485
  },
1323
1486
  pendingRuntimeState?.pending,
1487
+ this.logger,
1324
1488
  );
1325
1489
 
1326
1490
  const disableCompression = this.mc.config.getBoolean(
@@ -1337,10 +1501,14 @@ export class ContainerRuntime
1337
1501
  const disablePartialFlush = this.mc.config.getBoolean(
1338
1502
  "Fluid.ContainerRuntime.DisablePartialFlush",
1339
1503
  );
1504
+
1505
+ const legacySendBatchFn = makeLegacySendBatchFn(this.submitFn, this.innerDeltaManager);
1506
+
1340
1507
  this.outbox = new Outbox({
1341
1508
  shouldSend: () => this.canSendOps(),
1342
1509
  pendingStateManager: this.pendingStateManager,
1343
- containerContext: this.context,
1510
+ submitBatchFn: this.submitBatchFn,
1511
+ legacySendBatchFn,
1344
1512
  compressor: new OpCompressor(this.mc.logger),
1345
1513
  splitter: opSplitter,
1346
1514
  config: {
@@ -1360,41 +1528,43 @@ export class ContainerRuntime
1360
1528
  closeContainer: this.closeFn,
1361
1529
  });
1362
1530
 
1363
- this.context.quorum.on("removeMember", (clientId: string) => {
1531
+ this._quorum = quorum;
1532
+ this._quorum.on("removeMember", (clientId: string) => {
1364
1533
  this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
1365
1534
  });
1366
1535
 
1367
- this.summaryStateUpdateMethod = this.mc.config.getString(
1368
- "Fluid.ContainerRuntime.Test.SummaryStateUpdateMethodV2",
1369
- );
1536
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1537
+ this._audience = audience!;
1538
+
1370
1539
  const closeSummarizerDelayOverride = this.mc.config.getNumber(
1371
1540
  "Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs",
1372
1541
  );
1373
1542
  this.closeSummarizerDelayMs = closeSummarizerDelayOverride ?? defaultCloseSummarizerDelayMs;
1374
1543
  this.validateSummaryBeforeUpload =
1375
- this.mc.config.getBoolean("Fluid.ContainerRuntime.Test.ValidateSummaryBeforeUpload") ??
1376
- false;
1544
+ this.mc.config.getBoolean("Fluid.Summarizer.ValidateSummaryBeforeUpload") ?? false;
1377
1545
 
1378
1546
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
1379
1547
 
1380
1548
  this.dirtyContainer =
1381
- this.context.attachState !== AttachState.Attached ||
1382
- this.pendingStateManager.hasPendingMessages();
1383
- this.context.updateDirtyContainerState(this.dirtyContainer);
1549
+ this.attachState !== AttachState.Attached || this.hasPendingMessages();
1550
+ context.updateDirtyContainerState(this.dirtyContainer);
1384
1551
 
1385
1552
  if (this.summariesDisabled) {
1386
1553
  this.mc.logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
1387
1554
  } else {
1388
- const orderedClientLogger = ChildLogger.create(this.logger, "OrderedClientElection");
1555
+ const orderedClientLogger = createChildLogger({
1556
+ logger: this.logger,
1557
+ namespace: "OrderedClientElection",
1558
+ });
1389
1559
  const orderedClientCollection = new OrderedClientCollection(
1390
1560
  orderedClientLogger,
1391
- this.context.deltaManager,
1392
- this.context.quorum,
1561
+ this.innerDeltaManager,
1562
+ this._quorum,
1393
1563
  );
1394
1564
  const orderedClientElectionForSummarizer = new OrderedClientElection(
1395
1565
  orderedClientLogger,
1396
1566
  orderedClientCollection,
1397
- electedSummarizerData ?? this.context.deltaManager.lastSequenceNumber,
1567
+ electedSummarizerData ?? this.innerDeltaManager.lastSequenceNumber,
1398
1568
  SummarizerClientElection.isClientEligible,
1399
1569
  );
1400
1570
 
@@ -1405,7 +1575,7 @@ export class ContainerRuntime
1405
1575
  this.maxOpsSinceLastSummary,
1406
1576
  );
1407
1577
 
1408
- if (this.context.clientDetails.type === summarizerClientType) {
1578
+ if (this.isSummarizerClient) {
1409
1579
  this._summarizer = new Summarizer(
1410
1580
  this /* ISummarizerRuntime */,
1411
1581
  () => this.summaryConfiguration,
@@ -1420,19 +1590,19 @@ export class ContainerRuntime
1420
1590
  () => this.innerDeltaManager.active,
1421
1591
  ),
1422
1592
  );
1423
- } else if (
1424
- SummarizerClientElection.clientDetailsPermitElection(this.context.clientDetails)
1425
- ) {
1593
+ } else if (SummarizerClientElection.clientDetailsPermitElection(this.clientDetails)) {
1426
1594
  // Only create a SummaryManager and SummarizerClientElection
1427
1595
  // if summaries are enabled and we are not the summarizer client.
1428
1596
  const defaultAction = () => {
1429
1597
  if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
1430
- this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
1598
+ this.mc.logger.sendTelemetryEvent({ eventName: "SummaryStatus:Behind" });
1431
1599
  // unregister default to no log on every op after falling behind
1432
1600
  // and register summary ack handler to re-register this handler
1433
1601
  // after successful summary
1434
1602
  this.summaryCollection.once(MessageType.SummaryAck, () => {
1435
- this.logger.sendTelemetryEvent({ eventName: "SummaryStatus:CaughtUp" });
1603
+ this.mc.logger.sendTelemetryEvent({
1604
+ eventName: "SummaryStatus:CaughtUp",
1605
+ });
1436
1606
  // we've caught up, so re-register the default action to monitor for
1437
1607
  // falling behind, and unregister ourself
1438
1608
  this.summaryCollection.on("default", defaultAction);
@@ -1449,7 +1619,7 @@ export class ContainerRuntime
1449
1619
  this, // IConnectedState
1450
1620
  this.summaryCollection,
1451
1621
  this.logger,
1452
- this.formRequestSummarizerFn(this.context.loader),
1622
+ this.formRequestSummarizerFn(loader),
1453
1623
  new Throttler(
1454
1624
  60 * 1000, // 60 sec delay window
1455
1625
  30 * 1000, // 30 sec max delay
@@ -1461,6 +1631,9 @@ export class ContainerRuntime
1461
1631
  },
1462
1632
  this.heuristicsDisabled,
1463
1633
  );
1634
+ this.summaryManager.on("summarize", (eventProps) => {
1635
+ this.emit("summarize", eventProps);
1636
+ });
1464
1637
  this.summaryManager.start();
1465
1638
  }
1466
1639
  }
@@ -1500,7 +1673,7 @@ export class ContainerRuntime
1500
1673
  ...getDeviceSpec(),
1501
1674
  });
1502
1675
 
1503
- this.logger.sendTelemetryEvent({
1676
+ this.mc.logger.sendTelemetryEvent({
1504
1677
  eventName: "ContainerLoadStats",
1505
1678
  ...this.createContainerMetadata,
1506
1679
  ...this.dataStores.containerLoadStats,
@@ -1516,18 +1689,17 @@ export class ContainerRuntime
1516
1689
  disableAttachReorder: this.disableAttachReorder,
1517
1690
  disablePartialFlush,
1518
1691
  idCompressorEnabled: this.idCompressorEnabled,
1519
- summaryStateUpdateMethod: this.summaryStateUpdateMethod,
1520
1692
  closeSummarizerDelayOverride,
1521
1693
  }),
1522
1694
  telemetryDocumentId: this.telemetryDocumentId,
1523
1695
  groupedBatchingEnabled: this.groupedBatchingEnabled,
1524
1696
  });
1525
1697
 
1526
- ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
1698
+ ReportOpPerfTelemetry(this.clientId, this.deltaManager, this.logger);
1527
1699
  BindBatchTracker(this, this.logger);
1528
1700
 
1529
1701
  this.entryPoint = new LazyPromise(async () => {
1530
- if (this.context.clientDetails.type === summarizerClientType) {
1702
+ if (this.isSummarizerClient) {
1531
1703
  assert(
1532
1704
  this._summarizer !== undefined,
1533
1705
  0x5bf /* Summarizer object is undefined in a summarizer client */,
@@ -1551,7 +1723,7 @@ export class ContainerRuntime
1551
1723
  }
1552
1724
  this._disposed = true;
1553
1725
 
1554
- this.logger.sendTelemetryEvent(
1726
+ this.mc.logger.sendTelemetryEvent(
1555
1727
  {
1556
1728
  eventName: "ContainerRuntimeDisposed",
1557
1729
  isDirty: this.isDirty,
@@ -1575,6 +1747,7 @@ export class ContainerRuntime
1575
1747
  /**
1576
1748
  * Notifies this object about the request made to the container.
1577
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
1578
1751
  */
1579
1752
  public async request(request: IRequest): Promise<IResponse> {
1580
1753
  try {
@@ -1632,7 +1805,7 @@ export class ContainerRuntime
1632
1805
  subRequest.url.startsWith("/"),
1633
1806
  0x126 /* "Expected createSubRequest url to include a leading slash" */,
1634
1807
  );
1635
- return dataStore.IFluidRouter.request(subRequest);
1808
+ return dataStore.request(subRequest);
1636
1809
  }
1637
1810
 
1638
1811
  return create404Response(request);
@@ -1680,6 +1853,7 @@ export class ContainerRuntime
1680
1853
  dataStoreContext.packagePath,
1681
1854
  request?.headers,
1682
1855
  );
1856
+
1683
1857
  return dataStoreChannel;
1684
1858
  }
1685
1859
 
@@ -1774,7 +1948,7 @@ export class ContainerRuntime
1774
1948
  this.mc.logger.sendTelemetryEvent({
1775
1949
  eventName: "ReconnectsWithNoProgress",
1776
1950
  attempts: this.consecutiveReconnects,
1777
- pendingMessages: this.pendingStateManager.pendingMessagesCount,
1951
+ pendingMessages: this.pendingMessagesCount,
1778
1952
  });
1779
1953
  }
1780
1954
 
@@ -1841,19 +2015,17 @@ export class ContainerRuntime
1841
2015
  * Parse an op's type and actual content from given serialized content
1842
2016
  * ! Note: this format needs to be in-line with what is set in the "ContainerRuntime.submit(...)" method
1843
2017
  */
1844
- private parseOpContent(serializedContent?: string): {
1845
- type: ContainerMessageType;
1846
- contents: unknown;
1847
- } {
2018
+ private parseOpContent(serializedContent?: string): ContainerRuntimeMessage {
1848
2019
  assert(serializedContent !== undefined, 0x6d5 /* content must be defined */);
1849
- const parsed = JSON.parse(serializedContent);
1850
- assert(parsed.type !== undefined, 0x6d6 /* incorrect op content format */);
1851
- 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 };
1852
2024
  }
1853
2025
 
1854
2026
  private async applyStashedOp(op: string): Promise<unknown> {
1855
2027
  // Need to parse from string for back-compat
1856
- const { type, contents } = this.parseOpContent(op);
2028
+ const { type, contents, compatDetails } = this.parseOpContent(op);
1857
2029
  switch (type) {
1858
2030
  case ContainerMessageType.FluidDataStoreOp:
1859
2031
  return this.dataStores.applyStashedOp(contents as IEnvelope);
@@ -1872,8 +2044,27 @@ export class ContainerRuntime
1872
2044
  throw new Error("chunkedOp not expected here");
1873
2045
  case ContainerMessageType.Rejoin:
1874
2046
  throw new Error("rejoin not expected here");
1875
- default:
1876
- 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
+ }
1877
2068
  }
1878
2069
  }
1879
2070
 
@@ -1887,32 +2078,6 @@ export class ContainerRuntime
1887
2078
  return;
1888
2079
  }
1889
2080
 
1890
- // If attachment blobs were added while disconnected, we need to delay
1891
- // propagation of the "connected" event until we have uploaded them to
1892
- // ensure we don't submit ops referencing a blob that has not been uploaded
1893
- // Note that the inner (non-proxy) delta manager is needed here to get the readonly information.
1894
- const connecting =
1895
- connected && !this._connected && !this.innerDeltaManager.readOnlyInfo.readonly;
1896
- if (connecting && this.blobManager.hasPendingOfflineUploads) {
1897
- assert(
1898
- !this.delayConnectClientId,
1899
- 0x392 /* Connect event delay must be canceled before subsequent connect event */,
1900
- );
1901
- assert(!!clientId, 0x393 /* Must have clientId when connecting */);
1902
- this.delayConnectClientId = clientId;
1903
- this.blobManager.onConnected().then(
1904
- () => {
1905
- // make sure we didn't reconnect before the promise resolved
1906
- if (this.delayConnectClientId === clientId && !this.disposed) {
1907
- this.delayConnectClientId = undefined;
1908
- this.setConnectionStateCore(connected, clientId);
1909
- }
1910
- },
1911
- (error) => this.closeFn(error),
1912
- );
1913
- return;
1914
- }
1915
-
1916
2081
  this.setConnectionStateCore(connected, clientId);
1917
2082
  }
1918
2083
 
@@ -1926,6 +2091,14 @@ export class ContainerRuntime
1926
2091
  // There might be no change of state due to Container calling this API after loading runtime.
1927
2092
  const changeOfState = this._connected !== connected;
1928
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
+
1929
2102
  this._connected = connected;
1930
2103
 
1931
2104
  if (!connected) {
@@ -1952,7 +2125,7 @@ export class ContainerRuntime
1952
2125
  {
1953
2126
  dataLoss: 1,
1954
2127
  attempts: this.consecutiveReconnects,
1955
- pendingMessages: this.pendingStateManager.pendingMessagesCount,
2128
+ pendingMessages: this.pendingMessagesCount,
1956
2129
  },
1957
2130
  ),
1958
2131
  );
@@ -1977,24 +2150,30 @@ export class ContainerRuntime
1977
2150
  public process(messageArg: ISequencedDocumentMessage, local: boolean) {
1978
2151
  this.verifyNotClosed();
1979
2152
 
1980
- // 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.
1981
2154
  // It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
1982
2155
  // or something different, like a system message.
1983
- const runtimeMessage = messageArg.type === MessageType.Operation;
2156
+ const modernRuntimeMessage = messageArg.type === MessageType.Operation;
1984
2157
 
1985
2158
  // Do shallow copy of message, as the processing flow will modify it.
1986
2159
  const messageCopy = { ...messageArg };
1987
2160
  for (const message of this.remoteMessageProcessor.process(messageCopy)) {
1988
- this.processCore(message, local, runtimeMessage);
2161
+ this.processCore(message, local, modernRuntimeMessage);
1989
2162
  }
1990
2163
  }
1991
2164
 
1992
2165
  private _processedClientSequenceNumber: number | undefined;
1993
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
+ */
1994
2173
  private processCore(
1995
- message: ISequencedDocumentMessage,
2174
+ message: ISequencedDocumentMessage | SequencedContainerRuntimeMessage,
1996
2175
  local: boolean,
1997
- runtimeMessage: boolean,
2176
+ modernRuntimeMessage: boolean,
1998
2177
  ) {
1999
2178
  // Surround the actual processing of the operation with messages to the schedule manager indicating
2000
2179
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
@@ -2005,8 +2184,10 @@ export class ContainerRuntime
2005
2184
 
2006
2185
  try {
2007
2186
  let localOpMetadata: unknown;
2008
- if (local && runtimeMessage && message.type !== ContainerMessageType.ChunkedOp) {
2009
- localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
2187
+ if (local && modernRuntimeMessage && message.type !== ContainerMessageType.ChunkedOp) {
2188
+ localOpMetadata = this.pendingStateManager.processPendingLocalMessage(
2189
+ message as SequencedContainerRuntimeMessage,
2190
+ );
2010
2191
  }
2011
2192
 
2012
2193
  // If there are no more pending messages after processing a local message,
@@ -2015,53 +2196,14 @@ export class ContainerRuntime
2015
2196
  this.updateDocumentDirtyState(false);
2016
2197
  }
2017
2198
 
2018
- const type = message.type as ContainerMessageType;
2019
- switch (type) {
2020
- case ContainerMessageType.Attach:
2021
- this.dataStores.processAttachMessage(message, local);
2022
- break;
2023
- case ContainerMessageType.Alias:
2024
- this.processAliasMessage(message, localOpMetadata, local);
2025
- break;
2026
- case ContainerMessageType.FluidDataStoreOp:
2027
- this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
2028
- break;
2029
- case ContainerMessageType.BlobAttach:
2030
- this.blobManager.processBlobAttachOp(message, local);
2031
- break;
2032
- case ContainerMessageType.IdAllocation:
2033
- assert(
2034
- this.idCompressor !== undefined,
2035
- 0x67c /* IdCompressor should be defined if enabled */,
2036
- );
2037
- this.idCompressor.finalizeCreationRange(message.contents as IdCreationRange);
2038
- break;
2039
- case ContainerMessageType.ChunkedOp:
2040
- case ContainerMessageType.Rejoin:
2041
- break;
2042
- default:
2043
- if (runtimeMessage) {
2044
- const error = DataProcessingError.create(
2045
- // Former assert 0x3ce
2046
- "Runtime message of unknown type",
2047
- "OpProcessing",
2048
- message,
2049
- {
2050
- local,
2051
- type: message.type,
2052
- contentType: typeof message.contents,
2053
- batch: (message.metadata as IBatchMetadata | undefined)?.batch,
2054
- compression: message.compression,
2055
- },
2056
- );
2057
- this.closeFn(error);
2058
- throw error;
2059
- }
2060
- }
2199
+ this.validateAndProcessRuntimeMessage(
2200
+ message,
2201
+ localOpMetadata,
2202
+ local,
2203
+ modernRuntimeMessage,
2204
+ );
2061
2205
 
2062
- if (runtimeMessage || this.groupedBatchingEnabled) {
2063
- this.emit("op", message, runtimeMessage);
2064
- }
2206
+ this.emit("op", message, modernRuntimeMessage);
2065
2207
 
2066
2208
  this.scheduleManager.afterOpProcessing(undefined, message);
2067
2209
 
@@ -2076,13 +2218,74 @@ export class ContainerRuntime
2076
2218
  throw e;
2077
2219
  }
2078
2220
  }
2079
-
2080
- 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(
2081
2227
  message: ISequencedDocumentMessage,
2082
2228
  localOpMetadata: unknown,
2083
2229
  local: boolean,
2084
- ) {
2085
- 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
+ }
2086
2289
  }
2087
2290
 
2088
2291
  /**
@@ -2091,7 +2294,7 @@ export class ContainerRuntime
2091
2294
  */
2092
2295
  private sendSignalTelemetryEvent(clientSignalSequenceNumber: number) {
2093
2296
  const duration = Date.now() - this._perfSignalData.signalTimestamp;
2094
- this.logger.sendPerformanceEvent({
2297
+ this.mc.logger.sendPerformanceEvent({
2095
2298
  eventName: "SignalLatency",
2096
2299
  duration,
2097
2300
  signalsLost: this._perfSignalData.signalsLost,
@@ -2119,7 +2322,7 @@ export class ContainerRuntime
2119
2322
  ) {
2120
2323
  this._perfSignalData.signalsLost++;
2121
2324
  this._perfSignalData.trackingSignalSequenceNumber = undefined;
2122
- this.logger.sendErrorEvent({
2325
+ this.mc.logger.sendErrorEvent({
2123
2326
  eventName: "SignalLost",
2124
2327
  type: envelope.contents.type,
2125
2328
  signalsLost: this._perfSignalData.signalsLost,
@@ -2147,6 +2350,12 @@ export class ContainerRuntime
2147
2350
  this.dataStores.processSignal(envelope.address, transformed, local);
2148
2351
  }
2149
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
+ */
2150
2359
  public async getRootDataStore(id: string, wait = true): Promise<IFluidRouter> {
2151
2360
  return this.getRootDataStoreChannel(id, wait);
2152
2361
  }
@@ -2222,15 +2431,30 @@ export class ContainerRuntime
2222
2431
  return result;
2223
2432
  }
2224
2433
 
2225
- public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
2226
- const internalId = uuid();
2227
- return channelToDataStore(
2228
- await this._createDataStore(pkg, internalId),
2229
- internalId,
2230
- this,
2231
- this.dataStores,
2232
- this.mc.logger,
2233
- );
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;
2234
2458
  }
2235
2459
 
2236
2460
  public createDetachedRootDataStore(
@@ -2247,25 +2471,37 @@ export class ContainerRuntime
2247
2471
  return this.dataStores.createDetachedDataStoreCore(pkg, false);
2248
2472
  }
2249
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
+ */
2250
2491
  public async _createDataStoreWithProps(
2251
2492
  pkg: string | string[],
2252
2493
  props?: any,
2253
2494
  id = uuid(),
2254
2495
  ): Promise<IDataStore> {
2255
- const fluidDataStore = await this.dataStores
2256
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
2257
- .realize();
2258
- return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
2259
- }
2260
-
2261
- private async _createDataStore(
2262
- pkg: string | string[],
2263
- id = uuid(),
2264
- props?: any,
2265
- ): Promise<IFluidDataStoreChannel> {
2266
- return this.dataStores
2267
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
2268
- .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
+ );
2269
2505
  }
2270
2506
 
2271
2507
  private canSendOps() {
@@ -2281,13 +2517,14 @@ export class ContainerRuntime
2281
2517
  return this.flushMode !== FlushMode.Immediate || this._orderSequentiallyCalls !== 0;
2282
2518
  }
2283
2519
 
2520
+ private readonly _quorum: IQuorumClients;
2284
2521
  public getQuorum(): IQuorumClients {
2285
- return this.context.quorum;
2522
+ return this._quorum;
2286
2523
  }
2287
2524
 
2525
+ private readonly _audience: IAudience;
2288
2526
  public getAudience(): IAudience {
2289
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2290
- return this.context.audience!;
2527
+ return this._audience;
2291
2528
  }
2292
2529
 
2293
2530
  /**
@@ -2298,7 +2535,7 @@ export class ContainerRuntime
2298
2535
  return this.dirtyContainer;
2299
2536
  }
2300
2537
 
2301
- private isContainerMessageDirtyable(type: ContainerMessageType, contents: any) {
2538
+ private isContainerMessageDirtyable({ type, contents }: ContainerRuntimeMessage) {
2302
2539
  // For legacy purposes, exclude the old built-in AgentScheduler from dirty consideration as a special-case.
2303
2540
  // Ultimately we should have no special-cases from the ContainerRuntime's perspective.
2304
2541
  if (type === ContainerMessageType.Attach) {
@@ -2347,12 +2584,12 @@ export class ContainerRuntime
2347
2584
  public submitSignal(type: string, content: any) {
2348
2585
  this.verifyNotClosed();
2349
2586
  const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content);
2350
- return this.context.submitSignalFn(envelope);
2587
+ return this.submitSignalFn(envelope);
2351
2588
  }
2352
2589
 
2353
2590
  public submitDataStoreSignal(address: string, type: string, content: any) {
2354
2591
  const envelope = this.createNewSignalEnvelope(address, type, content);
2355
- return this.context.submitSignalFn(envelope);
2592
+ return this.submitSignalFn(envelope);
2356
2593
  }
2357
2594
 
2358
2595
  public setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void {
@@ -2404,15 +2641,7 @@ export class ContainerRuntime
2404
2641
  return summarizeResult.summary;
2405
2642
  }
2406
2643
 
2407
- public async getAbsoluteUrl(relativeUrl: string): Promise<string | undefined> {
2408
- if (this.context.getAbsoluteUrl === undefined) {
2409
- throw new Error("Driver does not implement getAbsoluteUrl");
2410
- }
2411
- if (this.attachState !== AttachState.Attached) {
2412
- return undefined;
2413
- }
2414
- return this.context.getAbsoluteUrl(relativeUrl);
2415
- }
2644
+ public readonly getAbsoluteUrl: (relativeUrl: string) => Promise<string | undefined>;
2416
2645
 
2417
2646
  private async summarizeInternal(
2418
2647
  fullTree: boolean,
@@ -2453,7 +2682,7 @@ export class ContainerRuntime
2453
2682
  fullGC?: boolean;
2454
2683
  /** True to run GC sweep phase after the mark phase */
2455
2684
  runSweep?: boolean;
2456
- }): Promise<IRootSummaryTreeWithStats> {
2685
+ }): Promise<ISummaryTreeWithStats> {
2457
2686
  this.verifyNotClosed();
2458
2687
 
2459
2688
  const {
@@ -2476,9 +2705,8 @@ export class ContainerRuntime
2476
2705
  });
2477
2706
 
2478
2707
  try {
2479
- let gcStats: IGCStats | undefined;
2480
2708
  if (runGC) {
2481
- gcStats = await this.collectGarbage(
2709
+ await this.collectGarbage(
2482
2710
  { logger: summaryLogger, runSweep, fullGC },
2483
2711
  telemetryContext,
2484
2712
  );
@@ -2495,9 +2723,9 @@ export class ContainerRuntime
2495
2723
  0x12f /* "Container Runtime's summarize should always return a tree" */,
2496
2724
  );
2497
2725
 
2498
- return { stats, summary, gcStats };
2726
+ return { stats, summary };
2499
2727
  } finally {
2500
- this.logger.sendTelemetryEvent({
2728
+ this.mc.logger.sendTelemetryEvent({
2501
2729
  eventName: "SummarizeTelemetry",
2502
2730
  details: telemetryContext.serialize(),
2503
2731
  });
@@ -2569,7 +2797,7 @@ export class ContainerRuntime
2569
2797
  /**
2570
2798
  * After GC has run and identified nodes that are sweep ready, this is called to delete the sweep ready nodes.
2571
2799
  * @param sweepReadyRoutes - The routes of nodes that are sweep ready and should be deleted.
2572
- * @returns - The routes of nodes that were deleted.
2800
+ * @returns The routes of nodes that were deleted.
2573
2801
  */
2574
2802
  public deleteSweepReadyNodes(sweepReadyRoutes: string[]): string[] {
2575
2803
  const { dataStoreRoutes, blobManagerRoutes } =
@@ -2640,7 +2868,7 @@ export class ContainerRuntime
2640
2868
  /**
2641
2869
  * From a given list of routes, separate and return routes that belong to blob manager and data stores.
2642
2870
  * @param routes - A list of routes that can belong to data stores or blob manager.
2643
- * @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
2644
2872
  * for data stores.
2645
2873
  */
2646
2874
  private getDataStoreAndBlobManagerRoutes(routes: string[]) {
@@ -2696,12 +2924,15 @@ export class ContainerRuntime
2696
2924
  * @param options - options controlling how the summary is generated or submitted
2697
2925
  */
2698
2926
  public async submitSummary(options: ISubmitSummaryOptions): Promise<SubmitSummaryResult> {
2699
- const { fullTree = false, refreshLatestAck, summaryLogger } = options;
2927
+ const { fullTree = false, finalAttempt = false, refreshLatestAck, summaryLogger } = options;
2700
2928
  // The summary number for this summary. This will be updated during the summary process, so get it now and
2701
2929
  // use it for all events logged during this summary.
2702
2930
  const summaryNumber = this.nextSummaryNumber;
2703
- const summaryNumberLogger = ChildLogger.create(summaryLogger, undefined, {
2704
- all: { summaryNumber },
2931
+ const summaryNumberLogger = createChildLogger({
2932
+ logger: summaryLogger,
2933
+ properties: {
2934
+ all: { summaryNumber },
2935
+ },
2705
2936
  });
2706
2937
 
2707
2938
  assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
@@ -2709,7 +2940,10 @@ export class ContainerRuntime
2709
2940
  let latestSnapshotVersionId: string | undefined;
2710
2941
  if (refreshLatestAck) {
2711
2942
  const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(
2712
- ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }),
2943
+ createChildLogger({
2944
+ logger: summaryNumberLogger,
2945
+ properties: { all: { safeSummary: true } },
2946
+ }),
2713
2947
  );
2714
2948
  const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
2715
2949
  latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
@@ -2718,6 +2952,50 @@ export class ContainerRuntime
2718
2952
  await this.waitForDeltaManagerToCatchup(latestSnapshotRefSeq, summaryNumberLogger);
2719
2953
  }
2720
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
+
2721
2999
  const shouldPauseInboundSignal =
2722
3000
  this.mc.config.getBoolean(
2723
3001
  "Fluid.ContainerRuntime.SubmitSummary.disableInboundSignalPause",
@@ -2788,7 +3066,7 @@ export class ContainerRuntime
2788
3066
  }
2789
3067
 
2790
3068
  const trace = Trace.start();
2791
- let summarizeResult: IRootSummaryTreeWithStats;
3069
+ let summarizeResult: ISummaryTreeWithStats;
2792
3070
  // If the GC state needs to be reset, we need to force a full tree summary and update the unreferenced
2793
3071
  // state of all the nodes.
2794
3072
  const forcedFullTree = this.garbageCollector.summaryStateNeedsReset;
@@ -2807,6 +3085,38 @@ export class ContainerRuntime
2807
3085
  error,
2808
3086
  };
2809
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
+
2810
3120
  const { summary: summaryTree, stats: partialStats } = summarizeResult;
2811
3121
 
2812
3122
  // Now that we have generated the summary, update the message at last summary to the last message processed.
@@ -2843,21 +3153,6 @@ export class ContainerRuntime
2843
3153
  forcedFullTree,
2844
3154
  } as const;
2845
3155
 
2846
- // If validateSummaryBeforeUpload is true, validate that the summary generated by the summarizer nodes is
2847
- // correct before this summary is uploaded.
2848
- if (this.validateSummaryBeforeUpload) {
2849
- const validateResult = this.summarizerNode.validateSummary();
2850
- if (!validateResult.success) {
2851
- const { success, ...loggingProps } = validateResult;
2852
- const error = new RetriableSummaryError(
2853
- validateResult.reason,
2854
- validateResult.retryAfterSeconds,
2855
- { ...loggingProps },
2856
- );
2857
- return { stage: "base", ...generateSummaryData, error };
2858
- }
2859
- }
2860
-
2861
3156
  continueResult = checkContinue();
2862
3157
  if (!continueResult.continue) {
2863
3158
  return { stage: "generate", ...generateSummaryData, error: continueResult.error };
@@ -2881,7 +3176,7 @@ export class ContainerRuntime
2881
3176
  } else if (lastAck === undefined) {
2882
3177
  summaryContext = {
2883
3178
  proposalHandle: undefined,
2884
- ackHandle: this.context.getLoadedFromVersion()?.id,
3179
+ ackHandle: this.loadedFromVersionId,
2885
3180
  referenceSequenceNumber: summaryRefSeqNum,
2886
3181
  };
2887
3182
  } else {
@@ -2960,8 +3255,78 @@ export class ContainerRuntime
2960
3255
  }
2961
3256
  }
2962
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
+
2963
3328
  private hasPendingMessages() {
2964
- return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
3329
+ return this.pendingMessagesCount !== 0;
2965
3330
  }
2966
3331
 
2967
3332
  private updateDocumentDirtyState(dirty: boolean) {
@@ -2982,7 +3347,6 @@ export class ContainerRuntime
2982
3347
  this.dirtyContainer = dirty;
2983
3348
  if (this.emitDirtyDocumentEvent) {
2984
3349
  this.emit(dirty ? "dirty" : "saved");
2985
- this.context.updateDirtyContainerState(dirty);
2986
3350
  }
2987
3351
  }
2988
3352
 
@@ -2995,7 +3359,10 @@ export class ContainerRuntime
2995
3359
  address: id,
2996
3360
  contents,
2997
3361
  };
2998
- this.submit(ContainerMessageType.FluidDataStoreOp, envelope, localOpMetadata);
3362
+ this.submit(
3363
+ { type: ContainerMessageType.FluidDataStoreOp, contents: envelope },
3364
+ localOpMetadata,
3365
+ );
2999
3366
  }
3000
3367
 
3001
3368
  public submitDataStoreAliasOp(contents: any, localOpMetadata: unknown): void {
@@ -3004,12 +3371,15 @@ export class ContainerRuntime
3004
3371
  throw new UsageError("malformedDataStoreAliasMessage");
3005
3372
  }
3006
3373
 
3007
- this.submit(ContainerMessageType.Alias, contents, localOpMetadata);
3374
+ this.submit({ type: ContainerMessageType.Alias, contents }, localOpMetadata);
3008
3375
  }
3009
3376
 
3010
- public async uploadBlob(blob: ArrayBufferLike): Promise<IFluidHandle<ArrayBufferLike>> {
3377
+ public async uploadBlob(
3378
+ blob: ArrayBufferLike,
3379
+ signal?: AbortSignal,
3380
+ ): Promise<IFluidHandle<ArrayBufferLike>> {
3011
3381
  this.verifyNotClosed();
3012
- return this.blobManager.createBlob(blob);
3382
+ return this.blobManager.createBlob(blob, signal);
3013
3383
  }
3014
3384
 
3015
3385
  private maybeSubmitIdAllocationOp(type: ContainerMessageType) {
@@ -3023,7 +3393,7 @@ export class ContainerRuntime
3023
3393
  );
3024
3394
  idRange = this.idCompressor.takeNextCreationRange();
3025
3395
  // Don't include the idRange if there weren't any Ids allocated
3026
- idRange = idRange?.ids?.first !== undefined ? idRange : undefined;
3396
+ idRange = idRange?.ids !== undefined ? idRange : undefined;
3027
3397
  }
3028
3398
 
3029
3399
  if (idRange !== undefined) {
@@ -3047,8 +3417,7 @@ export class ContainerRuntime
3047
3417
  }
3048
3418
 
3049
3419
  private submit(
3050
- type: ContainerMessageType,
3051
- contents: any,
3420
+ containerRuntimeMessage: ContainerRuntimeMessage,
3052
3421
  localOpMetadata: unknown = undefined,
3053
3422
  metadata: Record<string, unknown> | undefined = undefined,
3054
3423
  ): void {
@@ -3061,17 +3430,18 @@ export class ContainerRuntime
3061
3430
  0x132 /* "sending ops in detached container" */,
3062
3431
  );
3063
3432
 
3064
- const serializedContent = JSON.stringify({ type, contents });
3433
+ const serializedContent = JSON.stringify(containerRuntimeMessage);
3065
3434
 
3066
3435
  // Note that the real (non-proxy) delta manager is used here to get the readonly info. This is because
3067
3436
  // container runtime's ability to submit ops depend on the actual readonly state of the delta manager.
3068
3437
  if (this.innerDeltaManager.readOnlyInfo.readonly) {
3069
- this.logger.sendTelemetryEvent({
3438
+ this.mc.logger.sendTelemetryEvent({
3070
3439
  eventName: "SubmitOpInReadonly",
3071
3440
  connected: this.connected,
3072
3441
  });
3073
3442
  }
3074
3443
 
3444
+ const type = containerRuntimeMessage.type;
3075
3445
  const message: BatchMessage = {
3076
3446
  contents: serializedContent,
3077
3447
  type,
@@ -3130,7 +3500,7 @@ export class ContainerRuntime
3130
3500
  throw error;
3131
3501
  }
3132
3502
 
3133
- if (this.isContainerMessageDirtyable(type, contents)) {
3503
+ if (this.isContainerMessageDirtyable(containerRuntimeMessage)) {
3134
3504
  this.updateDocumentDirtyState(true);
3135
3505
  }
3136
3506
  }
@@ -3186,9 +3556,9 @@ export class ContainerRuntime
3186
3556
  assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
3187
3557
 
3188
3558
  // back-compat: ADO #1385: Make this call unconditional in the future
3189
- return this.context.submitSummaryFn !== undefined
3190
- ? this.context.submitSummaryFn(contents, referenceSequenceNumber)
3191
- : this.context.submitFn(MessageType.Summarize, contents, false);
3559
+ return this.submitSummaryFn !== undefined
3560
+ ? this.submitSummaryFn(contents, referenceSequenceNumber)
3561
+ : this.submitFn(MessageType.Summarize, contents, false);
3192
3562
  }
3193
3563
 
3194
3564
  /**
@@ -3243,38 +3613,38 @@ export class ContainerRuntime
3243
3613
 
3244
3614
  private reSubmit(message: IPendingBatchMessage) {
3245
3615
  // Need to parse from string for back-compat
3246
- const { contents, type } = this.parseOpContent(message.content);
3247
- this.reSubmitCore(type, contents, message.localOpMetadata, message.opMetadata);
3616
+ const containerRuntimeMessage = this.parseOpContent(message.content);
3617
+ this.reSubmitCore(containerRuntimeMessage, message.localOpMetadata, message.opMetadata);
3248
3618
  }
3249
3619
 
3250
3620
  /**
3251
3621
  * Finds the right store and asks it to resubmit the message. This typically happens when we
3252
3622
  * reconnect and there are pending messages.
3253
- * @param content - The content of the original message.
3623
+ * @param message - The original ContainerRuntimeMessage.
3254
3624
  * @param localOpMetadata - The local metadata associated with the original message.
3255
3625
  */
3256
3626
  private reSubmitCore(
3257
- type: ContainerMessageType,
3258
- content: any,
3627
+ message: ContainerRuntimeMessage,
3259
3628
  localOpMetadata: unknown,
3260
3629
  opMetadata: Record<string, unknown> | undefined,
3261
3630
  ) {
3262
- switch (type) {
3631
+ const contents = message.contents;
3632
+ switch (message.type) {
3263
3633
  case ContainerMessageType.FluidDataStoreOp:
3264
3634
  // For Operations, call resubmitDataStoreOp which will find the right store
3265
3635
  // and trigger resubmission on it.
3266
- this.dataStores.resubmitDataStoreOp(content, localOpMetadata);
3636
+ this.dataStores.resubmitDataStoreOp(contents, localOpMetadata);
3267
3637
  break;
3268
3638
  case ContainerMessageType.Attach:
3269
3639
  case ContainerMessageType.Alias:
3270
- this.submit(type, content, localOpMetadata);
3640
+ this.submit(message, localOpMetadata);
3271
3641
  break;
3272
3642
  case ContainerMessageType.IdAllocation:
3273
3643
  // Remove the stashedState from the op if it's a stashed op
3274
- if (content.stashedState !== undefined) {
3275
- delete content.stashedState;
3644
+ if (contents.stashedState !== undefined) {
3645
+ delete contents.stashedState;
3276
3646
  }
3277
- this.submit(type, content, localOpMetadata);
3647
+ this.submit(message, localOpMetadata);
3278
3648
  break;
3279
3649
  case ContainerMessageType.ChunkedOp:
3280
3650
  throw new Error(`chunkedOp not expected here`);
@@ -3282,10 +3652,33 @@ export class ContainerRuntime
3282
3652
  this.blobManager.reSubmit(opMetadata);
3283
3653
  break;
3284
3654
  case ContainerMessageType.Rejoin:
3285
- this.submit(type, content);
3655
+ this.submit(message);
3286
3656
  break;
3287
- default:
3288
- 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
+ }
3289
3682
  }
3290
3683
  }
3291
3684
 
@@ -3299,6 +3692,7 @@ export class ContainerRuntime
3299
3692
  this.dataStores.rollbackDataStoreOp(contents as IEnvelope, localOpMetadata);
3300
3693
  break;
3301
3694
  default:
3695
+ // Don't check message.compatDetails because this is for rolling back a local op so the type will be known
3302
3696
  throw new Error(`Can't rollback ${type}`);
3303
3697
  }
3304
3698
  }
@@ -3326,12 +3720,22 @@ export class ContainerRuntime
3326
3720
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
3327
3721
  public async refreshLatestSummaryAck(options: IRefreshSummaryAckOptions) {
3328
3722
  const { proposalHandle, ackHandle, summaryRefSeq, summaryLogger } = options;
3723
+ // proposalHandle is always passed from RunningSummarizer.
3724
+ assert(proposalHandle !== undefined, 0x766 /* proposalHandle should be available */);
3329
3725
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
3330
- // The call to fetch the snapshot is very expensive and not always needed.
3331
- // It should only be done by the summarizerNode, if required.
3332
- // When fetching from storage we will always get the latest version and do not use the ackHandle.
3333
- const fetchLatestSnapshot: () => Promise<IFetchSnapshotResult> = async () => {
3334
- 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(
3335
3739
  summaryLogger,
3336
3740
  {
3337
3741
  eventName: "RefreshLatestSummaryAckFetch",
@@ -3339,27 +3743,9 @@ export class ContainerRuntime
3339
3743
  targetSequenceNumber: summaryRefSeq,
3340
3744
  },
3341
3745
  readAndParseBlob,
3746
+ null,
3342
3747
  );
3343
3748
 
3344
- /**
3345
- * back-compat - Older loaders and drivers (pre 2.0.0-internal.1.4) don't have fetchSource as a param in the
3346
- * getVersions API. So, they will not fetch the latest snapshot from network in the previous fetch call. For
3347
- * these scenarios, fetch the snapshot corresponding to the ack handle to have the same behavior before the
3348
- * change that started fetching latest snapshot always.
3349
- */
3350
- if (fetchResult.latestSnapshotRefSeq < summaryRefSeq) {
3351
- fetchResult = await this.fetchSnapshotFromStorage(
3352
- summaryLogger,
3353
- {
3354
- eventName: "RefreshLatestSummaryAckFetchBackCompat",
3355
- ackHandle,
3356
- targetSequenceNumber: summaryRefSeq,
3357
- },
3358
- readAndParseBlob,
3359
- ackHandle,
3360
- );
3361
- }
3362
-
3363
3749
  /**
3364
3750
  * If the fetched snapshot is older than the one for which the ack was received, close the container.
3365
3751
  * This should never happen because an ack should be sent after the latest summary is updated in the server.
@@ -3381,33 +3767,16 @@ export class ContainerRuntime
3381
3767
  fetchedSnapshotRefSeq: fetchResult.latestSnapshotRefSeq,
3382
3768
  },
3383
3769
  );
3384
- this.closeFn(error);
3770
+ this.disposeFn(error);
3385
3771
  throw error;
3386
3772
  }
3387
3773
 
3388
- // In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
3389
- // wait for the delta manager to catch up before refreshing the latest Summary.
3390
- await this.waitForDeltaManagerToCatchup(
3391
- fetchResult.latestSnapshotRefSeq,
3392
- summaryLogger,
3393
- );
3394
-
3395
- return {
3396
- snapshotTree: fetchResult.snapshotTree,
3397
- snapshotRefSeq: fetchResult.latestSnapshotRefSeq,
3398
- };
3399
- };
3400
-
3401
- const result = await this.summarizerNode.refreshLatestSummary(
3402
- proposalHandle,
3403
- summaryRefSeq,
3404
- fetchLatestSnapshot,
3405
- readAndParseBlob,
3406
- summaryLogger,
3407
- );
3774
+ await this.closeStaleSummarizer("RefreshLatestSummaryAckFetch");
3775
+ return;
3776
+ }
3408
3777
 
3409
3778
  // Notify the garbage collector so it can update its latest summary state.
3410
- await this.garbageCollector.refreshLatestSummary(proposalHandle, result, readAndParseBlob);
3779
+ await this.garbageCollector.refreshLatestSummary(result);
3411
3780
  }
3412
3781
 
3413
3782
  /**
@@ -3420,51 +3789,49 @@ export class ContainerRuntime
3420
3789
  summaryLogger: ITelemetryLoggerExt,
3421
3790
  ): Promise<{ latestSnapshotRefSeq: number; latestSnapshotVersionId: string | undefined }> {
3422
3791
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
3423
- const { snapshotTree, versionId, latestSnapshotRefSeq } =
3424
- await this.fetchLatestSnapshotFromStorage(
3425
- summaryLogger,
3426
- {
3427
- eventName: "RefreshLatestSummaryFromServerFetch",
3428
- },
3429
- readAndParseBlob,
3430
- );
3431
- const fetchLatestSnapshot: IFetchSnapshotResult = {
3432
- snapshotTree,
3433
- snapshotRefSeq: latestSnapshotRefSeq,
3434
- };
3435
- const result = await this.summarizerNode.refreshLatestSummary(
3436
- undefined /* proposalHandle */,
3437
- latestSnapshotRefSeq,
3438
- async () => fetchLatestSnapshot,
3439
- readAndParseBlob,
3792
+ const { versionId, latestSnapshotRefSeq } = await this.fetchSnapshotFromStorage(
3440
3793
  summaryLogger,
3441
- );
3442
-
3443
- // Notify the garbage collector so it can update its latest summary state.
3444
- await this.garbageCollector.refreshLatestSummary(
3445
- undefined /* proposalHandle */,
3446
- result,
3794
+ {
3795
+ eventName: "RefreshLatestSummaryFromServerFetch",
3796
+ },
3447
3797
  readAndParseBlob,
3798
+ null,
3448
3799
  );
3449
3800
 
3801
+ await this.closeStaleSummarizer("RefreshLatestSummaryFromServerFetch");
3802
+
3450
3803
  return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
3451
3804
  }
3452
3805
 
3453
- private async fetchLatestSnapshotFromStorage(
3454
- logger: ITelemetryLoggerExt,
3455
- event: ITelemetryGenericEvent,
3456
- readAndParseBlob: ReadAndParseBlob,
3457
- ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; latestSnapshotRefSeq: number }> {
3458
- 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();
3459
3821
  }
3460
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
+ */
3461
3828
  private async fetchSnapshotFromStorage(
3462
3829
  logger: ITelemetryLoggerExt,
3463
3830
  event: ITelemetryGenericEvent,
3464
3831
  readAndParseBlob: ReadAndParseBlob,
3465
3832
  versionId: string | null,
3466
3833
  ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; latestSnapshotRefSeq: number }> {
3467
- const snapshotResults = await PerformanceEvent.timedExecAsync(
3834
+ return PerformanceEvent.timedExecAsync(
3468
3835
  logger,
3469
3836
  event,
3470
3837
  async (perfEvent: {
@@ -3510,77 +3877,75 @@ export class ContainerRuntime
3510
3877
  };
3511
3878
  },
3512
3879
  );
3513
-
3514
- // We choose to close the summarizer after the snapshot cache is updated to avoid
3515
- // situations which the main client (which is likely to be re-elected as the leader again)
3516
- // loads the summarizer from cache.
3517
- if (this.summaryStateUpdateMethod === "restart") {
3518
- const error = new GenericError("Restarting summarizer instead of refreshing");
3519
-
3520
- this.mc.logger.sendTelemetryEvent(
3521
- {
3522
- ...event,
3523
- eventName: "ClosingSummarizerOnSummaryStale",
3524
- codePath: event.eventName,
3525
- message: "Stopping fetch from storage",
3526
- versionId: versionId != null ? versionId : undefined,
3527
- closeSummarizerDelayMs: this.closeSummarizerDelayMs,
3528
- },
3529
- error,
3530
- );
3531
-
3532
- // Delay 10 seconds before restarting summarizer to prevent the summarizer from restarting too frequently.
3533
- await delay(this.closeSummarizerDelayMs);
3534
- this._summarizer?.stop("latestSummaryStateStale");
3535
- this.closeFn();
3536
- throw error;
3537
- }
3538
-
3539
- return snapshotResults;
3540
3880
  }
3541
3881
 
3542
3882
  public notifyAttaching() {} // do nothing (deprecated method)
3543
3883
 
3544
- public getPendingLocalState(): unknown {
3545
- if (this._orderSequentiallyCalls !== 0) {
3546
- throw new UsageError("can't get state during orderSequentially");
3547
- }
3548
- // Flush pending batch.
3549
- // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
3550
- // to close current batch.
3551
- 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();
3552
3910
 
3553
- return {
3554
- pending: this.pendingStateManager.getLocalState(),
3555
- pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
3556
- };
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
+ );
3557
3922
  }
3558
3923
 
3559
- public readonly summarizeOnDemand: ISummarizer["summarizeOnDemand"] = (...args) => {
3560
- if (this.clientDetails.type === summarizerClientType) {
3561
- return this.summarizer.summarizeOnDemand(...args);
3924
+ public summarizeOnDemand(options: IOnDemandSummarizeOptions): ISummarizeResults {
3925
+ if (this.isSummarizerClient) {
3926
+ return this.summarizer.summarizeOnDemand(options);
3562
3927
  } else if (this.summaryManager !== undefined) {
3563
- return this.summaryManager.summarizeOnDemand(...args);
3928
+ return this.summaryManager.summarizeOnDemand(options);
3564
3929
  } else {
3565
3930
  // If we're not the summarizer, and we don't have a summaryManager, we expect that
3566
3931
  // disableSummaries is turned on. We are throwing instead of returning a failure here,
3567
3932
  // because it is a misuse of the API rather than an expected failure.
3568
3933
  throw new UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
3569
3934
  }
3570
- };
3935
+ }
3571
3936
 
3572
- public readonly enqueueSummarize: ISummarizer["enqueueSummarize"] = (...args) => {
3573
- if (this.clientDetails.type === summarizerClientType) {
3574
- return this.summarizer.enqueueSummarize(...args);
3937
+ public enqueueSummarize(options: IEnqueueSummarizeOptions): EnqueueSummarizeResult {
3938
+ if (this.isSummarizerClient) {
3939
+ return this.summarizer.enqueueSummarize(options);
3575
3940
  } else if (this.summaryManager !== undefined) {
3576
- return this.summaryManager.enqueueSummarize(...args);
3941
+ return this.summaryManager.enqueueSummarize(options);
3577
3942
  } else {
3578
3943
  // If we're not the summarizer, and we don't have a summaryManager, we expect that
3579
3944
  // generateSummaries is turned off. We are throwing instead of returning a failure here,
3580
3945
  // because it is a misuse of the API rather than an expected failure.
3581
3946
  throw new UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
3582
3947
  }
3583
- };
3948
+ }
3584
3949
 
3585
3950
  /**
3586
3951
  * * Forms a function that will request a Summarizer.