@fluidframework/container-runtime 2.1.0-276326 → 2.1.0-281041

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 (236) hide show
  1. package/README.md +74 -21
  2. package/api-extractor/api-extractor.current.json +5 -0
  3. package/api-extractor/api-extractor.legacy.json +1 -1
  4. package/api-extractor.json +1 -1
  5. package/api-report/container-runtime.legacy.public.api.md +9 -0
  6. package/container-runtime.test-files.tar +0 -0
  7. package/dist/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
  8. package/dist/blobManager/blobManager.d.ts.map +1 -0
  9. package/dist/{blobManager.js → blobManager/blobManager.js} +42 -83
  10. package/dist/blobManager/blobManager.js.map +1 -0
  11. package/dist/blobManager/blobManagerSnapSum.d.ts +30 -0
  12. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -0
  13. package/dist/blobManager/blobManagerSnapSum.js +82 -0
  14. package/dist/blobManager/blobManagerSnapSum.js.map +1 -0
  15. package/dist/blobManager/index.d.ts +7 -0
  16. package/dist/blobManager/index.d.ts.map +1 -0
  17. package/dist/blobManager/index.js +16 -0
  18. package/dist/blobManager/index.js.map +1 -0
  19. package/dist/channelCollection.d.ts +1 -1
  20. package/dist/channelCollection.d.ts.map +1 -1
  21. package/dist/channelCollection.js +40 -8
  22. package/dist/channelCollection.js.map +1 -1
  23. package/dist/containerRuntime.d.ts +15 -10
  24. package/dist/containerRuntime.d.ts.map +1 -1
  25. package/dist/containerRuntime.js +199 -162
  26. package/dist/containerRuntime.js.map +1 -1
  27. package/dist/dataStoreContext.d.ts +5 -0
  28. package/dist/dataStoreContext.d.ts.map +1 -1
  29. package/dist/dataStoreContext.js +16 -5
  30. package/dist/dataStoreContext.js.map +1 -1
  31. package/dist/gc/garbageCollection.d.ts +1 -1
  32. package/dist/gc/garbageCollection.d.ts.map +1 -1
  33. package/dist/gc/garbageCollection.js +16 -10
  34. package/dist/gc/garbageCollection.js.map +1 -1
  35. package/dist/gc/gcDefinitions.d.ts +4 -2
  36. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  37. package/dist/gc/gcDefinitions.js.map +1 -1
  38. package/dist/gc/gcHelpers.d.ts.map +1 -1
  39. package/dist/gc/gcHelpers.js +12 -0
  40. package/dist/gc/gcHelpers.js.map +1 -1
  41. package/dist/gc/gcTelemetry.d.ts +3 -2
  42. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  43. package/dist/gc/gcTelemetry.js +6 -6
  44. package/dist/gc/gcTelemetry.js.map +1 -1
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/legacy.d.ts +1 -1
  49. package/dist/metadata.d.ts +7 -1
  50. package/dist/metadata.d.ts.map +1 -1
  51. package/dist/metadata.js +6 -0
  52. package/dist/metadata.js.map +1 -1
  53. package/dist/opLifecycle/batchManager.d.ts +8 -1
  54. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  55. package/dist/opLifecycle/batchManager.js +37 -16
  56. package/dist/opLifecycle/batchManager.js.map +1 -1
  57. package/dist/opLifecycle/definitions.d.ts +1 -1
  58. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  59. package/dist/opLifecycle/definitions.js.map +1 -1
  60. package/dist/opLifecycle/index.d.ts +1 -1
  61. package/dist/opLifecycle/index.d.ts.map +1 -1
  62. package/dist/opLifecycle/index.js +2 -1
  63. package/dist/opLifecycle/index.js.map +1 -1
  64. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  65. package/dist/opLifecycle/opCompressor.js +12 -8
  66. package/dist/opLifecycle/opCompressor.js.map +1 -1
  67. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  68. package/dist/opLifecycle/opGroupingManager.js +14 -11
  69. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  70. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  71. package/dist/opLifecycle/opSplitter.js +11 -6
  72. package/dist/opLifecycle/opSplitter.js.map +1 -1
  73. package/dist/opLifecycle/outbox.d.ts +22 -6
  74. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  75. package/dist/opLifecycle/outbox.js +43 -21
  76. package/dist/opLifecycle/outbox.js.map +1 -1
  77. package/dist/opLifecycle/remoteMessageProcessor.d.ts +22 -6
  78. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  79. package/dist/opLifecycle/remoteMessageProcessor.js +59 -9
  80. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  81. package/dist/packageVersion.d.ts +1 -1
  82. package/dist/packageVersion.js +1 -1
  83. package/dist/packageVersion.js.map +1 -1
  84. package/dist/pendingStateManager.d.ts +39 -13
  85. package/dist/pendingStateManager.d.ts.map +1 -1
  86. package/dist/pendingStateManager.js +98 -33
  87. package/dist/pendingStateManager.js.map +1 -1
  88. package/dist/public.d.ts +1 -1
  89. package/dist/scheduleManager.js +4 -0
  90. package/dist/scheduleManager.js.map +1 -1
  91. package/dist/summary/index.d.ts +1 -1
  92. package/dist/summary/index.d.ts.map +1 -1
  93. package/dist/summary/index.js +1 -2
  94. package/dist/summary/index.js.map +1 -1
  95. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  96. package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  97. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  98. package/dist/summary/summaryFormat.d.ts +0 -1
  99. package/dist/summary/summaryFormat.d.ts.map +1 -1
  100. package/dist/summary/summaryFormat.js +7 -4
  101. package/dist/summary/summaryFormat.js.map +1 -1
  102. package/internal.d.ts +1 -1
  103. package/legacy.d.ts +1 -1
  104. package/lib/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
  105. package/lib/blobManager/blobManager.d.ts.map +1 -0
  106. package/lib/{blobManager.js → blobManager/blobManager.js} +40 -83
  107. package/lib/blobManager/blobManager.js.map +1 -0
  108. package/lib/blobManager/blobManagerSnapSum.d.ts +30 -0
  109. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -0
  110. package/lib/blobManager/blobManagerSnapSum.js +75 -0
  111. package/lib/blobManager/blobManagerSnapSum.js.map +1 -0
  112. package/lib/blobManager/index.d.ts +7 -0
  113. package/lib/blobManager/index.d.ts.map +1 -0
  114. package/lib/blobManager/index.js +7 -0
  115. package/lib/blobManager/index.js.map +1 -0
  116. package/lib/channelCollection.d.ts +1 -1
  117. package/lib/channelCollection.d.ts.map +1 -1
  118. package/lib/channelCollection.js +40 -8
  119. package/lib/channelCollection.js.map +1 -1
  120. package/lib/containerRuntime.d.ts +15 -10
  121. package/lib/containerRuntime.d.ts.map +1 -1
  122. package/lib/containerRuntime.js +149 -112
  123. package/lib/containerRuntime.js.map +1 -1
  124. package/lib/dataStoreContext.d.ts +5 -0
  125. package/lib/dataStoreContext.d.ts.map +1 -1
  126. package/lib/dataStoreContext.js +17 -6
  127. package/lib/dataStoreContext.js.map +1 -1
  128. package/lib/gc/garbageCollection.d.ts +1 -1
  129. package/lib/gc/garbageCollection.d.ts.map +1 -1
  130. package/lib/gc/garbageCollection.js +16 -10
  131. package/lib/gc/garbageCollection.js.map +1 -1
  132. package/lib/gc/gcDefinitions.d.ts +4 -2
  133. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  134. package/lib/gc/gcDefinitions.js.map +1 -1
  135. package/lib/gc/gcHelpers.d.ts.map +1 -1
  136. package/lib/gc/gcHelpers.js +12 -0
  137. package/lib/gc/gcHelpers.js.map +1 -1
  138. package/lib/gc/gcTelemetry.d.ts +3 -2
  139. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  140. package/lib/gc/gcTelemetry.js +6 -6
  141. package/lib/gc/gcTelemetry.js.map +1 -1
  142. package/lib/index.d.ts +1 -1
  143. package/lib/index.d.ts.map +1 -1
  144. package/lib/index.js.map +1 -1
  145. package/lib/legacy.d.ts +1 -1
  146. package/lib/metadata.d.ts +7 -1
  147. package/lib/metadata.d.ts.map +1 -1
  148. package/lib/metadata.js +4 -1
  149. package/lib/metadata.js.map +1 -1
  150. package/lib/opLifecycle/batchManager.d.ts +8 -1
  151. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  152. package/lib/opLifecycle/batchManager.js +35 -15
  153. package/lib/opLifecycle/batchManager.js.map +1 -1
  154. package/lib/opLifecycle/definitions.d.ts +1 -1
  155. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  156. package/lib/opLifecycle/definitions.js.map +1 -1
  157. package/lib/opLifecycle/index.d.ts +1 -1
  158. package/lib/opLifecycle/index.d.ts.map +1 -1
  159. package/lib/opLifecycle/index.js +1 -1
  160. package/lib/opLifecycle/index.js.map +1 -1
  161. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  162. package/lib/opLifecycle/opCompressor.js +12 -8
  163. package/lib/opLifecycle/opCompressor.js.map +1 -1
  164. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  165. package/lib/opLifecycle/opGroupingManager.js +14 -11
  166. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  167. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  168. package/lib/opLifecycle/opSplitter.js +11 -6
  169. package/lib/opLifecycle/opSplitter.js.map +1 -1
  170. package/lib/opLifecycle/outbox.d.ts +22 -6
  171. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  172. package/lib/opLifecycle/outbox.js +44 -22
  173. package/lib/opLifecycle/outbox.js.map +1 -1
  174. package/lib/opLifecycle/remoteMessageProcessor.d.ts +22 -6
  175. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  176. package/lib/opLifecycle/remoteMessageProcessor.js +57 -7
  177. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  178. package/lib/packageVersion.d.ts +1 -1
  179. package/lib/packageVersion.js +1 -1
  180. package/lib/packageVersion.js.map +1 -1
  181. package/lib/pendingStateManager.d.ts +39 -13
  182. package/lib/pendingStateManager.d.ts.map +1 -1
  183. package/lib/pendingStateManager.js +99 -34
  184. package/lib/pendingStateManager.js.map +1 -1
  185. package/lib/public.d.ts +1 -1
  186. package/lib/scheduleManager.js +4 -0
  187. package/lib/scheduleManager.js.map +1 -1
  188. package/lib/summary/index.d.ts +1 -1
  189. package/lib/summary/index.d.ts.map +1 -1
  190. package/lib/summary/index.js +1 -1
  191. package/lib/summary/index.js.map +1 -1
  192. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  193. package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -0
  194. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  195. package/lib/summary/summaryFormat.d.ts +0 -1
  196. package/lib/summary/summaryFormat.d.ts.map +1 -1
  197. package/lib/summary/summaryFormat.js +5 -2
  198. package/lib/summary/summaryFormat.js.map +1 -1
  199. package/package.json +49 -34
  200. package/src/{blobManager.ts → blobManager/blobManager.ts} +57 -123
  201. package/src/blobManager/blobManagerSnapSum.ts +133 -0
  202. package/src/blobManager/index.ts +19 -0
  203. package/src/channelCollection.ts +48 -11
  204. package/src/containerRuntime.ts +213 -158
  205. package/src/dataStoreContext.ts +30 -6
  206. package/src/gc/garbageCollection.ts +17 -12
  207. package/src/gc/gcDefinitions.ts +7 -2
  208. package/src/gc/gcHelpers.ts +18 -6
  209. package/src/gc/gcTelemetry.ts +20 -8
  210. package/src/index.ts +1 -1
  211. package/src/metadata.ts +11 -1
  212. package/src/opLifecycle/README.md +0 -8
  213. package/src/opLifecycle/batchManager.ts +46 -16
  214. package/src/opLifecycle/definitions.ts +1 -1
  215. package/src/opLifecycle/index.ts +8 -1
  216. package/src/opLifecycle/opCompressor.ts +12 -8
  217. package/src/opLifecycle/opGroupingManager.ts +14 -11
  218. package/src/opLifecycle/opSplitter.ts +10 -6
  219. package/src/opLifecycle/outbox.ts +64 -26
  220. package/src/opLifecycle/remoteMessageProcessor.ts +84 -11
  221. package/src/packageVersion.ts +1 -1
  222. package/src/pendingStateManager.ts +177 -60
  223. package/src/scheduleManager.ts +6 -2
  224. package/src/summary/README.md +81 -0
  225. package/src/summary/index.ts +0 -1
  226. package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
  227. package/src/summary/summaryFormat.ts +4 -2
  228. package/src/summary/summaryFormats.md +69 -8
  229. package/tsconfig.json +0 -1
  230. package/dist/blobManager.d.ts.map +0 -1
  231. package/dist/blobManager.js.map +0 -1
  232. package/lib/blobManager.d.ts.map +0 -1
  233. package/lib/blobManager.js.map +0 -1
  234. package/src/summary/images/appTree.png +0 -0
  235. package/src/summary/images/protocolAndAppTree.png +0 -0
  236. package/src/summary/images/summaryTree.png +0 -0
