@fluidframework/container-runtime 2.23.0 → 2.31.0

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 (317) hide show
  1. package/CHANGELOG.md +593 -537
  2. package/api-report/container-runtime.legacy.alpha.api.md +0 -246
  3. package/dist/blobManager/blobManager.d.ts +11 -9
  4. package/dist/blobManager/blobManager.d.ts.map +1 -1
  5. package/dist/blobManager/blobManager.js +38 -39
  6. package/dist/blobManager/blobManager.js.map +1 -1
  7. package/dist/blobManager/blobManagerSnapSum.d.ts +2 -4
  8. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  9. package/dist/blobManager/blobManagerSnapSum.js +6 -6
  10. package/dist/blobManager/blobManagerSnapSum.js.map +1 -1
  11. package/dist/channelCollection.d.ts +1 -7
  12. package/dist/channelCollection.d.ts.map +1 -1
  13. package/dist/channelCollection.js +2 -27
  14. package/dist/channelCollection.js.map +1 -1
  15. package/dist/connectionTelemetry.d.ts +0 -43
  16. package/dist/connectionTelemetry.d.ts.map +1 -1
  17. package/dist/connectionTelemetry.js.map +1 -1
  18. package/dist/containerRuntime.d.ts +40 -145
  19. package/dist/containerRuntime.d.ts.map +1 -1
  20. package/dist/containerRuntime.js +149 -364
  21. package/dist/containerRuntime.js.map +1 -1
  22. package/dist/dataStoreContext.d.ts +6 -14
  23. package/dist/dataStoreContext.d.ts.map +1 -1
  24. package/dist/dataStoreContext.js +14 -26
  25. package/dist/dataStoreContext.js.map +1 -1
  26. package/dist/gc/garbageCollection.d.ts.map +1 -1
  27. package/dist/gc/garbageCollection.js +2 -20
  28. package/dist/gc/garbageCollection.js.map +1 -1
  29. package/dist/gc/gcConfigs.d.ts.map +1 -1
  30. package/dist/gc/gcConfigs.js +0 -2
  31. package/dist/gc/gcConfigs.js.map +1 -1
  32. package/dist/gc/gcDefinitions.d.ts +8 -24
  33. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  34. package/dist/gc/gcDefinitions.js +1 -3
  35. package/dist/gc/gcDefinitions.js.map +1 -1
  36. package/dist/gc/gcHelpers.d.ts.map +1 -1
  37. package/dist/gc/gcHelpers.js +1 -4
  38. package/dist/gc/gcHelpers.js.map +1 -1
  39. package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
  40. package/dist/gc/gcSummaryStateTracker.js +0 -1
  41. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  42. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  43. package/dist/gc/gcTelemetry.js +6 -18
  44. package/dist/gc/gcTelemetry.js.map +1 -1
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +2 -2
  48. package/dist/index.js.map +1 -1
  49. package/dist/legacy.d.ts +0 -29
  50. package/dist/messageTypes.d.ts.map +1 -1
  51. package/dist/messageTypes.js.map +1 -1
  52. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  53. package/dist/opLifecycle/batchManager.js +16 -5
  54. package/dist/opLifecycle/batchManager.js.map +1 -1
  55. package/dist/opLifecycle/outbox.d.ts +12 -3
  56. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  57. package/dist/opLifecycle/outbox.js +41 -21
  58. package/dist/opLifecycle/outbox.js.map +1 -1
  59. package/dist/packageVersion.d.ts +1 -1
  60. package/dist/packageVersion.js +1 -1
  61. package/dist/packageVersion.js.map +1 -1
  62. package/dist/pendingStateManager.d.ts +1 -0
  63. package/dist/pendingStateManager.d.ts.map +1 -1
  64. package/dist/pendingStateManager.js +12 -2
  65. package/dist/pendingStateManager.js.map +1 -1
  66. package/dist/runCounter.d.ts +11 -0
  67. package/dist/runCounter.d.ts.map +1 -0
  68. package/dist/runCounter.js +43 -0
  69. package/dist/runCounter.js.map +1 -0
  70. package/dist/runtimeLayerCompatState.d.ts +51 -0
  71. package/dist/runtimeLayerCompatState.d.ts.map +1 -0
  72. package/dist/runtimeLayerCompatState.js +123 -0
  73. package/dist/runtimeLayerCompatState.js.map +1 -0
  74. package/dist/signalTelemetryProcessing.d.ts +33 -0
  75. package/dist/signalTelemetryProcessing.d.ts.map +1 -0
  76. package/dist/signalTelemetryProcessing.js +149 -0
  77. package/dist/signalTelemetryProcessing.js.map +1 -0
  78. package/dist/summary/documentSchema.d.ts +7 -31
  79. package/dist/summary/documentSchema.d.ts.map +1 -1
  80. package/dist/summary/documentSchema.js +2 -18
  81. package/dist/summary/documentSchema.js.map +1 -1
  82. package/dist/summary/index.d.ts +2 -1
  83. package/dist/summary/index.d.ts.map +1 -1
  84. package/dist/summary/index.js +7 -1
  85. package/dist/summary/index.js.map +1 -1
  86. package/dist/summary/orderedClientElection.d.ts +1 -3
  87. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  88. package/dist/summary/orderedClientElection.js.map +1 -1
  89. package/dist/summary/runWhileConnectedCoordinator.d.ts +1 -3
  90. package/dist/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
  91. package/dist/summary/runWhileConnectedCoordinator.js +2 -7
  92. package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
  93. package/dist/summary/runningSummarizer.d.ts +1 -2
  94. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  95. package/dist/summary/runningSummarizer.js +4 -23
  96. package/dist/summary/runningSummarizer.js.map +1 -1
  97. package/dist/summary/summarizer.d.ts +2 -5
  98. package/dist/summary/summarizer.d.ts.map +1 -1
  99. package/dist/summary/summarizer.js +3 -11
  100. package/dist/summary/summarizer.js.map +1 -1
  101. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  102. package/dist/summary/summarizerClientElection.js +0 -1
  103. package/dist/summary/summarizerClientElection.js.map +1 -1
  104. package/dist/summary/summarizerHeuristics.d.ts +1 -1
  105. package/dist/summary/summarizerHeuristics.d.ts.map +1 -1
  106. package/dist/summary/summarizerHeuristics.js.map +1 -1
  107. package/dist/summary/summarizerNode/summarizerNode.d.ts +2 -2
  108. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  109. package/dist/summary/summarizerNode/summarizerNode.js +4 -4
  110. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  111. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +1 -18
  112. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  113. package/dist/summary/summarizerNode/summarizerNodeUtils.js +0 -27
  114. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  115. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +2 -2
  116. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  117. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +1 -2
  118. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  119. package/dist/summary/summarizerTypes.d.ts +109 -22
  120. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  121. package/dist/summary/summarizerTypes.js.map +1 -1
  122. package/dist/summary/summaryFormat.d.ts +3 -9
  123. package/dist/summary/summaryFormat.d.ts.map +1 -1
  124. package/dist/summary/summaryFormat.js.map +1 -1
  125. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  126. package/dist/summary/summaryGenerator.js +3 -9
  127. package/dist/summary/summaryGenerator.js.map +1 -1
  128. package/dist/summary/summaryHelpers.d.ts +19 -0
  129. package/dist/summary/summaryHelpers.d.ts.map +1 -0
  130. package/dist/summary/summaryHelpers.js +90 -0
  131. package/dist/summary/summaryHelpers.js.map +1 -0
  132. package/dist/summary/summaryManager.d.ts.map +1 -1
  133. package/dist/summary/summaryManager.js +0 -2
  134. package/dist/summary/summaryManager.js.map +1 -1
  135. package/lib/blobManager/blobManager.d.ts +11 -9
  136. package/lib/blobManager/blobManager.d.ts.map +1 -1
  137. package/lib/blobManager/blobManager.js +37 -37
  138. package/lib/blobManager/blobManager.js.map +1 -1
  139. package/lib/blobManager/blobManagerSnapSum.d.ts +2 -4
  140. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  141. package/lib/blobManager/blobManagerSnapSum.js +6 -6
  142. package/lib/blobManager/blobManagerSnapSum.js.map +1 -1
  143. package/lib/channelCollection.d.ts +1 -7
  144. package/lib/channelCollection.d.ts.map +1 -1
  145. package/lib/channelCollection.js +3 -30
  146. package/lib/channelCollection.js.map +1 -1
  147. package/lib/connectionTelemetry.d.ts +0 -43
  148. package/lib/connectionTelemetry.d.ts.map +1 -1
  149. package/lib/connectionTelemetry.js.map +1 -1
  150. package/lib/containerRuntime.d.ts +40 -145
  151. package/lib/containerRuntime.d.ts.map +1 -1
  152. package/lib/containerRuntime.js +151 -372
  153. package/lib/containerRuntime.js.map +1 -1
  154. package/lib/dataStoreContext.d.ts +6 -14
  155. package/lib/dataStoreContext.d.ts.map +1 -1
  156. package/lib/dataStoreContext.js +14 -26
  157. package/lib/dataStoreContext.js.map +1 -1
  158. package/lib/gc/garbageCollection.d.ts.map +1 -1
  159. package/lib/gc/garbageCollection.js +3 -23
  160. package/lib/gc/garbageCollection.js.map +1 -1
  161. package/lib/gc/gcConfigs.d.ts.map +1 -1
  162. package/lib/gc/gcConfigs.js +0 -2
  163. package/lib/gc/gcConfigs.js.map +1 -1
  164. package/lib/gc/gcDefinitions.d.ts +8 -24
  165. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  166. package/lib/gc/gcDefinitions.js +1 -3
  167. package/lib/gc/gcDefinitions.js.map +1 -1
  168. package/lib/gc/gcHelpers.d.ts.map +1 -1
  169. package/lib/gc/gcHelpers.js +1 -4
  170. package/lib/gc/gcHelpers.js.map +1 -1
  171. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  172. package/lib/gc/gcSummaryStateTracker.js +0 -1
  173. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  174. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  175. package/lib/gc/gcTelemetry.js +7 -21
  176. package/lib/gc/gcTelemetry.js.map +1 -1
  177. package/lib/index.d.ts +2 -2
  178. package/lib/index.d.ts.map +1 -1
  179. package/lib/index.js +2 -2
  180. package/lib/index.js.map +1 -1
  181. package/lib/legacy.d.ts +0 -29
  182. package/lib/messageTypes.d.ts.map +1 -1
  183. package/lib/messageTypes.js.map +1 -1
  184. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  185. package/lib/opLifecycle/batchManager.js +16 -5
  186. package/lib/opLifecycle/batchManager.js.map +1 -1
  187. package/lib/opLifecycle/outbox.d.ts +12 -3
  188. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  189. package/lib/opLifecycle/outbox.js +43 -23
  190. package/lib/opLifecycle/outbox.js.map +1 -1
  191. package/lib/packageVersion.d.ts +1 -1
  192. package/lib/packageVersion.js +1 -1
  193. package/lib/packageVersion.js.map +1 -1
  194. package/lib/pendingStateManager.d.ts +1 -0
  195. package/lib/pendingStateManager.d.ts.map +1 -1
  196. package/lib/pendingStateManager.js +12 -2
  197. package/lib/pendingStateManager.js.map +1 -1
  198. package/lib/runCounter.d.ts +11 -0
  199. package/lib/runCounter.d.ts.map +1 -0
  200. package/lib/runCounter.js +39 -0
  201. package/lib/runCounter.js.map +1 -0
  202. package/lib/runtimeLayerCompatState.d.ts +51 -0
  203. package/lib/runtimeLayerCompatState.d.ts.map +1 -0
  204. package/lib/runtimeLayerCompatState.js +118 -0
  205. package/lib/runtimeLayerCompatState.js.map +1 -0
  206. package/lib/signalTelemetryProcessing.d.ts +33 -0
  207. package/lib/signalTelemetryProcessing.d.ts.map +1 -0
  208. package/lib/signalTelemetryProcessing.js +145 -0
  209. package/lib/signalTelemetryProcessing.js.map +1 -0
  210. package/lib/summary/documentSchema.d.ts +7 -31
  211. package/lib/summary/documentSchema.d.ts.map +1 -1
  212. package/lib/summary/documentSchema.js +2 -18
  213. package/lib/summary/documentSchema.js.map +1 -1
  214. package/lib/summary/index.d.ts +2 -1
  215. package/lib/summary/index.d.ts.map +1 -1
  216. package/lib/summary/index.js +1 -0
  217. package/lib/summary/index.js.map +1 -1
  218. package/lib/summary/orderedClientElection.d.ts +1 -3
  219. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  220. package/lib/summary/orderedClientElection.js.map +1 -1
  221. package/lib/summary/runWhileConnectedCoordinator.d.ts +1 -3
  222. package/lib/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
  223. package/lib/summary/runWhileConnectedCoordinator.js +2 -7
  224. package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
  225. package/lib/summary/runningSummarizer.d.ts +1 -2
  226. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  227. package/lib/summary/runningSummarizer.js +4 -23
  228. package/lib/summary/runningSummarizer.js.map +1 -1
  229. package/lib/summary/summarizer.d.ts +2 -5
  230. package/lib/summary/summarizer.d.ts.map +1 -1
  231. package/lib/summary/summarizer.js +3 -11
  232. package/lib/summary/summarizer.js.map +1 -1
  233. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  234. package/lib/summary/summarizerClientElection.js +0 -1
  235. package/lib/summary/summarizerClientElection.js.map +1 -1
  236. package/lib/summary/summarizerHeuristics.d.ts +1 -1
  237. package/lib/summary/summarizerHeuristics.d.ts.map +1 -1
  238. package/lib/summary/summarizerHeuristics.js.map +1 -1
  239. package/lib/summary/summarizerNode/summarizerNode.d.ts +2 -2
  240. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  241. package/lib/summary/summarizerNode/summarizerNode.js +5 -5
  242. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  243. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +1 -18
  244. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  245. package/lib/summary/summarizerNode/summarizerNodeUtils.js +1 -25
  246. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  247. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +2 -2
  248. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  249. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +1 -2
  250. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  251. package/lib/summary/summarizerTypes.d.ts +109 -22
  252. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  253. package/lib/summary/summarizerTypes.js.map +1 -1
  254. package/lib/summary/summaryFormat.d.ts +3 -9
  255. package/lib/summary/summaryFormat.d.ts.map +1 -1
  256. package/lib/summary/summaryFormat.js.map +1 -1
  257. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  258. package/lib/summary/summaryGenerator.js +3 -9
  259. package/lib/summary/summaryGenerator.js.map +1 -1
  260. package/lib/summary/summaryHelpers.d.ts +19 -0
  261. package/lib/summary/summaryHelpers.d.ts.map +1 -0
  262. package/lib/summary/summaryHelpers.js +84 -0
  263. package/lib/summary/summaryHelpers.js.map +1 -0
  264. package/lib/summary/summaryManager.d.ts.map +1 -1
  265. package/lib/summary/summaryManager.js +0 -2
  266. package/lib/summary/summaryManager.js.map +1 -1
  267. package/lib/tsdoc-metadata.json +1 -1
  268. package/package.json +20 -23
  269. package/src/blobManager/blobManager.ts +70 -62
  270. package/src/blobManager/blobManagerSnapSum.ts +7 -9
  271. package/src/channelCollection.ts +4 -32
  272. package/src/connectionTelemetry.ts +0 -51
  273. package/src/containerRuntime.ts +259 -622
  274. package/src/dataStoreContext.ts +24 -33
  275. package/src/gc/{garbageCollection.md → README.md} +17 -19
  276. package/src/gc/garbageCollection.ts +9 -26
  277. package/src/gc/gcConfigs.ts +3 -6
  278. package/src/gc/gcDefinitions.ts +10 -28
  279. package/src/gc/gcHelpers.ts +0 -5
  280. package/src/gc/gcSummaryStateTracker.ts +1 -2
  281. package/src/gc/gcTelemetry.ts +8 -15
  282. package/src/index.ts +6 -6
  283. package/src/messageTypes.ts +0 -2
  284. package/src/opLifecycle/batchManager.ts +20 -6
  285. package/src/opLifecycle/outbox.ts +64 -24
  286. package/src/packageVersion.ts +1 -1
  287. package/src/pendingStateManager.ts +18 -2
  288. package/src/runCounter.ts +25 -0
  289. package/src/runtimeLayerCompatState.ts +143 -0
  290. package/src/signalTelemetryProcessing.ts +233 -0
  291. package/src/summary/documentSchema.ts +7 -38
  292. package/src/summary/index.ts +12 -0
  293. package/src/summary/orderedClientElection.ts +1 -3
  294. package/src/summary/runWhileConnectedCoordinator.ts +3 -8
  295. package/src/summary/runningSummarizer.ts +12 -20
  296. package/src/summary/summarizer.ts +6 -18
  297. package/src/summary/summarizerClientElection.ts +0 -2
  298. package/src/summary/summarizerHeuristics.ts +1 -2
  299. package/src/summary/summarizerNode/summarizerNode.ts +6 -5
  300. package/src/summary/summarizerNode/summarizerNodeUtils.ts +1 -27
  301. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +2 -3
  302. package/src/summary/summarizerTypes.ts +119 -23
  303. package/src/summary/summaryFormat.ts +4 -13
  304. package/src/summary/summaryGenerator.ts +1 -8
  305. package/src/summary/summaryHelpers.ts +118 -0
  306. package/src/summary/summaryManager.ts +0 -2
  307. package/tsconfig.json +1 -0
  308. package/dist/layerCompatState.d.ts +0 -19
  309. package/dist/layerCompatState.d.ts.map +0 -1
  310. package/dist/layerCompatState.js +0 -64
  311. package/dist/layerCompatState.js.map +0 -1
  312. package/lib/layerCompatState.d.ts +0 -19
  313. package/lib/layerCompatState.d.ts.map +0 -1
  314. package/lib/layerCompatState.js +0 -60
  315. package/lib/layerCompatState.js.map +0 -1
  316. package/prettier.config.cjs +0 -8
  317. package/src/layerCompatState.ts +0 -75
