@fluidframework/container-runtime 2.101.1 → 2.103.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 (229) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/container-runtime.test-files.tar +0 -0
  3. package/dist/batchTracker.d.ts +1 -1
  4. package/dist/batchTracker.d.ts.map +1 -1
  5. package/dist/batchTracker.js +1 -1
  6. package/dist/batchTracker.js.map +1 -1
  7. package/dist/blobManager/blobManagerSnapSum.d.ts +2 -2
  8. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  9. package/dist/blobManager/blobManagerSnapSum.js.map +1 -1
  10. package/dist/connectionTelemetry.js.map +1 -1
  11. package/dist/containerRuntime.d.ts +16 -5
  12. package/dist/containerRuntime.d.ts.map +1 -1
  13. package/dist/containerRuntime.js +160 -18
  14. package/dist/containerRuntime.js.map +1 -1
  15. package/dist/dataStore.d.ts +2 -2
  16. package/dist/dataStore.d.ts.map +1 -1
  17. package/dist/dataStore.js.map +1 -1
  18. package/dist/dataStoreContext.d.ts.map +1 -1
  19. package/dist/dataStoreContext.js +1 -4
  20. package/dist/dataStoreContext.js.map +1 -1
  21. package/dist/dataStoreContexts.d.ts.map +1 -1
  22. package/dist/dataStoreContexts.js.map +1 -1
  23. package/dist/deltaScheduler.d.ts +2 -2
  24. package/dist/deltaScheduler.d.ts.map +1 -1
  25. package/dist/deltaScheduler.js.map +1 -1
  26. package/dist/gc/garbageCollection.d.ts +2 -2
  27. package/dist/gc/garbageCollection.d.ts.map +1 -1
  28. package/dist/gc/garbageCollection.js +12 -5
  29. package/dist/gc/garbageCollection.js.map +1 -1
  30. package/dist/gc/gcDefinitions.d.ts +3 -3
  31. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  32. package/dist/gc/gcDefinitions.js.map +1 -1
  33. package/dist/gc/gcTelemetry.d.ts +3 -3
  34. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  35. package/dist/gc/gcTelemetry.js.map +1 -1
  36. package/dist/inboundBatchAggregator.d.ts +2 -2
  37. package/dist/inboundBatchAggregator.d.ts.map +1 -1
  38. package/dist/inboundBatchAggregator.js.map +1 -1
  39. package/dist/opLifecycle/duplicateBatchDetector.d.ts +39 -3
  40. package/dist/opLifecycle/duplicateBatchDetector.d.ts.map +1 -1
  41. package/dist/opLifecycle/duplicateBatchDetector.js +57 -15
  42. package/dist/opLifecycle/duplicateBatchDetector.js.map +1 -1
  43. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  44. package/dist/opLifecycle/opCompressor.js.map +1 -1
  45. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  46. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  47. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  48. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  49. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  50. package/dist/opLifecycle/opSplitter.js.map +1 -1
  51. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  52. package/dist/opLifecycle/outbox.js.map +1 -1
  53. package/dist/packageVersion.d.ts +1 -1
  54. package/dist/packageVersion.js +1 -1
  55. package/dist/packageVersion.js.map +1 -1
  56. package/dist/pendingStateManager.d.ts +48 -1
  57. package/dist/pendingStateManager.d.ts.map +1 -1
  58. package/dist/pendingStateManager.js +54 -1
  59. package/dist/pendingStateManager.js.map +1 -1
  60. package/dist/runtimeLayerCompatState.d.ts +2 -2
  61. package/dist/signalTelemetryProcessing.d.ts +2 -2
  62. package/dist/signalTelemetryProcessing.d.ts.map +1 -1
  63. package/dist/signalTelemetryProcessing.js.map +1 -1
  64. package/dist/summary/documentSchema.d.ts +2 -2
  65. package/dist/summary/documentSchema.d.ts.map +1 -1
  66. package/dist/summary/documentSchema.js +35 -3
  67. package/dist/summary/documentSchema.js.map +1 -1
  68. package/dist/summary/orderedClientElection.d.ts +2 -2
  69. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  70. package/dist/summary/orderedClientElection.js.map +1 -1
  71. package/dist/summary/summarizerClientElection.d.ts +2 -2
  72. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  73. package/dist/summary/summarizerClientElection.js.map +1 -1
  74. package/dist/summary/summarizerNode/summarizerNode.d.ts +3 -3
  75. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  76. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  77. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +2 -2
  78. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  79. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  80. package/dist/summary/summarizerTypes.d.ts +3 -3
  81. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  82. package/dist/summary/summarizerTypes.js.map +1 -1
  83. package/dist/summary/summaryCollection.d.ts.map +1 -1
  84. package/dist/summary/summaryCollection.js.map +1 -1
  85. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts +2 -2
  86. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  87. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  88. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts +2 -2
  89. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  90. package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  91. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts +2 -2
  92. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts.map +1 -1
  93. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  94. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +2 -2
  95. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -1
  96. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  97. package/dist/summary/summaryManager.d.ts.map +1 -1
  98. package/dist/summary/summaryManager.js.map +1 -1
  99. package/lib/batchTracker.d.ts +1 -1
  100. package/lib/batchTracker.d.ts.map +1 -1
  101. package/lib/batchTracker.js +1 -1
  102. package/lib/batchTracker.js.map +1 -1
  103. package/lib/blobManager/blobManagerSnapSum.d.ts +2 -2
  104. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  105. package/lib/blobManager/blobManagerSnapSum.js.map +1 -1
  106. package/lib/connectionTelemetry.js.map +1 -1
  107. package/lib/containerRuntime.d.ts +16 -5
  108. package/lib/containerRuntime.d.ts.map +1 -1
  109. package/lib/containerRuntime.js +160 -18
  110. package/lib/containerRuntime.js.map +1 -1
  111. package/lib/dataStore.d.ts +2 -2
  112. package/lib/dataStore.d.ts.map +1 -1
  113. package/lib/dataStore.js.map +1 -1
  114. package/lib/dataStoreContext.d.ts.map +1 -1
  115. package/lib/dataStoreContext.js +2 -5
  116. package/lib/dataStoreContext.js.map +1 -1
  117. package/lib/dataStoreContexts.d.ts.map +1 -1
  118. package/lib/dataStoreContexts.js.map +1 -1
  119. package/lib/deltaScheduler.d.ts +2 -2
  120. package/lib/deltaScheduler.d.ts.map +1 -1
  121. package/lib/deltaScheduler.js +1 -1
  122. package/lib/deltaScheduler.js.map +1 -1
  123. package/lib/gc/garbageCollection.d.ts +2 -2
  124. package/lib/gc/garbageCollection.d.ts.map +1 -1
  125. package/lib/gc/garbageCollection.js +12 -5
  126. package/lib/gc/garbageCollection.js.map +1 -1
  127. package/lib/gc/gcDefinitions.d.ts +3 -3
  128. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  129. package/lib/gc/gcDefinitions.js.map +1 -1
  130. package/lib/gc/gcTelemetry.d.ts +3 -3
  131. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  132. package/lib/gc/gcTelemetry.js.map +1 -1
  133. package/lib/inboundBatchAggregator.d.ts +2 -2
  134. package/lib/inboundBatchAggregator.d.ts.map +1 -1
  135. package/lib/inboundBatchAggregator.js.map +1 -1
  136. package/lib/opLifecycle/duplicateBatchDetector.d.ts +39 -3
  137. package/lib/opLifecycle/duplicateBatchDetector.d.ts.map +1 -1
  138. package/lib/opLifecycle/duplicateBatchDetector.js +57 -15
  139. package/lib/opLifecycle/duplicateBatchDetector.js.map +1 -1
  140. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  141. package/lib/opLifecycle/opCompressor.js.map +1 -1
  142. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  143. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  144. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  145. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  146. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  147. package/lib/opLifecycle/opSplitter.js.map +1 -1
  148. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  149. package/lib/opLifecycle/outbox.js.map +1 -1
  150. package/lib/packageVersion.d.ts +1 -1
  151. package/lib/packageVersion.js +1 -1
  152. package/lib/packageVersion.js.map +1 -1
  153. package/lib/pendingStateManager.d.ts +48 -1
  154. package/lib/pendingStateManager.d.ts.map +1 -1
  155. package/lib/pendingStateManager.js +54 -1
  156. package/lib/pendingStateManager.js.map +1 -1
  157. package/lib/runtimeLayerCompatState.d.ts +2 -2
  158. package/lib/signalTelemetryProcessing.d.ts +2 -2
  159. package/lib/signalTelemetryProcessing.d.ts.map +1 -1
  160. package/lib/signalTelemetryProcessing.js.map +1 -1
  161. package/lib/summary/documentSchema.d.ts +2 -2
  162. package/lib/summary/documentSchema.d.ts.map +1 -1
  163. package/lib/summary/documentSchema.js +35 -3
  164. package/lib/summary/documentSchema.js.map +1 -1
  165. package/lib/summary/orderedClientElection.d.ts +2 -2
  166. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  167. package/lib/summary/orderedClientElection.js.map +1 -1
  168. package/lib/summary/summarizerClientElection.d.ts +2 -2
  169. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  170. package/lib/summary/summarizerClientElection.js.map +1 -1
  171. package/lib/summary/summarizerNode/summarizerNode.d.ts +3 -3
  172. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  173. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  174. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +2 -2
  175. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  176. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  177. package/lib/summary/summarizerTypes.d.ts +3 -3
  178. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  179. package/lib/summary/summarizerTypes.js.map +1 -1
  180. package/lib/summary/summaryCollection.d.ts.map +1 -1
  181. package/lib/summary/summaryCollection.js.map +1 -1
  182. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts +2 -2
  183. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  184. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  185. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts +2 -2
  186. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  187. package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  188. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts +2 -2
  189. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts.map +1 -1
  190. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  191. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +2 -2
  192. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -1
  193. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  194. package/lib/summary/summaryManager.d.ts.map +1 -1
  195. package/lib/summary/summaryManager.js.map +1 -1
  196. package/package.json +18 -18
  197. package/src/batchTracker.ts +3 -3
  198. package/src/blobManager/blobManagerSnapSum.ts +2 -2
  199. package/src/connectionTelemetry.ts +3 -3
  200. package/src/containerRuntime.ts +188 -25
  201. package/src/dataStore.ts +3 -3
  202. package/src/dataStoreContext.ts +2 -4
  203. package/src/dataStoreContexts.ts +2 -2
  204. package/src/deltaScheduler.ts +2 -5
  205. package/src/gc/garbageCollection.ts +16 -9
  206. package/src/gc/gcDefinitions.ts +3 -3
  207. package/src/gc/gcTelemetry.ts +3 -3
  208. package/src/inboundBatchAggregator.ts +2 -2
  209. package/src/opLifecycle/duplicateBatchDetector.ts +103 -23
  210. package/src/opLifecycle/opCompressor.ts +2 -2
  211. package/src/opLifecycle/opDecompressor.ts +2 -2
  212. package/src/opLifecycle/opGroupingManager.ts +2 -2
  213. package/src/opLifecycle/opSplitter.ts +2 -2
  214. package/src/opLifecycle/outbox.ts +2 -2
  215. package/src/packageVersion.ts +1 -1
  216. package/src/pendingStateManager.ts +80 -2
  217. package/src/signalTelemetryProcessing.ts +2 -2
  218. package/src/summary/documentSchema.ts +58 -5
  219. package/src/summary/orderedClientElection.ts +3 -3
  220. package/src/summary/summarizerClientElection.ts +2 -2
  221. package/src/summary/summarizerNode/summarizerNode.ts +3 -3
  222. package/src/summary/summarizerNode/summarizerNodeUtils.ts +2 -2
  223. package/src/summary/summarizerTypes.ts +3 -3
  224. package/src/summary/summaryCollection.ts +2 -2
  225. package/src/summary/summaryDelayLoadedModule/runningSummarizer.ts +2 -4
  226. package/src/summary/summaryDelayLoadedModule/summarizer.ts +3 -3
  227. package/src/summary/summaryDelayLoadedModule/summarizerHeuristics.ts +2 -2
  228. package/src/summary/summaryDelayLoadedModule/summaryGenerator.ts +2 -2
  229. package/src/summary/summaryManager.ts +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"gcTelemetry.d.ts","sourceRoot":"","sources":["../../src/gc/gcTelemetry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gcTelemetry.d.ts","sourceRoot":"","sources":["../../src/gc/gcTelemetry.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gcTelemetry.js","sourceRoot":"","sources":["../../src/gc/gcTelemetry.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { Tagged } from \"@fluidframework/core-interfaces\";\nimport type { IGarbageCollectionData } from \"@fluidframework/runtime-definitions/internal\";\nimport {\n\ttype ITelemetryLoggerExt,\n\ttype MonitoringContext,\n\tgenerateStack,\n\ttagCodeArtifacts,\n\ttype ITelemetryPropertiesExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { RuntimeHeaderData } from \"../containerRuntime.js\";\nimport type { ICreateContainerMetadata } from \"../summary/index.js\";\n\nimport {\n\ttype GCFeatureMatrix,\n\tGCNodeType,\n\ttype IGarbageCollectorConfigs,\n\tUnreferencedState,\n} from \"./gcDefinitions.js\";\nimport type { UnreferencedStateTracker } from \"./gcUnreferencedStateTracker.js\";\n\ntype NodeUsageType = \"Changed\" | \"Loaded\" | \"Revived\" | \"Realized\";\n\n/**\n * Properties that are common to IUnreferencedEventProps and INodeUsageProps\n */\ninterface ICommonProps {\n\tusageType: NodeUsageType;\n\tcompletedGCRuns: number;\n\tisTombstoned: boolean;\n\tlastSummaryTime?: number;\n\theaders?: RuntimeHeaderData;\n\tadditionalProps?: ITelemetryPropertiesExt;\n}\n\n/**\n * The event that is logged when unreferenced node is used after a certain time.\n */\n\ninterface IUnreferencedEventProps extends ICreateContainerMetadata, ICommonProps {\n\t/**\n\t * The id that GC uses to track the node. May or may not match id\n\t */\n\ttrackedId: string;\n\tstate: UnreferencedState;\n\t/**\n\t * The full path (in GC Path format) to the node in question\n\t */\n\tid: Tagged<string>;\n\tfromId?: Tagged<string>;\n\n\ttype: GCNodeType;\n\tunrefTime: number;\n\tage: number;\n\t// Expanding GC feature matrix. Without doing this, the configs cannot be logged in telemetry directly.\n\tgcConfigs: Omit<IGarbageCollectorConfigs, \"persistedGcFeatureMatrix\"> & {\n\t\t[K in keyof GCFeatureMatrix]: GCFeatureMatrix[K];\n\t};\n\ttimeout?: number;\n}\n\n/**\n * Properties passed to nodeUsed function when a node is used.\n */\ninterface INodeUsageProps extends ICommonProps {\n\t/**\n\t * The full path (in GC Path format) to the node in question\n\t */\n\tid: string;\n\t/**\n\t * Latest timestamp received from the server, as a baseline for computing GC state/age\n\t */\n\tcurrentReferenceTimestampMs: number;\n\t/**\n\t * The package path of the node. This may not be available if the node hasn't been loaded yet.\n\t *\n\t * @see {@link @fluidframework/runtime-definitions#IFluidDataStoreContext.packagePath} for more details.\n\t */\n\tpackagePath: readonly string[] | undefined;\n\t/**\n\t * In case of Revived - what node added the reference?\n\t */\n\tfromId?: string;\n\t/**\n\t * In case of Revived - was it revived due to autorecovery?\n\t */\n\tautorecovery?: true;\n\t/**\n\t * URL (including query string) if this usage came from a request\n\t */\n\trequestUrl?: string;\n\t/**\n\t * Original request headers if this usage came from a request or handle.get\n\t */\n\trequestHeaders?: string;\n}\n\n/**\n * Encapsulates the logic that tracks the various telemetry logged by the Garbage Collector.\n *\n * These events are not logged as errors, just generic events, since there can be false positives:\n *\n * 1. inactiveObject telemetry - When an inactive node is used - A node that has been unreferenced for inactiveTimeoutMs.\n * 2. tombstoneReadyObject telemetry - When a tombstone-ready node is used - A node that has been unreferenced for tombstoneTimeoutMs.\n * 3. sweepReadyObject telemetry - When a sweep-ready node is used - A node that has been unreferenced for tombstoneTimeoutMs + sweepGracePeriodMs.\n *\n * These events are logged as errors since they are based on the core GC logic:\n *\n * 1. Tombstone telemetry - When a tombstoned node is used - A node that has been marked as tombstone.\n * 2. Unknown outbound reference telemetry - When a node is referenced but GC was not notified of it when the new reference appeared.\n *\n * Note: The telemetry for a Deleted node being used is logged elsewhere in this package.\n */\nexport class GCTelemetryTracker {\n\t// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one\n\t// per event per node.\n\tprivate readonly loggedUnreferencedEvents: Set<string> = new Set();\n\t// Queue for unreferenced events that should be logged the next time GC runs.\n\tprivate pendingEventsQueue: IUnreferencedEventProps[] = [];\n\n\tconstructor(\n\t\tprivate readonly mc: MonitoringContext,\n\t\tprivate readonly configs: IGarbageCollectorConfigs,\n\t\tprivate readonly isSummarizerClient: boolean,\n\n\t\tprivate readonly createContainerMetadata: ICreateContainerMetadata,\n\n\t\tprivate readonly getNodeType: (nodeId: string) => GCNodeType,\n\t\tprivate readonly getNodeStateTracker: (\n\t\t\tnodeId: string,\n\t\t) => UnreferencedStateTracker | undefined,\n\t\tprivate readonly getNodePackagePath: (\n\t\t\tnodePath: string,\n\t\t) => Promise<readonly string[] | undefined>,\n\t) {}\n\n\t/**\n\t * Returns whether an event should be logged for a node that isn't active anymore.\n\t *\n\t * @remarks\n\t * This does not apply to tombstoned nodes for which an event is always logged. Some scenarios where we won't log:\n\t *\n\t * 1. When a DDS is changed. The corresponding data store's event will be logged instead.\n\t *\n\t * 2. An event is logged only once per container instance per event per node.\n\t */\n\tprivate shouldLogNonActiveEvent(\n\t\tnodeType: GCNodeType,\n\t\tusageType: NodeUsageType,\n\t\tnodeStateTracker: UnreferencedStateTracker,\n\t\tuniqueEventId: string,\n\t): boolean {\n\t\tif (nodeStateTracker.state === UnreferencedState.Active) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (nodeType === GCNodeType.Other) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// For sub data store (DDS) nodes, if they are changed, its data store will also be changed,\n\t\t// so skip logging to make the telemetry less noisy.\n\n\t\tif (nodeType === GCNodeType.SubDataStore && usageType === \"Changed\") {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Non-tombstone events are logged once per event per node. A unique id is generated by joining\n\t\t// node state (inactive / sweep ready), node's id and usage (loaded / changed / revived).\n\t\tif (this.loggedUnreferencedEvents.has(uniqueEventId)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Called when a node is used. If the node is inactive or tombstoned, log telemetry indicating object is used\n\t * when it should not have been.\n\t * @param trackedId - The id that GC uses to track the node. For SubDataStore nodes, this should be the DataStore ID.\n\t * @param INodeUsageProps - All kind of details about this event to be logged\n\t */\n\tpublic nodeUsed(\n\t\ttrackedId: string,\n\t\t{\n\t\t\tusageType,\n\t\t\tcurrentReferenceTimestampMs,\n\t\t\tpackagePath,\n\t\t\tid: untaggedId,\n\t\t\tfromId: untaggedFromId,\n\t\t\tisTombstoned,\n\t\t\t...otherNodeUsageProps\n\t\t}: INodeUsageProps,\n\t): void {\n\t\t// Note: For SubDataStore Load usage, trackedId will be the DataStore's id, not the full path in question.\n\t\t// This is necessary because the SubDataStore path may be unrecognized by GC (if suited for a custom request handler)\n\t\tconst nodeStateTracker = this.getNodeStateTracker(trackedId);\n\t\tconst nodeType = this.getNodeType(untaggedId);\n\n\t\tconst timeout = (() => {\n\t\t\tswitch (nodeStateTracker?.state) {\n\t\t\t\tcase UnreferencedState.Inactive: {\n\t\t\t\t\treturn this.configs.inactiveTimeoutMs;\n\t\t\t\t}\n\t\t\t\tcase UnreferencedState.TombstoneReady: {\n\t\t\t\t\treturn this.configs.tombstoneTimeoutMs;\n\t\t\t\t}\n\t\t\t\tcase UnreferencedState.SweepReady: {\n\t\t\t\t\treturn this.configs.tombstoneTimeoutMs === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: this.configs.tombstoneTimeoutMs + this.configs.sweepGracePeriodMs;\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t\tconst { persistedGcFeatureMatrix, ...configs } = this.configs;\n\t\tconst unrefEventProps = {\n\t\t\ttrackedId,\n\t\t\ttype: nodeType,\n\t\t\tunrefTime: nodeStateTracker?.unreferencedTimestampMs ?? -1,\n\t\t\tage:\n\t\t\t\tnodeStateTracker === undefined\n\t\t\t\t\t? -1\n\t\t\t\t\t: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs,\n\t\t\ttimeout,\n\t\t\tisTombstoned,\n\t\t\t...tagCodeArtifacts({ id: untaggedId, fromId: untaggedFromId }),\n\t\t\t...otherNodeUsageProps,\n\t\t\t...this.createContainerMetadata,\n\t\t\tgcConfigs: { ...configs, ...persistedGcFeatureMatrix },\n\t\t} satisfies Omit<IUnreferencedEventProps, \"state\" | \"usageType\"> &\n\t\t\ttypeof otherNodeUsageProps;\n\n\t\t// If the node that is used is tombstoned, log a tombstone telemetry.\n\t\tif (isTombstoned) {\n\t\t\tthis.logTombstoneUsageTelemetry(unrefEventProps, nodeType, usageType, packagePath);\n\t\t}\n\n\t\t// After logging tombstone telemetry, if the node's unreferenced state is not tracked, there is nothing\n\t\t// else to log.\n\t\tif (nodeStateTracker === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst state = nodeStateTracker.state;\n\t\tconst uniqueEventId = `${state}-${untaggedId}-${usageType}`;\n\n\t\tif (!this.shouldLogNonActiveEvent(nodeType, usageType, nodeStateTracker, uniqueEventId)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Add the unique event id so that we don't generate a log for this event again in this session.\n\t\tthis.loggedUnreferencedEvents.add(uniqueEventId);\n\n\t\t// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.\n\t\t// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives\n\t\t// but it's a good signal nonetheless and we can consume it with a grain of salt.\n\t\t// Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.\n\t\t// SweepReady errors are usages of Objects that will be deleted by GC Sweep!\n\t\tif (this.isSummarizerClient) {\n\t\t\tthis.pendingEventsQueue.push({\n\t\t\t\t...unrefEventProps, // Note: Contains some properties from INodeUsageProps as well\n\t\t\t\tusageType,\n\t\t\t\tstate,\n\t\t\t});\n\t\t} else {\n\t\t\t// For non-summarizer clients, only log \"Loaded\" type events since these objects may not be loaded in the\n\t\t\t// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)\n\t\t\t// Events generated:\n\t\t\t// InactiveObject_Loaded, SweepReadyObject_Loaded\n\t\t\tif (usageType === \"Loaded\") {\n\t\t\t\tconst { id, fromId, headers, gcConfigs, additionalProps, ...detailedProps } =\n\t\t\t\t\tunrefEventProps;\n\t\t\t\tconst event = {\n\t\t\t\t\teventName: `${state}Object_${usageType}`,\n\t\t\t\t\t...tagCodeArtifacts({ pkg: packagePath?.join(\"/\") }),\n\t\t\t\t\tstack: generateStack(30),\n\t\t\t\t\tid,\n\t\t\t\t\tfromId,\n\t\t\t\t\theaders: { ...headers },\n\t\t\t\t\tdetails: { ...detailedProps, ...additionalProps },\n\t\t\t\t\tgcConfigs,\n\t\t\t\t};\n\n\t\t\t\t// These are logged as generic events and not errors because there can be false positives. The Tombstone\n\t\t\t\t// and Delete errors are separately logged and are reliable.\n\t\t\t\tthis.mc.logger.sendTelemetryEvent(event);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Logs telemetry when a tombstoned object is changed, revived or loaded.\n\t */\n\tprivate logTombstoneUsageTelemetry(\n\t\tunrefEventProps: Omit<IUnreferencedEventProps, \"state\" | \"usageType\">,\n\n\t\tnodeType: GCNodeType,\n\t\tusageType: NodeUsageType,\n\t\tpackagePath?: readonly string[],\n\t): void {\n\t\t// This will log the following events:\n\t\t// GC_Tombstone_DataStore_Requested, GC_Tombstone_DataStore_Changed, GC_Tombstone_DataStore_Revived\n\t\t// GC_Tombstone_SubDataStore_Requested, GC_Tombstone_SubDataStore_Changed, GC_Tombstone_SubDataStore_Revived\n\t\t// GC_Tombstone_Blob_Requested, GC_Tombstone_Blob_Changed, GC_Tombstone_Blob_Revived\n\t\tconst { id, fromId, headers, gcConfigs, additionalProps, ...detailedProps } =\n\t\t\tunrefEventProps;\n\t\tconst eventUsageName = usageType === \"Loaded\" ? \"Requested\" : usageType;\n\t\tconst event = {\n\t\t\teventName: `GC_Tombstone_${nodeType}_${eventUsageName}`,\n\t\t\t...tagCodeArtifacts({ pkg: packagePath?.join(\"/\") }),\n\t\t\tstack: generateStack(30),\n\t\t\tid,\n\t\t\tfromId,\n\t\t\theaders: { ...headers },\n\t\t\tdetails: { ...detailedProps, ...additionalProps }, // Also includes some properties from INodeUsageProps type\n\t\t\tgcConfigs,\n\t\t};\n\n\t\tif (\n\t\t\tusageType === \"Loaded\" &&\n\t\t\tthis.configs.throwOnTombstoneLoad &&\n\t\t\theaders?.allowTombstone !== true\n\t\t) {\n\t\t\tthis.mc.logger.sendErrorEvent(event);\n\t\t} else {\n\t\t\tthis.mc.logger.sendTelemetryEvent(event);\n\t\t}\n\t}\n\n\t/**\n\t * Log all new references or outbound routes in the current graph that haven't been explicitly notified to GC.\n\t * The principle is that every new reference or outbound route must be notified to GC via the\n\t * addedOutboundReference method. It it hasn't, its a bug and we want to identify these scenarios.\n\t *\n\t * In more simple terms:\n\t * Missing Explicit References = Current References - Previous References - Explicitly Added References;\n\t *\n\t * @param currentGCData - The GC data (reference graph) from the current GC run.\n\t * @param previousGCData - The GC data (reference graph) from the previous GC run.\n\t * @param explicitReferences - New references added explicity between the previous and the current run.\n\t */\n\tpublic logIfMissingExplicitReferences(\n\t\tcurrentGCData: IGarbageCollectionData,\n\t\tpreviousGCData: IGarbageCollectionData,\n\t\texplicitReferences: Map<string, string[]>,\n\t\tlogger: ITelemetryLoggerExt,\n\t): void {\n\t\tfor (const [nodeId, currentOutboundRoutes] of Object.entries(currentGCData.gcNodes)) {\n\t\t\tconst previousRoutes = previousGCData.gcNodes[nodeId] ?? [];\n\t\t\tconst explicitRoutes = explicitReferences.get(nodeId) ?? [];\n\n\t\t\t/**\n\t\t\t * 1. For routes in the current GC data, routes that were not present in previous GC data and did not have\n\t\t\t * explicit references should be added to missing explicit routes list.\n\t\t\t * 2. Only include data store and blob routes since GC only works for these two.\n\t\t\t * Note: Due to a bug with de-duped blobs, only adding data store routes for now.\n\t\t\t * 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be\n\t\t\t * explicit routes to them.\n\t\t\t */\n\t\t\tconst missingExplicitRoutes: string[] = [];\n\t\t\tfor (const route of currentOutboundRoutes) {\n\t\t\t\tconst nodeType = this.getNodeType(route);\n\t\t\t\tif (\n\t\t\t\t\t(nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) &&\n\t\t\t\t\t!nodeId.startsWith(route) &&\n\t\t\t\t\t!previousRoutes.includes(route) &&\n\t\t\t\t\t!explicitRoutes.includes(route)\n\t\t\t\t) {\n\t\t\t\t\tmissingExplicitRoutes.push(route);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (missingExplicitRoutes.length > 0) {\n\t\t\t\t// Send as Generic not Error since there are known corner cases where this will fire.\n\t\t\t\t// E.g. If an old client re-references a node via an attach op (that doesn't include GC Data)\n\t\t\t\tlogger.sendTelemetryEvent({\n\t\t\t\t\teventName: \"gcUnknownOutboundReferences\",\n\t\t\t\t\t...tagCodeArtifacts({\n\t\t\t\t\t\tid: nodeId,\n\t\t\t\t\t\troutes: JSON.stringify(missingExplicitRoutes),\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Log events that are pending in pendingEventsQueue. This is called after GC runs in the summarizer client\n\t * so that the state of an unreferenced node is updated.\n\t */\n\tpublic async logPendingEvents(logger: ITelemetryLoggerExt): Promise<void> {\n\t\t// Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at\n\t\t// summary time they are then logged.\n\t\t// Events generated:\n\t\t// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived\n\t\t// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived\n\t\tfor (const eventProps of this.pendingEventsQueue) {\n\t\t\tconst {\n\t\t\t\tusageType,\n\t\t\t\tstate,\n\t\t\t\tid,\n\t\t\t\tfromId,\n\t\t\t\theaders,\n\t\t\t\tgcConfigs,\n\t\t\t\tadditionalProps,\n\t\t\t\t...detailedProps\n\t\t\t} = eventProps;\n\t\t\t/**\n\t\t\t * Revived event is logged only if the node is active. If the node is not active, the reference to it was\n\t\t\t * from another unreferenced node and this scenario is not interesting to log.\n\t\t\t * Loaded and Changed events are logged only if the node is not active. If the node is active, it was\n\t\t\t * revived and a Revived event will be logged for it.\n\t\t\t */\n\t\t\tconst nodeStateTracker = this.getNodeStateTracker(detailedProps.trackedId); // Note: This is never SubDataStore path\n\t\t\tconst active =\n\t\t\t\tnodeStateTracker === undefined || nodeStateTracker.state === UnreferencedState.Active;\n\t\t\tif ((usageType === \"Revived\") === active) {\n\t\t\t\tconst pkg = await this.getNodePackagePath(id.value);\n\t\t\t\tconst fromPkg = fromId ? await this.getNodePackagePath(fromId.value) : undefined;\n\t\t\t\tconst event = {\n\t\t\t\t\teventName: `${state}Object_${usageType}`,\n\t\t\t\t\tid,\n\t\t\t\t\tfromId,\n\t\t\t\t\theaders: { ...headers },\n\t\t\t\t\tdetails: { ...detailedProps, ...additionalProps },\n\t\t\t\t\tgcConfigs,\n\t\t\t\t\t...tagCodeArtifacts({\n\t\t\t\t\t\tpkg: pkg?.join(\"/\"),\n\t\t\t\t\t\tfromPkg: fromPkg?.join(\"/\"),\n\t\t\t\t\t}),\n\t\t\t\t};\n\n\t\t\t\t// These are logged as generic events and not errors because there can be false positives. The Tombstone\n\t\t\t\t// and Delete errors are separately logged and are reliable.\n\t\t\t\tlogger.sendTelemetryEvent(event);\n\t\t\t}\n\t\t}\n\t\tthis.pendingEventsQueue = [];\n\t}\n}\n"]}
1
+ {"version":3,"file":"gcTelemetry.js","sourceRoot":"","sources":["../../src/gc/gcTelemetry.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { Tagged } from \"@fluidframework/core-interfaces\";\nimport type { IGarbageCollectionData } from \"@fluidframework/runtime-definitions/internal\";\nimport {\n\ttype TelemetryLoggerExt,\n\ttype MonitoringContext,\n\tgenerateStack,\n\ttagCodeArtifacts,\n\ttype ITelemetryPropertiesExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { RuntimeHeaderData } from \"../containerRuntime.js\";\nimport type { ICreateContainerMetadata } from \"../summary/index.js\";\n\nimport {\n\ttype GCFeatureMatrix,\n\tGCNodeType,\n\ttype IGarbageCollectorConfigs,\n\tUnreferencedState,\n} from \"./gcDefinitions.js\";\nimport type { UnreferencedStateTracker } from \"./gcUnreferencedStateTracker.js\";\n\ntype NodeUsageType = \"Changed\" | \"Loaded\" | \"Revived\" | \"Realized\";\n\n/**\n * Properties that are common to IUnreferencedEventProps and INodeUsageProps\n */\ninterface ICommonProps {\n\tusageType: NodeUsageType;\n\tcompletedGCRuns: number;\n\tisTombstoned: boolean;\n\tlastSummaryTime?: number;\n\theaders?: RuntimeHeaderData;\n\tadditionalProps?: ITelemetryPropertiesExt;\n}\n\n/**\n * The event that is logged when unreferenced node is used after a certain time.\n */\n\ninterface IUnreferencedEventProps extends ICreateContainerMetadata, ICommonProps {\n\t/**\n\t * The id that GC uses to track the node. May or may not match id\n\t */\n\ttrackedId: string;\n\tstate: UnreferencedState;\n\t/**\n\t * The full path (in GC Path format) to the node in question\n\t */\n\tid: Tagged<string>;\n\tfromId?: Tagged<string>;\n\n\ttype: GCNodeType;\n\tunrefTime: number;\n\tage: number;\n\t// Expanding GC feature matrix. Without doing this, the configs cannot be logged in telemetry directly.\n\tgcConfigs: Omit<IGarbageCollectorConfigs, \"persistedGcFeatureMatrix\"> & {\n\t\t[K in keyof GCFeatureMatrix]: GCFeatureMatrix[K];\n\t};\n\ttimeout?: number;\n}\n\n/**\n * Properties passed to nodeUsed function when a node is used.\n */\ninterface INodeUsageProps extends ICommonProps {\n\t/**\n\t * The full path (in GC Path format) to the node in question\n\t */\n\tid: string;\n\t/**\n\t * Latest timestamp received from the server, as a baseline for computing GC state/age\n\t */\n\tcurrentReferenceTimestampMs: number;\n\t/**\n\t * The package path of the node. This may not be available if the node hasn't been loaded yet.\n\t *\n\t * @see {@link @fluidframework/runtime-definitions#IFluidDataStoreContext.packagePath} for more details.\n\t */\n\tpackagePath: readonly string[] | undefined;\n\t/**\n\t * In case of Revived - what node added the reference?\n\t */\n\tfromId?: string;\n\t/**\n\t * In case of Revived - was it revived due to autorecovery?\n\t */\n\tautorecovery?: true;\n\t/**\n\t * URL (including query string) if this usage came from a request\n\t */\n\trequestUrl?: string;\n\t/**\n\t * Original request headers if this usage came from a request or handle.get\n\t */\n\trequestHeaders?: string;\n}\n\n/**\n * Encapsulates the logic that tracks the various telemetry logged by the Garbage Collector.\n *\n * These events are not logged as errors, just generic events, since there can be false positives:\n *\n * 1. inactiveObject telemetry - When an inactive node is used - A node that has been unreferenced for inactiveTimeoutMs.\n * 2. tombstoneReadyObject telemetry - When a tombstone-ready node is used - A node that has been unreferenced for tombstoneTimeoutMs.\n * 3. sweepReadyObject telemetry - When a sweep-ready node is used - A node that has been unreferenced for tombstoneTimeoutMs + sweepGracePeriodMs.\n *\n * These events are logged as errors since they are based on the core GC logic:\n *\n * 1. Tombstone telemetry - When a tombstoned node is used - A node that has been marked as tombstone.\n * 2. Unknown outbound reference telemetry - When a node is referenced but GC was not notified of it when the new reference appeared.\n *\n * Note: The telemetry for a Deleted node being used is logged elsewhere in this package.\n */\nexport class GCTelemetryTracker {\n\t// Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one\n\t// per event per node.\n\tprivate readonly loggedUnreferencedEvents: Set<string> = new Set();\n\t// Queue for unreferenced events that should be logged the next time GC runs.\n\tprivate pendingEventsQueue: IUnreferencedEventProps[] = [];\n\n\tconstructor(\n\t\tprivate readonly mc: MonitoringContext,\n\t\tprivate readonly configs: IGarbageCollectorConfigs,\n\t\tprivate readonly isSummarizerClient: boolean,\n\n\t\tprivate readonly createContainerMetadata: ICreateContainerMetadata,\n\n\t\tprivate readonly getNodeType: (nodeId: string) => GCNodeType,\n\t\tprivate readonly getNodeStateTracker: (\n\t\t\tnodeId: string,\n\t\t) => UnreferencedStateTracker | undefined,\n\t\tprivate readonly getNodePackagePath: (\n\t\t\tnodePath: string,\n\t\t) => Promise<readonly string[] | undefined>,\n\t) {}\n\n\t/**\n\t * Returns whether an event should be logged for a node that isn't active anymore.\n\t *\n\t * @remarks\n\t * This does not apply to tombstoned nodes for which an event is always logged. Some scenarios where we won't log:\n\t *\n\t * 1. When a DDS is changed. The corresponding data store's event will be logged instead.\n\t *\n\t * 2. An event is logged only once per container instance per event per node.\n\t */\n\tprivate shouldLogNonActiveEvent(\n\t\tnodeType: GCNodeType,\n\t\tusageType: NodeUsageType,\n\t\tnodeStateTracker: UnreferencedStateTracker,\n\t\tuniqueEventId: string,\n\t): boolean {\n\t\tif (nodeStateTracker.state === UnreferencedState.Active) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (nodeType === GCNodeType.Other) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// For sub data store (DDS) nodes, if they are changed, its data store will also be changed,\n\t\t// so skip logging to make the telemetry less noisy.\n\n\t\tif (nodeType === GCNodeType.SubDataStore && usageType === \"Changed\") {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Non-tombstone events are logged once per event per node. A unique id is generated by joining\n\t\t// node state (inactive / sweep ready), node's id and usage (loaded / changed / revived).\n\t\tif (this.loggedUnreferencedEvents.has(uniqueEventId)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Called when a node is used. If the node is inactive or tombstoned, log telemetry indicating object is used\n\t * when it should not have been.\n\t * @param trackedId - The id that GC uses to track the node. For SubDataStore nodes, this should be the DataStore ID.\n\t * @param INodeUsageProps - All kind of details about this event to be logged\n\t */\n\tpublic nodeUsed(\n\t\ttrackedId: string,\n\t\t{\n\t\t\tusageType,\n\t\t\tcurrentReferenceTimestampMs,\n\t\t\tpackagePath,\n\t\t\tid: untaggedId,\n\t\t\tfromId: untaggedFromId,\n\t\t\tisTombstoned,\n\t\t\t...otherNodeUsageProps\n\t\t}: INodeUsageProps,\n\t): void {\n\t\t// Note: For SubDataStore Load usage, trackedId will be the DataStore's id, not the full path in question.\n\t\t// This is necessary because the SubDataStore path may be unrecognized by GC (if suited for a custom request handler)\n\t\tconst nodeStateTracker = this.getNodeStateTracker(trackedId);\n\t\tconst nodeType = this.getNodeType(untaggedId);\n\n\t\tconst timeout = (() => {\n\t\t\tswitch (nodeStateTracker?.state) {\n\t\t\t\tcase UnreferencedState.Inactive: {\n\t\t\t\t\treturn this.configs.inactiveTimeoutMs;\n\t\t\t\t}\n\t\t\t\tcase UnreferencedState.TombstoneReady: {\n\t\t\t\t\treturn this.configs.tombstoneTimeoutMs;\n\t\t\t\t}\n\t\t\t\tcase UnreferencedState.SweepReady: {\n\t\t\t\t\treturn this.configs.tombstoneTimeoutMs === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: this.configs.tombstoneTimeoutMs + this.configs.sweepGracePeriodMs;\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t\tconst { persistedGcFeatureMatrix, ...configs } = this.configs;\n\t\tconst unrefEventProps = {\n\t\t\ttrackedId,\n\t\t\ttype: nodeType,\n\t\t\tunrefTime: nodeStateTracker?.unreferencedTimestampMs ?? -1,\n\t\t\tage:\n\t\t\t\tnodeStateTracker === undefined\n\t\t\t\t\t? -1\n\t\t\t\t\t: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs,\n\t\t\ttimeout,\n\t\t\tisTombstoned,\n\t\t\t...tagCodeArtifacts({ id: untaggedId, fromId: untaggedFromId }),\n\t\t\t...otherNodeUsageProps,\n\t\t\t...this.createContainerMetadata,\n\t\t\tgcConfigs: { ...configs, ...persistedGcFeatureMatrix },\n\t\t} satisfies Omit<IUnreferencedEventProps, \"state\" | \"usageType\"> &\n\t\t\ttypeof otherNodeUsageProps;\n\n\t\t// If the node that is used is tombstoned, log a tombstone telemetry.\n\t\tif (isTombstoned) {\n\t\t\tthis.logTombstoneUsageTelemetry(unrefEventProps, nodeType, usageType, packagePath);\n\t\t}\n\n\t\t// After logging tombstone telemetry, if the node's unreferenced state is not tracked, there is nothing\n\t\t// else to log.\n\t\tif (nodeStateTracker === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst state = nodeStateTracker.state;\n\t\tconst uniqueEventId = `${state}-${untaggedId}-${usageType}`;\n\n\t\tif (!this.shouldLogNonActiveEvent(nodeType, usageType, nodeStateTracker, uniqueEventId)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Add the unique event id so that we don't generate a log for this event again in this session.\n\t\tthis.loggedUnreferencedEvents.add(uniqueEventId);\n\n\t\t// For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.\n\t\t// For non-summarizer client, log the event now since GC won't run on it. This may result in false positives\n\t\t// but it's a good signal nonetheless and we can consume it with a grain of salt.\n\t\t// Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.\n\t\t// SweepReady errors are usages of Objects that will be deleted by GC Sweep!\n\t\tif (this.isSummarizerClient) {\n\t\t\tthis.pendingEventsQueue.push({\n\t\t\t\t...unrefEventProps, // Note: Contains some properties from INodeUsageProps as well\n\t\t\t\tusageType,\n\t\t\t\tstate,\n\t\t\t});\n\t\t} else {\n\t\t\t// For non-summarizer clients, only log \"Loaded\" type events since these objects may not be loaded in the\n\t\t\t// summarizer clients if they are based off of user actions (such as scrolling to content for these objects)\n\t\t\t// Events generated:\n\t\t\t// InactiveObject_Loaded, SweepReadyObject_Loaded\n\t\t\tif (usageType === \"Loaded\") {\n\t\t\t\tconst { id, fromId, headers, gcConfigs, additionalProps, ...detailedProps } =\n\t\t\t\t\tunrefEventProps;\n\t\t\t\tconst event = {\n\t\t\t\t\teventName: `${state}Object_${usageType}`,\n\t\t\t\t\t...tagCodeArtifacts({ pkg: packagePath?.join(\"/\") }),\n\t\t\t\t\tstack: generateStack(30),\n\t\t\t\t\tid,\n\t\t\t\t\tfromId,\n\t\t\t\t\theaders: { ...headers },\n\t\t\t\t\tdetails: { ...detailedProps, ...additionalProps },\n\t\t\t\t\tgcConfigs,\n\t\t\t\t};\n\n\t\t\t\t// These are logged as generic events and not errors because there can be false positives. The Tombstone\n\t\t\t\t// and Delete errors are separately logged and are reliable.\n\t\t\t\tthis.mc.logger.sendTelemetryEvent(event);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Logs telemetry when a tombstoned object is changed, revived or loaded.\n\t */\n\tprivate logTombstoneUsageTelemetry(\n\t\tunrefEventProps: Omit<IUnreferencedEventProps, \"state\" | \"usageType\">,\n\n\t\tnodeType: GCNodeType,\n\t\tusageType: NodeUsageType,\n\t\tpackagePath?: readonly string[],\n\t): void {\n\t\t// This will log the following events:\n\t\t// GC_Tombstone_DataStore_Requested, GC_Tombstone_DataStore_Changed, GC_Tombstone_DataStore_Revived\n\t\t// GC_Tombstone_SubDataStore_Requested, GC_Tombstone_SubDataStore_Changed, GC_Tombstone_SubDataStore_Revived\n\t\t// GC_Tombstone_Blob_Requested, GC_Tombstone_Blob_Changed, GC_Tombstone_Blob_Revived\n\t\tconst { id, fromId, headers, gcConfigs, additionalProps, ...detailedProps } =\n\t\t\tunrefEventProps;\n\t\tconst eventUsageName = usageType === \"Loaded\" ? \"Requested\" : usageType;\n\t\tconst event = {\n\t\t\teventName: `GC_Tombstone_${nodeType}_${eventUsageName}`,\n\t\t\t...tagCodeArtifacts({ pkg: packagePath?.join(\"/\") }),\n\t\t\tstack: generateStack(30),\n\t\t\tid,\n\t\t\tfromId,\n\t\t\theaders: { ...headers },\n\t\t\tdetails: { ...detailedProps, ...additionalProps }, // Also includes some properties from INodeUsageProps type\n\t\t\tgcConfigs,\n\t\t};\n\n\t\tif (\n\t\t\tusageType === \"Loaded\" &&\n\t\t\tthis.configs.throwOnTombstoneLoad &&\n\t\t\theaders?.allowTombstone !== true\n\t\t) {\n\t\t\tthis.mc.logger.sendErrorEvent(event);\n\t\t} else {\n\t\t\tthis.mc.logger.sendTelemetryEvent(event);\n\t\t}\n\t}\n\n\t/**\n\t * Log all new references or outbound routes in the current graph that haven't been explicitly notified to GC.\n\t * The principle is that every new reference or outbound route must be notified to GC via the\n\t * addedOutboundReference method. It it hasn't, its a bug and we want to identify these scenarios.\n\t *\n\t * In more simple terms:\n\t * Missing Explicit References = Current References - Previous References - Explicitly Added References;\n\t *\n\t * @param currentGCData - The GC data (reference graph) from the current GC run.\n\t * @param previousGCData - The GC data (reference graph) from the previous GC run.\n\t * @param explicitReferences - New references added explicity between the previous and the current run.\n\t */\n\tpublic logIfMissingExplicitReferences(\n\t\tcurrentGCData: IGarbageCollectionData,\n\t\tpreviousGCData: IGarbageCollectionData,\n\t\texplicitReferences: Map<string, string[]>,\n\t\tlogger: TelemetryLoggerExt,\n\t): void {\n\t\tfor (const [nodeId, currentOutboundRoutes] of Object.entries(currentGCData.gcNodes)) {\n\t\t\tconst previousRoutes = previousGCData.gcNodes[nodeId] ?? [];\n\t\t\tconst explicitRoutes = explicitReferences.get(nodeId) ?? [];\n\n\t\t\t/**\n\t\t\t * 1. For routes in the current GC data, routes that were not present in previous GC data and did not have\n\t\t\t * explicit references should be added to missing explicit routes list.\n\t\t\t * 2. Only include data store and blob routes since GC only works for these two.\n\t\t\t * Note: Due to a bug with de-duped blobs, only adding data store routes for now.\n\t\t\t * 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be\n\t\t\t * explicit routes to them.\n\t\t\t */\n\t\t\tconst missingExplicitRoutes: string[] = [];\n\t\t\tfor (const route of currentOutboundRoutes) {\n\t\t\t\tconst nodeType = this.getNodeType(route);\n\t\t\t\tif (\n\t\t\t\t\t(nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) &&\n\t\t\t\t\t!nodeId.startsWith(route) &&\n\t\t\t\t\t!previousRoutes.includes(route) &&\n\t\t\t\t\t!explicitRoutes.includes(route)\n\t\t\t\t) {\n\t\t\t\t\tmissingExplicitRoutes.push(route);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (missingExplicitRoutes.length > 0) {\n\t\t\t\t// Send as Generic not Error since there are known corner cases where this will fire.\n\t\t\t\t// E.g. If an old client re-references a node via an attach op (that doesn't include GC Data)\n\t\t\t\tlogger.sendTelemetryEvent({\n\t\t\t\t\teventName: \"gcUnknownOutboundReferences\",\n\t\t\t\t\t...tagCodeArtifacts({\n\t\t\t\t\t\tid: nodeId,\n\t\t\t\t\t\troutes: JSON.stringify(missingExplicitRoutes),\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Log events that are pending in pendingEventsQueue. This is called after GC runs in the summarizer client\n\t * so that the state of an unreferenced node is updated.\n\t */\n\tpublic async logPendingEvents(logger: TelemetryLoggerExt): Promise<void> {\n\t\t// Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at\n\t\t// summary time they are then logged.\n\t\t// Events generated:\n\t\t// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived\n\t\t// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived\n\t\tfor (const eventProps of this.pendingEventsQueue) {\n\t\t\tconst {\n\t\t\t\tusageType,\n\t\t\t\tstate,\n\t\t\t\tid,\n\t\t\t\tfromId,\n\t\t\t\theaders,\n\t\t\t\tgcConfigs,\n\t\t\t\tadditionalProps,\n\t\t\t\t...detailedProps\n\t\t\t} = eventProps;\n\t\t\t/**\n\t\t\t * Revived event is logged only if the node is active. If the node is not active, the reference to it was\n\t\t\t * from another unreferenced node and this scenario is not interesting to log.\n\t\t\t * Loaded and Changed events are logged only if the node is not active. If the node is active, it was\n\t\t\t * revived and a Revived event will be logged for it.\n\t\t\t */\n\t\t\tconst nodeStateTracker = this.getNodeStateTracker(detailedProps.trackedId); // Note: This is never SubDataStore path\n\t\t\tconst active =\n\t\t\t\tnodeStateTracker === undefined || nodeStateTracker.state === UnreferencedState.Active;\n\t\t\tif ((usageType === \"Revived\") === active) {\n\t\t\t\tconst pkg = await this.getNodePackagePath(id.value);\n\t\t\t\tconst fromPkg = fromId ? await this.getNodePackagePath(fromId.value) : undefined;\n\t\t\t\tconst event = {\n\t\t\t\t\teventName: `${state}Object_${usageType}`,\n\t\t\t\t\tid,\n\t\t\t\t\tfromId,\n\t\t\t\t\theaders: { ...headers },\n\t\t\t\t\tdetails: { ...detailedProps, ...additionalProps },\n\t\t\t\t\tgcConfigs,\n\t\t\t\t\t...tagCodeArtifacts({\n\t\t\t\t\t\tpkg: pkg?.join(\"/\"),\n\t\t\t\t\t\tfromPkg: fromPkg?.join(\"/\"),\n\t\t\t\t\t}),\n\t\t\t\t};\n\n\t\t\t\t// These are logged as generic events and not errors because there can be false positives. The Tombstone\n\t\t\t\t// and Delete errors are separately logged and are reliable.\n\t\t\t\tlogger.sendTelemetryEvent(event);\n\t\t\t}\n\t\t}\n\t\tthis.pendingEventsQueue = [];\n\t}\n}\n"]}
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import type { IDeltaManagerFull } from "@fluidframework/container-definitions/internal";
6
- import { type ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
6
+ import { type TelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
7
7
  /**
8
8
  * This class ensures that we aggregate a complete batch of incoming ops before processing them. It basically ensures
9
9
  * that we never start processing ops in a batch IF we do not have all ops in the batch.
@@ -17,7 +17,7 @@ export declare class InboundBatchAggregator {
17
17
  private localPaused;
18
18
  private timePaused;
19
19
  private batchCount;
20
- constructor(deltaManager: IDeltaManagerFull, getClientId: () => string | undefined, logger: ITelemetryLoggerExt);
20
+ constructor(deltaManager: IDeltaManagerFull, getClientId: () => string | undefined, logger: TelemetryLoggerExt);
21
21
  dispose(): void;
22
22
  /**
23
23
  * Callback for DeltaManager's "op" event, for us to make decision if op processing should
@@ -1 +1 @@
1
- {"version":3,"file":"inboundBatchAggregator.d.ts","sourceRoot":"","sources":["../src/inboundBatchAggregator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gDAAgD,CAAC;AAIxF,OAAO,EACN,KAAK,mBAAmB,EAIxB,MAAM,0CAA0C,CAAC;AAWlD;;;GAGG;AACH,qBAAa,sBAAsB;IAQjC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IATxB,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAK;gBAGL,YAAY,EAAE,iBAAiB,EAC/B,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS,EACrC,MAAM,EAAE,mBAAmB;IAgBtC,OAAO,IAAI,IAAI;IAKtB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CA4ChC;IAEF;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CA8G3B;IAEF,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,WAAW;CA0BnB"}
1
+ {"version":3,"file":"inboundBatchAggregator.d.ts","sourceRoot":"","sources":["../src/inboundBatchAggregator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gDAAgD,CAAC;AAIxF,OAAO,EACN,KAAK,kBAAkB,EAIvB,MAAM,0CAA0C,CAAC;AAWlD;;;GAGG;AACH,qBAAa,sBAAsB;IAQjC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IATxB,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAK;gBAGL,YAAY,EAAE,iBAAiB,EAC/B,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS,EACrC,MAAM,EAAE,kBAAkB;IAgBrC,OAAO,IAAI,IAAI;IAKtB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CA4ChC;IAEF;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CA8G3B;IAEF,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,WAAW;CA0BnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"inboundBatchAggregator.js","sourceRoot":"","sources":["../src/inboundBatchAggregator.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { performanceNow } from \"@fluid-internal/client-utils\";\nimport type { IDeltaManagerFull } from \"@fluidframework/container-definitions/internal\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport { isRuntimeMessage } from \"@fluidframework/driver-utils/internal\";\nimport {\n\ttype ITelemetryLoggerExt,\n\tDataCorruptionError,\n\tDataProcessingError,\n\textractSafePropertiesFromMessage,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { IBatchMetadata } from \"./metadata.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\ntype IRuntimeMessageMetadata =\n\t| undefined\n\t| {\n\t\t\tbatch?: boolean;\n\t };\n\n/**\n * This class ensures that we aggregate a complete batch of incoming ops before processing them. It basically ensures\n * that we never start processing ops in a batch IF we do not have all ops in the batch.\n */\nexport class InboundBatchAggregator {\n\tprivate pauseSequenceNumber: number | undefined;\n\tprivate currentBatchClientId: string | undefined;\n\tprivate localPaused = false;\n\tprivate timePaused = 0;\n\tprivate batchCount = 0;\n\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManagerFull,\n\t\tprivate readonly getClientId: () => string | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t) {\n\t\t// Listen for updates and peek at the inbound\n\t\tthis.deltaManager.inbound.on(\"push\", this.trackPending);\n\n\t\tconst allPending = this.deltaManager.inbound.toArray();\n\t\tfor (const pending of allPending) {\n\t\t\tthis.trackPending(pending);\n\t\t}\n\n\t\t// We are intentionally directly listening to the \"op\" to inspect system ops as well.\n\t\t// If we do not observe system ops, we are likely to hit an error when system ops\n\t\t// precedes start of incomplete batch.\n\t\tthis.deltaManager.on(\"op\", this.afterOpProcessing);\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.deltaManager.off(\"op\", this.afterOpProcessing);\n\t\tthis.deltaManager.inbound.off(\"push\", this.trackPending);\n\t}\n\n\t/**\n\t * Callback for DeltaManager's \"op\" event, for us to make decision if op processing should\n\t * be paused or not after that.\n\t */\n\tprivate readonly afterOpProcessing = (message: ISequencedDocumentMessage): void => {\n\t\tassert(\n\t\t\t!this.localPaused,\n\t\t\t0x294 /* \"can't have op processing paused if we are processing an op\" */,\n\t\t);\n\n\t\t// If the inbound queue is ever empty, nothing to do!\n\t\tif (this.deltaManager.inbound.length === 0) {\n\t\t\tassert(\n\t\t\t\tthis.pauseSequenceNumber === undefined,\n\t\t\t\t0x295 /* \"there should be no pending batch if we have no ops\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// The queue is\n\t\t// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:\n\t\t// - here (processing ops until reaching start of incomplete batch)\n\t\t// - in trackPending(), when queue was empty and start of batch showed up.\n\t\t// 2. resumed when batch end comes in (in trackPending())\n\n\t\t// do we have incomplete batch to worry about?\n\t\tif (this.pauseSequenceNumber !== undefined) {\n\t\t\tif (message.sequenceNumber >= this.pauseSequenceNumber) {\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t// Former assert 0x296\n\t\t\t\t\t\"Incomplete batch\",\n\t\t\t\t\t\"InboundBatchAggregator\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: message.type,\n\t\t\t\t\t\tcontentType: typeof message.contents,\n\t\t\t\t\t\tbatch: (message.metadata as IBatchMetadata | undefined)?.batch,\n\t\t\t\t\t\tcompression: message.compression,\n\t\t\t\t\t\tpauseSeqNum: this.pauseSequenceNumber,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!\n\t\t\tif (message.sequenceNumber + 1 === this.pauseSequenceNumber) {\n\t\t\t\tthis.pauseQueue();\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Called for each incoming op (i.e. inbound \"push\" notification)\n\t */\n\tprivate readonly trackPending = (message: ISequencedDocumentMessage): void => {\n\t\tassert(\n\t\t\tthis.deltaManager.inbound.length > 0,\n\t\t\t0x298 /* \"we have something in the queue that generates this event\" */,\n\t\t);\n\n\t\tassert(\n\t\t\t(this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined),\n\t\t\t0x299 /* \"non-synchronized state\" */,\n\t\t);\n\n\t\tconst metadata = message.metadata as IRuntimeMessageMetadata;\n\t\t// batchMetadata will be true for the message that starts a batch, false for the one that ends it, and\n\t\t// undefined for all other messages.\n\t\tconst batchMetadata = metadata?.batch;\n\n\t\t// Protocol messages are never part of a runtime batch of messages\n\t\tif (!isRuntimeMessage(message)) {\n\t\t\t// Protocol messages should never show up in the middle of the batch!\n\t\t\tif (this.currentBatchClientId !== undefined) {\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t\"Received a system message during batch processing\", // Formerly known as assert 0x29a\n\t\t\t\t\t\"trackPending\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\t\t\tthis.currentBatchClientId === null ? \"null\" : this.currentBatchClientId,\n\t\t\t\t\t\tpauseSequenceNumber: this.pauseSequenceNumber,\n\t\t\t\t\t\tlocalBatch: this.currentBatchClientId === this.getClientId(),\n\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tassert(batchMetadata === undefined, 0x29b /* \"system op in a batch?\" */);\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29c /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.currentBatchClientId === undefined && batchMetadata === undefined) {\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29d /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// If we got here, the message is part of a batch. Either starting, in progress, or ending.\n\n\t\t// If this is not the start of the batch, error out if the message was sent by a client other than the one that\n\t\t// started the current batch (it should not be possible for ops from other clients to get interleaved with a batch).\n\t\tif (\n\t\t\tthis.currentBatchClientId !== undefined &&\n\t\t\tthis.currentBatchClientId !== message.clientId\n\t\t) {\n\t\t\tthrow new DataCorruptionError(\"OpBatchIncomplete\", {\n\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\tbatchClientId:\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\tthis.currentBatchClientId === null ? \"null\" : this.currentBatchClientId,\n\t\t\t\tpauseSequenceNumber: this.pauseSequenceNumber,\n\t\t\t\tlocalBatch: this.currentBatchClientId === this.getClientId(),\n\t\t\t\tlocalMessage: message.clientId === this.getClientId(),\n\t\t\t\t...extractSafePropertiesFromMessage(message),\n\t\t\t});\n\t\t}\n\n\t\t// The queue is\n\t\t// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:\n\t\t// - in afterOpProcessing() - processing ops until reaching start of incomplete batch\n\t\t// - here, when queue was empty and start of batch showed up (batchMetadata === true below).\n\t\t// 2. resumed when batch end comes in (batchMetadata === false below)\n\n\t\tif (batchMetadata === true) {\n\t\t\tassert(\n\t\t\t\tthis.currentBatchClientId === undefined,\n\t\t\t\t0x29e /* \"there can't be active batch\" */,\n\t\t\t);\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29f /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\tthis.pauseSequenceNumber = message.sequenceNumber;\n\t\t\t// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)\n\n\t\t\tthis.currentBatchClientId = message.clientId as string;\n\t\t\t// Start of the batch\n\t\t\t// Only pause processing if queue has no other ops!\n\t\t\t// If there are any other ops in the queue, processing will be stopped when they are processed!\n\t\t\tif (this.deltaManager.inbound.length === 1) {\n\t\t\t\tthis.pauseQueue();\n\t\t\t}\n\t\t} else if (batchMetadata === false) {\n\t\t\tassert(\n\t\t\t\tthis.pauseSequenceNumber !== undefined,\n\t\t\t\t0x2a0 /* \"batch presence was validated above\" */,\n\t\t\t);\n\t\t\t// Batch is complete, we can process it!\n\t\t\tthis.resumeQueue(this.pauseSequenceNumber, message);\n\t\t\tthis.pauseSequenceNumber = undefined;\n\t\t\tthis.currentBatchClientId = undefined;\n\t\t} else {\n\t\t\t// Continuation of current batch. Do nothing\n\t\t\tassert(this.currentBatchClientId !== undefined, 0x2a1 /* \"logic error\" */);\n\t\t}\n\t};\n\n\tprivate pauseQueue(): void {\n\t\tassert(!this.localPaused, 0x297 /* \"always called from resumed state\" */);\n\t\tthis.localPaused = true;\n\t\tthis.timePaused = performanceNow();\n\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\tthis.deltaManager.inbound.pause();\n\t}\n\n\tprivate resumeQueue(startBatch: number, messageEndBatch: ISequencedDocumentMessage): void {\n\t\tconst endBatch = messageEndBatch.sequenceNumber;\n\t\tconst duration = this.localPaused ? performanceNow() - this.timePaused : undefined;\n\n\t\tthis.batchCount++;\n\t\tif (this.batchCount % 1000 === 1) {\n\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"BatchStats\",\n\t\t\t\tsequenceNumber: endBatch,\n\t\t\t\tlength: endBatch - startBatch + 1,\n\t\t\t\tmsnDistance: endBatch - messageEndBatch.minimumSequenceNumber,\n\t\t\t\tduration,\n\t\t\t\tbatchCount: this.batchCount,\n\t\t\t\tinterrupted: this.localPaused,\n\t\t\t});\n\t\t}\n\n\t\t// Return early if no change in value\n\t\tif (!this.localPaused) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.localPaused = false;\n\n\t\tthis.deltaManager.inbound.resume();\n\t}\n}\n"]}
1
+ {"version":3,"file":"inboundBatchAggregator.js","sourceRoot":"","sources":["../src/inboundBatchAggregator.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { performanceNow } from \"@fluid-internal/client-utils\";\nimport type { IDeltaManagerFull } from \"@fluidframework/container-definitions/internal\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport { isRuntimeMessage } from \"@fluidframework/driver-utils/internal\";\nimport {\n\ttype TelemetryLoggerExt,\n\tDataCorruptionError,\n\tDataProcessingError,\n\textractSafePropertiesFromMessage,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { IBatchMetadata } from \"./metadata.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\ntype IRuntimeMessageMetadata =\n\t| undefined\n\t| {\n\t\t\tbatch?: boolean;\n\t };\n\n/**\n * This class ensures that we aggregate a complete batch of incoming ops before processing them. It basically ensures\n * that we never start processing ops in a batch IF we do not have all ops in the batch.\n */\nexport class InboundBatchAggregator {\n\tprivate pauseSequenceNumber: number | undefined;\n\tprivate currentBatchClientId: string | undefined;\n\tprivate localPaused = false;\n\tprivate timePaused = 0;\n\tprivate batchCount = 0;\n\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManagerFull,\n\t\tprivate readonly getClientId: () => string | undefined,\n\t\tprivate readonly logger: TelemetryLoggerExt,\n\t) {\n\t\t// Listen for updates and peek at the inbound\n\t\tthis.deltaManager.inbound.on(\"push\", this.trackPending);\n\n\t\tconst allPending = this.deltaManager.inbound.toArray();\n\t\tfor (const pending of allPending) {\n\t\t\tthis.trackPending(pending);\n\t\t}\n\n\t\t// We are intentionally directly listening to the \"op\" to inspect system ops as well.\n\t\t// If we do not observe system ops, we are likely to hit an error when system ops\n\t\t// precedes start of incomplete batch.\n\t\tthis.deltaManager.on(\"op\", this.afterOpProcessing);\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.deltaManager.off(\"op\", this.afterOpProcessing);\n\t\tthis.deltaManager.inbound.off(\"push\", this.trackPending);\n\t}\n\n\t/**\n\t * Callback for DeltaManager's \"op\" event, for us to make decision if op processing should\n\t * be paused or not after that.\n\t */\n\tprivate readonly afterOpProcessing = (message: ISequencedDocumentMessage): void => {\n\t\tassert(\n\t\t\t!this.localPaused,\n\t\t\t0x294 /* \"can't have op processing paused if we are processing an op\" */,\n\t\t);\n\n\t\t// If the inbound queue is ever empty, nothing to do!\n\t\tif (this.deltaManager.inbound.length === 0) {\n\t\t\tassert(\n\t\t\t\tthis.pauseSequenceNumber === undefined,\n\t\t\t\t0x295 /* \"there should be no pending batch if we have no ops\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// The queue is\n\t\t// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:\n\t\t// - here (processing ops until reaching start of incomplete batch)\n\t\t// - in trackPending(), when queue was empty and start of batch showed up.\n\t\t// 2. resumed when batch end comes in (in trackPending())\n\n\t\t// do we have incomplete batch to worry about?\n\t\tif (this.pauseSequenceNumber !== undefined) {\n\t\t\tif (message.sequenceNumber >= this.pauseSequenceNumber) {\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t// Former assert 0x296\n\t\t\t\t\t\"Incomplete batch\",\n\t\t\t\t\t\"InboundBatchAggregator\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: message.type,\n\t\t\t\t\t\tcontentType: typeof message.contents,\n\t\t\t\t\t\tbatch: (message.metadata as IBatchMetadata | undefined)?.batch,\n\t\t\t\t\t\tcompression: message.compression,\n\t\t\t\t\t\tpauseSeqNum: this.pauseSequenceNumber,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!\n\t\t\tif (message.sequenceNumber + 1 === this.pauseSequenceNumber) {\n\t\t\t\tthis.pauseQueue();\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Called for each incoming op (i.e. inbound \"push\" notification)\n\t */\n\tprivate readonly trackPending = (message: ISequencedDocumentMessage): void => {\n\t\tassert(\n\t\t\tthis.deltaManager.inbound.length > 0,\n\t\t\t0x298 /* \"we have something in the queue that generates this event\" */,\n\t\t);\n\n\t\tassert(\n\t\t\t(this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined),\n\t\t\t0x299 /* \"non-synchronized state\" */,\n\t\t);\n\n\t\tconst metadata = message.metadata as IRuntimeMessageMetadata;\n\t\t// batchMetadata will be true for the message that starts a batch, false for the one that ends it, and\n\t\t// undefined for all other messages.\n\t\tconst batchMetadata = metadata?.batch;\n\n\t\t// Protocol messages are never part of a runtime batch of messages\n\t\tif (!isRuntimeMessage(message)) {\n\t\t\t// Protocol messages should never show up in the middle of the batch!\n\t\t\tif (this.currentBatchClientId !== undefined) {\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t\"Received a system message during batch processing\", // Formerly known as assert 0x29a\n\t\t\t\t\t\"trackPending\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\t\t\tthis.currentBatchClientId === null ? \"null\" : this.currentBatchClientId,\n\t\t\t\t\t\tpauseSequenceNumber: this.pauseSequenceNumber,\n\t\t\t\t\t\tlocalBatch: this.currentBatchClientId === this.getClientId(),\n\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tassert(batchMetadata === undefined, 0x29b /* \"system op in a batch?\" */);\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29c /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.currentBatchClientId === undefined && batchMetadata === undefined) {\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29d /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// If we got here, the message is part of a batch. Either starting, in progress, or ending.\n\n\t\t// If this is not the start of the batch, error out if the message was sent by a client other than the one that\n\t\t// started the current batch (it should not be possible for ops from other clients to get interleaved with a batch).\n\t\tif (\n\t\t\tthis.currentBatchClientId !== undefined &&\n\t\t\tthis.currentBatchClientId !== message.clientId\n\t\t) {\n\t\t\tthrow new DataCorruptionError(\"OpBatchIncomplete\", {\n\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\tbatchClientId:\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\tthis.currentBatchClientId === null ? \"null\" : this.currentBatchClientId,\n\t\t\t\tpauseSequenceNumber: this.pauseSequenceNumber,\n\t\t\t\tlocalBatch: this.currentBatchClientId === this.getClientId(),\n\t\t\t\tlocalMessage: message.clientId === this.getClientId(),\n\t\t\t\t...extractSafePropertiesFromMessage(message),\n\t\t\t});\n\t\t}\n\n\t\t// The queue is\n\t\t// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:\n\t\t// - in afterOpProcessing() - processing ops until reaching start of incomplete batch\n\t\t// - here, when queue was empty and start of batch showed up (batchMetadata === true below).\n\t\t// 2. resumed when batch end comes in (batchMetadata === false below)\n\n\t\tif (batchMetadata === true) {\n\t\t\tassert(\n\t\t\t\tthis.currentBatchClientId === undefined,\n\t\t\t\t0x29e /* \"there can't be active batch\" */,\n\t\t\t);\n\t\t\tassert(\n\t\t\t\t!this.localPaused,\n\t\t\t\t0x29f /* \"we should be processing ops when there is no active batch\" */,\n\t\t\t);\n\t\t\tthis.pauseSequenceNumber = message.sequenceNumber;\n\t\t\t// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)\n\n\t\t\tthis.currentBatchClientId = message.clientId as string;\n\t\t\t// Start of the batch\n\t\t\t// Only pause processing if queue has no other ops!\n\t\t\t// If there are any other ops in the queue, processing will be stopped when they are processed!\n\t\t\tif (this.deltaManager.inbound.length === 1) {\n\t\t\t\tthis.pauseQueue();\n\t\t\t}\n\t\t} else if (batchMetadata === false) {\n\t\t\tassert(\n\t\t\t\tthis.pauseSequenceNumber !== undefined,\n\t\t\t\t0x2a0 /* \"batch presence was validated above\" */,\n\t\t\t);\n\t\t\t// Batch is complete, we can process it!\n\t\t\tthis.resumeQueue(this.pauseSequenceNumber, message);\n\t\t\tthis.pauseSequenceNumber = undefined;\n\t\t\tthis.currentBatchClientId = undefined;\n\t\t} else {\n\t\t\t// Continuation of current batch. Do nothing\n\t\t\tassert(this.currentBatchClientId !== undefined, 0x2a1 /* \"logic error\" */);\n\t\t}\n\t};\n\n\tprivate pauseQueue(): void {\n\t\tassert(!this.localPaused, 0x297 /* \"always called from resumed state\" */);\n\t\tthis.localPaused = true;\n\t\tthis.timePaused = performanceNow();\n\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\tthis.deltaManager.inbound.pause();\n\t}\n\n\tprivate resumeQueue(startBatch: number, messageEndBatch: ISequencedDocumentMessage): void {\n\t\tconst endBatch = messageEndBatch.sequenceNumber;\n\t\tconst duration = this.localPaused ? performanceNow() - this.timePaused : undefined;\n\n\t\tthis.batchCount++;\n\t\tif (this.batchCount % 1000 === 1) {\n\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"BatchStats\",\n\t\t\t\tsequenceNumber: endBatch,\n\t\t\t\tlength: endBatch - startBatch + 1,\n\t\t\t\tmsnDistance: endBatch - messageEndBatch.minimumSequenceNumber,\n\t\t\t\tduration,\n\t\t\t\tbatchCount: this.batchCount,\n\t\t\t\tinterrupted: this.localPaused,\n\t\t\t});\n\t\t}\n\n\t\t// Return early if no change in value\n\t\tif (!this.localPaused) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.localPaused = false;\n\n\t\tthis.deltaManager.inbound.resume();\n\t}\n}\n"]}
@@ -4,6 +4,30 @@
4
4
  */
5
5
  import type { ITelemetryContext } from "@fluidframework/runtime-definitions/internal";
6
6
  import type { BatchStartInfo } from "./remoteMessageProcessor.js";
7
+ /**
8
+ * Identifying info for a previously-recorded batch that we can include in DuplicateBatch telemetry
9
+ * to help diagnose where the duplicate came from.
10
+ *
11
+ * @remarks `batchIdExplicit` distinguishes the two main duplicate-source scenarios:
12
+ * - `true`: the batchId was stamped on the wire as explicit metadata, indicating a resubmit (PendingStateManager.replayPendingStates).
13
+ * - `false`: the batchId was derived from the wire `clientId` and `batchStartCsn`, indicating a fresh, non-resubmit batch.
14
+ */
15
+ export interface RecordedBatchInfo {
16
+ /**
17
+ * Wire clientId on the message that started the batch (NOT necessarily the `originalClientId`
18
+ * encoded in the batchId for resubmits).
19
+ */
20
+ readonly clientId: string;
21
+ /**
22
+ * Wire client sequence number at the start of the batch.
23
+ */
24
+ readonly batchStartCsn: number;
25
+ /**
26
+ * True if the batchId came from explicit metadata on the wire (i.e. a resubmit),
27
+ * false if it was derived from clientId + batchStartCsn (i.e. a fresh submit).
28
+ */
29
+ readonly batchIdExplicit: boolean;
30
+ }
7
31
  /**
8
32
  * Detects duplicate batches that can arise from the "parallel fork" scenario:
9
33
  * Container 1 is serialized, and Containers 2 and 3 are rehydrated from that state.
@@ -18,22 +42,34 @@ export declare class DuplicateBatchDetector {
18
42
  */
19
43
  private readonly seqNumByBatchId;
20
44
  /**
21
- * We map from sequenceNumber to batchId to find which ones we can stop tracking as MSN advances
45
+ * Map from sequenceNumber to the recorded batch info. Used to clear out old entries as MSN
46
+ * advances, and to report identifying info about the original occurrence when a duplicate
47
+ * is detected.
48
+ */
49
+ private readonly batchesBySeqNum;
50
+ /**
51
+ * Number of inbound batches processed since the last summary. Reset by getRecentBatchInfoForSummary.
52
+ */
53
+ private processedBatchCount;
54
+ /**
55
+ * Largest tracked-batch count observed since the last summary. Reset by getRecentBatchInfoForSummary.
22
56
  */
23
- private readonly batchIdsBySeqNum;
57
+ private peakTrackedBatchCount;
24
58
  /**
25
59
  * Initialize from snapshot data if provided - otherwise initialize empty
26
60
  */
27
61
  constructor(batchIdsFromSnapshot: [number, string][] | undefined);
28
62
  /**
29
63
  * Records this batch's batchId, and checks if it's a duplicate of a batch we've already seen.
30
- * If it's a duplicate, also return the sequence number of the other batch for logging.
64
+ * If it's a duplicate, also return the sequence number of the other batch (and identifying info,
65
+ * if the other batch was seen during this container session rather than loaded from snapshot) for logging.
31
66
  *
32
67
  * @remarks We also use the minimumSequenceNumber to clear out old batchIds that are no longer at risk for duplicates.
33
68
  */
34
69
  processInboundBatch(batchStart: BatchStartInfo): {
35
70
  duplicate: true;
36
71
  otherSequenceNumber: number;
72
+ otherBatchInfo: RecordedBatchInfo | undefined;
37
73
  } | {
38
74
  duplicate: false;
39
75
  };
@@ -1 +1 @@
1
- {"version":3,"file":"duplicateBatchDetector.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/duplicateBatchDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAGtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;;;GAOG;AACH,qBAAa,sBAAsB;IAClC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAE7D;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAE9D;;OAEG;gBACS,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS;IAShE;;;;;OAKG;IACI,mBAAmB,CACzB,UAAU,EAAE,cAAc,GACxB;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,SAAS,EAAE,KAAK,CAAA;KAAE;IAoC1E;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;OAKG;IACI,4BAA4B,CAClC,gBAAgB,CAAC,EAAE,iBAAiB,GAClC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS;CAajC"}
1
+ {"version":3,"file":"duplicateBatchDetector.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/duplicateBatchDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAGtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC;AAYD;;;;;;;GAOG;AACH,qBAAa,sBAAsB;IAClC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAE7D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IAEpE;;OAEG;IACH,OAAO,CAAC,mBAAmB,CAAK;IAEhC;;OAEG;IACH,OAAO,CAAC,qBAAqB,CAAK;IAElC;;OAEG;gBACS,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS;IAYhE;;;;;;OAMG;IACI,mBAAmB,CAAC,UAAU,EAAE,cAAc,GAClD;QACA,SAAS,EAAE,IAAI,CAAC;QAChB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,cAAc,EAAE,iBAAiB,GAAG,SAAS,CAAC;KAC7C,GACD;QAAE,SAAS,EAAE,KAAK,CAAA;KAAE;IAoDvB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;OAKG;IACI,4BAA4B,CAClC,gBAAgB,CAAC,EAAE,iBAAiB,GAClC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS;CAsBjC"}
@@ -22,24 +22,39 @@ export class DuplicateBatchDetector {
22
22
  */
23
23
  this.seqNumByBatchId = new Map();
24
24
  /**
25
- * We map from sequenceNumber to batchId to find which ones we can stop tracking as MSN advances
25
+ * Map from sequenceNumber to the recorded batch info. Used to clear out old entries as MSN
26
+ * advances, and to report identifying info about the original occurrence when a duplicate
27
+ * is detected.
26
28
  */
27
- this.batchIdsBySeqNum = new Map();
29
+ this.batchesBySeqNum = new Map();
30
+ /**
31
+ * Number of inbound batches processed since the last summary. Reset by getRecentBatchInfoForSummary.
32
+ */
33
+ this.processedBatchCount = 0;
34
+ /**
35
+ * Largest tracked-batch count observed since the last summary. Reset by getRecentBatchInfoForSummary.
36
+ */
37
+ this.peakTrackedBatchCount = 0;
28
38
  if (batchIdsFromSnapshot) {
29
39
  for (const [seqNum, batchId] of batchIdsFromSnapshot) {
30
- this.batchIdsBySeqNum.set(seqNum, batchId);
40
+ // Entries loaded from a snapshot don't carry the original clientId/csn/explicit-bit;
41
+ // we record them with `info: undefined` so duplicate telemetry can indicate that.
42
+ this.batchesBySeqNum.set(seqNum, { batchId, info: undefined });
31
43
  this.seqNumByBatchId.set(batchId, seqNum);
32
44
  }
45
+ this.peakTrackedBatchCount = this.batchesBySeqNum.size;
33
46
  }
34
47
  }
35
48
  /**
36
49
  * Records this batch's batchId, and checks if it's a duplicate of a batch we've already seen.
37
- * If it's a duplicate, also return the sequence number of the other batch for logging.
50
+ * If it's a duplicate, also return the sequence number of the other batch (and identifying info,
51
+ * if the other batch was seen during this container session rather than loaded from snapshot) for logging.
38
52
  *
39
53
  * @remarks We also use the minimumSequenceNumber to clear out old batchIds that are no longer at risk for duplicates.
40
54
  */
41
55
  processInboundBatch(batchStart) {
42
56
  const { sequenceNumber, minimumSequenceNumber } = batchStart.keyMessage;
57
+ this.processedBatchCount++;
43
58
  // Glance at this batch's MSN. Any batchIds we're tracking with a lower sequence number are now safe to forget.
44
59
  // Why? Because any other client holding the same batch locally would have seen the earlier batch and closed before submitting its duplicate.
45
60
  this.clearOldBatchIds(minimumSequenceNumber);
@@ -51,14 +66,29 @@ export class DuplicateBatchDetector {
51
66
  // O(1) duplicate check + get otherSequenceNumber in one lookup
52
67
  const otherSequenceNumber = this.seqNumByBatchId.get(batchId);
53
68
  if (otherSequenceNumber !== undefined) {
54
- assert(this.batchIdsBySeqNum.get(otherSequenceNumber) === batchId, 0xce0 /* batchIdToSeqNum and seqNumToBatchId should be in sync for duplicate */);
55
- return { duplicate: true, otherSequenceNumber };
69
+ const other = this.batchesBySeqNum.get(otherSequenceNumber);
70
+ assert(other?.batchId === batchId, 0xce0 /* batchIdToSeqNum and seqNumToBatchId should be in sync for duplicate */);
71
+ return {
72
+ duplicate: true,
73
+ otherSequenceNumber,
74
+ otherBatchInfo: other.info,
75
+ };
56
76
  }
57
77
  // Now we know it's not a duplicate, so add it to the tracked batchIds and return.
58
- assert(!this.batchIdsBySeqNum.has(sequenceNumber), 0xce1 /* seqNumToBatchId and batchIdToSeqNum should be in sync */);
59
- // Add new batch
60
- this.batchIdsBySeqNum.set(sequenceNumber, batchId);
78
+ assert(!this.batchesBySeqNum.has(sequenceNumber), 0xce1 /* seqNumToBatchId and batchIdToSeqNum should be in sync */);
79
+ // Add new batch. Record identifying info so we can report it if a future duplicate matches us.
80
+ const info = {
81
+ clientId: batchStart.clientId,
82
+ batchStartCsn: batchStart.batchStartCsn,
83
+ // True iff the wire carried explicit batchId metadata (resubmit path).
84
+ // False indicates the batchId was derived from clientId + batchStartCsn (fresh submit).
85
+ batchIdExplicit: batchStart.batchId !== undefined,
86
+ };
87
+ this.batchesBySeqNum.set(sequenceNumber, { batchId, info });
61
88
  this.seqNumByBatchId.set(batchId, sequenceNumber);
89
+ if (this.batchesBySeqNum.size > this.peakTrackedBatchCount) {
90
+ this.peakTrackedBatchCount = this.batchesBySeqNum.size;
91
+ }
62
92
  return { duplicate: false };
63
93
  }
64
94
  /**
@@ -66,10 +96,10 @@ export class DuplicateBatchDetector {
66
96
  * since the batch start has been processed by all clients, and local batches are deduped and the forked client would close.
67
97
  */
68
98
  clearOldBatchIds(msn) {
69
- for (const [sequenceNumber, batchId] of this.batchIdsBySeqNum) {
99
+ for (const [sequenceNumber, recorded] of this.batchesBySeqNum) {
70
100
  if (sequenceNumber < msn) {
71
- this.batchIdsBySeqNum.delete(sequenceNumber);
72
- this.seqNumByBatchId.delete(batchId);
101
+ this.batchesBySeqNum.delete(sequenceNumber);
102
+ this.seqNumByBatchId.delete(recorded.batchId);
73
103
  }
74
104
  else {
75
105
  break;
@@ -83,11 +113,23 @@ export class DuplicateBatchDetector {
83
113
  * @returns A serializable object representing the state of the detector, or undefined if there is nothing to save.
84
114
  */
85
115
  getRecentBatchInfoForSummary(telemetryContext) {
86
- if (this.batchIdsBySeqNum.size === 0) {
116
+ if (telemetryContext !== undefined) {
117
+ const prefix = "fluid_DuplicateBatchDetector_";
118
+ telemetryContext.set(prefix, "recentBatchCount", this.batchesBySeqNum.size);
119
+ telemetryContext.set(prefix, "peakRecentBatchCount", this.peakTrackedBatchCount);
120
+ telemetryContext.set(prefix, "processedBatchCount", this.processedBatchCount);
121
+ }
122
+ // Reset per-window perf counters so each summary covers only the activity since the
123
+ // previous one. Peak resets to the current size (the floor for the next window).
124
+ this.processedBatchCount = 0;
125
+ this.peakTrackedBatchCount = this.batchesBySeqNum.size;
126
+ if (this.batchesBySeqNum.size === 0) {
87
127
  return undefined;
88
128
  }
89
- telemetryContext?.set("fluid_DuplicateBatchDetector_", "recentBatchCount", this.batchIdsBySeqNum.size);
90
- return [...this.batchIdsBySeqNum.entries()];
129
+ return [...this.batchesBySeqNum.entries()].map(([seqNum, recorded]) => [
130
+ seqNum,
131
+ recorded.batchId,
132
+ ]);
91
133
  }
92
134
  }
93
135
  //# sourceMappingURL=duplicateBatchDetector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"duplicateBatchDetector.js","sourceRoot":"","sources":["../../src/opLifecycle/duplicateBatchDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAG7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD;;;;;;;GAOG;AACH,MAAM,OAAO,sBAAsB;IAWlC;;OAEG;IACH,YAAY,oBAAoD;QAbhE;;WAEG;QACc,oBAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE7D;;WAEG;QACc,qBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;QAM7D,IAAI,oBAAoB,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC;gBACtD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAC3C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC3C,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,mBAAmB,CACzB,UAA0B;QAE1B,MAAM,EAAE,cAAc,EAAE,qBAAqB,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC;QAExE,+GAA+G;QAC/G,6IAA6I;QAC7I,IAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QAE7C,6EAA6E;QAC7E,oGAAoG;QACpG,4EAA4E;QAC5E,4GAA4G;QAC5G,MAAM,OAAO,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAEhD,+DAA+D;QAC/D,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9D,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;YACvC,MAAM,CACL,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,mBAAmB,CAAC,KAAK,OAAO,EAC1D,KAAK,CAAC,yEAAyE,CAC/E,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC;QACjD,CAAC;QAED,kFAAkF;QAClF,MAAM,CACL,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,EAC1C,KAAK,CAAC,2DAA2D,CACjE,CAAC;QAEF,gBAAgB;QAChB,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAElD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACK,gBAAgB,CAAC,GAAW;QACnC,KAAK,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC/D,IAAI,cAAc,GAAG,GAAG,EAAE,CAAC;gBAC1B,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBAC7C,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC;iBAAM,CAAC;gBACP,MAAM;YACP,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,4BAA4B,CAClC,gBAAoC;QAEpC,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACtC,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,gBAAgB,EAAE,GAAG,CACpB,+BAA+B,EAC/B,kBAAkB,EAClB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAC1B,CAAC;QAEF,OAAO,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ITelemetryContext } from \"@fluidframework/runtime-definitions/internal\";\n\nimport { getEffectiveBatchId } from \"./batchManager.js\";\nimport type { BatchStartInfo } from \"./remoteMessageProcessor.js\";\n\n/**\n * Detects duplicate batches that can arise from the \"parallel fork\" scenario:\n * Container 1 is serialized, and Containers 2 and 3 are rehydrated from that state.\n * They both catch up and (re)connect in parallel (at the same time), submitting the same local state,\n * sharing the same batchId and sequence number.\n *\n * For \"serial fork\" detection scenarios see PendingStateManager.\n */\nexport class DuplicateBatchDetector {\n\t/**\n\t * Map from batchId to sequenceNumber\n\t */\n\tprivate readonly seqNumByBatchId = new Map<string, number>();\n\n\t/**\n\t * We map from sequenceNumber to batchId to find which ones we can stop tracking as MSN advances\n\t */\n\tprivate readonly batchIdsBySeqNum = new Map<number, string>();\n\n\t/**\n\t * Initialize from snapshot data if provided - otherwise initialize empty\n\t */\n\tconstructor(batchIdsFromSnapshot: [number, string][] | undefined) {\n\t\tif (batchIdsFromSnapshot) {\n\t\t\tfor (const [seqNum, batchId] of batchIdsFromSnapshot) {\n\t\t\t\tthis.batchIdsBySeqNum.set(seqNum, batchId);\n\t\t\t\tthis.seqNumByBatchId.set(batchId, seqNum);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Records this batch's batchId, and checks if it's a duplicate of a batch we've already seen.\n\t * If it's a duplicate, also return the sequence number of the other batch for logging.\n\t *\n\t * @remarks We also use the minimumSequenceNumber to clear out old batchIds that are no longer at risk for duplicates.\n\t */\n\tpublic processInboundBatch(\n\t\tbatchStart: BatchStartInfo,\n\t): { duplicate: true; otherSequenceNumber: number } | { duplicate: false } {\n\t\tconst { sequenceNumber, minimumSequenceNumber } = batchStart.keyMessage;\n\n\t\t// Glance at this batch's MSN. Any batchIds we're tracking with a lower sequence number are now safe to forget.\n\t\t// Why? Because any other client holding the same batch locally would have seen the earlier batch and closed before submitting its duplicate.\n\t\tthis.clearOldBatchIds(minimumSequenceNumber);\n\n\t\t// getEffectiveBatchId is only needed in the SUPER rare/surprising case where\n\t\t// the original batch (not resubmitted, so no batchId) arrives in parallel with a resubmitted batch.\n\t\t// In the presence of typical network conditions, this would not be possible\n\t\t// (the original batch should roundtrip WAY before another container could rehydrate, connect, and resubmit)\n\t\tconst batchId = getEffectiveBatchId(batchStart);\n\n\t\t// O(1) duplicate check + get otherSequenceNumber in one lookup\n\t\tconst otherSequenceNumber = this.seqNumByBatchId.get(batchId);\n\t\tif (otherSequenceNumber !== undefined) {\n\t\t\tassert(\n\t\t\t\tthis.batchIdsBySeqNum.get(otherSequenceNumber) === batchId,\n\t\t\t\t0xce0 /* batchIdToSeqNum and seqNumToBatchId should be in sync for duplicate */,\n\t\t\t);\n\t\t\treturn { duplicate: true, otherSequenceNumber };\n\t\t}\n\n\t\t// Now we know it's not a duplicate, so add it to the tracked batchIds and return.\n\t\tassert(\n\t\t\t!this.batchIdsBySeqNum.has(sequenceNumber),\n\t\t\t0xce1 /* seqNumToBatchId and batchIdToSeqNum should be in sync */,\n\t\t);\n\n\t\t// Add new batch\n\t\tthis.batchIdsBySeqNum.set(sequenceNumber, batchId);\n\t\tthis.seqNumByBatchId.set(batchId, sequenceNumber);\n\n\t\treturn { duplicate: false };\n\t}\n\n\t/**\n\t * Batches that started before the MSN are not at risk for a sequenced duplicate to arrive,\n\t * since the batch start has been processed by all clients, and local batches are deduped and the forked client would close.\n\t */\n\tprivate clearOldBatchIds(msn: number): void {\n\t\tfor (const [sequenceNumber, batchId] of this.batchIdsBySeqNum) {\n\t\t\tif (sequenceNumber < msn) {\n\t\t\t\tthis.batchIdsBySeqNum.delete(sequenceNumber);\n\t\t\t\tthis.seqNumByBatchId.delete(batchId);\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns a snapshot of the state of the detector which can be included in a summary\n\t * and used to \"rehydrate\" this class when loading from a snapshot.\n\t *\n\t * @returns A serializable object representing the state of the detector, or undefined if there is nothing to save.\n\t */\n\tpublic getRecentBatchInfoForSummary(\n\t\ttelemetryContext?: ITelemetryContext,\n\t): [number, string][] | undefined {\n\t\tif (this.batchIdsBySeqNum.size === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\ttelemetryContext?.set(\n\t\t\t\"fluid_DuplicateBatchDetector_\",\n\t\t\t\"recentBatchCount\",\n\t\t\tthis.batchIdsBySeqNum.size,\n\t\t);\n\n\t\treturn [...this.batchIdsBySeqNum.entries()];\n\t}\n}\n"]}
1
+ {"version":3,"file":"duplicateBatchDetector.js","sourceRoot":"","sources":["../../src/opLifecycle/duplicateBatchDetector.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ITelemetryContext } from \"@fluidframework/runtime-definitions/internal\";\n\nimport { getEffectiveBatchId } from \"./batchManager.js\";\nimport type { BatchStartInfo } from \"./remoteMessageProcessor.js\";\n\n/**\n * Identifying info for a previously-recorded batch that we can include in DuplicateBatch telemetry\n * to help diagnose where the duplicate came from.\n *\n * @remarks `batchIdExplicit` distinguishes the two main duplicate-source scenarios:\n * - `true`: the batchId was stamped on the wire as explicit metadata, indicating a resubmit (PendingStateManager.replayPendingStates).\n * - `false`: the batchId was derived from the wire `clientId` and `batchStartCsn`, indicating a fresh, non-resubmit batch.\n */\nexport interface RecordedBatchInfo {\n\t/**\n\t * Wire clientId on the message that started the batch (NOT necessarily the `originalClientId`\n\t * encoded in the batchId for resubmits).\n\t */\n\treadonly clientId: string;\n\t/**\n\t * Wire client sequence number at the start of the batch.\n\t */\n\treadonly batchStartCsn: number;\n\t/**\n\t * True if the batchId came from explicit metadata on the wire (i.e. a resubmit),\n\t * false if it was derived from clientId + batchStartCsn (i.e. a fresh submit).\n\t */\n\treadonly batchIdExplicit: boolean;\n}\n\ninterface RecordedBatch {\n\treadonly batchId: string;\n\t/**\n\t * Identifying info for the batch as observed at runtime.\n\t * `undefined` if the batch was loaded from a summary snapshot (where only the\n\t * `[seqNum, batchId]` pair is persisted).\n\t */\n\treadonly info: RecordedBatchInfo | undefined;\n}\n\n/**\n * Detects duplicate batches that can arise from the \"parallel fork\" scenario:\n * Container 1 is serialized, and Containers 2 and 3 are rehydrated from that state.\n * They both catch up and (re)connect in parallel (at the same time), submitting the same local state,\n * sharing the same batchId and sequence number.\n *\n * For \"serial fork\" detection scenarios see PendingStateManager.\n */\nexport class DuplicateBatchDetector {\n\t/**\n\t * Map from batchId to sequenceNumber\n\t */\n\tprivate readonly seqNumByBatchId = new Map<string, number>();\n\n\t/**\n\t * Map from sequenceNumber to the recorded batch info. Used to clear out old entries as MSN\n\t * advances, and to report identifying info about the original occurrence when a duplicate\n\t * is detected.\n\t */\n\tprivate readonly batchesBySeqNum = new Map<number, RecordedBatch>();\n\n\t/**\n\t * Number of inbound batches processed since the last summary. Reset by getRecentBatchInfoForSummary.\n\t */\n\tprivate processedBatchCount = 0;\n\n\t/**\n\t * Largest tracked-batch count observed since the last summary. Reset by getRecentBatchInfoForSummary.\n\t */\n\tprivate peakTrackedBatchCount = 0;\n\n\t/**\n\t * Initialize from snapshot data if provided - otherwise initialize empty\n\t */\n\tconstructor(batchIdsFromSnapshot: [number, string][] | undefined) {\n\t\tif (batchIdsFromSnapshot) {\n\t\t\tfor (const [seqNum, batchId] of batchIdsFromSnapshot) {\n\t\t\t\t// Entries loaded from a snapshot don't carry the original clientId/csn/explicit-bit;\n\t\t\t\t// we record them with `info: undefined` so duplicate telemetry can indicate that.\n\t\t\t\tthis.batchesBySeqNum.set(seqNum, { batchId, info: undefined });\n\t\t\t\tthis.seqNumByBatchId.set(batchId, seqNum);\n\t\t\t}\n\t\t\tthis.peakTrackedBatchCount = this.batchesBySeqNum.size;\n\t\t}\n\t}\n\n\t/**\n\t * Records this batch's batchId, and checks if it's a duplicate of a batch we've already seen.\n\t * If it's a duplicate, also return the sequence number of the other batch (and identifying info,\n\t * if the other batch was seen during this container session rather than loaded from snapshot) for logging.\n\t *\n\t * @remarks We also use the minimumSequenceNumber to clear out old batchIds that are no longer at risk for duplicates.\n\t */\n\tpublic processInboundBatch(batchStart: BatchStartInfo):\n\t\t| {\n\t\t\t\tduplicate: true;\n\t\t\t\totherSequenceNumber: number;\n\t\t\t\totherBatchInfo: RecordedBatchInfo | undefined;\n\t\t }\n\t\t| { duplicate: false } {\n\t\tconst { sequenceNumber, minimumSequenceNumber } = batchStart.keyMessage;\n\t\tthis.processedBatchCount++;\n\n\t\t// Glance at this batch's MSN. Any batchIds we're tracking with a lower sequence number are now safe to forget.\n\t\t// Why? Because any other client holding the same batch locally would have seen the earlier batch and closed before submitting its duplicate.\n\t\tthis.clearOldBatchIds(minimumSequenceNumber);\n\n\t\t// getEffectiveBatchId is only needed in the SUPER rare/surprising case where\n\t\t// the original batch (not resubmitted, so no batchId) arrives in parallel with a resubmitted batch.\n\t\t// In the presence of typical network conditions, this would not be possible\n\t\t// (the original batch should roundtrip WAY before another container could rehydrate, connect, and resubmit)\n\t\tconst batchId = getEffectiveBatchId(batchStart);\n\n\t\t// O(1) duplicate check + get otherSequenceNumber in one lookup\n\t\tconst otherSequenceNumber = this.seqNumByBatchId.get(batchId);\n\t\tif (otherSequenceNumber !== undefined) {\n\t\t\tconst other = this.batchesBySeqNum.get(otherSequenceNumber);\n\t\t\tassert(\n\t\t\t\tother?.batchId === batchId,\n\t\t\t\t0xce0 /* batchIdToSeqNum and seqNumToBatchId should be in sync for duplicate */,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tduplicate: true,\n\t\t\t\totherSequenceNumber,\n\t\t\t\totherBatchInfo: other.info,\n\t\t\t};\n\t\t}\n\n\t\t// Now we know it's not a duplicate, so add it to the tracked batchIds and return.\n\t\tassert(\n\t\t\t!this.batchesBySeqNum.has(sequenceNumber),\n\t\t\t0xce1 /* seqNumToBatchId and batchIdToSeqNum should be in sync */,\n\t\t);\n\n\t\t// Add new batch. Record identifying info so we can report it if a future duplicate matches us.\n\t\tconst info: RecordedBatchInfo | undefined = {\n\t\t\tclientId: batchStart.clientId,\n\t\t\tbatchStartCsn: batchStart.batchStartCsn,\n\t\t\t// True iff the wire carried explicit batchId metadata (resubmit path).\n\t\t\t// False indicates the batchId was derived from clientId + batchStartCsn (fresh submit).\n\t\t\tbatchIdExplicit: batchStart.batchId !== undefined,\n\t\t};\n\t\tthis.batchesBySeqNum.set(sequenceNumber, { batchId, info });\n\t\tthis.seqNumByBatchId.set(batchId, sequenceNumber);\n\t\tif (this.batchesBySeqNum.size > this.peakTrackedBatchCount) {\n\t\t\tthis.peakTrackedBatchCount = this.batchesBySeqNum.size;\n\t\t}\n\n\t\treturn { duplicate: false };\n\t}\n\n\t/**\n\t * Batches that started before the MSN are not at risk for a sequenced duplicate to arrive,\n\t * since the batch start has been processed by all clients, and local batches are deduped and the forked client would close.\n\t */\n\tprivate clearOldBatchIds(msn: number): void {\n\t\tfor (const [sequenceNumber, recorded] of this.batchesBySeqNum) {\n\t\t\tif (sequenceNumber < msn) {\n\t\t\t\tthis.batchesBySeqNum.delete(sequenceNumber);\n\t\t\t\tthis.seqNumByBatchId.delete(recorded.batchId);\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns a snapshot of the state of the detector which can be included in a summary\n\t * and used to \"rehydrate\" this class when loading from a snapshot.\n\t *\n\t * @returns A serializable object representing the state of the detector, or undefined if there is nothing to save.\n\t */\n\tpublic getRecentBatchInfoForSummary(\n\t\ttelemetryContext?: ITelemetryContext,\n\t): [number, string][] | undefined {\n\t\tif (telemetryContext !== undefined) {\n\t\t\tconst prefix = \"fluid_DuplicateBatchDetector_\";\n\t\t\ttelemetryContext.set(prefix, \"recentBatchCount\", this.batchesBySeqNum.size);\n\t\t\ttelemetryContext.set(prefix, \"peakRecentBatchCount\", this.peakTrackedBatchCount);\n\t\t\ttelemetryContext.set(prefix, \"processedBatchCount\", this.processedBatchCount);\n\t\t}\n\n\t\t// Reset per-window perf counters so each summary covers only the activity since the\n\t\t// previous one. Peak resets to the current size (the floor for the next window).\n\t\tthis.processedBatchCount = 0;\n\t\tthis.peakTrackedBatchCount = this.batchesBySeqNum.size;\n\n\t\tif (this.batchesBySeqNum.size === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn [...this.batchesBySeqNum.entries()].map(([seqNum, recorded]) => [\n\t\t\tseqNum,\n\t\t\trecorded.batchId,\n\t\t]);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"opCompressor.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opCompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAW5E,OAAO,KAAK,EAAwB,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAGrF;;;;;GAKG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAEjC,MAAM,EAAE,oBAAoB;IAIxC;;;;;OAKG;IACI,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,sBAAsB;IAyC3E;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAyB9B"}
1
+ {"version":3,"file":"opCompressor.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opCompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAW5E,OAAO,KAAK,EAAwB,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAGrF;;;;;GAKG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,MAAM,EAAE,oBAAoB;IAIxC;;;;;OAKG;IACI,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,sBAAsB;IAyC3E;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAyB9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"opCompressor.js","sourceRoot":"","sources":["../../src/opLifecycle/opCompressor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IsoBuffer } from \"@fluid-internal/client-utils\";\nimport type { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tDataProcessingError,\n\tcreateChildLogger,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { compress } from \"lz4js\";\n\nimport { CompressionAlgorithms } from \"../compressionDefinitions.js\";\n\nimport type { OutboundBatchMessage, OutboundSingletonBatch } from \"./definitions.js\";\nimport { estimateSocketSize } from \"./outbox.js\";\n\n/**\n * Compresses batches of ops.\n *\n * @remarks Only single-message batches are supported\n * Use opGroupingManager to group a batch into a singleton batch suitable for compression.\n */\nexport class OpCompressor {\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(logger: ITelemetryBaseLogger) {\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpCompressor\" });\n\t}\n\n\t/**\n\t * Combines the contents of the singleton batch into a single JSON string and compresses it, putting\n\t * the resulting string as the message contents in place of the original uncompressed payload.\n\t * @param batch - The batch to compress. Must have only 1 message\n\t * @returns A singleton batch containing a single compressed message\n\t */\n\tpublic compressBatch(batch: OutboundSingletonBatch): OutboundSingletonBatch {\n\t\tassert(\n\t\t\tbatch.contentSizeInBytes > 0 && batch.messages.length === 1,\n\t\t\t0x5a4 /* Batch should not be empty and should contain a single message */,\n\t\t);\n\n\t\tconst compressionStart = Date.now();\n\t\tconst contentsAsBuffer = new TextEncoder().encode(this.serializeBatchContents(batch));\n\t\tconst compressedContents = compress(contentsAsBuffer);\n\t\tconst compressedContent = IsoBuffer.from(compressedContents).toString(\"base64\");\n\t\tconst duration = Date.now() - compressionStart;\n\n\t\tconst messages: [OutboundBatchMessage] = [\n\t\t\t{\n\t\t\t\t...batch.messages[0],\n\t\t\t\tcontents: JSON.stringify({ packedContents: compressedContent }),\n\t\t\t\tmetadata: batch.messages[0].metadata,\n\t\t\t\tcompression: CompressionAlgorithms.lz4,\n\t\t\t},\n\t\t];\n\n\t\tconst compressedBatch: OutboundSingletonBatch = {\n\t\t\tcontentSizeInBytes: compressedContent.length,\n\t\t\tmessages,\n\t\t\treferenceSequenceNumber: batch.referenceSequenceNumber,\n\t\t};\n\n\t\tif (batch.contentSizeInBytes > 200000) {\n\t\t\tthis.logger.sendPerformanceEvent({\n\t\t\t\teventName: \"CompressedBatch\",\n\t\t\t\tduration,\n\t\t\t\tsizeBeforeCompression: batch.contentSizeInBytes,\n\t\t\t\tsizeAfterCompression: compressedBatch.contentSizeInBytes,\n\t\t\t\topCount: compressedBatch.messages.length,\n\t\t\t\tsocketSize: estimateSocketSize(compressedBatch),\n\t\t\t});\n\t\t}\n\n\t\treturn compressedBatch;\n\t}\n\n\t/**\n\t * Combine the batch's content strings into a single JSON string (a serialized array)\n\t */\n\tprivate serializeBatchContents(batch: OutboundSingletonBatch): string {\n\t\tconst [message, ...none] = batch.messages;\n\t\tassert(none.length === 0, 0xb78 /* Batch should only contain a single message */);\n\t\ttry {\n\t\t\t// This is expressed as a JSON array, for legacy reasons\n\t\t\treturn `[${message.contents}]`;\n\t\t} catch (error: unknown) {\n\t\t\tif ((error as Partial<Error>).message === \"Invalid string length\") {\n\t\t\t\t// This is how string interpolation signals that\n\t\t\t\t// the content size exceeds its capacity\n\t\t\t\tconst dpe = DataProcessingError.create(\"Payload too large\", \"OpCompressor\");\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: \"BatchTooLarge\",\n\t\t\t\t\t\tsize: batch.contentSizeInBytes,\n\t\t\t\t\t\tlength: batch.messages.length,\n\t\t\t\t\t},\n\t\t\t\t\tdpe,\n\t\t\t\t);\n\t\t\t\tthrow dpe;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"opCompressor.js","sourceRoot":"","sources":["../../src/opLifecycle/opCompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAC7D,OAAO,EACN,mBAAmB,EACnB,iBAAiB,GAEjB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,OAAO,YAAY;IAGxB,YAAY,MAA4B;QACvC,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;IACxE,CAAC;IAED;;;;;OAKG;IACI,aAAa,CAAC,KAA6B;QACjD,MAAM,CACL,KAAK,CAAC,kBAAkB,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAC3D,KAAK,CAAC,mEAAmE,CACzE,CAAC;QAEF,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,gBAAgB,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAC;QACtF,MAAM,kBAAkB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACtD,MAAM,iBAAiB,GAAG,SAAS,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,CAAC;QAE/C,MAAM,QAAQ,GAA2B;YACxC;gBACC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACpB,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC;gBAC/D,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ;gBACpC,WAAW,EAAE,qBAAqB,CAAC,GAAG;aACtC;SACD,CAAC;QAEF,MAAM,eAAe,GAA2B;YAC/C,kBAAkB,EAAE,iBAAiB,CAAC,MAAM;YAC5C,QAAQ;YACR,uBAAuB,EAAE,KAAK,CAAC,uBAAuB;SACtD,CAAC;QAEF,IAAI,KAAK,CAAC,kBAAkB,GAAG,MAAM,EAAE,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;gBAChC,SAAS,EAAE,iBAAiB;gBAC5B,QAAQ;gBACR,qBAAqB,EAAE,KAAK,CAAC,kBAAkB;gBAC/C,oBAAoB,EAAE,eAAe,CAAC,kBAAkB;gBACxD,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,MAAM;gBACxC,UAAU,EAAE,kBAAkB,CAAC,eAAe,CAAC;aAC/C,CAAC,CAAC;QACJ,CAAC;QAED,OAAO,eAAe,CAAC;IACxB,CAAC;IAED;;OAEG;IACK,sBAAsB,CAAC,KAA6B;QAC3D,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAClF,IAAI,CAAC;YACJ,wDAAwD;YACxD,OAAO,IAAI,OAAO,CAAC,QAAQ,GAAG,CAAC;QAChC,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACzB,IAAK,KAAwB,CAAC,OAAO,KAAK,uBAAuB,EAAE,CAAC;gBACnE,gDAAgD;gBAChD,wCAAwC;gBACxC,MAAM,GAAG,GAAG,mBAAmB,CAAC,MAAM,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;gBAC5E,IAAI,CAAC,MAAM,CAAC,cAAc,CACzB;oBACC,SAAS,EAAE,eAAe;oBAC1B,IAAI,EAAE,KAAK,CAAC,kBAAkB;oBAC9B,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;iBAC7B,EACD,GAAG,CACH,CAAC;gBACF,MAAM,GAAG,CAAC;YACX,CAAC;YAED,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IsoBuffer } from \"@fluid-internal/client-utils\";\nimport type { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tDataProcessingError,\n\tcreateChildLogger,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { compress } from \"lz4js\";\n\nimport { CompressionAlgorithms } from \"../compressionDefinitions.js\";\n\nimport type { OutboundBatchMessage, OutboundSingletonBatch } from \"./definitions.js\";\nimport { estimateSocketSize } from \"./outbox.js\";\n\n/**\n * Compresses batches of ops.\n *\n * @remarks Only single-message batches are supported\n * Use opGroupingManager to group a batch into a singleton batch suitable for compression.\n */\nexport class OpCompressor {\n\tprivate readonly logger: TelemetryLoggerExt;\n\n\tconstructor(logger: ITelemetryBaseLogger) {\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpCompressor\" });\n\t}\n\n\t/**\n\t * Combines the contents of the singleton batch into a single JSON string and compresses it, putting\n\t * the resulting string as the message contents in place of the original uncompressed payload.\n\t * @param batch - The batch to compress. Must have only 1 message\n\t * @returns A singleton batch containing a single compressed message\n\t */\n\tpublic compressBatch(batch: OutboundSingletonBatch): OutboundSingletonBatch {\n\t\tassert(\n\t\t\tbatch.contentSizeInBytes > 0 && batch.messages.length === 1,\n\t\t\t0x5a4 /* Batch should not be empty and should contain a single message */,\n\t\t);\n\n\t\tconst compressionStart = Date.now();\n\t\tconst contentsAsBuffer = new TextEncoder().encode(this.serializeBatchContents(batch));\n\t\tconst compressedContents = compress(contentsAsBuffer);\n\t\tconst compressedContent = IsoBuffer.from(compressedContents).toString(\"base64\");\n\t\tconst duration = Date.now() - compressionStart;\n\n\t\tconst messages: [OutboundBatchMessage] = [\n\t\t\t{\n\t\t\t\t...batch.messages[0],\n\t\t\t\tcontents: JSON.stringify({ packedContents: compressedContent }),\n\t\t\t\tmetadata: batch.messages[0].metadata,\n\t\t\t\tcompression: CompressionAlgorithms.lz4,\n\t\t\t},\n\t\t];\n\n\t\tconst compressedBatch: OutboundSingletonBatch = {\n\t\t\tcontentSizeInBytes: compressedContent.length,\n\t\t\tmessages,\n\t\t\treferenceSequenceNumber: batch.referenceSequenceNumber,\n\t\t};\n\n\t\tif (batch.contentSizeInBytes > 200000) {\n\t\t\tthis.logger.sendPerformanceEvent({\n\t\t\t\teventName: \"CompressedBatch\",\n\t\t\t\tduration,\n\t\t\t\tsizeBeforeCompression: batch.contentSizeInBytes,\n\t\t\t\tsizeAfterCompression: compressedBatch.contentSizeInBytes,\n\t\t\t\topCount: compressedBatch.messages.length,\n\t\t\t\tsocketSize: estimateSocketSize(compressedBatch),\n\t\t\t});\n\t\t}\n\n\t\treturn compressedBatch;\n\t}\n\n\t/**\n\t * Combine the batch's content strings into a single JSON string (a serialized array)\n\t */\n\tprivate serializeBatchContents(batch: OutboundSingletonBatch): string {\n\t\tconst [message, ...none] = batch.messages;\n\t\tassert(none.length === 0, 0xb78 /* Batch should only contain a single message */);\n\t\ttry {\n\t\t\t// This is expressed as a JSON array, for legacy reasons\n\t\t\treturn `[${message.contents}]`;\n\t\t} catch (error: unknown) {\n\t\t\tif ((error as Partial<Error>).message === \"Invalid string length\") {\n\t\t\t\t// This is how string interpolation signals that\n\t\t\t\t// the content size exceeds its capacity\n\t\t\t\tconst dpe = DataProcessingError.create(\"Payload too large\", \"OpCompressor\");\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: \"BatchTooLarge\",\n\t\t\t\t\t\tsize: batch.contentSizeInBytes,\n\t\t\t\t\t\tlength: batch.messages.length,\n\t\t\t\t\t},\n\t\t\t\t\tdpe,\n\t\t\t\t);\n\t\t\t\tthrow dpe;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"opDecompressor.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opDecompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAE5E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAiB7F;;;;;;;;;;GAUG;AACH,qBAAa,cAAc;IAC1B,OAAO,CAAC,WAAW,CAAS;IAG5B,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAEjC,MAAM,EAAE,oBAAoB;IAIjC,mBAAmB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO;IA2CvE,IAAW,kBAAkB,IAAI,OAAO,CAEvC;IAED;;OAEG;IACH,OAAO,CAAC,oBAAoB,CAAS;IAErC;;;OAGG;IACI,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI;IA8BnE;;;OAGG;IACI,MAAM,CAAC,OAAO,EAAE,yBAAyB,GAAG,yBAAyB;CAmC5E"}
1
+ {"version":3,"file":"opDecompressor.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opDecompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAE5E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAiB7F;;;;;;;;;;GAUG;AACH,qBAAa,cAAc;IAC1B,OAAO,CAAC,WAAW,CAAS;IAG5B,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,MAAM,EAAE,oBAAoB;IAIjC,mBAAmB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO;IA2CvE,IAAW,kBAAkB,IAAI,OAAO,CAEvC;IAED;;OAEG;IACH,OAAO,CAAC,oBAAoB,CAAS;IAErC;;;OAGG;IACI,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI;IA8BnE;;;OAGG;IACI,MAAM,CAAC,OAAO,EAAE,yBAAyB,GAAG,yBAAyB;CAmC5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"opDecompressor.js","sourceRoot":"","sources":["../../src/opLifecycle/opDecompressor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IsoBuffer, Uint8ArrayToString } from \"@fluid-internal/client-utils\";\nimport type { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tcreateChildLogger,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { decompress } from \"lz4js\";\n\nimport { CompressionAlgorithms } from \"../compressionDefinitions.js\";\nimport type { IBatchMetadata } from \"../metadata.js\";\n\n/**\n * Compression makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.\n */\ninterface IPackedContentsContents {\n\tpackedContents: string;\n}\n\n/**\n * State machine that \"unrolls\" contents of compressed batches of ops after decompressing them.\n * This class relies on some implicit contracts defined below:\n * 1. A compressed batch will have its first message with batch metadata set to true and compressed set to true\n * 2. Messages in the middle of a compressed batch will have neither batch metadata nor the compression property set\n * 3. The final message of a batch will have batch metadata set to false\n * 4. An individually compressed op will have undefined batch metadata and compression set to true\n *\n * Compressed batches from current code are always a single message but this class needs to handle a legacy compressed batch with multiple messages\n * because we need that functionality for back compat.\n */\nexport class OpDecompressor {\n\tprivate activeBatch = false;\n\t// TODO: better typing\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tprivate rootMessageContents: any | undefined;\n\tprivate processedCount = 0;\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(logger: ITelemetryBaseLogger) {\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpDecompressor\" });\n\t}\n\n\tpublic isCompressedMessage(message: ISequencedDocumentMessage): boolean {\n\t\tif (message.compression === CompressionAlgorithms.lz4) {\n\t\t\treturn true;\n\t\t}\n\n\t\t/**\n\t\t * Back-compat self healing mechanism for ADO:3538, as loaders from\n\t\t * version client_v2.0.0-internal.1.2.0 to client_v2.0.0-internal.2.2.0 do not\n\t\t * support adding the proper compression metadata to compressed messages submitted\n\t\t * by the runtime. Should be removed after the loader reaches sufficient saturation\n\t\t * for a version greater or equal than client_v2.0.0-internal.2.2.0.\n\t\t *\n\t\t * The condition holds true for compressed messages, regardless of metadata. We are ultimately\n\t\t * looking for a message with a single property `packedContents` inside `contents`, of type 'string'\n\t\t * with a base64 encoded value.\n\t\t */\n\t\ttry {\n\t\t\tif (\n\t\t\t\tmessage.contents !== null &&\n\t\t\t\ttypeof message.contents === \"object\" &&\n\t\t\t\tObject.keys(message.contents).length === 1 &&\n\t\t\t\ttypeof (message.contents as { packedContents?: unknown }).packedContents ===\n\t\t\t\t\t\"string\" &&\n\t\t\t\t(message.contents as IPackedContentsContents).packedContents.length > 0 &&\n\t\t\t\tIsoBuffer.from(\n\t\t\t\t\t(message.contents as IPackedContentsContents).packedContents,\n\t\t\t\t\t\"base64\",\n\t\t\t\t).toString(\"base64\") === (message.contents as IPackedContentsContents).packedContents\n\t\t\t) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: \"LegacyCompression\",\n\t\t\t\t\ttype: message.type,\n\t\t\t\t\tbatch: (message.metadata as IBatchMetadata | undefined)?.batch,\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic get currentlyUnrolling(): boolean {\n\t\treturn this.activeBatch;\n\t}\n\n\t/**\n\t * Is the decompressed and stored batch only comprised of a single message\n\t */\n\tprivate isSingleMessageBatch = false;\n\n\t/**\n\t * Decompress the given compressed message and store it to be subsequently unrolled.\n\t * The stored message will be of type `any[]` where each element represents a message's `contents`\n\t */\n\tpublic decompressAndStore(message: ISequencedDocumentMessage): void {\n\t\tassert(\n\t\t\tmessage.compression === undefined || message.compression === CompressionAlgorithms.lz4,\n\t\t\t0x511 /* Only lz4 compression is supported */,\n\t\t);\n\t\tassert(\n\t\t\tthis.isCompressedMessage(message),\n\t\t\t0x940 /* provided message should be compressed */,\n\t\t);\n\n\t\tassert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);\n\t\tthis.activeBatch = true;\n\n\t\tconst batchMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\t\tif (batchMetadata === undefined) {\n\t\t\tthis.isSingleMessageBatch = true;\n\t\t} else {\n\t\t\tassert(batchMetadata === true, 0x941 /* invalid batch metadata */);\n\t\t}\n\n\t\tconst contents = IsoBuffer.from(\n\t\t\t(message.contents as IPackedContentsContents).packedContents,\n\t\t\t\"base64\",\n\t\t);\n\t\tconst decompressedMessage = decompress(contents);\n\t\tconst intoString = Uint8ArrayToString(decompressedMessage);\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\tthis.rootMessageContents = JSON.parse(intoString);\n\t}\n\n\t/**\n\t * Unroll the next message from the decompressed content provided to {@link decompressAndStore}\n\t * @returns the unrolled `ISequencedDocumentMessage`\n\t */\n\tpublic unroll(message: ISequencedDocumentMessage): ISequencedDocumentMessage {\n\t\tassert(this.currentlyUnrolling, 0x942 /* not currently unrolling */);\n\t\tassert(this.rootMessageContents !== undefined, 0x943 /* missing rootMessageContents */);\n\t\tassert(\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\t\tthis.rootMessageContents.length > this.processedCount,\n\t\t\t0x944 /* no more content to unroll */,\n\t\t);\n\n\t\tconst batchMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\n\t\tif (batchMetadata === false || this.isSingleMessageBatch) {\n\t\t\t// End of compressed batch\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\t\tconst returnMessage = newMessage(message, this.rootMessageContents[this.processedCount]);\n\n\t\t\tthis.activeBatch = false;\n\t\t\tthis.isSingleMessageBatch = false;\n\t\t\tthis.rootMessageContents = undefined;\n\t\t\tthis.processedCount = 0;\n\n\t\t\treturn returnMessage;\n\t\t} else if (batchMetadata === true) {\n\t\t\t// Start of compressed batch\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\t\treturn newMessage(message, this.rootMessageContents[this.processedCount++]);\n\t\t}\n\n\t\tassert(batchMetadata === undefined, 0x945 /* invalid batch metadata */);\n\t\tassert(message.contents === undefined, 0x512 /* Expecting empty message */);\n\n\t\t// Continuation of compressed batch\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\treturn newMessage(message, this.rootMessageContents[this.processedCount++]);\n\t}\n}\n\n// We should not be mutating the input message nor its metadata\nconst newMessage = (\n\toriginalMessage: ISequencedDocumentMessage,\n\tcontents: unknown,\n): ISequencedDocumentMessage => ({\n\t...originalMessage,\n\tcontents,\n\tcompression: undefined,\n\t// TODO: It should already be the case that we're not modifying any metadata, not clear if/why this shallow clone should be required.\n\n\tmetadata:\n\t\toriginalMessage.metadata === undefined ? undefined : { ...originalMessage.metadata },\n});\n"]}
1
+ {"version":3,"file":"opDecompressor.js","sourceRoot":"","sources":["../../src/opLifecycle/opDecompressor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IsoBuffer, Uint8ArrayToString } from \"@fluid-internal/client-utils\";\nimport type { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tcreateChildLogger,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { decompress } from \"lz4js\";\n\nimport { CompressionAlgorithms } from \"../compressionDefinitions.js\";\nimport type { IBatchMetadata } from \"../metadata.js\";\n\n/**\n * Compression makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.\n */\ninterface IPackedContentsContents {\n\tpackedContents: string;\n}\n\n/**\n * State machine that \"unrolls\" contents of compressed batches of ops after decompressing them.\n * This class relies on some implicit contracts defined below:\n * 1. A compressed batch will have its first message with batch metadata set to true and compressed set to true\n * 2. Messages in the middle of a compressed batch will have neither batch metadata nor the compression property set\n * 3. The final message of a batch will have batch metadata set to false\n * 4. An individually compressed op will have undefined batch metadata and compression set to true\n *\n * Compressed batches from current code are always a single message but this class needs to handle a legacy compressed batch with multiple messages\n * because we need that functionality for back compat.\n */\nexport class OpDecompressor {\n\tprivate activeBatch = false;\n\t// TODO: better typing\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tprivate rootMessageContents: any | undefined;\n\tprivate processedCount = 0;\n\tprivate readonly logger: TelemetryLoggerExt;\n\n\tconstructor(logger: ITelemetryBaseLogger) {\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpDecompressor\" });\n\t}\n\n\tpublic isCompressedMessage(message: ISequencedDocumentMessage): boolean {\n\t\tif (message.compression === CompressionAlgorithms.lz4) {\n\t\t\treturn true;\n\t\t}\n\n\t\t/**\n\t\t * Back-compat self healing mechanism for ADO:3538, as loaders from\n\t\t * version client_v2.0.0-internal.1.2.0 to client_v2.0.0-internal.2.2.0 do not\n\t\t * support adding the proper compression metadata to compressed messages submitted\n\t\t * by the runtime. Should be removed after the loader reaches sufficient saturation\n\t\t * for a version greater or equal than client_v2.0.0-internal.2.2.0.\n\t\t *\n\t\t * The condition holds true for compressed messages, regardless of metadata. We are ultimately\n\t\t * looking for a message with a single property `packedContents` inside `contents`, of type 'string'\n\t\t * with a base64 encoded value.\n\t\t */\n\t\ttry {\n\t\t\tif (\n\t\t\t\tmessage.contents !== null &&\n\t\t\t\ttypeof message.contents === \"object\" &&\n\t\t\t\tObject.keys(message.contents).length === 1 &&\n\t\t\t\ttypeof (message.contents as { packedContents?: unknown }).packedContents ===\n\t\t\t\t\t\"string\" &&\n\t\t\t\t(message.contents as IPackedContentsContents).packedContents.length > 0 &&\n\t\t\t\tIsoBuffer.from(\n\t\t\t\t\t(message.contents as IPackedContentsContents).packedContents,\n\t\t\t\t\t\"base64\",\n\t\t\t\t).toString(\"base64\") === (message.contents as IPackedContentsContents).packedContents\n\t\t\t) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: \"LegacyCompression\",\n\t\t\t\t\ttype: message.type,\n\t\t\t\t\tbatch: (message.metadata as IBatchMetadata | undefined)?.batch,\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic get currentlyUnrolling(): boolean {\n\t\treturn this.activeBatch;\n\t}\n\n\t/**\n\t * Is the decompressed and stored batch only comprised of a single message\n\t */\n\tprivate isSingleMessageBatch = false;\n\n\t/**\n\t * Decompress the given compressed message and store it to be subsequently unrolled.\n\t * The stored message will be of type `any[]` where each element represents a message's `contents`\n\t */\n\tpublic decompressAndStore(message: ISequencedDocumentMessage): void {\n\t\tassert(\n\t\t\tmessage.compression === undefined || message.compression === CompressionAlgorithms.lz4,\n\t\t\t0x511 /* Only lz4 compression is supported */,\n\t\t);\n\t\tassert(\n\t\t\tthis.isCompressedMessage(message),\n\t\t\t0x940 /* provided message should be compressed */,\n\t\t);\n\n\t\tassert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);\n\t\tthis.activeBatch = true;\n\n\t\tconst batchMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\t\tif (batchMetadata === undefined) {\n\t\t\tthis.isSingleMessageBatch = true;\n\t\t} else {\n\t\t\tassert(batchMetadata === true, 0x941 /* invalid batch metadata */);\n\t\t}\n\n\t\tconst contents = IsoBuffer.from(\n\t\t\t(message.contents as IPackedContentsContents).packedContents,\n\t\t\t\"base64\",\n\t\t);\n\t\tconst decompressedMessage = decompress(contents);\n\t\tconst intoString = Uint8ArrayToString(decompressedMessage);\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\tthis.rootMessageContents = JSON.parse(intoString);\n\t}\n\n\t/**\n\t * Unroll the next message from the decompressed content provided to {@link decompressAndStore}\n\t * @returns the unrolled `ISequencedDocumentMessage`\n\t */\n\tpublic unroll(message: ISequencedDocumentMessage): ISequencedDocumentMessage {\n\t\tassert(this.currentlyUnrolling, 0x942 /* not currently unrolling */);\n\t\tassert(this.rootMessageContents !== undefined, 0x943 /* missing rootMessageContents */);\n\t\tassert(\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\t\tthis.rootMessageContents.length > this.processedCount,\n\t\t\t0x944 /* no more content to unroll */,\n\t\t);\n\n\t\tconst batchMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\n\t\tif (batchMetadata === false || this.isSingleMessageBatch) {\n\t\t\t// End of compressed batch\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\t\tconst returnMessage = newMessage(message, this.rootMessageContents[this.processedCount]);\n\n\t\t\tthis.activeBatch = false;\n\t\t\tthis.isSingleMessageBatch = false;\n\t\t\tthis.rootMessageContents = undefined;\n\t\t\tthis.processedCount = 0;\n\n\t\t\treturn returnMessage;\n\t\t} else if (batchMetadata === true) {\n\t\t\t// Start of compressed batch\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\t\treturn newMessage(message, this.rootMessageContents[this.processedCount++]);\n\t\t}\n\n\t\tassert(batchMetadata === undefined, 0x945 /* invalid batch metadata */);\n\t\tassert(message.contents === undefined, 0x512 /* Expecting empty message */);\n\n\t\t// Continuation of compressed batch\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\treturn newMessage(message, this.rootMessageContents[this.processedCount++]);\n\t}\n}\n\n// We should not be mutating the input message nor its metadata\nconst newMessage = (\n\toriginalMessage: ISequencedDocumentMessage,\n\tcontents: unknown,\n): ISequencedDocumentMessage => ({\n\t...originalMessage,\n\tcontents,\n\tcompression: undefined,\n\t// TODO: It should already be the case that we're not modifying any metadata, not clear if/why this shallow clone should be required.\n\n\tmetadata:\n\t\toriginalMessage.metadata === undefined ? undefined : { ...originalMessage.metadata },\n});\n"]}