@@ -2,8 +2,10 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { assert } from "@fluidframework/core-utils/internal";
5
6
  import { MessageType, } from "@fluidframework/driver-definitions/internal";
6
7
  import { ContainerMessageType, } from "../messageTypes.js";
8
+ import { asBatchMetadata } from "../metadata.js";
7
9
  import { isGroupedBatch } from "./opGroupingManager.js";
8
10
  import { isChunkedMessage } from "./opSplitter.js";
9
11
  /**
@@ -17,6 +19,7 @@ export class RemoteMessageProcessor {
17
19
  this.opSplitter = opSplitter;
18
20
  this.opDecompressor = opDecompressor;
19
21
  this.opGroupingManager = opGroupingManager;
22
+ this.processorBatch = [];
20
23
  }
21
24
  get partialMessages() {
22
25
  return this.opSplitter.chunks;
@@ -25,7 +28,7 @@ export class RemoteMessageProcessor {
25
28
  this.opSplitter.clearPartialChunks(clientId);
26
29
  }
27
30
  /**
28
- * Ungroups and Unchunks the runtime ops encapsulated by the single remoteMessage received over the wire
31
+ * Ungroups and Unchunks the runtime ops of a batch received over the wire
29
32
  * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized
30
33
  * (grouped, compressed, and/or chunked).
31
34
  * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure
@@ -40,9 +43,8 @@ export class RemoteMessageProcessor {
40
43
  * 3. If grouped, ungroup the message
41
44
  * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound
42
45
  *
43
- * @returns the unchunked, decompressed, ungrouped, unpacked SequencedContainerRuntimeMessages encapsulated in the remote message.
44
- * For ops that weren't virtualized (e.g. System ops that the ContainerRuntime will ultimately ignore),
45
- * a singleton array [remoteMessageCopy] is returned
46
+ * @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch
47
+ * or undefined if the batch is not yet complete.
46
48
  */
