@fluidframework/container-runtime 2.0.0-internal.3.0.5 → 2.0.0-internal.3.1.1

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 (329) hide show
  1. package/.eslintrc.js +19 -19
  2. package/.mocharc.js +2 -2
  3. package/api-extractor.json +2 -2
  4. package/dist/batchTracker.d.ts.map +1 -1
  5. package/dist/batchTracker.js +2 -1
  6. package/dist/batchTracker.js.map +1 -1
  7. package/dist/blobManager.d.ts +9 -2
  8. package/dist/blobManager.d.ts.map +1 -1
  9. package/dist/blobManager.js +80 -33
  10. package/dist/blobManager.js.map +1 -1
  11. package/dist/connectionTelemetry.d.ts.map +1 -1
  12. package/dist/connectionTelemetry.js +11 -9
  13. package/dist/connectionTelemetry.js.map +1 -1
  14. package/dist/containerHandleContext.d.ts.map +1 -1
  15. package/dist/containerHandleContext.js +3 -1
  16. package/dist/containerHandleContext.js.map +1 -1
  17. package/dist/containerRuntime.d.ts +11 -1
  18. package/dist/containerRuntime.d.ts.map +1 -1
  19. package/dist/containerRuntime.js +116 -72
  20. package/dist/containerRuntime.js.map +1 -1
  21. package/dist/dataStore.d.ts.map +1 -1
  22. package/dist/dataStore.js +11 -9
  23. package/dist/dataStore.js.map +1 -1
  24. package/dist/dataStoreContext.d.ts +18 -13
  25. package/dist/dataStoreContext.d.ts.map +1 -1
  26. package/dist/dataStoreContext.js +68 -55
  27. package/dist/dataStoreContext.js.map +1 -1
  28. package/dist/dataStoreContexts.d.ts.map +1 -1
  29. package/dist/dataStoreContexts.js +7 -3
  30. package/dist/dataStoreContexts.js.map +1 -1
  31. package/dist/dataStoreRegistry.d.ts.map +1 -1
  32. package/dist/dataStoreRegistry.js +3 -1
  33. package/dist/dataStoreRegistry.js.map +1 -1
  34. package/dist/dataStores.d.ts +26 -1
  35. package/dist/dataStores.d.ts.map +1 -1
  36. package/dist/dataStores.js +103 -18
  37. package/dist/dataStores.js.map +1 -1
  38. package/dist/deltaScheduler.d.ts.map +1 -1
  39. package/dist/deltaScheduler.js +8 -3
  40. package/dist/deltaScheduler.js.map +1 -1
  41. package/dist/garbageCollection.d.ts +33 -14
  42. package/dist/garbageCollection.d.ts.map +1 -1
  43. package/dist/garbageCollection.js +178 -92
  44. package/dist/garbageCollection.js.map +1 -1
  45. package/dist/garbageCollectionConstants.d.ts +1 -0
  46. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  47. package/dist/garbageCollectionConstants.js +4 -1
  48. package/dist/garbageCollectionConstants.js.map +1 -1
  49. package/dist/garbageCollectionHelpers.d.ts +26 -0
  50. package/dist/garbageCollectionHelpers.d.ts.map +1 -0
  51. package/dist/garbageCollectionHelpers.js +45 -0
  52. package/dist/garbageCollectionHelpers.js.map +1 -0
  53. package/dist/gcSweepReadyUsageDetection.d.ts +5 -5
  54. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  55. package/dist/gcSweepReadyUsageDetection.js +14 -10
  56. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  57. package/dist/index.d.ts +2 -2
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/opLifecycle/batchManager.d.ts +5 -5
  61. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  62. package/dist/opLifecycle/batchManager.js +19 -12
  63. package/dist/opLifecycle/batchManager.js.map +1 -1
  64. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  65. package/dist/opLifecycle/definitions.js.map +1 -1
  66. package/dist/opLifecycle/index.d.ts.map +1 -1
  67. package/dist/opLifecycle/index.js.map +1 -1
  68. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  69. package/dist/opLifecycle/opCompressor.js.map +1 -1
  70. package/dist/opLifecycle/opDecompressor.d.ts +0 -4
  71. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  72. package/dist/opLifecycle/opDecompressor.js +7 -43
  73. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  74. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  75. package/dist/opLifecycle/opSplitter.js +4 -1
  76. package/dist/opLifecycle/opSplitter.js.map +1 -1
  77. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  78. package/dist/opLifecycle/outbox.js +20 -19
  79. package/dist/opLifecycle/outbox.js.map +1 -1
  80. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  81. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  82. package/dist/opProperties.d.ts.map +1 -1
  83. package/dist/opProperties.js +1 -3
  84. package/dist/opProperties.js.map +1 -1
  85. package/dist/orderedClientElection.d.ts.map +1 -1
  86. package/dist/orderedClientElection.js +10 -4
  87. package/dist/orderedClientElection.js.map +1 -1
  88. package/dist/packageVersion.d.ts +1 -1
  89. package/dist/packageVersion.js +1 -1
  90. package/dist/packageVersion.js.map +1 -1
  91. package/dist/pendingStateManager.d.ts +7 -0
  92. package/dist/pendingStateManager.d.ts.map +1 -1
  93. package/dist/pendingStateManager.js +7 -4
  94. package/dist/pendingStateManager.js.map +1 -1
  95. package/dist/runWhileConnectedCoordinator.d.ts.map +1 -1
  96. package/dist/runWhileConnectedCoordinator.js.map +1 -1
  97. package/dist/runningSummarizer.d.ts.map +1 -1
  98. package/dist/runningSummarizer.js +34 -21
  99. package/dist/runningSummarizer.js.map +1 -1
  100. package/dist/scheduleManager.d.ts.map +1 -1
  101. package/dist/scheduleManager.js +3 -2
  102. package/dist/scheduleManager.js.map +1 -1
  103. package/dist/serializedSnapshotStorage.d.ts +2 -2
  104. package/dist/serializedSnapshotStorage.d.ts.map +1 -1
  105. package/dist/serializedSnapshotStorage.js +5 -3
  106. package/dist/serializedSnapshotStorage.js.map +1 -1
  107. package/dist/summarizer.d.ts +2 -2
  108. package/dist/summarizer.d.ts.map +1 -1
  109. package/dist/summarizer.js +37 -17
  110. package/dist/summarizer.js.map +1 -1
  111. package/dist/summarizerClientElection.d.ts.map +1 -1
  112. package/dist/summarizerClientElection.js.map +1 -1
  113. package/dist/summarizerHandle.d.ts.map +1 -1
  114. package/dist/summarizerHandle.js.map +1 -1
  115. package/dist/summarizerHeuristics.d.ts.map +1 -1
  116. package/dist/summarizerHeuristics.js +6 -9
  117. package/dist/summarizerHeuristics.js.map +1 -1
  118. package/dist/summarizerTypes.d.ts +21 -21
  119. package/dist/summarizerTypes.d.ts.map +1 -1
  120. package/dist/summarizerTypes.js.map +1 -1
  121. package/dist/summaryCollection.d.ts.map +1 -1
  122. package/dist/summaryCollection.js +18 -8
  123. package/dist/summaryCollection.js.map +1 -1
  124. package/dist/summaryFormat.d.ts +5 -2
  125. package/dist/summaryFormat.d.ts.map +1 -1
  126. package/dist/summaryFormat.js +18 -10
  127. package/dist/summaryFormat.js.map +1 -1
  128. package/dist/summaryGenerator.d.ts.map +1 -1
  129. package/dist/summaryGenerator.js +35 -16
  130. package/dist/summaryGenerator.js.map +1 -1
  131. package/dist/summaryManager.d.ts.map +1 -1
  132. package/dist/summaryManager.js +21 -9
  133. package/dist/summaryManager.js.map +1 -1
  134. package/dist/throttler.d.ts +2 -2
  135. package/dist/throttler.d.ts.map +1 -1
  136. package/dist/throttler.js +4 -4
  137. package/dist/throttler.js.map +1 -1
  138. package/garbageCollection.md +15 -2
  139. package/lib/batchTracker.d.ts.map +1 -1
  140. package/lib/batchTracker.js +2 -1
  141. package/lib/batchTracker.js.map +1 -1
  142. package/lib/blobManager.d.ts +9 -2
  143. package/lib/blobManager.d.ts.map +1 -1
  144. package/lib/blobManager.js +82 -35
  145. package/lib/blobManager.js.map +1 -1
  146. package/lib/connectionTelemetry.d.ts.map +1 -1
  147. package/lib/connectionTelemetry.js +11 -9
  148. package/lib/connectionTelemetry.js.map +1 -1
  149. package/lib/containerHandleContext.d.ts.map +1 -1
  150. package/lib/containerHandleContext.js +3 -1
  151. package/lib/containerHandleContext.js.map +1 -1
  152. package/lib/containerRuntime.d.ts +11 -1
  153. package/lib/containerRuntime.d.ts.map +1 -1
  154. package/lib/containerRuntime.js +122 -78
  155. package/lib/containerRuntime.js.map +1 -1
  156. package/lib/dataStore.d.ts.map +1 -1
  157. package/lib/dataStore.js +11 -9
  158. package/lib/dataStore.js.map +1 -1
  159. package/lib/dataStoreContext.d.ts +18 -13
  160. package/lib/dataStoreContext.d.ts.map +1 -1
  161. package/lib/dataStoreContext.js +71 -58
  162. package/lib/dataStoreContext.js.map +1 -1
  163. package/lib/dataStoreContexts.d.ts.map +1 -1
  164. package/lib/dataStoreContexts.js +7 -3
  165. package/lib/dataStoreContexts.js.map +1 -1
  166. package/lib/dataStoreRegistry.d.ts.map +1 -1
  167. package/lib/dataStoreRegistry.js +3 -1
  168. package/lib/dataStoreRegistry.js.map +1 -1
  169. package/lib/dataStores.d.ts +26 -1
  170. package/lib/dataStores.d.ts.map +1 -1
  171. package/lib/dataStores.js +109 -24
  172. package/lib/dataStores.js.map +1 -1
  173. package/lib/deltaScheduler.d.ts.map +1 -1
  174. package/lib/deltaScheduler.js +9 -4
  175. package/lib/deltaScheduler.js.map +1 -1
  176. package/lib/garbageCollection.d.ts +33 -14
  177. package/lib/garbageCollection.d.ts.map +1 -1
  178. package/lib/garbageCollection.js +180 -94
  179. package/lib/garbageCollection.js.map +1 -1
  180. package/lib/garbageCollectionConstants.d.ts +1 -0
  181. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  182. package/lib/garbageCollectionConstants.js +3 -0
  183. package/lib/garbageCollectionConstants.js.map +1 -1
  184. package/lib/garbageCollectionHelpers.d.ts +26 -0
  185. package/lib/garbageCollectionHelpers.d.ts.map +1 -0
  186. package/lib/garbageCollectionHelpers.js +40 -0
  187. package/lib/garbageCollectionHelpers.js.map +1 -0
  188. package/lib/gcSweepReadyUsageDetection.d.ts +5 -5
  189. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  190. package/lib/gcSweepReadyUsageDetection.js +14 -10
  191. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  192. package/lib/index.d.ts +2 -2
  193. package/lib/index.d.ts.map +1 -1
  194. package/lib/index.js +1 -1
  195. package/lib/index.js.map +1 -1
  196. package/lib/opLifecycle/batchManager.d.ts +5 -5
  197. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  198. package/lib/opLifecycle/batchManager.js +19 -12
  199. package/lib/opLifecycle/batchManager.js.map +1 -1
  200. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  201. package/lib/opLifecycle/definitions.js.map +1 -1
  202. package/lib/opLifecycle/index.d.ts.map +1 -1
  203. package/lib/opLifecycle/index.js.map +1 -1
  204. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  205. package/lib/opLifecycle/opCompressor.js.map +1 -1
  206. package/lib/opLifecycle/opDecompressor.d.ts +0 -4
  207. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  208. package/lib/opLifecycle/opDecompressor.js +7 -43
  209. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  210. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  211. package/lib/opLifecycle/opSplitter.js +5 -2
  212. package/lib/opLifecycle/opSplitter.js.map +1 -1
  213. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  214. package/lib/opLifecycle/outbox.js +20 -19
  215. package/lib/opLifecycle/outbox.js.map +1 -1
  216. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  217. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  218. package/lib/opProperties.d.ts.map +1 -1
  219. package/lib/opProperties.js +1 -3
  220. package/lib/opProperties.js.map +1 -1
  221. package/lib/orderedClientElection.d.ts.map +1 -1
  222. package/lib/orderedClientElection.js +10 -4
  223. package/lib/orderedClientElection.js.map +1 -1
  224. package/lib/packageVersion.d.ts +1 -1
  225. package/lib/packageVersion.js +1 -1
  226. package/lib/packageVersion.js.map +1 -1
  227. package/lib/pendingStateManager.d.ts +7 -0
  228. package/lib/pendingStateManager.d.ts.map +1 -1
  229. package/lib/pendingStateManager.js +7 -4
  230. package/lib/pendingStateManager.js.map +1 -1
  231. package/lib/runWhileConnectedCoordinator.d.ts.map +1 -1
  232. package/lib/runWhileConnectedCoordinator.js.map +1 -1
  233. package/lib/runningSummarizer.d.ts.map +1 -1
  234. package/lib/runningSummarizer.js +35 -22
  235. package/lib/runningSummarizer.js.map +1 -1
  236. package/lib/scheduleManager.d.ts.map +1 -1
  237. package/lib/scheduleManager.js +3 -2
  238. package/lib/scheduleManager.js.map +1 -1
  239. package/lib/serializedSnapshotStorage.d.ts +2 -2
  240. package/lib/serializedSnapshotStorage.d.ts.map +1 -1
  241. package/lib/serializedSnapshotStorage.js +5 -3
  242. package/lib/serializedSnapshotStorage.js.map +1 -1
  243. package/lib/summarizer.d.ts +2 -2
  244. package/lib/summarizer.d.ts.map +1 -1
  245. package/lib/summarizer.js +37 -17
  246. package/lib/summarizer.js.map +1 -1
  247. package/lib/summarizerClientElection.d.ts.map +1 -1
  248. package/lib/summarizerClientElection.js.map +1 -1
  249. package/lib/summarizerHandle.d.ts.map +1 -1
  250. package/lib/summarizerHandle.js.map +1 -1
  251. package/lib/summarizerHeuristics.d.ts.map +1 -1
  252. package/lib/summarizerHeuristics.js +6 -9
  253. package/lib/summarizerHeuristics.js.map +1 -1
  254. package/lib/summarizerTypes.d.ts +21 -21
  255. package/lib/summarizerTypes.d.ts.map +1 -1
  256. package/lib/summarizerTypes.js.map +1 -1
  257. package/lib/summaryCollection.d.ts.map +1 -1
  258. package/lib/summaryCollection.js +18 -8
  259. package/lib/summaryCollection.js.map +1 -1
  260. package/lib/summaryFormat.d.ts +5 -2
  261. package/lib/summaryFormat.d.ts.map +1 -1
  262. package/lib/summaryFormat.js +20 -12
  263. package/lib/summaryFormat.js.map +1 -1
  264. package/lib/summaryGenerator.d.ts.map +1 -1
  265. package/lib/summaryGenerator.js +35 -16
  266. package/lib/summaryGenerator.js.map +1 -1
  267. package/lib/summaryManager.d.ts.map +1 -1
  268. package/lib/summaryManager.js +21 -9
  269. package/lib/summaryManager.js.map +1 -1
  270. package/lib/throttler.d.ts +2 -2
  271. package/lib/throttler.d.ts.map +1 -1
  272. package/lib/throttler.js +4 -4
  273. package/lib/throttler.js.map +1 -1
  274. package/package.json +121 -115
  275. package/prettier.config.cjs +1 -1
  276. package/src/batchTracker.ts +54 -49
  277. package/src/blobManager.ts +793 -672
  278. package/src/connectionTelemetry.ts +280 -249
  279. package/src/containerHandleContext.ts +27 -29
  280. package/src/containerRuntime.ts +3168 -2988
  281. package/src/dataStore.ts +172 -159
  282. package/src/dataStoreContext.ts +1098 -1055
  283. package/src/dataStoreContexts.ts +178 -161
  284. package/src/dataStoreRegistry.ts +25 -20
  285. package/src/dataStores.ts +884 -728
  286. package/src/deltaScheduler.ts +158 -150
  287. package/src/garbageCollection.ts +1860 -1688
  288. package/src/garbageCollectionConstants.ts +3 -0
  289. package/src/garbageCollectionHelpers.ts +61 -0
  290. package/src/gcSweepReadyUsageDetection.ts +89 -83
  291. package/src/index.ts +67 -66
  292. package/src/opLifecycle/README.md +152 -0
  293. package/src/opLifecycle/batchManager.ts +145 -141
  294. package/src/opLifecycle/definitions.ts +29 -29
  295. package/src/opLifecycle/index.ts +5 -5
  296. package/src/opLifecycle/opCompressor.ts +54 -53
  297. package/src/opLifecycle/opDecompressor.ts +100 -128
  298. package/src/opLifecycle/opSplitter.ts +214 -188
  299. package/src/opLifecycle/outbox.ts +204 -195
  300. package/src/opLifecycle/remoteMessageProcessor.ts +62 -62
  301. package/src/opProperties.ts +11 -9
  302. package/src/orderedClientElection.ts +489 -457
  303. package/src/packageVersion.ts +1 -1
  304. package/src/pendingStateManager.ts +384 -338
  305. package/src/runWhileConnectedCoordinator.ts +78 -71
  306. package/src/runningSummarizer.ts +619 -581
  307. package/src/scheduleManager.ts +299 -269
  308. package/src/serializedSnapshotStorage.ts +126 -112
  309. package/src/summarizer.ts +417 -381
  310. package/src/summarizerClientElection.ts +107 -100
  311. package/src/summarizerHandle.ts +11 -9
  312. package/src/summarizerHeuristics.ts +183 -186
  313. package/src/summarizerTypes.ts +344 -330
  314. package/src/summaryCollection.ts +378 -349
  315. package/src/summaryFormat.ts +165 -143
  316. package/src/summaryGenerator.ts +465 -410
  317. package/src/summaryManager.ts +377 -348
  318. package/src/throttler.ts +131 -122
  319. package/tsconfig.esnext.json +6 -6
  320. package/tsconfig.json +9 -13
  321. package/dist/garbageCollectionTombstoneUtils.d.ts +0 -14
  322. package/dist/garbageCollectionTombstoneUtils.d.ts.map +0 -1
  323. package/dist/garbageCollectionTombstoneUtils.js +0 -23
  324. package/dist/garbageCollectionTombstoneUtils.js.map +0 -1
  325. package/lib/garbageCollectionTombstoneUtils.d.ts +0 -14
  326. package/lib/garbageCollectionTombstoneUtils.d.ts.map +0 -1
  327. package/lib/garbageCollectionTombstoneUtils.js +0 -19
  328. package/lib/garbageCollectionTombstoneUtils.js.map +0 -1
  329. package/src/garbageCollectionTombstoneUtils.ts +0 -28