@@ -14,7 +14,6 @@ import {
14
14
  import { IDataStoreAliasMessage } from "./dataStore.js";
15
15
  import { GarbageCollectionMessage } from "./gc/index.js";
16
16
  import { IChunkedOp } from "./opLifecycle/index.js";
17
- // eslint-disable-next-line import/no-deprecated
18
17
  import { IDocumentSchemaChangeMessage } from "./summary/index.js";
19
18
 
20
19
  /**
@@ -115,7 +114,6 @@ export type ContainerRuntimeGCMessage = TypedContainerRuntimeMessage<
115
114
  >;
116
115
  export type ContainerRuntimeDocumentSchemaMessage = TypedContainerRuntimeMessage<
117
116
  ContainerMessageType.DocumentSchemaChange,
118
- // eslint-disable-next-line import/no-deprecated
119
117
  IDocumentSchemaChangeMessage
120
118
  >;
121
119
 
@@ -4,6 +4,11 @@
4
4
  */
5
5
 
6
6
  import { assert } from "@fluidframework/core-utils/internal";
7
+ import {
8
+ LoggingError,
9
+ tagData,
10
+ TelemetryDataTag,
11
+ } from "@fluidframework/telemetry-utils/internal";
7
12
 
8
13
  import { ICompressionRuntimeOptions } from "../containerRuntime.js";
9
14
  import { asBatchMetadata, type IBatchMetadata } from "../metadata.js";
@@ -99,7 +104,8 @@ export class BatchManager {
99
104
  private get referenceSequenceNumber(): number | undefined {
100
105
  return this.pendingBatch.length === 0
101
106
  ? undefined
102
- : this.pendingBatch[this.pendingBatch.length - 1].referenceSequenceNumber;
107
+ : // NOTE: In case of reentrant ops, there could be multiple reference sequence numbers, but we will rebase before submitting.
108
+ this.pendingBatch[this.pendingBatch.length - 1].referenceSequenceNumber;
103
109
  }
104
110
 
105
111
  /**
@@ -166,17 +172,25 @@ export class BatchManager {
166
172
  * Capture the pending state at this point
167
173
  */
168
174
  public checkpoint(): IBatchCheckpoint {
175
+ const startSequenceNumber = this.clientSequenceNumber;
169
176
  const startPoint = this.pendingBatch.length;
170
177
  return {
171
178
  rollback: (process: (message: BatchMessage) => void) => {
172
- for (let i = this.pendingBatch.length; i > startPoint; ) {
173
- i--;
174
- const message = this.pendingBatch[i];
179
+ this.clientSequenceNumber = startSequenceNumber;
180
+ const rollbackOpsLifo = this.pendingBatch.splice(startPoint).reverse();
181
+ for (const message of rollbackOpsLifo) {
175
182
  this.batchContentSize -= message.contents?.length ?? 0;
176
183
  process(message);
177
184
  }
178
-
179
- this.pendingBatch.length = startPoint;
185
+ const count = this.pendingBatch.length - startPoint;
186
+ if (count !== 0) {
187
+ throw new LoggingError("Ops generated durning rollback", {
188
+ count,
189
+ ...tagData(TelemetryDataTag.UserData, {
190
+ ops: JSON.stringify(this.pendingBatch.slice(startPoint).map((b) => b.contents)),
191
+ }),
192
+ });
193
+ }
180
194
  },
181
195
  };
182
196
  }
@@ -6,8 +6,9 @@
6
6
  import { ICriticalContainerError } from "@fluidframework/container-definitions";
7
7
  import { IBatchMessage } from "@fluidframework/container-definitions/internal";
8
8
  import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
9
- import { assert } from "@fluidframework/core-utils/internal";
9
+ import { assert, Lazy } from "@fluidframework/core-utils/internal";
10
10
  import {
11
+ DataProcessingError,
11
12
  GenericError,
12
13
  UsageError,
13
14
  createChildLogger,
@@ -34,9 +35,16 @@ import { ensureContentsDeserialized } from "./remoteMessageProcessor.js";
34
35
 
35
36
  export interface IOutboxConfig {
36
37
  readonly compressionOptions: ICompressionRuntimeOptions;
37
- // The maximum size of a batch that we can send over the wire.
38
+ /**
39
+ * The maximum size of a batch that we can send over the wire.
40
+ */
38
41
  readonly maxBatchSizeInBytes: number;
39
- readonly disablePartialFlush: boolean;
42
+ /**
43
+ * If true, maybeFlushPartialBatch will flush the batch if the reference sequence number changed
44
+ * since the batch started. Otherwise, it will throw in this case (apart from reentrancy which is handled elsewhere).
45
+ * Once the new throw-based flow is proved in a production environment, this option will be removed.
46
+ */
47
+ readonly flushPartialBatches: boolean;
40
48
  }
41
49
 
42
50
  export interface IOutboxParameters {
@@ -170,8 +178,9 @@ export class Outbox {
170
178
 
171
179
  /**
172
180
  * Detect whether batching has been interrupted by an incoming message being processed. In this case,
173
- * we will flush the accumulated messages to account for that and create a new batch with the new
174
- * message as the first message.
181
+ * we will flush the accumulated messages to account for that (if allowed) and create a new batch with the new
182
+ * message as the first message. If flushing partial batch is not enabled, we will throw (except for reentrant ops).
183
+ * This would indicate we expected this case to be precluded by logic elsewhere.
175
184
  *
176
185
  * @remarks - To detect batch interruption, we compare both the reference sequence number
177
186
  * (i.e. last message processed by DeltaManager) and the client sequence number of the
@@ -183,9 +192,8 @@ export class Outbox {
183
192
  const blobAttachSeqNums = this.blobAttachBatch.sequenceNumbers;
184
193
  const idAllocSeqNums = this.idAllocationBatch.sequenceNumbers;
185
194
  assert(
186
- this.params.config.disablePartialFlush ||
187
- (sequenceNumbersMatch(mainBatchSeqNums, blobAttachSeqNums) &&
188
- sequenceNumbersMatch(mainBatchSeqNums, idAllocSeqNums)),
195
+ sequenceNumbersMatch(mainBatchSeqNums, blobAttachSeqNums) &&
196
+ sequenceNumbersMatch(mainBatchSeqNums, idAllocSeqNums),
189
197
  0x58d /* Reference sequence numbers from both batches must be in sync */,
190
198
  );
191
199
 
@@ -200,25 +208,54 @@ export class Outbox {
200
208
  return;
201
209
  }
202
210
 
211
+ // Reference and/or Client sequence number will be advancing while processing this batch,
212
+ // so we can't use this check to detect wrongdoing. But we will still log via telemetry.
213
+ // This is rare, and the reentrancy will be handled during Flush.
214
+ const expectedDueToReentrancy = this.isContextReentrant();
215
+
216
+ const errorWrapper = new Lazy(() =>
217
+ getLongStack(() =>
218
+ DataProcessingError.create(
219
+ "Sequence numbers advanced as if ops were processed while a batch is accumulating",
220
+ "outboxSequenceNumberCoherencyCheck",
221
+ ),
222
+ ),
223
+ );
203
224
  if (++this.mismatchedOpsReported <= this.maxMismatchedOpsToReport) {
204
225
  this.logger.sendTelemetryEvent(
205
226
  {
206
- category: this.params.config.disablePartialFlush ? "error" : "generic",
227
+ // Only log error if this is truly unexpected
228
+ category:
229
+ expectedDueToReentrancy || this.params.config.flushPartialBatches
230
+ ? "generic"
231
+ : "error",
207
232
  eventName: "ReferenceSequenceNumberMismatch",
208
- mainReferenceSequenceNumber: mainBatchSeqNums.referenceSequenceNumber,
209
- mainClientSequenceNumber: mainBatchSeqNums.clientSequenceNumber,
210
- blobAttachReferenceSequenceNumber: blobAttachSeqNums.referenceSequenceNumber,
211
- blobAttachClientSequenceNumber: blobAttachSeqNums.clientSequenceNumber,
212
- currentReferenceSequenceNumber: currentSequenceNumbers.referenceSequenceNumber,
213
- currentClientSequenceNumber: currentSequenceNumbers.clientSequenceNumber,
233
+ Data_details: {
234
+ expectedDueToReentrancy,
235
+ mainReferenceSequenceNumber: mainBatchSeqNums.referenceSequenceNumber,
236
+ mainClientSequenceNumber: mainBatchSeqNums.clientSequenceNumber,
237
+ blobAttachReferenceSequenceNumber: blobAttachSeqNums.referenceSequenceNumber,
238
+ blobAttachClientSequenceNumber: blobAttachSeqNums.clientSequenceNumber,
239
+ currentReferenceSequenceNumber: currentSequenceNumbers.referenceSequenceNumber,
240
+ currentClientSequenceNumber: currentSequenceNumbers.clientSequenceNumber,
241
+ },
214
242
  },
215
- getLongStack(() => new UsageError("Submission of an out of order message")),
243
+ errorWrapper.value,
216
244
  );
217
245
  }
218
246
 
219
- if (!this.params.config.disablePartialFlush) {
247
+ // If we're configured to flush partial batches, do that now and return (don't throw)
248
+ if (this.params.config.flushPartialBatches) {
220
249
  this.flushAll();
250
+ return;
221
251
  }
252
+
253
+ // If we are in a reentrant context, we know this can happen without causing any harm.
254
+ if (expectedDueToReentrancy) {
255
+ return;
256
+ }
257
+
258
+ throw errorWrapper.value;
222
259
  }
223
260
 
224
261
  public submit(message: BatchMessage): void {
@@ -273,17 +310,20 @@ export class Outbox {
273
310
  }
274
311
 
275
312
  private flushAll(resubmittingBatchId?: BatchId): void {
276
- // If we're resubmitting and all batches are empty, we need to flush an empty batch.
277
- // Note that we currently resubmit one batch at a time, so on resubmit, 2 of the 3 batches will *always* be empty.
278
- // It's theoretically possible that we don't *need* to resubmit this empty batch, and in those cases, it'll safely be ignored
279
- // by the rest of the system, including remote clients.
280
- // In some cases we *must* resubmit the empty batch (to match up with a non-empty version tracked locally by a container fork), so we do it always.
281
313
  const allBatchesEmpty =
282
314
  this.idAllocationBatch.empty && this.blobAttachBatch.empty && this.mainBatch.empty;
283
- if (resubmittingBatchId && allBatchesEmpty) {
284
- this.flushEmptyBatch(resubmittingBatchId);
315
+ if (allBatchesEmpty) {
316
+ // If we're resubmitting and all batches are empty, we need to flush an empty batch.
317
+ // Note that we currently resubmit one batch at a time, so on resubmit, 2 of the 3 batches will *always* be empty.
318
+ // It's theoretically possible that we don't *need* to resubmit this empty batch, and in those cases, it'll safely be ignored
319
+ // by the rest of the system, including remote clients.
320
+ // In some cases we *must* resubmit the empty batch (to match up with a non-empty version tracked locally by a container fork), so we do it always.
321
+ if (resubmittingBatchId) {
322
+ this.flushEmptyBatch(resubmittingBatchId);
323
+ }
285
324
  return;
286
325
  }
326
+
287
327
  // Don't use resubmittingBatchId for idAllocationBatch.
288
328
  // ID Allocation messages are not directly resubmitted so we don't want to reuse the batch ID.
289
329
  this.flushInternal(this.idAllocationBatch);
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.23.0";
9
+ export const pkgVersion = "2.31.0";
@@ -134,7 +134,7 @@ function scrubAndStringify(
134
134
  // Scrub the whole object in case there are unexpected keys
135
135
  const scrubbed: Record<string, unknown> = typesOfKeys(message);
136
136
 
137
- // For these known/expected keys, we can either drill in (for contents)
137
+ // For these known/expected keys, we can either drill into the object (for contents)
138
138
  // or just use the value as-is (since it's not personal info)
139
139
  scrubbed.contents = message.contents && typesOfKeys(message.contents);
140
140
  scrubbed.type = message.type;
@@ -145,7 +145,7 @@ function scrubAndStringify(
145
145
  /**
146
146
  * Finds and returns the index where the strings diverge, and the character at that index in each string (or undefined if not applicable)
147
147
  */
148
- export function findFirstCharacterMismatched(
148
+ function findFirstRawCharacterMismatched(
149
149
  a: string,
150
150
  b: string,
151
151
  ): [index: number, charA?: string, charB?: string] {
@@ -164,6 +164,22 @@ export function findFirstCharacterMismatched(
164
164
  : [minLength, a[minLength], b[minLength]];
165
165
  }
166
166
 
167
+ /**
168
+ * Finds and returns the index where the strings diverge, and the character at that index in each string (or undefined if not applicable)
169
+ * It scrubs non-ASCII characters since they convey more meaning (privacy consideration)
170
+ */
171
+ export function findFirstCharacterMismatched(
172
+ a: string,
173
+ b: string,
174
+ ): [index: number, charA?: string, charB?: string] {
175
+ const [index, rawCharA, rawCharB] = findFirstRawCharacterMismatched(a, b);
176
+
177
+ const charA = (rawCharA?.codePointAt(0) ?? 0) <= 0x7f ? rawCharA : "[non-ASCII]";
178
+ const charB = (rawCharB?.codePointAt(0) ?? 0) <= 0x7f ? rawCharB : "[non-ASCII]";
179
+
180
+ return [index, charA, charB];
181
+ }
182
+
167
183
  function withoutLocalOpMetadata(message: IPendingMessage): IPendingMessage {
168
184
  return {
169
185
  ...message,
@@ -0,0 +1,25 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export class RunCounter {
7
+ #runs = 0;
8
+
9
+ public get running(): boolean {
10
+ return this.#runs !== 0;
11
+ }
12
+
13
+ public get runs(): number {
14
+ return this.#runs;
15
+ }
16
+
17
+ public run<T>(act: () => T): T {
18
+ this.#runs++;
19
+ try {
20
+ return act();
21
+ } finally {
22
+ this.#runs--;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,143 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ checkLayerCompatibility,
8
+ type ILayerCompatDetails,
9
+ type ILayerCompatSupportRequirements,
10
+ } from "@fluid-internal/client-utils";
11
+ import type { ICriticalContainerError } from "@fluidframework/container-definitions";
12
+ import { UsageError } from "@fluidframework/telemetry-utils/internal";
13
+
14
+ import { pkgVersion } from "./packageVersion.js";
15
+
16
+ /**
17
+ * The core compatibility details of the Runtime layer that is the same across all layer boundaries.
18
+ * @internal
19
+ */
20
+ export const runtimeCoreCompatDetails = {
21
+ /**
22
+ * The package version of the Runtime layer.
23
+ */
24
+ pkgVersion,
25
+ /**
26
+ * The current generation of the Runtime layer.
27
+ */
28
+ generation: 1,
29
+ };
30
+
31
+ /**
32
+ * Runtime's compatibility details that is exposed to the Loader layer.
33
+ * @internal
34
+ */
35
+ export const runtimeCompatDetailsForLoader: ILayerCompatDetails = {
36
+ ...runtimeCoreCompatDetails,
37
+ /**
38
+ * The features supported by the Runtime layer across the Runtime / Loader boundary.
39
+ */
40
+ supportedFeatures: new Set<string>(),
41
+ };
42
+
43
+ /**
44
+ * The requirements that the Loader layer must meet to be compatible with this Runtime.
45
+ * @internal
46
+ */
47
+ export const loaderSupportRequirements: ILayerCompatSupportRequirements = {
48
+ /**
49
+ * Minimum generation that Loader must be at to be compatible with Runtime. Note that 0 is used here so
50
+ * that Loader layers before the introduction of the layer compatibility enforcement are compatible.
51
+ */
52
+ minSupportedGeneration: 0,
53
+ /**
54
+ * The features that the Loader must support to be compatible with Runtime.
55
+ */
56
+ requiredFeatures: [],
57
+ };
58
+
59
+ /**
60
+ * Runtime's compatibility details that is exposed to the DataStore layer.
61
+ * @internal
62
+ */
63
+ export const runtimeCompatDetailsForDataStore: ILayerCompatDetails = {
64
+ ...runtimeCoreCompatDetails,
65
+ /**
66
+ * The features supported by the Runtime layer across the Runtime / Loader boundary.
67
+ */
68
+ supportedFeatures: new Set<string>(),
69
+ };
70
+
71
+ /**
72
+ * The requirements that the DataStore layer must meet to be compatible with this Runtime.
73
+ * @internal
74
+ */
75
+ export const dataStoreSupportRequirements: ILayerCompatSupportRequirements = {
76
+ /**
77
+ * Minimum generation that DataStore must be at to be compatible with Runtime. Note that 0 is used here so
78
+ * that DataStore layers before the introduction of the layer compatibility enforcement are compatible.
79
+ */
80
+ minSupportedGeneration: 0,
81
+ /**
82
+ * The features that the DataStore must support to be compatible with Runtime.
83
+ */
84
+ requiredFeatures: [],
85
+ };
86
+
87
+ /**
88
+ * Validates that the Loader layer is compatible with this Runtime.
89
+ * @internal
90
+ */
91
+ export function validateLoaderCompatibility(
92
+ maybeloaderCompatDetailsForRuntime: ILayerCompatDetails | undefined,
93
+ disposeFn: (error?: ICriticalContainerError) => void,
94
+ ): void {
95
+ const layerCheckResult = checkLayerCompatibility(
96
+ loaderSupportRequirements,
97
+ maybeloaderCompatDetailsForRuntime,
98
+ );
99
+ if (!layerCheckResult.isCompatible) {
100
+ const error = new UsageError("Runtime is not compatible with Loader", {
101
+ errorDetails: JSON.stringify({
102
+ runtimeVersion: runtimeCoreCompatDetails.pkgVersion,
103
+ loaderVersion: maybeloaderCompatDetailsForRuntime?.pkgVersion,
104
+ runtimeGeneration: runtimeCoreCompatDetails.generation,
105
+ loaderGeneration: maybeloaderCompatDetailsForRuntime?.generation,
106
+ minSupportedGeneration: loaderSupportRequirements.minSupportedGeneration,
107
+ isGenerationCompatible: layerCheckResult.isGenerationCompatible,
108
+ unsupportedFeatures: layerCheckResult.unsupportedFeatures,
109
+ }),
110
+ });
111
+ disposeFn(error);
112
+ throw error;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Validates that the DataStore layer is compatible with this Runtime.
118
+ * @internal
119
+ */
120
+ export function validateDatastoreCompatibility(
121
+ maybeDataStoreCompatDetails: ILayerCompatDetails | undefined,
122
+ disposeFn: () => void,
123
+ ): void {
124
+ const layerCheckResult = checkLayerCompatibility(
125
+ dataStoreSupportRequirements,
126
+ maybeDataStoreCompatDetails,
127
+ );
128
+ if (!layerCheckResult.isCompatible) {
129
+ const error = new UsageError("Runtime is not compatible with DataStore", {
130
+ errorDetails: JSON.stringify({
131
+ runtimeVersion: runtimeCoreCompatDetails.pkgVersion,
132
+ dataStoreVersion: maybeDataStoreCompatDetails?.pkgVersion,
133
+ runtimeGeneration: runtimeCoreCompatDetails.generation,
134
+ dataStoreGeneration: maybeDataStoreCompatDetails?.generation,
135
+ minSupportedGeneration: dataStoreSupportRequirements.minSupportedGeneration,
136
+ isGenerationCompatible: layerCheckResult.isGenerationCompatible,
137
+ unsupportedFeatures: layerCheckResult.unsupportedFeatures,
138
+ }),
139
+ });
140
+ disposeFn();
141
+ throw error;
142
+ }
143
+ }
@@ -0,0 +1,233 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import type { ISignalEnvelope } from "@fluidframework/core-interfaces/internal";
7
+ import type {
8
+ ITelemetryLoggerExt,
9
+ TelemetryEventPropertyTypeExt,
10
+ } from "@fluidframework/telemetry-utils/internal";
11
+
12
+ const defaultTelemetrySignalSampleCount = 100;
13
+
14
+ /**
15
+ * Set of stats/values used to keep track of telemetry related to signals.
16
+ */
17
+ interface ISignalTelemetryTracking {
18
+ /**
19
+ * Accumulates the total number of broadcast signals sent during the current signal latency measurement window.
20
+ * This value represents the total number of signals sent since the latency measurement began and is used
21
+ * logged in telemetry when the latency measurement completes.
22
+ */
23
+ totalSignalsSentInLatencyWindow: number;
24
+
25
+ /**
26
+ * Counts the number of broadcast signals sent since the last latency measurement was initiated.
27
+ * This counter increments with each broadcast signal sent. When a new latency measurement starts,
28
+ * this counter is added to `totalSignalsSentInLatencyWindow` and then reset to zero.
29
+ */
30
+ signalsSentSinceLastLatencyMeasurement: number;
31
+
32
+ /**
33
+ * Number of signals that were expected but not received.
34
+ */
35
+ signalsLost: number;
36
+
37
+ /**
38
+ * Number of signals received out of order/non-sequentially.
39
+ */
40
+ signalsOutOfOrder: number;
41
+
42
+ /**
43
+ * Timestamp before submitting the signal we will trace.
44
+ */
45
+ signalTimestamp: number;
46
+
47
+ /**
48
+ * Signal we will trace for roundtrip latency.
49
+ */
50
+ roundTripSignalSequenceNumber: number | undefined;
51
+
52
+ /**
53
+ * Next expected signal sequence number to be received.
54
+ */
55
+ trackingSignalSequenceNumber: number | undefined;
56
+
57
+ /**
58
+ * Inclusive lower bound of signal monitoring window.
59
+ * Used by the logic that checks if signals are received out of order.
60
+ */
61
+ minimumTrackingSignalSequenceNumber: number | undefined;
62
+ }
63
+
64
+ export class SignalTelemetryManager {
65
+ private readonly signalTracking: ISignalTelemetryTracking = {
66
+ totalSignalsSentInLatencyWindow: 0,
67
+ signalsLost: 0,
68
+ signalsOutOfOrder: 0,
69
+ signalsSentSinceLastLatencyMeasurement: 0,
70
+ signalTimestamp: 0,
71
+ roundTripSignalSequenceNumber: undefined,
72
+ trackingSignalSequenceNumber: undefined,
73
+ minimumTrackingSignalSequenceNumber: undefined,
74
+ };
75
+
76
+ /**
77
+ * Identifier to track broadcast signals being submitted in order to
78
+ * allow collection of data around the roundtrip of signal messages.
79
+ */
80
+ private broadcastSignalSequenceNumber: number = 0;
81
+
82
+ /**
83
+ * Resets the signal tracking state in the {@link SignalTelemetryManager}.
84
+ */
85
+ public resetTracking(): void {
86
+ this.signalTracking.signalsLost = 0;
87
+ this.signalTracking.signalsOutOfOrder = 0;
88
+ this.signalTracking.signalTimestamp = 0;
89
+ this.signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
90
+ this.signalTracking.totalSignalsSentInLatencyWindow = 0;
91
+ this.signalTracking.roundTripSignalSequenceNumber = undefined;
92
+ this.signalTracking.trackingSignalSequenceNumber = undefined;
93
+ this.signalTracking.minimumTrackingSignalSequenceNumber = undefined;
94
+ }
95
+
96
+ /**
97
+ * Perform telemetry-related processing of incoming signals.
98
+ * @param envelope - The signal envelope to process.
99
+ * @param logger - The telemetry logger to use for emitting telemetry events.
100
+ * @param consecutiveReconnects - The number of consecutive reconnects that have occurred. Only used for logging.
101
+ */
102
+ public trackReceivedSignal(
103
+ envelope: ISignalEnvelope,
104
+ logger: ITelemetryLoggerExt,
105
+ consecutiveReconnects: number,
106
+ ): void {
107
+ const {
108
+ clientBroadcastSignalSequenceNumber,
109
+ contents: envelopeContents,
110
+ address: envelopeAddress,
111
+ } = envelope;
112
+
113
+ if (clientBroadcastSignalSequenceNumber === undefined) {
114
+ return undefined;
115
+ }
116
+
117
+ // If no tracking window has been set, nothing to do
118
+ if (
119
+ this.signalTracking.trackingSignalSequenceNumber === undefined ||
120
+ this.signalTracking.minimumTrackingSignalSequenceNumber === undefined
121
+ ) {
122
+ return undefined;
123
+ }
124
+
125
+ if (
126
+ clientBroadcastSignalSequenceNumber >= this.signalTracking.trackingSignalSequenceNumber
127
+ ) {
128
+ // Calculate the number of signals lost and log the event.
129
+ const signalsLost =
130
+ clientBroadcastSignalSequenceNumber - this.signalTracking.trackingSignalSequenceNumber;
131
+ if (signalsLost > 0) {
132
+ this.signalTracking.signalsLost += signalsLost;
133
+ logger.sendErrorEvent({
134
+ eventName: "SignalLost",
135
+ details: {
136
+ signalsLost, // Number of lost signals detected.
137
+ expectedSequenceNumber: this.signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
138
+ clientBroadcastSignalSequenceNumber, // Actual signal sequence number received.
139
+ },
140
+ });
141
+ }
142
+ // Update the tracking signal sequence number to the next expected signal in the sequence.
143
+ this.signalTracking.trackingSignalSequenceNumber =
144
+ clientBroadcastSignalSequenceNumber + 1;
145
+ } else if (
146
+ // Check if this is a signal in range of interest.
147
+ clientBroadcastSignalSequenceNumber >=
148
+ this.signalTracking.minimumTrackingSignalSequenceNumber
149
+ ) {
150
+ this.signalTracking.signalsOutOfOrder++;
151
+ const details: TelemetryEventPropertyTypeExt = {
152
+ expectedSequenceNumber: this.signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
153
+ clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
154
+ };
155
+ // Only log `contents.type` when address is for container to avoid chance that contents type is customer data.
156
+ if (envelopeAddress === undefined) {
157
+ details.contentsType = envelopeContents.type; // Type of signal that was received out of order.
158
+ }
159
+ logger.sendTelemetryEvent({
160
+ eventName: "SignalOutOfOrder",
161
+ details,
162
+ });
163
+ }
164
+
165
+ if (
166
+ this.signalTracking.roundTripSignalSequenceNumber !== undefined &&
167
+ clientBroadcastSignalSequenceNumber >= this.signalTracking.roundTripSignalSequenceNumber
168
+ ) {
169
+ if (
170
+ clientBroadcastSignalSequenceNumber ===
171
+ this.signalTracking.roundTripSignalSequenceNumber
172
+ ) {
173
+ // Latency tracked signal has been received.
174
+ // We now emit telemetry with the roundtrip duration of the tracked signal.
175
+ // The telemetry event also includes metrics for broadcast signals (sent, lost, and out of order),
176
+ // and these metrics are reset after emitting the event.
177
+ const duration = Date.now() - this.signalTracking.signalTimestamp;
178
+ logger.sendPerformanceEvent({
179
+ eventName: "SignalLatency",
180
+ details: {
181
+ duration, // Roundtrip duration of the tracked signal in milliseconds.
182
+ sent: this.signalTracking.totalSignalsSentInLatencyWindow, // Signals sent since the last logged SignalLatency event.
183
+ lost: this.signalTracking.signalsLost, // Signals lost since the last logged SignalLatency event.
184
+ outOfOrder: this.signalTracking.signalsOutOfOrder, // Out of order signals since the last logged SignalLatency event.
185
+ reconnectCount: consecutiveReconnects, // Container reconnect count.
186
+ },
187
+ });
188
+ this.signalTracking.signalsLost = 0;
189
+ this.signalTracking.signalsOutOfOrder = 0;
190
+ this.signalTracking.signalTimestamp = 0;
191
+ this.signalTracking.totalSignalsSentInLatencyWindow = 0;
192
+ }
193
+ this.signalTracking.roundTripSignalSequenceNumber = undefined;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Updates tracking state for broadcast signals based on the provided signal envelope, and updates the
199
+ * envelope with additional information that the signal needs to have stamped on it.
200
+ * @remarks Do not call this for non-broadcast signals.
201
+ * @param envelope - The signal envelope to process.
202
+ */
203
+ public applyTrackingToBroadcastSignalEnvelope(envelope: ISignalEnvelope): void {
204
+ const broadcastSignalSequenceNumber = ++this.broadcastSignalSequenceNumber;
205
+
206
+ // Stamp with the broadcast signal sequence number.
207
+ envelope.clientBroadcastSignalSequenceNumber = broadcastSignalSequenceNumber;
208
+
209
+ this.signalTracking.signalsSentSinceLastLatencyMeasurement++;
210
+
211
+ // If we don't have a signal monitoring window yet,
212
+ // initialize tracking to expect the next signal sent by the connected client.
213
+ if (
214
+ this.signalTracking.minimumTrackingSignalSequenceNumber === undefined ||
215
+ this.signalTracking.trackingSignalSequenceNumber === undefined
216
+ ) {
217
+ this.signalTracking.minimumTrackingSignalSequenceNumber = broadcastSignalSequenceNumber;
218
+ this.signalTracking.trackingSignalSequenceNumber = broadcastSignalSequenceNumber;
219
+ }
220
+
221
+ // Start tracking roundtrip for a new signal only if we are not tracking one already (and sampling logic is met)
222
+ if (
223
+ this.signalTracking.roundTripSignalSequenceNumber === undefined &&
224
+ broadcastSignalSequenceNumber % defaultTelemetrySignalSampleCount === 1
225
+ ) {
226
+ this.signalTracking.signalTimestamp = Date.now();
227
+ this.signalTracking.roundTripSignalSequenceNumber = broadcastSignalSequenceNumber;
228
+ this.signalTracking.totalSignalsSentInLatencyWindow +=
229
+ this.signalTracking.signalsSentSinceLastLatencyMeasurement;
230
+ this.signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
231
+ }
232
+ }
233
+ }