47
49
  process(remoteMessageCopy) {
48
50
  let message = remoteMessageCopy;
@@ -51,7 +53,7 @@ export class RemoteMessageProcessor {
51
53
  const chunkProcessingResult = this.opSplitter.processChunk(message);
52
54
  // Only continue further if current chunk is the final chunk
53
55
  if (!chunkProcessingResult.isFinalChunk) {
54
- return [];
56
+ return;
55
57
  }
56
58
  // This message will always be compressed
57
59
  message = chunkProcessingResult.message;
@@ -67,11 +69,59 @@ export class RemoteMessageProcessor {
67
69
  }
68
70
  }
69
71
  if (isGroupedBatch(message)) {
70
- return this.opGroupingManager.ungroupOp(message).map(unpack);
72
+ // We should be awaiting a new batch (batchStartCsn undefined)
73
+ assert(this.batchStartCsn === undefined, "Grouped batch interrupting another batch");
74
+ assert(this.processorBatch.length === 0, "Processor batch should be empty on grouped batch");
75
+ return {
76
+ messages: this.opGroupingManager.ungroupOp(message).map(unpack),
77
+ batchStartCsn: message.clientSequenceNumber,
78
+ };
71
79
  }
80
+ const batchStartCsn = this.getAndUpdateBatchStartCsn(message);
72
81
  // Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked
73
82
  unpackRuntimeMessage(message);
74
- return [message];
83
+ this.processorBatch.push(message);
84
+ // this.batchStartCsn is undefined only if we have processed all messages in the batch.
85
+ // If it's still defined, we're still in the middle of a batch, so we return nothing, letting
86
+ // containerRuntime know that we're waiting for more messages to complete the batch.
87
+ if (this.batchStartCsn !== undefined) {
88
+ // batch not yet complete
89
+ return undefined;
90
+ }
91
+ const messages = [...this.processorBatch];
92
+ this.processorBatch.length = 0;
93
+ return {
94
+ messages,
95
+ batchStartCsn,
96
+ };
97
+ }
98
+ /**
99
+ * Based on pre-existing batch tracking info and the current message's batch metadata,
100
+ * this will return the starting CSN for this message's batch, and will also update
101
+ * the batch tracking info (this.batchStartCsn) based on whether we're still mid-batch.
102
+ */
103
+ getAndUpdateBatchStartCsn(message) {
104
+ const batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;
105
+ if (this.batchStartCsn === undefined) {
106
+ // We are waiting for a new batch
107
+ assert(batchMetadataFlag !== false, "Unexpected batch end marker");
108
+ // Start of a new multi-message batch
109
+ if (batchMetadataFlag === true) {
110
+ this.batchStartCsn = message.clientSequenceNumber;
111
+ return this.batchStartCsn;
112
+ }
113
+ // Single-message batch (Since metadata flag is undefined)
114
+ // IMPORTANT: Leave this.batchStartCsn undefined, we're ready for the next batch now.
115
+ return message.clientSequenceNumber;
116
+ }
117
+ // We are in the middle or end of an existing multi-message batch. Return the current batchStartCsn
118
+ const batchStartCsn = this.batchStartCsn;
119
+ assert(batchMetadataFlag !== true, "Unexpected batch start marker");
120
+ if (batchMetadataFlag === false) {
121
+ // Batch end? Then get ready for the next batch to start
122
+ this.batchStartCsn = undefined;
123
+ }
124
+ return batchStartCsn;
75
125
  }
76
126
  }
77
127
  /** Takes an incoming message and if the contents is a string, JSON.parse's it in place */