@@ -7,38 +7,33 @@ import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definition
7
7
  import { assert, delay, Deferred, PromiseTimer } from "@fluidframework/common-utils";
8
8
  import { UsageError } from "@fluidframework/container-utils";
9
9
  import { isRuntimeMessage } from "@fluidframework/driver-utils";
10
- import {
11
- ISequencedDocumentMessage,
12
- MessageType,
13
- } from "@fluidframework/protocol-definitions";
10
+ import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions";
14
11
  import { ChildLogger } from "@fluidframework/telemetry-utils";
15
- import {
16
- ISummaryConfiguration,
17
- } from "./containerRuntime";
12
+ import { ISummaryConfiguration } from "./containerRuntime";
18
13
  import { opSize } from "./opProperties";
19
14
  import { SummarizeHeuristicRunner } from "./summarizerHeuristics";
20
15
  import {
21
- IEnqueueSummarizeOptions,
22
- ISummarizeOptions,
23
- ISummarizeHeuristicData,
24
- ISummarizeHeuristicRunner,
25
- IOnDemandSummarizeOptions,
26
- EnqueueSummarizeResult,
27
- SummarizerStopReason,
28
- ISubmitSummaryOptions,
29
- SubmitSummaryResult,
30
- ISummaryCancellationToken,
31
- ISummarizeResults,
32
- ISummarizeTelemetryProperties,
33
- ISummarizerRuntime,
34
- ISummarizeRunnerTelemetry,
16
+ IEnqueueSummarizeOptions,
17
+ ISummarizeOptions,
18
+ ISummarizeHeuristicData,
19
+ ISummarizeHeuristicRunner,
20
+ IOnDemandSummarizeOptions,
21
+ EnqueueSummarizeResult,
22
+ SummarizerStopReason,
23
+ ISubmitSummaryOptions,
24
+ SubmitSummaryResult,
25
+ ISummaryCancellationToken,
26
+ ISummarizeResults,
27
+ ISummarizeTelemetryProperties,
28
+ ISummarizerRuntime,
29
+ ISummarizeRunnerTelemetry,
35
30
  } from "./summarizerTypes";