@@ -1 +1 @@
1
- {"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.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 {\n\tMessageType,\n\tISequencedDocumentMessage,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport {\n\tContainerMessageType,\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessageOrSystemMessage,\n\ttype InboundSequencedRecentlyAddedContainerRuntimeMessage,\n} from \"../messageTypes.js\";\n\nimport { OpDecompressor } from \"./opDecompressor.js\";\nimport { OpGroupingManager, isGroupedBatch } from \"./opGroupingManager.js\";\nimport { OpSplitter, isChunkedMessage } from \"./opSplitter.js\";\n\n/**\n * Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,\n * potentially across numerous inbound ops.\n *\n * @internal\n */\nexport class RemoteMessageProcessor {\n\tconstructor(\n\t\tprivate readonly opSplitter: OpSplitter,\n\t\tprivate readonly opDecompressor: OpDecompressor,\n\t\tprivate readonly opGroupingManager: OpGroupingManager,\n\t) {}\n\n\tpublic get partialMessages(): ReadonlyMap<string, string[]> {\n\t\treturn this.opSplitter.chunks;\n\t}\n\n\tpublic clearPartialMessagesFor(clientId: string) {\n\t\tthis.opSplitter.clearPartialChunks(clientId);\n\t}\n\n\t/**\n\t * Ungroups and Unchunks the runtime ops encapsulated by the single remoteMessage received over the wire\n\t * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized\n\t * (grouped, compressed, and/or chunked).\n\t * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure\n\t * depends on this object instance.\n\t * Note remoteMessageCopy.contents (and other object props) MUST not be modified,\n\t * but may be overwritten (as is the case with contents).\n\t *\n\t * Incoming messages will always have compression, chunking, and grouped batching happen in a defined order and that order cannot be changed.\n\t * When processing these messages, the order is:\n\t * 1. If chunked, process the chunk and only continue if this is a final chunk\n\t * 2. If compressed, decompress the message and store for further unrolling of the decompressed content\n\t * 3. If grouped, ungroup the message\n\t * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound\n\t *\n\t * @returns the unchunked, decompressed, ungrouped, unpacked SequencedContainerRuntimeMessages encapsulated in the remote message.\n\t * For ops that weren't virtualized (e.g. System ops that the ContainerRuntime will ultimately ignore),\n\t * a singleton array [remoteMessageCopy] is returned\n\t */\n\tpublic process(\n\t\tremoteMessageCopy: ISequencedDocumentMessage,\n\t): InboundSequencedContainerRuntimeMessageOrSystemMessage[] {\n\t\tlet message = remoteMessageCopy;\n\t\tensureContentsDeserialized(message);\n\n\t\tif (isChunkedMessage(message)) {\n\t\t\tconst chunkProcessingResult = this.opSplitter.processChunk(message);\n\t\t\t// Only continue further if current chunk is the final chunk\n\t\t\tif (!chunkProcessingResult.isFinalChunk) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\t// This message will always be compressed\n\t\t\tmessage = chunkProcessingResult.message;\n\t\t}\n\n\t\tif (this.opDecompressor.isCompressedMessage(message)) {\n\t\t\tthis.opDecompressor.decompressAndStore(message);\n\t\t}\n\n\t\tif (this.opDecompressor.currentlyUnrolling) {\n\t\t\tmessage = this.opDecompressor.unroll(message);\n\t\t\t// Need to unpack after unrolling if not a groupedBatch\n\t\t\tif (!isGroupedBatch(message)) {\n\t\t\t\tunpack(message);\n\t\t\t}\n\t\t}\n\n\t\tif (isGroupedBatch(message)) {\n\t\t\treturn this.opGroupingManager.ungroupOp(message).map(unpack);\n\t\t}\n\n\t\t// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked\n\t\tunpackRuntimeMessage(message);\n\t\treturn [message as InboundSequencedContainerRuntimeMessageOrSystemMessage];\n\t}\n}\n\n/** Takes an incoming message and if the contents is a string, JSON.parse's it in place */\nfunction ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void {\n\t// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!\n\t// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.\n\t// Old ops may contain empty string (I assume noops).\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t}\n}\n\n/**\n * For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.\n *\n * The return type illustrates the assumption that the message param\n * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns\n * (but there is no runtime validation of the 'type' or 'compatDetails' values).\n */\nfunction unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {\n\t// We assume the contents is an InboundContainerRuntimeMessage (the message is \"packed\")\n\tconst contents = message.contents as InboundContainerRuntimeMessage;\n\n\t// We're going to unpack message in-place (promoting those properties of contents up to message itself)\n\tconst messageUnpacked = message as InboundSequencedContainerRuntimeMessage;\n\n\tmessageUnpacked.type = contents.type;\n\tmessageUnpacked.contents = contents.contents;\n\tif (\"compatDetails\" in contents) {\n\t\t(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =\n\t\t\tcontents.compatDetails;\n\t}\n\treturn messageUnpacked;\n}\n\n/**\n * Unpacks runtime messages.\n *\n * @remarks This API makes no promises regarding backward-compatibility. This is internal API.\n * @param message - message (as it observed in storage / service)\n * @returns whether the given message was unpacked\n *\n * @internal\n */\nexport function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolean {\n\tif (message.type !== MessageType.Operation) {\n\t\t// Legacy format, but it's already \"unpacked\",\n\t\t// i.e. message.type is actually ContainerMessageType.\n\t\t// Or it's non-runtime message.\n\t\t// Nothing to do in such case.\n\t\treturn false;\n\t}\n\n\t// legacy op format?\n\t// TODO: Unsure if this is a real format we should be concerned with. There doesn't appear to be anything prepared to handle the address member.\n\tif (\n\t\t(message.contents as { address?: unknown }).address !== undefined &&\n\t\t(message.contents as { type?: unknown }).type === undefined\n\t) {\n\t\tmessage.type = ContainerMessageType.FluidDataStoreOp;\n\t} else {\n\t\t// new format\n\t\tunpack(message);\n\t}\n\n\treturn true;\n}\n"]}
1
+ {"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.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 {\n\tMessageType,\n\tISequencedDocumentMessage,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport {\n\tContainerMessageType,\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype InboundSequencedRecentlyAddedContainerRuntimeMessage,\n} from \"../messageTypes.js\";\nimport { asBatchMetadata } from \"../metadata.js\";\n\nimport { OpDecompressor } from \"./opDecompressor.js\";\nimport { OpGroupingManager, isGroupedBatch } from \"./opGroupingManager.js\";\nimport { OpSplitter, isChunkedMessage } from \"./opSplitter.js\";\n\n/**\n * Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,\n * potentially across numerous inbound ops.\n *\n * @internal\n */\nexport class RemoteMessageProcessor {\n\t/**\n\t * Client Sequence Number of the first message in the current batch being processed.\n\t * If undefined, we are expecting the next message to start a new batch.\n\t *\n\t * @remarks For chunked batches, this is the CSN of the \"representative\" chunk (the final chunk)\n\t */\n\tprivate batchStartCsn: number | undefined;\n\tprivate readonly processorBatch: InboundSequencedContainerRuntimeMessage[] = [];\n\n\tconstructor(\n\t\tprivate readonly opSplitter: OpSplitter,\n\t\tprivate readonly opDecompressor: OpDecompressor,\n\t\tprivate readonly opGroupingManager: OpGroupingManager,\n\t) {}\n\n\tpublic get partialMessages(): ReadonlyMap<string, string[]> {\n\t\treturn this.opSplitter.chunks;\n\t}\n\n\tpublic clearPartialMessagesFor(clientId: string) {\n\t\tthis.opSplitter.clearPartialChunks(clientId);\n\t}\n\n\t/**\n\t * Ungroups and Unchunks the runtime ops of a batch received over the wire\n\t * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized\n\t * (grouped, compressed, and/or chunked).\n\t * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure\n\t * depends on this object instance.\n\t * Note remoteMessageCopy.contents (and other object props) MUST not be modified,\n\t * but may be overwritten (as is the case with contents).\n\t *\n\t * Incoming messages will always have compression, chunking, and grouped batching happen in a defined order and that order cannot be changed.\n\t * When processing these messages, the order is:\n\t * 1. If chunked, process the chunk and only continue if this is a final chunk\n\t * 2. If compressed, decompress the message and store for further unrolling of the decompressed content\n\t * 3. If grouped, ungroup the message\n\t * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound\n\t *\n\t * @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch\n\t * or undefined if the batch is not yet complete.\n\t */\n\tpublic process(remoteMessageCopy: ISequencedDocumentMessage):\n\t\t| {\n\t\t\t\tmessages: InboundSequencedContainerRuntimeMessage[];\n\t\t\t\tbatchStartCsn: number;\n\t\t }\n\t\t| undefined {\n\t\tlet message = remoteMessageCopy;\n\n\t\tensureContentsDeserialized(message);\n\n\t\tif (isChunkedMessage(message)) {\n\t\t\tconst chunkProcessingResult = this.opSplitter.processChunk(message);\n\t\t\t// Only continue further if current chunk is the final chunk\n\t\t\tif (!chunkProcessingResult.isFinalChunk) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// This message will always be compressed\n\t\t\tmessage = chunkProcessingResult.message;\n\t\t}\n\n\t\tif (this.opDecompressor.isCompressedMessage(message)) {\n\t\t\tthis.opDecompressor.decompressAndStore(message);\n\t\t}\n\n\t\tif (this.opDecompressor.currentlyUnrolling) {\n\t\t\tmessage = this.opDecompressor.unroll(message);\n\t\t\t// Need to unpack after unrolling if not a groupedBatch\n\t\t\tif (!isGroupedBatch(message)) {\n\t\t\t\tunpack(message);\n\t\t\t}\n\t\t}\n\n\t\tif (isGroupedBatch(message)) {\n\t\t\t// We should be awaiting a new batch (batchStartCsn undefined)\n\t\t\tassert(this.batchStartCsn === undefined, \"Grouped batch interrupting another batch\");\n\t\t\tassert(\n\t\t\t\tthis.processorBatch.length === 0,\n\t\t\t\t\"Processor batch should be empty on grouped batch\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tmessages: this.opGroupingManager.ungroupOp(message).map(unpack),\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t};\n\t\t}\n\n\t\tconst batchStartCsn = this.getAndUpdateBatchStartCsn(message);\n\n\t\t// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked\n\t\tunpackRuntimeMessage(message);\n\t\tthis.processorBatch.push(message as InboundSequencedContainerRuntimeMessage);\n\n\t\t// this.batchStartCsn is undefined only if we have processed all messages in the batch.\n\t\t// If it's still defined, we're still in the middle of a batch, so we return nothing, letting\n\t\t// containerRuntime know that we're waiting for more messages to complete the batch.\n\t\tif (this.batchStartCsn !== undefined) {\n\t\t\t// batch not yet complete\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst messages = [...this.processorBatch];\n\t\tthis.processorBatch.length = 0;\n\t\treturn {\n\t\t\tmessages,\n\t\t\tbatchStartCsn,\n\t\t};\n\t}\n\n\t/**\n\t * Based on pre-existing batch tracking info and the current message's batch metadata,\n\t * this will return the starting CSN for this message's batch, and will also update\n\t * the batch tracking info (this.batchStartCsn) based on whether we're still mid-batch.\n\t */\n\tprivate getAndUpdateBatchStartCsn(message: ISequencedDocumentMessage): number {\n\t\tconst batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;\n\t\tif (this.batchStartCsn === undefined) {\n\t\t\t// We are waiting for a new batch\n\t\t\tassert(batchMetadataFlag !== false, \"Unexpected batch end marker\");\n\n\t\t\t// Start of a new multi-message batch\n\t\t\tif (batchMetadataFlag === true) {\n\t\t\t\tthis.batchStartCsn = message.clientSequenceNumber;\n\t\t\t\treturn this.batchStartCsn;\n\t\t\t}\n\n\t\t\t// Single-message batch (Since metadata flag is undefined)\n\t\t\t// IMPORTANT: Leave this.batchStartCsn undefined, we're ready for the next batch now.\n\t\t\treturn message.clientSequenceNumber;\n\t\t}\n\n\t\t// We are in the middle or end of an existing multi-message batch. Return the current batchStartCsn\n\t\tconst batchStartCsn = this.batchStartCsn;\n\n\t\tassert(batchMetadataFlag !== true, \"Unexpected batch start marker\");\n\t\tif (batchMetadataFlag === false) {\n\t\t\t// Batch end? Then get ready for the next batch to start\n\t\t\tthis.batchStartCsn = undefined;\n\t\t}\n\n\t\treturn batchStartCsn;\n\t}\n}\n\n/** Takes an incoming message and if the contents is a string, JSON.parse's it in place */\nfunction ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void {\n\t// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!\n\t// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.\n\t// Old ops may contain empty string (I assume noops).\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t}\n}\n\n/**\n * For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.\n *\n * The return type illustrates the assumption that the message param\n * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns\n * (but there is no runtime validation of the 'type' or 'compatDetails' values).\n */\nfunction unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {\n\t// We assume the contents is an InboundContainerRuntimeMessage (the message is \"packed\")\n\tconst contents = message.contents as InboundContainerRuntimeMessage;\n\n\t// We're going to unpack message in-place (promoting those properties of contents up to message itself)\n\tconst messageUnpacked = message as InboundSequencedContainerRuntimeMessage;\n\n\tmessageUnpacked.type = contents.type;\n\tmessageUnpacked.contents = contents.contents;\n\tif (\"compatDetails\" in contents) {\n\t\t(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =\n\t\t\tcontents.compatDetails;\n\t}\n\treturn messageUnpacked;\n}\n\n/**\n * Unpacks runtime messages.\n *\n * @remarks This API makes no promises regarding backward-compatibility. This is internal API.\n * @param message - message (as it observed in storage / service)\n * @returns whether the given message was unpacked\n *\n * @internal\n */\nexport function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolean {\n\tif (message.type !== MessageType.Operation) {\n\t\t// Legacy format, but it's already \"unpacked\",\n\t\t// i.e. message.type is actually ContainerMessageType.\n\t\t// Or it's non-runtime message.\n\t\t// Nothing to do in such case.\n\t\treturn false;\n\t}\n\n\t// legacy op format?\n\t// TODO: Unsure if this is a real format we should be concerned with. There doesn't appear to be anything prepared to handle the address member.\n\tif (\n\t\t(message.contents as { address?: unknown }).address !== undefined &&\n\t\t(message.contents as { type?: unknown }).type === undefined\n\t) {\n\t\tmessage.type = ContainerMessageType.FluidDataStoreOp;\n\t} else {\n\t\t// new format\n\t\tunpack(message);\n\t}\n\n\treturn true;\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/container-runtime";
8
- export declare const pkgVersion = "2.1.0-276326";
8
+ export declare const pkgVersion = "2.1.0-281041";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/container-runtime";
8
- export const pkgVersion = "2.1.0-276326";
8
+ export const pkgVersion = "2.1.0-281041";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,cAAc,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.1.0-276326\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,cAAc,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.1.0-281041\";\n"]}
@@ -5,11 +5,13 @@
5
5
  import { ICriticalContainerError } from "@fluidframework/container-definitions";
6
6
  import { IDisposable } from "@fluidframework/core-interfaces";
7
7
  import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
8
- import { InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
9
- import type { BatchMessage } from "./opLifecycle/index.js";
8
+ import { type InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
9
+ import { BatchId, BatchMessage } from "./opLifecycle/index.js";
10
10
  /**
11
11
  * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the
12
12
  * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.
13
+ *
14
+ * @remarks This is the current serialization format for pending local state when a Container is serialized.
13
15
  */
14
16
  export interface IPendingMessage {
15
17
  type: "message";
@@ -18,7 +20,16 @@ export interface IPendingMessage {
18
20
  localOpMetadata: unknown;
19
21
  opMetadata: Record<string, unknown> | undefined;
20
22
  sequenceNumber?: number;
21
- batchStartCsn?: number;
23
+ /** Info needed to compute the batchId on reconnect */
24
+ batchIdContext: {
25
+ /** The Batch's original clientId, from when it was first flushed to be submitted */
26
+ clientId: string;
27
+ /**
28
+ * The Batch's original clientSequenceNumber, from when it was first flushed to be submitted
29
+ * @remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)
30
+ */
31
+ batchStartCsn: number;
32
+ };
22
33
  }
23
34
  export interface IPendingLocalState {
24
35
  /**
@@ -26,18 +37,14 @@ export interface IPendingLocalState {
26
37
  */
27
38
  pendingStates: IPendingMessage[];
28
39
  }
29
- export interface IPendingBatchMessage {
30
- content: string;
31
- localOpMetadata: unknown;
32
- opMetadata: Record<string, unknown> | undefined;
33
- }
40
+ /** Info needed to replay/resubmit a pending message */
41
+ export type PendingMessageResubmitData = Pick<IPendingMessage, "content" | "localOpMetadata" | "opMetadata">;
34
42
  export interface IRuntimeStateHandler {
35
43
  connected(): boolean;
36
44
  clientId(): string | undefined;
37
45
  close(error?: ICriticalContainerError): void;
38
46
  applyStashedOp(content: string): Promise<unknown>;
39
- reSubmit(message: IPendingBatchMessage): void;
40
- reSubmitBatch(batch: IPendingBatchMessage[]): void;
47
+ reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;
41
48
  isActiveConnection: () => boolean;
42
49
  isAttached: () => boolean;
43
50
  }
@@ -53,16 +60,21 @@ export interface IRuntimeStateHandler {
53
60
  export declare class PendingStateManager implements IDisposable {
54
61
  private readonly stateHandler;
55
62
  private readonly logger;
63
+ /** Messages that will need to be resubmitted if not ack'd before the next reconnection */
56
64
  private readonly pendingMessages;
65
+ /** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */
57
66
  private readonly initialMessages;
58
67
  /**
59
68
  * Sequenced local ops that are saved when stashing since pending ops may depend on them
60
69
  */
61
70
  private savedOps;
71
+ /** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
72
+ private negativeCounter;
62
73
  private readonly disposeOnce;
63
74
  private isProcessingBatch;
64
75
  private pendingBatchBeginMessage;
65
- private clientId;
76
+ /** Used to ensure we don't replay ops on the same connection twice */
77
+ private clientIdFromLastReplay;
66
78
  /**
67
79
  * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with
68
80
  * 'hasPendingMessages'.
@@ -80,7 +92,7 @@ export declare class PendingStateManager implements IDisposable {
80
92
  */
81
93
  hasPendingMessages(): boolean;
82
94
  getLocalState(snapshotSequenceNumber?: number): IPendingLocalState;
83
- constructor(stateHandler: IRuntimeStateHandler, initialLocalState: IPendingLocalState | undefined, logger: ITelemetryLoggerExt | undefined);
95
+ constructor(stateHandler: IRuntimeStateHandler, stashedLocalState: IPendingLocalState | undefined, logger: ITelemetryLoggerExt);
84
96
  get disposed(): boolean;
85
97
  readonly dispose: () => void;
86
98
  /**
@@ -96,15 +108,29 @@ export declare class PendingStateManager implements IDisposable {
96
108
  * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.
97
109
  */
98
110
  applyStashedOpsAt(seqNum?: number): Promise<void>;
111
+ /**
112
+ * Processes the incoming batch from the server. It verifies that messages are received in the right order and
113
+ * that the batch information is correct.
114
+ * @param batch - The batch that is being processed.
115
+ * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch
116
+ */
117
+ processPendingLocalBatch(batch: InboundSequencedContainerRuntimeMessage[], batchStartCsn: number): {
118
+ message: InboundSequencedContainerRuntimeMessage;
119
+ localOpMetadata: unknown;
120
+ }[];
99
121
  /**
100
122
  * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
101
123
  * the batch information was preserved for batch messages.
102
124
  * @param message - The message that got ack'd and needs to be processed.
125
+ * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
126
+ * (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)
103
127
  */
104
- processPendingLocalMessage(message: InboundSequencedContainerRuntimeMessage): unknown;
128
+ private processPendingLocalMessage;
105
129
  /**
106
130
  * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.
107
131
  * @param message - The message that is being processed.
132
+ * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
133
+ * @param pendingMessage - The corresponding pendingMessage.
108
134
  */
109
135
  private maybeProcessBatchBegin;
110
136
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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"}
@@ -3,8 +3,11 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import { assert, Lazy } from "@fluidframework/core-utils/internal";
6
- import { DataProcessingError, LoggingError, } from "@fluidframework/telemetry-utils/internal";
6
+ import { DataProcessingError, LoggingError, extractSafePropertiesFromMessage, } from "@fluidframework/telemetry-utils/internal";
7
7
  import Deque from "double-ended-queue";
8
+ import { v4 as uuid } from "uuid";
9
+ import { asBatchMetadata } from "./metadata.js";
10
+ import { generateBatchId } from "./opLifecycle/index.js";
8
11
  import { pkgVersion } from "./packageVersion.js";
9
12
  function buildPendingMessageContent(
10
13
  // AnyComboFromUnion is needed need to gain access to compatDetails that
@@ -76,16 +79,19 @@ export class PendingStateManager {
76
79
  ],
77
80
  };
78
81
  }
79
- constructor(stateHandler, initialLocalState, logger) {
82
+ constructor(stateHandler, stashedLocalState, logger) {
80
83
  this.stateHandler = stateHandler;
81
84
  this.logger = logger;
85
+ /** Messages that will need to be resubmitted if not ack'd before the next reconnection */
82
86
  this.pendingMessages = new Deque();
83
- // This queue represents already acked messages.
87
+ /** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */
84
88
  this.initialMessages = new Deque();
85
89
  /**
86
90
  * Sequenced local ops that are saved when stashing since pending ops may depend on them
87
91
  */
88
92
  this.savedOps = [];
93
+ /** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
94
+ this.negativeCounter = -1;
89
95
  this.disposeOnce = new Lazy(() => {
90
96
  this.initialMessages.clear();
91
97
  this.pendingMessages.clear();
@@ -93,8 +99,8 @@ export class PendingStateManager {
93
99
  // Indicates whether we are processing a batch.
94
100
  this.isProcessingBatch = false;
95
101
  this.dispose = () => this.disposeOnce.value;
96
- if (initialLocalState?.pendingStates) {
97
- this.initialMessages.push(...initialLocalState.pendingStates);
102
+ if (stashedLocalState?.pendingStates) {
103
+ this.initialMessages.push(...stashedLocalState.pendingStates);
98
104
  }
99
105
  }
100
106
  get disposed() {
@@ -108,6 +114,14 @@ export class PendingStateManager {
108
114
  * or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)
109
115
  */
110
116
  onFlushBatch(batch, clientSequenceNumber) {
117
+ // If we're connected this is the client of the current connection,
118
+ // otherwise it's the clientId that just disconnected
119
+ // It's only undefined if we've NEVER connected. This is a tight corner case and we can
120
+ // simply make up a unique ID in this case.
121
+ const clientId = this.stateHandler.clientId() ?? uuid();
122
+ // If the batch was not yet sent, we need to assign a unique batchStartCsn
123
+ // Use a negative number to distinguish these from real CSNs
124
+ const batchStartCsn = clientSequenceNumber ?? this.negativeCounter--;
111
125
  for (const message of batch) {
112
126
  const { contents: content = "", referenceSequenceNumber, localOpMetadata, metadata: opMetadata, } = message;
113
127
  const pendingMessage = {
@@ -116,7 +130,8 @@ export class PendingStateManager {
116
130
  content,
117
131
  localOpMetadata,
118
132
  opMetadata,
119
- batchStartCsn: clientSequenceNumber,
133
+ // Note: We only need this on the first message.
134
+ batchIdContext: { clientId, batchStartCsn },
120
135
  };
121
136
  this.pendingMessages.push(pendingMessage);
122
137
  }
@@ -151,6 +166,7 @@ export class PendingStateManager {
151
166
  else {
152
167
  nextMessage.localOpMetadata = localOpMetadata;
153
168
  // then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
169
+ patchBatchIdContext(nextMessage); // Back compat
154
170
  this.pendingMessages.push(nextMessage);
155
171
  }
156
172
  }
@@ -159,17 +175,31 @@ export class PendingStateManager {
159
175
  }
160
176
  }
161
177
  }
178
+ /**
179
+ * Processes the incoming batch from the server. It verifies that messages are received in the right order and
180
+ * that the batch information is correct.
181
+ * @param batch - The batch that is being processed.
182
+ * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch
183
+ */
184
+ processPendingLocalBatch(batch, batchStartCsn) {
185
+ return batch.map((message) => ({
186
+ message,
187
+ localOpMetadata: this.processPendingLocalMessage(message, batchStartCsn),
188
+ }));
189
+ }
162
190
  /**
163
191
  * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
164
192
  * the batch information was preserved for batch messages.
165
193
  * @param message - The message that got ack'd and needs to be processed.
194
+ * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
195
+ * (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)
166
196
  */
167
- processPendingLocalMessage(message) {
168
- // Pre-processing part - This may be the start of a batch.
169
- this.maybeProcessBatchBegin(message);
197
+ processPendingLocalMessage(message, batchStartCsn) {
170
198
  // Get the next message from the pending queue. Verify a message exists.
171
199
  const pendingMessage = this.pendingMessages.peekFront();
172
200
  assert(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
201
+ // This may be the start of a batch.
202
+ this.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);
173
203
  pendingMessage.sequenceNumber = message.sequenceNumber;
174
204
  this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
175
205
  this.pendingMessages.shift();
@@ -188,8 +218,26 @@ export class PendingStateManager {
188
218
  /**
189
219
  * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.
190
220
  * @param message - The message that is being processed.
221
+ * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
222
+ * @param pendingMessage - The corresponding pendingMessage.
191
223
  */
192
- maybeProcessBatchBegin(message) {
224
+ maybeProcessBatchBegin(message, batchStartCsn, pendingMessage) {
225
+ if (!this.isProcessingBatch) {
226
+ // Expecting the start of a batch (maybe single-message).
227
+ if (pendingMessage.batchIdContext.batchStartCsn !== batchStartCsn) {
228
+ this.logger?.sendErrorEvent({
229
+ eventName: "BatchClientSequenceNumberMismatch",
230
+ details: {
231
+ processingBatch: !!this.pendingBatchBeginMessage,
232
+ pendingBatchCsn: pendingMessage.batchIdContext.batchStartCsn,
233
+ batchStartCsn,
234
+ messageBatchMetadata: message.metadata?.batch,
235
+ pendingMessageBatchMetadata: pendingMessage.opMetadata?.batch,
236
+ },
237
+ messageDetails: extractSafePropertiesFromMessage(message),
238
+ });
239
+ }
240
+ }
193
241
  // This message is the first in a batch if the "batch" property on the metadata is set to true
194
242
  if (message.metadata?.batch) {
195
243
  // We should not already be processing a batch and there should be no pending batch begin message.
@@ -249,8 +297,8 @@ export class PendingStateManager {
249
297
  replayPendingStates() {
250
298
  assert(this.stateHandler.connected(), 0x172 /* "The connection state is not consistent with the runtime" */);
251
299
  // This assert suggests we are about to send same ops twice, which will result in data loss.
252
- assert(this.clientId !== this.stateHandler.clientId(), 0x173 /* "replayPendingStates called twice for same clientId!" */);
253
- this.clientId = this.stateHandler.clientId();
300
+ assert(this.clientIdFromLastReplay !== this.stateHandler.clientId(), 0x173 /* "replayPendingStates called twice for same clientId!" */);
301
+ this.clientIdFromLastReplay = this.stateHandler.clientId();
254
302
  assert(this.initialMessages.isEmpty(), 0x174 /* "initial states should be empty before replaying pending" */);
255
303
  const initialPendingMessagesCount = this.pendingMessages.length;
256
304
  let remainingPendingMessagesCount = this.pendingMessages.length;
@@ -261,40 +309,49 @@ export class PendingStateManager {
261
309
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
262
310
  let pendingMessage = this.pendingMessages.shift();
263
311
  remainingPendingMessagesCount--;
264
- assert(pendingMessage.opMetadata?.batch !== false, 0x41b /* We cannot process batches in chunks */);
312
+ const batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;
313
+ assert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
314
+ // The next message starts a batch (possibly single-message), and we'll need its batchId.
315
+ // We'll find batchId on this message if it was previously generated.
316
+ // Otherwise, generate it now - this is the first time resubmitting this batch.
317
+ const batchId = asBatchMetadata(pendingMessage.opMetadata)?.batchId ??
318
+ generateBatchId(pendingMessage.batchIdContext.clientId, pendingMessage.batchIdContext.batchStartCsn);
265
319
  /**
266
- * We want to ensure grouped messages get processed in a batch.
320
+ * We must preserve the distinct batches on resubmit.
267
321
  * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
268
- * either receive the whole batch ack or nothing at all.
322
+ * either receive the whole batch ack or nothing at all. @see ScheduleManager for how this works.
269
323
  */
270
- if (pendingMessage.opMetadata?.batch) {
271
- assert(remainingPendingMessagesCount > 0, 0x554 /* Last pending message cannot be a batch begin */);
272
- const batch = [];
273
- // check is >= because batch end may be last pending message
274
- while (remainingPendingMessagesCount >= 0) {
275
- batch.push({
324
+ if (batchMetadataFlag === undefined) {
325
+ // Single-message batch
326
+ this.stateHandler.reSubmitBatch([
327
+ {
276
328
  content: pendingMessage.content,
277
329
  localOpMetadata: pendingMessage.localOpMetadata,
278
330
  opMetadata: pendingMessage.opMetadata,
279
- });
280
- if (pendingMessage.opMetadata?.batch === false) {
281
- break;
282
- }
283
- assert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
284
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
285
- pendingMessage = this.pendingMessages.shift();
286
- remainingPendingMessagesCount--;
287
- assert(pendingMessage.opMetadata?.batch !== true, 0x556 /* Batch start needs a corresponding batch end */);
288
- }
289
- this.stateHandler.reSubmitBatch(batch);
331
+ },
332
+ ], batchId);
333
+ continue;
290
334
  }
291
- else {
292
- this.stateHandler.reSubmit({
335
+ // else: batchMetadataFlag === true (It's a typical multi-message batch)
336
+ assert(remainingPendingMessagesCount > 0, 0x554 /* Last pending message cannot be a batch begin */);
337
+ const batch = [];
338
+ // check is >= because batch end may be last pending message
339
+ while (remainingPendingMessagesCount >= 0) {
340
+ batch.push({
293
341
  content: pendingMessage.content,
294
342
  localOpMetadata: pendingMessage.localOpMetadata,
295
343
  opMetadata: pendingMessage.opMetadata,
296
344
  });
345
+ if (pendingMessage.opMetadata?.batch === false) {
346
+ break;
347
+ }
348
+ assert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
349
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
350
+ pendingMessage = this.pendingMessages.shift();
351
+ remainingPendingMessagesCount--;
352
+ assert(pendingMessage.opMetadata?.batch !== true, 0x556 /* Batch start needs a corresponding batch end */);
297
353
  }
354
+ this.stateHandler.reSubmitBatch(batch, batchId);
298
355
  }
299
356
  // pending ops should no longer depend on previous sequenced local ops after resubmit
300
357
  this.savedOps = [];
@@ -310,4 +367,12 @@ export class PendingStateManager {
310
367
  }
311
368
  }
312
369
  }
370
+ /** For back-compat if trying to apply stashed ops that pre-date batchIdContext */
371
+ function patchBatchIdContext(message) {
372
+ const batchIdContext = message.batchIdContext;
373
+ if (batchIdContext === undefined) {
374
+ // Using uuid guarantees uniqueness, retaining existing behavior
375
+ message.batchIdContext = { clientId: uuid(), batchStartCsn: -1 };
376
+ }
377
+ }
313
378
  //# sourceMappingURL=pendingStateManager.js.map