36
31
  import { IClientSummaryWatcher, SummaryCollection } from "./summaryCollection";
37
32
  import {
38
- raceTimer,
39
- SummarizeReason,
40
- SummarizeResultBuilder,
41
- SummaryGenerator,
33
+ raceTimer,
34
+ SummarizeReason,
35
+ SummarizeResultBuilder,
36
+ SummaryGenerator,
42
37
  } from "./summaryGenerator";
43
38
 
44
39
  const maxSummarizeAckWaitTime = 10 * 60 * 1000; // 10 minutes
@@ -51,560 +46,603 @@ const maxSummarizeAckWaitTime = 10 * 60 * 1000; // 10 minutes
51
46
  * This object is created and controlled by Summarizer object.
52
47
  */
53
48
  export class RunningSummarizer implements IDisposable {
54
- public static async start(
55
- logger: ITelemetryLogger,
56
- summaryWatcher: IClientSummaryWatcher,
57
- configuration: ISummaryConfiguration,
58
- submitSummaryCallback: (options: ISubmitSummaryOptions) => Promise<SubmitSummaryResult>,
59
- heuristicData: ISummarizeHeuristicData,
60
- raiseSummarizingError: (errorMessage: string) => void,
61
- summaryCollection: SummaryCollection,
62
- cancellationToken: ISummaryCancellationToken,
63
- stopSummarizerCallback: (reason: SummarizerStopReason) => void,
64
- runtime: ISummarizerRuntime,
65
- ): Promise<RunningSummarizer> {
66
- const summarizer = new RunningSummarizer(
67
- logger,
68
- summaryWatcher,
69
- configuration,
70
- submitSummaryCallback,
71
- heuristicData,
72
- raiseSummarizingError,
73
- summaryCollection,
74
- cancellationToken,
75
- stopSummarizerCallback,
76
- runtime);
77
-
78
- await summarizer.waitStart();
79
-
80
- // Update heuristic counts
81
- // By the time we get here, there are potentially ops missing from the heuristic summary counts
82
- // Examples of where this could happen:
83
- // 1. Op is processed during the time that we are initiating the RunningSummarizer instance but before we
84
- // listen for the op events (will get missed by the handlers in the current workflow)
85
- // 2. Op was sequenced after the last time we summarized (op sequence number > summarize ref sequence number)
86
- const diff = runtime.deltaManager.lastSequenceNumber - (
87
- heuristicData.lastSuccessfulSummary.refSequenceNumber
88
- + heuristicData.numNonRuntimeOps
89
- + heuristicData.numRuntimeOps);
90
- heuristicData.hasMissingOpData = diff > 0;
91
-
92
- if (heuristicData.hasMissingOpData) {
93
- // Split the diff 50-50 and increment the counts appropriately
94
- heuristicData.numNonRuntimeOps += Math.ceil(diff / 2);
95
- heuristicData.numRuntimeOps += Math.floor(diff / 2);
96
- }
97
-
98
- // Update last seq number (in case the handlers haven't processed anything yet)
99
- heuristicData.lastOpSequenceNumber = runtime.deltaManager.lastSequenceNumber;
100
-
101
- // Start heuristics
102
- summarizer.heuristicRunner?.start();
103
- summarizer.heuristicRunner?.run();
104
-
105
- return summarizer;
106
- }
107
-
108
- public get disposed() { return this._disposed; }
109
- private stopping = false;
110
- private _disposed = false;
111
- private summarizingLock: Promise<void> | undefined;
112
- private refreshSummaryAckLock: Promise<void> | undefined;
113
- private tryWhileSummarizing = false;
114
- private readonly pendingAckTimer: PromiseTimer;
115
- private heuristicRunner?: ISummarizeHeuristicRunner;
116
- private readonly generator: SummaryGenerator;
117
- private readonly logger: ITelemetryLogger;
118
- private enqueuedSummary: {
119
- reason: SummarizeReason;
120
- afterSequenceNumber: number;
121
- options: ISummarizeOptions;
122
- readonly resultsBuilder: SummarizeResultBuilder;
123
- } | undefined;
124
- private summarizeCount = 0;
125
- private totalSuccessfulAttempts = 0;
126
- private initialized = false;
127
-
128
- private constructor(
129
- baseLogger: ITelemetryLogger,
130
- private readonly summaryWatcher: IClientSummaryWatcher,
131
- private readonly configuration: ISummaryConfiguration,
132
- private readonly submitSummaryCallback: (options: ISubmitSummaryOptions) => Promise<SubmitSummaryResult>,
133
- private readonly heuristicData: ISummarizeHeuristicData,
134
- private readonly raiseSummarizingError: (errorMessage: string) => void,
135
- private readonly summaryCollection: SummaryCollection,
136
- private readonly cancellationToken: ISummaryCancellationToken,
137
- private readonly stopSummarizerCallback: (reason: SummarizerStopReason) => void,
138
- private readonly runtime: ISummarizerRuntime,
139
- ) {
140
- const telemetryProps: ISummarizeRunnerTelemetry = {
141
- summarizeCount: () => this.summarizeCount,
142
- summarizerSuccessfulAttempts: () => this.totalSuccessfulAttempts,
143
- };
144
-
145
- this.logger = ChildLogger.create(
146
- baseLogger, "Running",
147
- {
148
- all: telemetryProps,
149
- },
150
- );
151
-
152
- if (configuration.state !== "disableHeuristics") {
153
- assert(this.configuration.state === "enabled", 0x2ea /* "Configuration state should be enabled" */);
154
- this.heuristicRunner = new SummarizeHeuristicRunner(
155
- heuristicData,
156
- this.configuration,
157
- (reason) => this.trySummarize(reason),
158
- this.logger);
159
- }
160
-
161
- assert(
162
- this.configuration.state !== "disabled",
163
- 0x2eb /* "Summary not supported with configuration disabled" */,
164
- );
165
-
166
- // Cap the maximum amount of time client will wait for a summarize op ack to maxSummarizeAckWaitTime
167
- // configuration.maxAckWaitTime is composed from defaults, server values, and runtime overrides
168
-
169
- const maxAckWaitTime = Math.min(this.configuration.maxAckWaitTime, maxSummarizeAckWaitTime);
170
-
171
- this.pendingAckTimer = new PromiseTimer(
172
- maxAckWaitTime,
173
- () => {
174
- // pre-0.58 error message: summaryAckWaitTimeout
175
- this.raiseSummarizingError("Pending summary ack not received in time");
176
- // Note: summarizeCount (from ChildLogger definition) may be 0,
177
- // since this code path is hit when RunningSummarizer first starts up,
178
- // before this instance has kicked off a new summarize run.
179
- this.logger.sendErrorEvent({
180
- eventName: "SummaryAckWaitTimeout",
181
- maxAckWaitTime,
182
- referenceSequenceNumber: this.heuristicData.lastAttempt.refSequenceNumber,
183
- summarySequenceNumber: this.heuristicData.lastAttempt.summarySequenceNumber,
184
- timePending: Date.now() - this.heuristicData.lastAttempt.summaryTime,
185
- });
186
- });
187
- // Set up pending ack timeout by op timestamp differences for previous summaries.
188
- summaryCollection.setPendingAckTimerTimeoutCallback(maxAckWaitTime, () => {
189
- if (this.pendingAckTimer.hasTimer) {
190
- this.logger.sendTelemetryEvent({
191
- eventName: "MissingSummaryAckFoundByOps",
192
- referenceSequenceNumber: this.heuristicData.lastAttempt.refSequenceNumber,
193
- summarySequenceNumber: this.heuristicData.lastAttempt.summarySequenceNumber,
194
- });
195
- this.pendingAckTimer.clear();
196
- }
197
- });
198
-
199
- this.generator = new SummaryGenerator(
200
- this.pendingAckTimer,
201
- this.heuristicData,
202
- this.submitSummaryCallback,
203
- this.raiseSummarizingError,
204
- () => { this.totalSuccessfulAttempts++; },
205
- this.summaryWatcher,
206
- this.logger,
207
- );
208
-
209
- // Listen for ops
210
- this.runtime.deltaManager.on("op", (op) => { this.handleOp(op); });
211
- }
212
-
213
- public dispose(): void {
214
- this.runtime.deltaManager.off("op", (op) => { this.handleOp(op); });
215
- this.summaryWatcher.dispose();
216
- this.heuristicRunner?.dispose();
217
- this.heuristicRunner = undefined;
218
- this.generator.dispose();
219
- this.pendingAckTimer.clear();
220
- this.disposeEnqueuedSummary();
221
- this._disposed = true;
222
- this.stopping = true;
223
- }
224
-
225
- /**
226
- * RunningSummarizer's logger includes the sequenced index of the current summary on each event.
227
- * If some other Summarizer code wants that event on their logs they can get it here,
228
- * but only if they're logging about that same summary.
229
- * @param summaryOpRefSeq - RefSeq number of the summary op, to ensure the log correlation will be correct
230
- */
231
- public tryGetCorrelatedLogger = (summaryOpRefSeq) =>
232
- this.heuristicData.lastAttempt.refSequenceNumber === summaryOpRefSeq
233
- ? this.logger
234
- : undefined;
235
-
236
- /** We only want a single heuristic runner micro-task (will provide better optimized grouping of ops) */
237
- private heuristicRunnerMicroTaskExists = false;
238
-
239
- public handleOp(op: ISequencedDocumentMessage) {
240
- this.heuristicData.lastOpSequenceNumber = op.sequenceNumber;
241
-
242
- if (isRuntimeMessage(op)) {
243
- this.heuristicData.numRuntimeOps++;
244
- } else {
245
- this.heuristicData.numNonRuntimeOps++;
246
- }
247
-
248
- this.heuristicData.totalOpsSize += opSize(op);
249
-
250
- // Check for enqueued on-demand summaries; Intentionally do nothing otherwise
251
- if (this.initialized
252
- && this.opCanTriggerSummary(op)
253
- && !this.tryRunEnqueuedSummary()
254
- && !this.heuristicRunnerMicroTaskExists) {
255
- this.heuristicRunnerMicroTaskExists = true;
256
- Promise.resolve().then(() => {
257
- this.heuristicRunner?.run();
258
- }).finally(() => {
259
- this.heuristicRunnerMicroTaskExists = false;
260
- });
261
- }
262
- }
263
-
264
- /**
265
- * Can the given op trigger a summary?
266
- * # Currently always prevents summaries for Summarize and SummaryAck/Nack ops
267
- * @param op - op to check
268
- * @returns true if this op can trigger a summary
269
- */
270
- private opCanTriggerSummary(op: ISequencedDocumentMessage): boolean {
271
- switch (op.type) {
272
- case MessageType.Summarize:
273
- case MessageType.SummaryAck:
274
- case MessageType.SummaryNack:
275
- return false;
276
- default:
277
- return isRuntimeMessage(op) || this.nonRuntimeOpCanTriggerSummary();
278
- }
279
- }
280
-
281
- private nonRuntimeOpCanTriggerSummary(): boolean {
282
- const opsSinceLastAck = this.heuristicData.lastOpSequenceNumber - this.heuristicData.lastSuccessfulSummary.refSequenceNumber;
283
- return this.configuration.state === "enabled"
284
- && (this.configuration.nonRuntimeHeuristicThreshold === undefined
285
- || this.configuration.nonRuntimeHeuristicThreshold <= opsSinceLastAck);
286
- }
287
-
288
- public async waitStop(allowLastSummary: boolean): Promise<void> {
289
- if (this.stopping) {
290
- return;
291
- }
292
-
293
- this.stopping = true;
294
-
295
- this.disposeEnqueuedSummary();
296
-
297
- // This will try to run lastSummary if needed.
298
- if (allowLastSummary && this.heuristicRunner?.shouldRunLastSummary()) {
299
- if (this.summarizingLock === undefined) {
300
- this.trySummarizeOnce(
301
- // summarizeProps
302
- { reason: "lastSummary" },
303
- // ISummarizeOptions, using defaults: { refreshLatestAck: false, fullTree: false }
304
- {});
305
- }
306
- }
307
-
308
- // Note that trySummarizeOnce() call above returns right away, without waiting.
309
- // So we need to wait for its completion, otherwise it would be destroyed right away.
310
- // That said, if summary lock was taken upfront, this wait might wait on multiple retries to
311
- // submit summary. We should reconsider this flow and make summarizer move to exit faster.
312
- // This resolves when the current pending summary gets an ack or fails.
313
- await this.summarizingLock;
314
- }
315
-
316
- private async waitStart() {
317
- // Wait no longer than ack timeout for all pending
318
- const waitStartResult = await raceTimer(
319
- this.summaryWatcher.waitFlushed(),
320
- this.pendingAckTimer.start(),
321
- );
322
- this.pendingAckTimer.clear();
323
-
324
- // Remove pending ack wait timeout by op timestamp comparison, because
325
- // it has race conditions with summaries submitted by this same client.
326
- this.summaryCollection.unsetPendingAckTimerTimeoutCallback();
327
-
328
- if (waitStartResult.result === "done" && waitStartResult.value !== undefined) {
329
- this.heuristicData.updateWithLastSummaryAckInfo({
330
- refSequenceNumber: waitStartResult.value.summaryOp.referenceSequenceNumber,
331
- // This will be the Summarizer starting point so only use timestamps from client's machine.
332
- summaryTime: Date.now(),
333
- summarySequenceNumber: waitStartResult.value.summaryOp.sequenceNumber,
334
- });
335
- }
336
- this.initialized = true;
337
- }
338
-
339
- /**
340
- * Blocks a new summarizer from running in case RefreshSummaryAck is being processed.
341
- * Assumes that caller checked upfront for lack of concurrent action (this.refreshSummaryAckLock)
342
- * before calling this API. I.e. caller is responsible for either erroring out or waiting on this promise.
343
- * Note: The refreshSummaryAckLock makes sure no summarizer gets enqueued or processed
344
- * until the refresh has completed. One can't rely uniquely on the summarizingLock as the
345
- * refreshLatestSummaryAck also happens during the time summarizingLock !== undefined.
346
- * Ex. Summarizer submits a summay + op and then waits for the Summary Ack to proceed
347
- * with the refreshLatestSummaryAck and complete the summary.
348
- * @param action - action to perform.
349
- * @returns - result of action.
350
- */
351
- public async lockedRefreshSummaryAckAction<T>(action: () => Promise<T>) {
352
- assert(this.refreshSummaryAckLock === undefined,
353
- 0x396 /* Refresh Summary Ack - Caller is responsible for checking lock */);
354
-
355
- const refreshSummaryAckLock = new Deferred<void>();
356
- this.refreshSummaryAckLock = refreshSummaryAckLock.promise;
357
-
358
- return action().finally(() => {
359
- refreshSummaryAckLock.resolve();
360
- this.refreshSummaryAckLock = undefined;
361
- });
362
- }
363
-
364
- /**
365
- * Runs single summary action that prevents any other concurrent actions.
366
- * Assumes that caller checked upfront for lack of concurrent action (this.summarizingLock)
367
- * before calling this API. I.e. caller is responsible for either erroring out or waiting on this promise.
368
- * @param action - action to perform.
369
- * @returns - result of action.
370
- */
371
- private async lockedSummaryAction<T>(action: () => Promise<T>) {
372
- assert(this.summarizingLock === undefined, 0x25b /* "Caller is responsible for checking lock" */);
373
-
374
- const summarizingLock = new Deferred<void>();
375
- this.summarizingLock = summarizingLock.promise;
376
-
377
- this.summarizeCount++;
378
- // Make sure the RefreshLatestSummaryAck is not being executed.
379
- await this.refreshSummaryAckLock;
380
-
381
- return action().finally(() => {
382
- summarizingLock.resolve();
383
- this.summarizingLock = undefined;
384
-
385
- const retry = this.tryWhileSummarizing;
386
- this.tryWhileSummarizing = false;
387
-
388
- // After summarizing, we should check to see if we need to summarize again.
389
- // Rerun the heuristics and check for enqueued summaries.
390
- if (!this.stopping && !this.tryRunEnqueuedSummary() && retry) {
391
- this.heuristicRunner?.run();
392
- }
393
- });
394
- }
395
-
396
- /**
397
- * Runs single summarize attempt
398
- * @param summarizeProps - props to log with each telemetry event associated with this attempt
399
- * @param options - summary options
400
- * @param cancellationToken - cancellation token to use to be able to cancel this summary, if needed
401
- * @param resultsBuilder - optional, result builder to use.
402
- * @returns ISummarizeResult - result of running a summary.
403
- */
404
- private trySummarizeOnce(
405
- summarizeProps: ISummarizeTelemetryProperties,
406
- options: ISummarizeOptions,
407
- cancellationToken = this.cancellationToken,
408
- resultsBuilder = new SummarizeResultBuilder()): ISummarizeResults {
409
- this.lockedSummaryAction(async () => {
410
- const summarizeResult = this.generator.summarize(
411
- summarizeProps,
412
- options,
413
- cancellationToken,
414
- resultsBuilder);
415
- // ensure we wait till the end of the process
416
- return summarizeResult.receivedSummaryAckOrNack;
417
- }).catch((error) => {
418
- // SummaryGenerator.summarize() does not throw exceptions - it converts them to failed result
419
- // on resultsBuilder
420
- // We do not care about exceptions on receivedSummaryAckOrNack - caller should check results
421
- // and take a appropriate action.
422
- });
423
-
424
- return resultsBuilder.build();
425
- }
426
-
427
- /** Heuristics summarize attempt. */
428
- private trySummarize(
429
- reason: SummarizeReason,
430
- cancellationToken = this.cancellationToken): void {
431
- if (this.summarizingLock !== undefined) {
432
- // lockedSummaryAction() will retry heuristic-based summary at the end of current attempt
433
- // if it's still needed
434
- this.tryWhileSummarizing = true;
435
- return;
436
- }
437
-
438
- this.lockedSummaryAction(async () => {
439
- const attempts: (ISummarizeOptions & { delaySeconds?: number; })[] = [
440
- { refreshLatestAck: false, fullTree: false },
441
- { refreshLatestAck: true, fullTree: false },
442
- { refreshLatestAck: true, fullTree: false, delaySeconds: 2 * 60 },
443
- { refreshLatestAck: true, fullTree: true, delaySeconds: 10 * 60 },
444
- ];
445
- let overrideDelaySeconds: number | undefined;
446
- let summaryAttempts = 0;
447
- let summaryAttemptsPerPhase = 0;
448
-
449
- let lastResult: { message: string; error: any; } | undefined;
450
-
451
- for (let summaryAttemptPhase = 0; summaryAttemptPhase < attempts.length;) {
452
- if (this.cancellationToken.cancelled) {
453
- return;
454
- }
455
-
456
- // We only want to attempt 1 summary when reason is "lastSummary"
457
- if (++summaryAttempts > 1 && reason === "lastSummary") {
458
- return;
459
- }
460
-
461
- summaryAttemptsPerPhase++;
462
-
463
- const { delaySeconds: regularDelaySeconds = 0, ...options } = attempts[summaryAttemptPhase];
464
- const delaySeconds = overrideDelaySeconds ?? regularDelaySeconds;
465
-
466
- const summarizeProps: ISummarizeTelemetryProperties = {
467
- reason,
468
- summaryAttempts,
469
- summaryAttemptsPerPhase,
470
- summaryAttemptPhase: summaryAttemptPhase + 1, // make everything 1-based
471
- ...options,
472
- };
473
-
474
- if (delaySeconds > 0) {
475
- this.logger.sendPerformanceEvent({
476
- eventName: "SummarizeAttemptDelay",
477
- duration: delaySeconds,
478
- summaryNackDelay: overrideDelaySeconds !== undefined,
479
- ...summarizeProps,
480
- });
481
- await delay(delaySeconds * 1000);
482
- }
483
-
484
- // Make sure the refresh Summary Ack is not being executed.
485
- await this.refreshSummaryAckLock;
486
-
487
- // Note: no need to account for cancellationToken.waitCancelled here, as
488
- // this is accounted SummaryGenerator.summarizeCore that controls receivedSummaryAckOrNack.
489
- const resultSummarize = this.generator.summarize(summarizeProps, options, cancellationToken);
490
- const result = await resultSummarize.receivedSummaryAckOrNack;
491
-
492
- if (result.success) {
493
- return;
494
- }
495
- // Check for retryDelay that can come from summaryNack or upload summary flow.
496
- // Retry the same step only once per retryAfter response.
497
- overrideDelaySeconds = result.retryAfterSeconds;
498
- if (overrideDelaySeconds === undefined || summaryAttemptsPerPhase > 1) {
499
- summaryAttemptPhase++;
500
- summaryAttemptsPerPhase = 0;
501
- }
502
- lastResult = result;
503
- }
504
-
505
- // If all attempts failed, log error (with last attempt info) and close the summarizer container
506
- this.logger.sendErrorEvent({
507
- eventName: "FailToSummarize",
508
- reason,
509
- message: lastResult?.message,
510
- }, lastResult?.error);
511
-
512
- this.stopSummarizerCallback("failToSummarize");
513
- }).catch((error) => {
514
- this.logger.sendErrorEvent({ eventName: "UnexpectedSummarizeError" }, error);
515
- });
516
- }
517
-
518
- /** {@inheritdoc (ISummarizer:interface).summarizeOnDemand} */
519
- public summarizeOnDemand(
520
- resultsBuilder: SummarizeResultBuilder = new SummarizeResultBuilder(),
521
- {
522
- reason,
523
- ...options
524
- }: IOnDemandSummarizeOptions): ISummarizeResults {
525
- if (this.stopping) {
526
- resultsBuilder.fail("RunningSummarizer stopped or disposed", undefined);
527
- return resultsBuilder.build();
528
- }
529
- // Check for concurrent summary attempts. If one is found,
530
- // return a promise that caller can await before trying again.
531
- if (this.summarizingLock !== undefined) {
532
- // The heuristics are blocking concurrent summarize attempts.
533
- throw new UsageError("Attempted to run an already-running summarizer on demand");
534
- }
535
-
536
- const result = this.trySummarizeOnce(
537
- { reason: `onDemand/${reason}` },
538
- options,
539
- this.cancellationToken,
540
- resultsBuilder);
541
- return result;
542
- }
543
-
544
- /** {@inheritdoc (ISummarizer:interface).enqueueSummarize} */
545
- public enqueueSummarize({
546
- reason,
547
- afterSequenceNumber = 0,
548
- override = false,
549
- ...options
550
- }: IEnqueueSummarizeOptions): EnqueueSummarizeResult {
551
- const onDemandReason = `enqueue;${reason}` as const;
552
- let overridden = false;
553
- if (this.enqueuedSummary !== undefined) {
554
- if (!override) {
555
- return { alreadyEnqueued: true };
556
- }
557
- // Override existing enqueued summarize attempt.
558
- this.enqueuedSummary.resultsBuilder.fail(
559
- "Aborted; overridden by another enqueue summarize attempt",
560
- undefined,
561
- );
562
- this.enqueuedSummary = undefined;
563
- overridden = true;
564
- }
565
- this.enqueuedSummary = {
566
- reason: onDemandReason,
567
- afterSequenceNumber,
568
- options,
569
- resultsBuilder: new SummarizeResultBuilder(),
570
- };
571
- const results = this.enqueuedSummary.resultsBuilder.build();
572
- this.tryRunEnqueuedSummary();
573
- return overridden ? {
574
- ...results,
575
- alreadyEnqueued: true,
576
- overridden: true,
577
- } : results;
578
- }
579
-
580
- private tryRunEnqueuedSummary() {
581
- if (this.stopping) {
582
- this.disposeEnqueuedSummary();
583
- return false;
584
- }
585
- if (
586
- this.enqueuedSummary === undefined
587
- || this.heuristicData.lastOpSequenceNumber < this.enqueuedSummary.afterSequenceNumber
588
- || this.summarizingLock !== undefined
589
- ) {
590
- // If no enqueued summary is ready or a summary is already in progress, take no action.
591
- return false;
592
- }
593
- const { reason, resultsBuilder, options } = this.enqueuedSummary;
594
- // Set to undefined first, so that subsequent enqueue attempt while summarize will occur later.
595
- this.enqueuedSummary = undefined;
596
- this.trySummarizeOnce(
597
- { reason: `enqueuedSummary/${reason}` },
598
- options,
599
- this.cancellationToken,
600
- resultsBuilder);
601
- return true;
602
- }
603
-
604
- private disposeEnqueuedSummary() {
605
- if (this.enqueuedSummary !== undefined) {
606
- this.enqueuedSummary.resultsBuilder.fail("RunningSummarizer stopped or disposed", undefined);
607
- this.enqueuedSummary = undefined;
608
- }
609
- }
49
+ public static async start(
50
+ logger: ITelemetryLogger,
51
+ summaryWatcher: IClientSummaryWatcher,
52
+ configuration: ISummaryConfiguration,
53
+ submitSummaryCallback: (options: ISubmitSummaryOptions) => Promise<SubmitSummaryResult>,
54
+ heuristicData: ISummarizeHeuristicData,
55
+ raiseSummarizingError: (errorMessage: string) => void,
56
+ summaryCollection: SummaryCollection,
57
+ cancellationToken: ISummaryCancellationToken,
58
+ stopSummarizerCallback: (reason: SummarizerStopReason) => void,
59
+ runtime: ISummarizerRuntime,
60
+ ): Promise<RunningSummarizer> {
61
+ const summarizer = new RunningSummarizer(
62
+ logger,
63
+ summaryWatcher,
64
+ configuration,
65
+ submitSummaryCallback,
66
+ heuristicData,
67
+ raiseSummarizingError,
68
+ summaryCollection,
69
+ cancellationToken,
70
+ stopSummarizerCallback,
71
+ runtime,
72
+ );
73
+
74
+ await summarizer.waitStart();
75
+
76
+ // Update heuristic counts
77
+ // By the time we get here, there are potentially ops missing from the heuristic summary counts
78
+ // Examples of where this could happen:
79
+ // 1. Op is processed during the time that we are initiating the RunningSummarizer instance but before we
80
+ // listen for the op events (will get missed by the handlers in the current workflow)
81
+ // 2. Op was sequenced after the last time we summarized (op sequence number > summarize ref sequence number)
82
+ const diff =
83
+ runtime.deltaManager.lastSequenceNumber -
84
+ (heuristicData.lastSuccessfulSummary.refSequenceNumber +
85
+ heuristicData.numNonRuntimeOps +
86
+ heuristicData.numRuntimeOps);
87
+ heuristicData.hasMissingOpData = diff > 0;
88
+
89
+ if (heuristicData.hasMissingOpData) {
90
+ // Split the diff 50-50 and increment the counts appropriately
91
+ heuristicData.numNonRuntimeOps += Math.ceil(diff / 2);
92
+ heuristicData.numRuntimeOps += Math.floor(diff / 2);
93
+ }
94
+
95
+ // Update last seq number (in case the handlers haven't processed anything yet)
96
+ heuristicData.lastOpSequenceNumber = runtime.deltaManager.lastSequenceNumber;
97
+
98
+ // Start heuristics
99
+ summarizer.heuristicRunner?.start();
100
+ summarizer.heuristicRunner?.run();
101
+
102
+ return summarizer;
103
+ }
104
+
105
+ public get disposed() {
106
+ return this._disposed;
107
+ }
108
+ private stopping = false;
109
+ private _disposed = false;
110
+ private summarizingLock: Promise<void> | undefined;
111
+ private refreshSummaryAckLock: Promise<void> | undefined;
112
+ private tryWhileSummarizing = false;
113
+ private readonly pendingAckTimer: PromiseTimer;
114
+ private heuristicRunner?: ISummarizeHeuristicRunner;
115
+ private readonly generator: SummaryGenerator;
116
+ private readonly logger: ITelemetryLogger;
117
+ private enqueuedSummary:
118
+ | {
119
+ reason: SummarizeReason;
120
+ afterSequenceNumber: number;
121
+ options: ISummarizeOptions;
122
+ readonly resultsBuilder: SummarizeResultBuilder;
123
+ }
124
+ | undefined;
125
+ private summarizeCount = 0;
126
+ private totalSuccessfulAttempts = 0;
127
+ private initialized = false;
128
+
129
+ private constructor(
130
+ baseLogger: ITelemetryLogger,
131
+ private readonly summaryWatcher: IClientSummaryWatcher,
132
+ private readonly configuration: ISummaryConfiguration,
133
+ private readonly submitSummaryCallback: (
134
+ options: ISubmitSummaryOptions,
135
+ ) => Promise<SubmitSummaryResult>,
136
+ private readonly heuristicData: ISummarizeHeuristicData,
137
+ private readonly raiseSummarizingError: (errorMessage: string) => void,
138
+ private readonly summaryCollection: SummaryCollection,
139
+ private readonly cancellationToken: ISummaryCancellationToken,
140
+ private readonly stopSummarizerCallback: (reason: SummarizerStopReason) => void,
141
+ private readonly runtime: ISummarizerRuntime,
142
+ ) {
143
+ const telemetryProps: ISummarizeRunnerTelemetry = {
144
+ summarizeCount: () => this.summarizeCount,
145
+ summarizerSuccessfulAttempts: () => this.totalSuccessfulAttempts,
146
+ };
147
+
148
+ this.logger = ChildLogger.create(baseLogger, "Running", {
149
+ all: telemetryProps,
150
+ });
151
+
152
+ if (configuration.state !== "disableHeuristics") {
153
+ assert(
154
+ this.configuration.state === "enabled",
155
+ 0x2ea /* "Configuration state should be enabled" */,
156
+ );
157
+ this.heuristicRunner = new SummarizeHeuristicRunner(
158
+ heuristicData,
159
+ this.configuration,
160
+ (reason) => this.trySummarize(reason),
161
+ this.logger,
162
+ );
163
+ }
164
+
165
+ assert(
166
+ this.configuration.state !== "disabled",
167
+ 0x2eb /* "Summary not supported with configuration disabled" */,
168
+ );
169
+
170
+ // Cap the maximum amount of time client will wait for a summarize op ack to maxSummarizeAckWaitTime
171
+ // configuration.maxAckWaitTime is composed from defaults, server values, and runtime overrides
172
+
173
+ const maxAckWaitTime = Math.min(this.configuration.maxAckWaitTime, maxSummarizeAckWaitTime);
174
+
175
+ this.pendingAckTimer = new PromiseTimer(maxAckWaitTime, () => {
176
+ // pre-0.58 error message: summaryAckWaitTimeout
177
+ this.raiseSummarizingError("Pending summary ack not received in time");
178
+ // Note: summarizeCount (from ChildLogger definition) may be 0,
179
+ // since this code path is hit when RunningSummarizer first starts up,
180
+ // before this instance has kicked off a new summarize run.
181
+ this.logger.sendErrorEvent({
182
+ eventName: "SummaryAckWaitTimeout",
183
+ maxAckWaitTime,
184
+ referenceSequenceNumber: this.heuristicData.lastAttempt.refSequenceNumber,
185
+ summarySequenceNumber: this.heuristicData.lastAttempt.summarySequenceNumber,
186
+ timePending: Date.now() - this.heuristicData.lastAttempt.summaryTime,
187
+ });
188
+ });
189
+ // Set up pending ack timeout by op timestamp differences for previous summaries.
190
+ summaryCollection.setPendingAckTimerTimeoutCallback(maxAckWaitTime, () => {
191
+ if (this.pendingAckTimer.hasTimer) {
192
+ this.logger.sendTelemetryEvent({
193
+ eventName: "MissingSummaryAckFoundByOps",
194
+ referenceSequenceNumber: this.heuristicData.lastAttempt.refSequenceNumber,
195
+ summarySequenceNumber: this.heuristicData.lastAttempt.summarySequenceNumber,
196
+ });
197
+ this.pendingAckTimer.clear();
198
+ }
199
+ });
200
+
201
+ this.generator = new SummaryGenerator(
202
+ this.pendingAckTimer,
203
+ this.heuristicData,
204
+ this.submitSummaryCallback,
205
+ this.raiseSummarizingError,
206
+ () => {
207
+ this.totalSuccessfulAttempts++;
208
+ },
209
+ this.summaryWatcher,
210
+ this.logger,
211
+ );
212
+
213
+ // Listen for ops
214
+ this.runtime.deltaManager.on("op", (op) => {
215
+ this.handleOp(op);
216
+ });
217
+ }
218
+
219
+ public dispose(): void {
220
+ this.runtime.deltaManager.off("op", (op) => {
221
+ this.handleOp(op);
222
+ });
223
+ this.summaryWatcher.dispose();
224
+ this.heuristicRunner?.dispose();
225
+ this.heuristicRunner = undefined;
226
+ this.generator.dispose();
227
+ this.pendingAckTimer.clear();
228
+ this.disposeEnqueuedSummary();
229
+ this._disposed = true;
230
+ this.stopping = true;
231
+ }
232
+
233
+ /**
234
+ * RunningSummarizer's logger includes the sequenced index of the current summary on each event.
235
+ * If some other Summarizer code wants that event on their logs they can get it here,
236
+ * but only if they're logging about that same summary.
237
+ * @param summaryOpRefSeq - RefSeq number of the summary op, to ensure the log correlation will be correct
238
+ */
239
+ public tryGetCorrelatedLogger = (summaryOpRefSeq) =>
240
+ this.heuristicData.lastAttempt.refSequenceNumber === summaryOpRefSeq
241
+ ? this.logger
242
+ : undefined;
243
+
244
+ /** We only want a single heuristic runner micro-task (will provide better optimized grouping of ops) */
245
+ private heuristicRunnerMicroTaskExists = false;
246
+
247
+ public handleOp(op: ISequencedDocumentMessage) {
248
+ this.heuristicData.lastOpSequenceNumber = op.sequenceNumber;
249
+
250
+ if (isRuntimeMessage(op)) {
251
+ this.heuristicData.numRuntimeOps++;
252
+ } else {
253
+ this.heuristicData.numNonRuntimeOps++;
254
+ }
255
+
256
+ this.heuristicData.totalOpsSize += opSize(op);
257
+
258
+ // Check for enqueued on-demand summaries; Intentionally do nothing otherwise
259
+ if (
260
+ this.initialized &&
261
+ this.opCanTriggerSummary(op) &&
262
+ !this.tryRunEnqueuedSummary() &&
263
+ !this.heuristicRunnerMicroTaskExists
264
+ ) {
265
+ this.heuristicRunnerMicroTaskExists = true;
266
+ Promise.resolve()
267
+ .then(() => {
268
+ this.heuristicRunner?.run();
269
+ })
270
+ .finally(() => {
271
+ this.heuristicRunnerMicroTaskExists = false;
272
+ });
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Can the given op trigger a summary?
278
+ * # Currently always prevents summaries for Summarize and SummaryAck/Nack ops
279
+ * @param op - op to check
280
+ * @returns true if this op can trigger a summary
281
+ */
282
+ private opCanTriggerSummary(op: ISequencedDocumentMessage): boolean {
283
+ switch (op.type) {
284
+ case MessageType.Summarize:
285
+ case MessageType.SummaryAck:
286
+ case MessageType.SummaryNack:
287
+ return false;
288
+ default:
289
+ return isRuntimeMessage(op) || this.nonRuntimeOpCanTriggerSummary();
290
+ }
291
+ }
292
+
293
+ private nonRuntimeOpCanTriggerSummary(): boolean {
294
+ const opsSinceLastAck =
295
+ this.heuristicData.lastOpSequenceNumber -
296
+ this.heuristicData.lastSuccessfulSummary.refSequenceNumber;
297
+ return (
298
+ this.configuration.state === "enabled" &&
299
+ (this.configuration.nonRuntimeHeuristicThreshold === undefined ||
300
+ this.configuration.nonRuntimeHeuristicThreshold <= opsSinceLastAck)
301
+ );
302
+ }
303
+
304
+ public async waitStop(allowLastSummary: boolean): Promise<void> {
305
+ if (this.stopping) {
306
+ return;
307
+ }
308
+
309
+ this.stopping = true;
310
+
311
+ this.disposeEnqueuedSummary();
312
+
313
+ // This will try to run lastSummary if needed.
314
+ if (allowLastSummary && this.heuristicRunner?.shouldRunLastSummary()) {
315
+ if (this.summarizingLock === undefined) {
316
+ this.trySummarizeOnce(
317
+ // summarizeProps
318
+ { reason: "lastSummary" },
319
+ // ISummarizeOptions, using defaults: { refreshLatestAck: false, fullTree: false }
320
+ {},
321
+ );
322
+ }
323
+ }
324
+
325
+ // Note that trySummarizeOnce() call above returns right away, without waiting.
326
+ // So we need to wait for its completion, otherwise it would be destroyed right away.
327
+ // That said, if summary lock was taken upfront, this wait might wait on multiple retries to
328
+ // submit summary. We should reconsider this flow and make summarizer move to exit faster.
329
+ // This resolves when the current pending summary gets an ack or fails.
330
+ await this.summarizingLock;
331
+ }
332
+
333
+ private async waitStart() {
334
+ // Wait no longer than ack timeout for all pending
335
+ const waitStartResult = await raceTimer(
336
+ this.summaryWatcher.waitFlushed(),
337
+ this.pendingAckTimer.start(),
338
+ );
339
+ this.pendingAckTimer.clear();
340
+
341
+ // Remove pending ack wait timeout by op timestamp comparison, because
342
+ // it has race conditions with summaries submitted by this same client.
343
+ this.summaryCollection.unsetPendingAckTimerTimeoutCallback();
344
+
345
+ if (waitStartResult.result === "done" && waitStartResult.value !== undefined) {
346
+ this.heuristicData.updateWithLastSummaryAckInfo({
347
+ refSequenceNumber: waitStartResult.value.summaryOp.referenceSequenceNumber,
348
+ // This will be the Summarizer starting point so only use timestamps from client's machine.
349
+ summaryTime: Date.now(),
350
+ summarySequenceNumber: waitStartResult.value.summaryOp.sequenceNumber,
351
+ });
352
+ }
353
+ this.initialized = true;
354
+ }
355
+
356
+ /**
357
+ * Blocks a new summarizer from running in case RefreshSummaryAck is being processed.
358
+ * Assumes that caller checked upfront for lack of concurrent action (this.refreshSummaryAckLock)
359
+ * before calling this API. I.e. caller is responsible for either erroring out or waiting on this promise.
360
+ * Note: The refreshSummaryAckLock makes sure no summarizer gets enqueued or processed
361
+ * until the refresh has completed. One can't rely uniquely on the summarizingLock as the
362
+ * refreshLatestSummaryAck also happens during the time summarizingLock !== undefined.
363
+ * Ex. Summarizer submits a summay + op and then waits for the Summary Ack to proceed
364
+ * with the refreshLatestSummaryAck and complete the summary.
365
+ * @param action - action to perform.
366
+ * @returns - result of action.
367
+ */
368
+ public async lockedRefreshSummaryAckAction<T>(action: () => Promise<T>) {
369
+ assert(
370
+ this.refreshSummaryAckLock === undefined,
371
+ 0x396 /* Refresh Summary Ack - Caller is responsible for checking lock */,
372
+ );
373
+
374
+ const refreshSummaryAckLock = new Deferred<void>();
375
+ this.refreshSummaryAckLock = refreshSummaryAckLock.promise;
376
+
377
+ return action().finally(() => {
378
+ refreshSummaryAckLock.resolve();
379
+ this.refreshSummaryAckLock = undefined;
380
+ });
381
+ }
382
+
383
+ /**
384
+ * Runs single summary action that prevents any other concurrent actions.
385
+ * Assumes that caller checked upfront for lack of concurrent action (this.summarizingLock)
386
+ * before calling this API. I.e. caller is responsible for either erroring out or waiting on this promise.
387
+ * @param action - action to perform.
388
+ * @returns - result of action.
389
+ */
390
+ private async lockedSummaryAction<T>(action: () => Promise<T>) {
391
+ assert(
392
+ this.summarizingLock === undefined,
393
+ 0x25b /* "Caller is responsible for checking lock" */,
394
+ );
395
+
396
+ const summarizingLock = new Deferred<void>();
397
+ this.summarizingLock = summarizingLock.promise;
398
+
399
+ this.summarizeCount++;
400
+ // Make sure the RefreshLatestSummaryAck is not being executed.
401
+ await this.refreshSummaryAckLock;
402
+
403
+ return action().finally(() => {
404
+ summarizingLock.resolve();
405
+ this.summarizingLock = undefined;
406
+
407
+ const retry = this.tryWhileSummarizing;
408
+ this.tryWhileSummarizing = false;
409
+
410
+ // After summarizing, we should check to see if we need to summarize again.
411
+ // Rerun the heuristics and check for enqueued summaries.
412
+ if (!this.stopping && !this.tryRunEnqueuedSummary() && retry) {
413
+ this.heuristicRunner?.run();
414
+ }
415
+ });
416
+ }
417
+
418
+ /**
419
+ * Runs single summarize attempt
420
+ * @param summarizeProps - props to log with each telemetry event associated with this attempt
421
+ * @param options - summary options
422
+ * @param cancellationToken - cancellation token to use to be able to cancel this summary, if needed
423
+ * @param resultsBuilder - optional, result builder to use.
424
+ * @returns ISummarizeResult - result of running a summary.
425
+ */
426
+ private trySummarizeOnce(
427
+ summarizeProps: ISummarizeTelemetryProperties,
428
+ options: ISummarizeOptions,
429
+ cancellationToken = this.cancellationToken,
430
+ resultsBuilder = new SummarizeResultBuilder(),
431
+ ): ISummarizeResults {
432
+ this.lockedSummaryAction(async () => {
433
+ const summarizeResult = this.generator.summarize(
434
+ summarizeProps,
435
+ options,
436
+ cancellationToken,
437
+ resultsBuilder,
438
+ );
439
+ // ensure we wait till the end of the process
440
+ return summarizeResult.receivedSummaryAckOrNack;
441
+ }).catch((error) => {
442
+ // SummaryGenerator.summarize() does not throw exceptions - it converts them to failed result
443
+ // on resultsBuilder
444
+ // We do not care about exceptions on receivedSummaryAckOrNack - caller should check results
445
+ // and take a appropriate action.
446
+ });
447
+
448
+ return resultsBuilder.build();
449
+ }
450
+
451
+ /** Heuristics summarize attempt. */
452
+ private trySummarize(
453
+ reason: SummarizeReason,
454
+ cancellationToken = this.cancellationToken,
455
+ ): void {
456
+ if (this.summarizingLock !== undefined) {
457
+ // lockedSummaryAction() will retry heuristic-based summary at the end of current attempt
458
+ // if it's still needed
459
+ this.tryWhileSummarizing = true;
460
+ return;
461
+ }
462
+
463
+ this.lockedSummaryAction(async () => {
464
+ const attempts: (ISummarizeOptions & { delaySeconds?: number })[] = [
465
+ { refreshLatestAck: false, fullTree: false },
466
+ { refreshLatestAck: true, fullTree: false },
467
+ { refreshLatestAck: true, fullTree: false, delaySeconds: 2 * 60 },
468
+ { refreshLatestAck: true, fullTree: true, delaySeconds: 10 * 60 },
469
+ ];
470
+ let overrideDelaySeconds: number | undefined;
471
+ let summaryAttempts = 0;
472
+ let summaryAttemptsPerPhase = 0;
473
+
474
+ let lastResult: { message: string; error: any } | undefined;
475
+
476
+ for (let summaryAttemptPhase = 0; summaryAttemptPhase < attempts.length; ) {
477
+ if (this.cancellationToken.cancelled) {
478
+ return;
479
+ }
480
+
481
+ // We only want to attempt 1 summary when reason is "lastSummary"
482
+ if (++summaryAttempts > 1 && reason === "lastSummary") {
483
+ return;
484
+ }
485
+
486
+ summaryAttemptsPerPhase++;
487
+
488
+ const { delaySeconds: regularDelaySeconds = 0, ...options } =
489
+ attempts[summaryAttemptPhase];
490
+ const delaySeconds = overrideDelaySeconds ?? regularDelaySeconds;
491
+
492
+ const summarizeProps: ISummarizeTelemetryProperties = {
493
+ reason,
494
+ summaryAttempts,
495
+ summaryAttemptsPerPhase,
496
+ summaryAttemptPhase: summaryAttemptPhase + 1, // make everything 1-based
497
+ ...options,
498
+ };
499
+
500
+ if (delaySeconds > 0) {
501
+ this.logger.sendPerformanceEvent({
502
+ eventName: "SummarizeAttemptDelay",
503
+ duration: delaySeconds,
504
+ summaryNackDelay: overrideDelaySeconds !== undefined,
505
+ ...summarizeProps,
506
+ });
507
+ await delay(delaySeconds * 1000);
508
+ }
509
+
510
+ // Make sure the refresh Summary Ack is not being executed.
511
+ await this.refreshSummaryAckLock;
512
+
513
+ // Note: no need to account for cancellationToken.waitCancelled here, as
514
+ // this is accounted SummaryGenerator.summarizeCore that controls receivedSummaryAckOrNack.
515
+ const resultSummarize = this.generator.summarize(
516
+ summarizeProps,
517
+ options,
518
+ cancellationToken,
519
+ );
520
+ const result = await resultSummarize.receivedSummaryAckOrNack;
521
+
522
+ if (result.success) {
523
+ return;
524
+ }
525
+ // Check for retryDelay that can come from summaryNack or upload summary flow.
526
+ // Retry the same step only once per retryAfter response.
527
+ overrideDelaySeconds = result.retryAfterSeconds;
528
+ if (overrideDelaySeconds === undefined || summaryAttemptsPerPhase > 1) {
529
+ summaryAttemptPhase++;
530
+ summaryAttemptsPerPhase = 0;
531
+ }
532
+ lastResult = result;
533
+ }
534
+
535
+ // If all attempts failed, log error (with last attempt info) and close the summarizer container
536
+ this.logger.sendErrorEvent(
537
+ {
538
+ eventName: "FailToSummarize",
539
+ reason,
540
+ message: lastResult?.message,
541
+ },
542
+ lastResult?.error,
543
+ );
544
+
545
+ this.stopSummarizerCallback("failToSummarize");
546
+ }).catch((error) => {
547
+ this.logger.sendErrorEvent({ eventName: "UnexpectedSummarizeError" }, error);
548
+ });
549
+ }
550
+
551
+ /** {@inheritdoc (ISummarizer:interface).summarizeOnDemand} */
552
+ public summarizeOnDemand(
553
+ resultsBuilder: SummarizeResultBuilder = new SummarizeResultBuilder(),
554
+ { reason, ...options }: IOnDemandSummarizeOptions,
555
+ ): ISummarizeResults {
556
+ if (this.stopping) {
557
+ resultsBuilder.fail("RunningSummarizer stopped or disposed", undefined);
558
+ return resultsBuilder.build();
559
+ }
560
+ // Check for concurrent summary attempts. If one is found,
561
+ // return a promise that caller can await before trying again.
562
+ if (this.summarizingLock !== undefined) {
563
+ // The heuristics are blocking concurrent summarize attempts.
564
+ throw new UsageError("Attempted to run an already-running summarizer on demand");
565
+ }
566
+
567
+ const result = this.trySummarizeOnce(
568
+ { reason: `onDemand/${reason}` },
569
+ options,
570
+ this.cancellationToken,
571
+ resultsBuilder,
572
+ );
573
+ return result;
574
+ }
575
+
576
+ /** {@inheritdoc (ISummarizer:interface).enqueueSummarize} */
577
+ public enqueueSummarize({
578
+ reason,
579
+ afterSequenceNumber = 0,
580
+ override = false,
581
+ ...options
582
+ }: IEnqueueSummarizeOptions): EnqueueSummarizeResult {
583
+ const onDemandReason = `enqueue;${reason}` as const;
584
+ let overridden = false;
585
+ if (this.enqueuedSummary !== undefined) {
586
+ if (!override) {
587
+ return { alreadyEnqueued: true };
588
+ }
589
+ // Override existing enqueued summarize attempt.
590
+ this.enqueuedSummary.resultsBuilder.fail(
591
+ "Aborted; overridden by another enqueue summarize attempt",
592
+ undefined,
593
+ );
594
+ this.enqueuedSummary = undefined;
595
+ overridden = true;
596
+ }
597
+ this.enqueuedSummary = {
598
+ reason: onDemandReason,
599
+ afterSequenceNumber,
600
+ options,
601
+ resultsBuilder: new SummarizeResultBuilder(),
602
+ };
603
+ const results = this.enqueuedSummary.resultsBuilder.build();
604
+ this.tryRunEnqueuedSummary();
605
+ return overridden
606
+ ? {
607
+ ...results,
608
+ alreadyEnqueued: true,
609
+ overridden: true,
610
+ }
611
+ : results;
612
+ }
613
+
614
+ private tryRunEnqueuedSummary() {
615
+ if (this.stopping) {
616
+ this.disposeEnqueuedSummary();
617
+ return false;
618
+ }
619
+ if (
620
+ this.enqueuedSummary === undefined ||
621
+ this.heuristicData.lastOpSequenceNumber < this.enqueuedSummary.afterSequenceNumber ||
622
+ this.summarizingLock !== undefined
623
+ ) {
624
+ // If no enqueued summary is ready or a summary is already in progress, take no action.
625
+ return false;
626
+ }
627
+ const { reason, resultsBuilder, options } = this.enqueuedSummary;
628
+ // Set to undefined first, so that subsequent enqueue attempt while summarize will occur later.
629
+ this.enqueuedSummary = undefined;
630
+ this.trySummarizeOnce(
631
+ { reason: `enqueuedSummary/${reason}` },
632
+ options,
633
+ this.cancellationToken,
634
+ resultsBuilder,
635
+ );
636
+ return true;
637
+ }
638
+
639
+ private disposeEnqueuedSummary() {
640
+ if (this.enqueuedSummary !== undefined) {
641
+ this.enqueuedSummary.resultsBuilder.fail(
642
+ "RunningSummarizer stopped or disposed",
643
+ undefined,
644
+ );
645
+ this.enqueuedSummary = undefined;
646
+ }
647
+ }
610
648
  }