@fluidframework/container-runtime 2.2.0 → 2.3.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 (181) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +15 -0
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/channelCollection.d.ts +1 -1
  5. package/dist/channelCollection.d.ts.map +1 -1
  6. package/dist/channelCollection.js +1 -16
  7. package/dist/channelCollection.js.map +1 -1
  8. package/dist/connectionTelemetry.d.ts +27 -3
  9. package/dist/connectionTelemetry.d.ts.map +1 -1
  10. package/dist/connectionTelemetry.js.map +1 -1
  11. package/dist/containerRuntime.d.ts +68 -13
  12. package/dist/containerRuntime.d.ts.map +1 -1
  13. package/dist/containerRuntime.js +262 -180
  14. package/dist/containerRuntime.js.map +1 -1
  15. package/dist/deltaManagerProxies.d.ts.map +1 -1
  16. package/dist/deltaManagerProxies.js +11 -4
  17. package/dist/deltaManagerProxies.js.map +1 -1
  18. package/dist/gc/garbageCollection.d.ts.map +1 -1
  19. package/dist/gc/garbageCollection.js +0 -2
  20. package/dist/gc/garbageCollection.js.map +1 -1
  21. package/dist/gc/gcHelpers.d.ts.map +1 -1
  22. package/dist/gc/gcHelpers.js +0 -8
  23. package/dist/gc/gcHelpers.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/legacy.d.ts +3 -1
  29. package/dist/messageTypes.d.ts +0 -9
  30. package/dist/messageTypes.d.ts.map +1 -1
  31. package/dist/messageTypes.js.map +1 -1
  32. package/dist/opLifecycle/batchManager.d.ts +9 -0
  33. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  34. package/dist/opLifecycle/batchManager.js +19 -6
  35. package/dist/opLifecycle/batchManager.js.map +1 -1
  36. package/dist/opLifecycle/duplicateBatchDetector.d.ts +32 -0
  37. package/dist/opLifecycle/duplicateBatchDetector.d.ts.map +1 -0
  38. package/dist/opLifecycle/duplicateBatchDetector.js +68 -0
  39. package/dist/opLifecycle/duplicateBatchDetector.js.map +1 -0
  40. package/dist/opLifecycle/index.d.ts +3 -2
  41. package/dist/opLifecycle/index.d.ts.map +1 -1
  42. package/dist/opLifecycle/index.js +4 -1
  43. package/dist/opLifecycle/index.js.map +1 -1
  44. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  45. package/dist/opLifecycle/opCompressor.js +0 -4
  46. package/dist/opLifecycle/opCompressor.js.map +1 -1
  47. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  48. package/dist/opLifecycle/opGroupingManager.js +0 -4
  49. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  50. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  51. package/dist/opLifecycle/opSplitter.js +1 -6
  52. package/dist/opLifecycle/opSplitter.js.map +1 -1
  53. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  54. package/dist/opLifecycle/outbox.js +1 -4
  55. package/dist/opLifecycle/outbox.js.map +1 -1
  56. package/dist/opLifecycle/remoteMessageProcessor.d.ts +37 -17
  57. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  58. package/dist/opLifecycle/remoteMessageProcessor.js +47 -37
  59. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  60. package/dist/packageVersion.d.ts +1 -1
  61. package/dist/packageVersion.js +1 -1
  62. package/dist/packageVersion.js.map +1 -1
  63. package/dist/pendingStateManager.d.ts +27 -17
  64. package/dist/pendingStateManager.d.ts.map +1 -1
  65. package/dist/pendingStateManager.js +85 -56
  66. package/dist/pendingStateManager.js.map +1 -1
  67. package/dist/scheduleManager.d.ts +2 -4
  68. package/dist/scheduleManager.d.ts.map +1 -1
  69. package/dist/scheduleManager.js +6 -37
  70. package/dist/scheduleManager.js.map +1 -1
  71. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  72. package/dist/summary/summarizerNode/summarizerNodeUtils.js +0 -2
  73. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  74. package/dist/summary/summaryCollection.d.ts.map +1 -1
  75. package/dist/summary/summaryCollection.js +5 -7
  76. package/dist/summary/summaryCollection.js.map +1 -1
  77. package/dist/summary/summaryFormat.d.ts.map +1 -1
  78. package/dist/summary/summaryFormat.js +1 -4
  79. package/dist/summary/summaryFormat.js.map +1 -1
  80. package/lib/channelCollection.d.ts +1 -1
  81. package/lib/channelCollection.d.ts.map +1 -1
  82. package/lib/channelCollection.js +1 -16
  83. package/lib/channelCollection.js.map +1 -1
  84. package/lib/connectionTelemetry.d.ts +27 -3
  85. package/lib/connectionTelemetry.d.ts.map +1 -1
  86. package/lib/connectionTelemetry.js.map +1 -1
  87. package/lib/containerRuntime.d.ts +68 -13
  88. package/lib/containerRuntime.d.ts.map +1 -1
  89. package/lib/containerRuntime.js +262 -181
  90. package/lib/containerRuntime.js.map +1 -1
  91. package/lib/deltaManagerProxies.d.ts.map +1 -1
  92. package/lib/deltaManagerProxies.js +11 -4
  93. package/lib/deltaManagerProxies.js.map +1 -1
  94. package/lib/gc/garbageCollection.d.ts.map +1 -1
  95. package/lib/gc/garbageCollection.js +0 -2
  96. package/lib/gc/garbageCollection.js.map +1 -1
  97. package/lib/gc/gcHelpers.d.ts.map +1 -1
  98. package/lib/gc/gcHelpers.js +0 -8
  99. package/lib/gc/gcHelpers.js.map +1 -1
  100. package/lib/index.d.ts +1 -1
  101. package/lib/index.d.ts.map +1 -1
  102. package/lib/index.js +1 -1
  103. package/lib/index.js.map +1 -1
  104. package/lib/legacy.d.ts +3 -1
  105. package/lib/messageTypes.d.ts +0 -9
  106. package/lib/messageTypes.d.ts.map +1 -1
  107. package/lib/messageTypes.js.map +1 -1
  108. package/lib/opLifecycle/batchManager.d.ts +9 -0
  109. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  110. package/lib/opLifecycle/batchManager.js +17 -5
  111. package/lib/opLifecycle/batchManager.js.map +1 -1
  112. package/lib/opLifecycle/duplicateBatchDetector.d.ts +32 -0
  113. package/lib/opLifecycle/duplicateBatchDetector.d.ts.map +1 -0
  114. package/lib/opLifecycle/duplicateBatchDetector.js +64 -0
  115. package/lib/opLifecycle/duplicateBatchDetector.js.map +1 -0
  116. package/lib/opLifecycle/index.d.ts +3 -2
  117. package/lib/opLifecycle/index.d.ts.map +1 -1
  118. package/lib/opLifecycle/index.js +2 -1
  119. package/lib/opLifecycle/index.js.map +1 -1
  120. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  121. package/lib/opLifecycle/opCompressor.js +0 -4
  122. package/lib/opLifecycle/opCompressor.js.map +1 -1
  123. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  124. package/lib/opLifecycle/opGroupingManager.js +0 -4
  125. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  126. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  127. package/lib/opLifecycle/opSplitter.js +1 -6
  128. package/lib/opLifecycle/opSplitter.js.map +1 -1
  129. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  130. package/lib/opLifecycle/outbox.js +1 -4
  131. package/lib/opLifecycle/outbox.js.map +1 -1
  132. package/lib/opLifecycle/remoteMessageProcessor.d.ts +37 -17
  133. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  134. package/lib/opLifecycle/remoteMessageProcessor.js +47 -37
  135. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  136. package/lib/packageVersion.d.ts +1 -1
  137. package/lib/packageVersion.js +1 -1
  138. package/lib/packageVersion.js.map +1 -1
  139. package/lib/pendingStateManager.d.ts +27 -17
  140. package/lib/pendingStateManager.d.ts.map +1 -1
  141. package/lib/pendingStateManager.js +85 -56
  142. package/lib/pendingStateManager.js.map +1 -1
  143. package/lib/scheduleManager.d.ts +2 -4
  144. package/lib/scheduleManager.d.ts.map +1 -1
  145. package/lib/scheduleManager.js +6 -37
  146. package/lib/scheduleManager.js.map +1 -1
  147. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  148. package/lib/summary/summarizerNode/summarizerNodeUtils.js +0 -2
  149. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  150. package/lib/summary/summaryCollection.d.ts.map +1 -1
  151. package/lib/summary/summaryCollection.js +5 -7
  152. package/lib/summary/summaryCollection.js.map +1 -1
  153. package/lib/summary/summaryFormat.d.ts.map +1 -1
  154. package/lib/summary/summaryFormat.js +1 -4
  155. package/lib/summary/summaryFormat.js.map +1 -1
  156. package/lib/tsdoc-metadata.json +1 -1
  157. package/package.json +49 -27
  158. package/src/channelCollection.ts +7 -21
  159. package/src/connectionTelemetry.ts +33 -3
  160. package/src/containerRuntime.ts +382 -233
  161. package/src/deltaManagerProxies.ts +11 -4
  162. package/src/gc/garbageCollection.ts +1 -3
  163. package/src/gc/gcHelpers.ts +4 -12
  164. package/src/index.ts +2 -0
  165. package/src/messageTypes.ts +0 -10
  166. package/src/opLifecycle/batchManager.ts +29 -7
  167. package/src/opLifecycle/duplicateBatchDetector.ts +78 -0
  168. package/src/opLifecycle/index.ts +4 -1
  169. package/src/opLifecycle/opCompressor.ts +2 -6
  170. package/src/opLifecycle/opGroupingManager.ts +2 -6
  171. package/src/opLifecycle/opSplitter.ts +2 -6
  172. package/src/opLifecycle/outbox.ts +1 -3
  173. package/src/opLifecycle/remoteMessageProcessor.ts +87 -59
  174. package/src/packageVersion.ts +1 -1
  175. package/src/pendingStateManager.ts +114 -66
  176. package/src/scheduleManager.ts +8 -47
  177. package/src/summary/summarizerNode/summarizerNodeUtils.ts +1 -3
  178. package/src/summary/summaryCollection.ts +7 -9
  179. package/src/summary/summaryFormat.ts +1 -3
  180. package/src/summary/summaryFormats.md +11 -9
  181. package/tsconfig.json +1 -0
@@ -22,6 +22,7 @@ export class RemoteMessageProcessor {
22
22
  this.opSplitter = opSplitter;
23
23
  this.opDecompressor = opDecompressor;
24
24
  this.opGroupingManager = opGroupingManager;
25
+ this.batchInProgress = false;
25
26
  }
26
27
  get partialMessages() {
27
28
  return this.opSplitter.chunks;
@@ -72,62 +73,72 @@ export class RemoteMessageProcessor {
72
73
  }
73
74
  }
74
75
  if (isGroupedBatch(message)) {
75
- // We should be awaiting a new batch (batchInProgress undefined)
76
- assert(this.batchInProgress === undefined, 0x9d3 /* Grouped batch interrupting another batch */);
76
+ // We should be awaiting a new batch (batchInProgress false)
77
+ assert(!this.batchInProgress, 0x9d3 /* Grouped batch interrupting another batch */);
77
78
  const batchId = asBatchMetadata(message.metadata)?.batchId;
78
79
  const groupedMessages = this.opGroupingManager.ungroupOp(message).map(unpack);
79
80
  return {
81
+ type: "fullBatch",
80
82
  messages: groupedMessages, // Will be [] for an empty batch
81
- batchStartCsn: message.clientSequenceNumber,
82
- clientId,
83
- batchId,
84
- // If the batch is empty, we need to return the sequence number aside
85
- emptyBatchSequenceNumber: groupedMessages.length === 0 ? message.sequenceNumber : undefined,
83
+ batchStart: {
84
+ batchStartCsn: message.clientSequenceNumber,
85
+ clientId,
86
+ batchId,
87
+ keyMessage: groupedMessages[0] ?? message, // For an empty batch, this is the empty grouped batch message. Needed for sequence numbers for this batch
88
+ },
89
+ length: groupedMessages.length, // Will be 0 for an empty batch
86
90
  };
87
91
  }
88
92
  // Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked
89
93
  unpackRuntimeMessage(message, logLegacyCase);
90
- const { batchEnded } = this.addMessageToBatch(message);
91
- if (!batchEnded) {
92
- // batch not yet complete
93
- return undefined;
94
- }
95
- const completedBatch = this.batchInProgress;
96
- this.batchInProgress = undefined;
97
- return completedBatch;
94
+ return this.getResultBasedOnBatchMetadata(message);
98
95
  }
99
96
  /**
100
- * Add the given message to the current batch, and indicate whether the batch is now complete.
101
- *
102
- * @returns batchEnded: true if the batch is now complete, batchEnded: false if more messages are expected
97
+ * Now that the message has been "unwrapped" as to any virtualization (grouping, compression, chunking),
98
+ * inspect the batch metadata flag and determine what kind of result to return.
103
99
  */
104
- addMessageToBatch(message) {
100
+ getResultBasedOnBatchMetadata(message) {
105
101
  const batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;
106
- if (this.batchInProgress === undefined) {
102
+ if (!this.batchInProgress) {
107
103
  // We are waiting for a new batch
108
104
  assert(batchMetadataFlag !== false, 0x9d5 /* Unexpected batch end marker */);
109
105
  // Start of a new multi-message batch
110
106
  if (batchMetadataFlag === true) {
111
- this.batchInProgress = {
112
- messages: [message],
113
- batchId: asBatchMetadata(message.metadata)?.batchId,
114
- clientId: message.clientId,
115
- batchStartCsn: message.clientSequenceNumber,
107
+ this.batchInProgress = true;
108
+ return {
109
+ type: "batchStartingMessage",
110
+ batchStart: {
111
+ batchId: asBatchMetadata(message.metadata)?.batchId,
112
+ clientId: message.clientId,
113
+ batchStartCsn: message.clientSequenceNumber,
114
+ keyMessage: message,
115
+ },
116
+ nextMessage: message,
116
117
  };
117
- return { batchEnded: false };
118
118
  }
119
119
  // Single-message batch (Since metadata flag is undefined)
120
- this.batchInProgress = {
120
+ return {
121
+ type: "fullBatch",
121
122
  messages: [message],
122
- batchStartCsn: message.clientSequenceNumber,
123
- clientId: message.clientId,
124
- batchId: asBatchMetadata(message.metadata)?.batchId,
123
+ batchStart: {
124
+ batchStartCsn: message.clientSequenceNumber,
125
+ clientId: message.clientId,
126
+ batchId: asBatchMetadata(message.metadata)?.batchId,
127
+ keyMessage: message,
128
+ },
129
+ length: 1,
125
130
  };
126
- return { batchEnded: true };
127
131
  }
128
132
  assert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);
129
- this.batchInProgress.messages.push(message);
130
- return { batchEnded: batchMetadataFlag === false };
133
+ // Clear batchInProgress state if the batch is ending
134
+ if (batchMetadataFlag === false) {
135
+ this.batchInProgress = false;
136
+ }
137
+ return {
138
+ type: "nextBatchMessage",
139
+ nextMessage: message,
140
+ batchEnd: batchMetadataFlag === false,
141
+ };
131
142
  }
132
143
  }
133
144
  /**
@@ -137,10 +148,9 @@ export class RemoteMessageProcessor {
137
148
  * @param logLegacyCase - callback to log when legacy op is encountered
138
149
  */
139
150
  export function ensureContentsDeserialized(mutableMessage, hasModernRuntimeMessageEnvelope, logLegacyCase) {
140
- // Currently the loader layer is parsing the contents of the message as JSON if it is a string,
141
- // so we never expect to see this case.
142
- // We intend to remove that logic from the Loader, at which point we will have it here.
143
- // Only hasModernRuntimeMessageEnvelope true will be expected to have JSON contents.
151
+ // This should become unconditional once (Loader LTS) DeltaManager.processInboundMessage() stops parsing content (ADO #12052)
152
+ // Note: Until that change is made in the loader, this case will never be hit.
153
+ // Then there will be a long time of needing both cases, until LTS catches up to the change.
144
154
  let didParseJsonContents;
145
155
  if (typeof mutableMessage.contents === "string" && mutableMessage.contents !== "") {
146
156
  mutableMessage.contents = JSON.parse(mutableMessage.contents);
@@ -1 +1 @@
1
- {"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAC7D,OAAO,EACN,WAAW,GAEX,MAAM,6CAA6C,CAAC;AAErD,OAAO,EACN,oBAAoB,GAIpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,OAAO,EAAqB,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAc,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAsB/D,SAAS,iBAAiB,CACzB,OAAkC;IAElC,MAAM,CACL,OAAO,CAAC,QAAQ,KAAK,IAAI,EACzB,KAAK,CAAC,sEAAsE,CAC5E,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,sBAAsB;IAQlC,YACkB,UAAsB,EACtB,cAA8B,EAC9B,iBAAoC;QAFpC,eAAU,GAAV,UAAU,CAAY;QACtB,mBAAc,GAAd,cAAc,CAAgB;QAC9B,sBAAiB,GAAjB,iBAAiB,CAAmB;IACnD,CAAC;IAEJ,IAAW,eAAe;QACzB,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;IAC/B,CAAC;IAEM,uBAAuB,CAAC,QAAgB;QAC9C,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACI,OAAO,CACb,iBAA4C,EAC5C,aAAyC;QAEzC,IAAI,OAAO,GAAG,iBAAiB,CAAC;QAEhC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAElC,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,MAAM,qBAAqB,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YACpE,4DAA4D;YAC5D,IAAI,CAAC,qBAAqB,CAAC,YAAY,EAAE,CAAC;gBACzC,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,yCAAyC;YACzC,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC;QACzC,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACjD,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;YAC5C,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC9C,uDAAuD;YACvD,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,MAAM,CAAC,OAAO,CAAC,CAAC;YACjB,CAAC;QACF,CAAC;QAED,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,gEAAgE;YAChE,MAAM,CACL,IAAI,CAAC,eAAe,KAAK,SAAS,EAClC,KAAK,CAAC,8CAA8C,CACpD,CAAC;YACF,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;YAC3D,MAAM,eAAe,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC9E,OAAO;gBACN,QAAQ,EAAE,eAAe,EAAE,gCAAgC;gBAC3D,aAAa,EAAE,OAAO,CAAC,oBAAoB;gBAC3C,QAAQ;gBACR,OAAO;gBACP,qEAAqE;gBACrE,wBAAwB,EACvB,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;aAClE,CAAC;QACH,CAAC;QAED,oGAAoG;QACpG,oBAAoB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAE7C,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,iBAAiB,CAC5C,OAAyE,CACzE,CAAC;QAEF,IAAI,CAAC,UAAU,EAAE,CAAC;YACjB,yBAAyB;YACzB,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC;QAC5C,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QACjC,OAAO,cAAc,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACK,iBAAiB,CACxB,OAAuE;QAEvE,MAAM,iBAAiB,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;QACnE,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YACxC,iCAAiC;YACjC,MAAM,CAAC,iBAAiB,KAAK,KAAK,EAAE,KAAK,CAAC,iCAAiC,CAAC,CAAC;YAE7E,qCAAqC;YACrC,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBAChC,IAAI,CAAC,eAAe,GAAG;oBACtB,QAAQ,EAAE,CAAC,OAAO,CAAC;oBACnB,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO;oBACnD,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,aAAa,EAAE,OAAO,CAAC,oBAAoB;iBAC3C,CAAC;gBAEF,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;YAC9B,CAAC;YAED,0DAA0D;YAC1D,IAAI,CAAC,eAAe,GAAG;gBACtB,QAAQ,EAAE,CAAC,OAAO,CAAC;gBACnB,aAAa,EAAE,OAAO,CAAC,oBAAoB;gBAC3C,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO;aACnD,CAAC;YACF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QACD,MAAM,CAAC,iBAAiB,KAAK,IAAI,EAAE,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAE9E,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE5C,OAAO,EAAE,UAAU,EAAE,iBAAiB,KAAK,KAAK,EAAE,CAAC;IACpD,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACzC,cAAyC,EACzC,+BAAwC,EACxC,aAAyC;IAEzC,+FAA+F;IAC/F,uCAAuC;IACvC,uFAAuF;IACvF,oFAAoF;IACpF,IAAI,oBAA6B,CAAC;IAClC,IAAI,OAAO,cAAc,CAAC,QAAQ,KAAK,QAAQ,IAAI,cAAc,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QACnF,cAAc,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC9D,oBAAoB,GAAG,IAAI,CAAC;IAC7B,CAAC;SAAM,CAAC;QACP,oBAAoB,GAAG,KAAK,CAAC;IAC9B,CAAC;IAED,iFAAiF;IACjF,6CAA6C;IAC7C,uEAAuE;IACvE,IAAI,oBAAoB,EAAE,CAAC;QAC1B,aAAa,CAAC,8CAA8C,CAAC,CAAC;IAC/D,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,MAAM,CAAC,OAAkC;IACjD,wFAAwF;IACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAA0C,CAAC;IAEpE,uGAAuG;IACvG,MAAM,eAAe,GAAG,OAAkD,CAAC;IAE3E,eAAe,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IACrC,eAAe,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;IAC7C,IAAI,eAAe,IAAI,QAAQ,EAAE,CAAC;QAChC,eAAwE,CAAC,aAAa;YACtF,QAAQ,CAAC,aAAa,CAAC;IACzB,CAAC;IACD,OAAO,eAAe,CAAC;AACxB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAkC,EAClC,gBAA4C,GAAG,EAAE,GAAE,CAAC;IAEpD,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,SAAS,EAAE,CAAC;QAC5C,8CAA8C;QAC9C,sDAAsD;QACtD,+BAA+B;QAC/B,8BAA8B;QAC9B,OAAO,KAAK,CAAC;IACd,CAAC;IAED,oBAAoB;IACpB,gJAAgJ;IAChJ,IACE,OAAO,CAAC,QAAkC,CAAC,OAAO,KAAK,SAAS;QAChE,OAAO,CAAC,QAA+B,CAAC,IAAI,KAAK,SAAS,EAC1D,CAAC;QACF,OAAO,CAAC,IAAI,GAAG,oBAAoB,CAAC,gBAAgB,CAAC;QACrD,aAAa,CAAC,0CAA0C,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACP,aAAa;QACb,MAAM,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC","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/** Messages being received as a batch, with details needed to process the batch */\nexport interface InboundBatch {\n\t/** Messages in this batch */\n\treadonly messages: InboundSequencedContainerRuntimeMessage[];\n\t/** Batch ID, if present */\n\treadonly batchId: string | undefined;\n\t/** clientId that sent this batch. Used to compute Batch ID if needed */\n\treadonly clientId: string;\n\t/**\n\t * Client Sequence Number of the first message in the batch.\n\t * Used to compute Batch ID if needed\n\t *\n\t * @remarks For chunked batches, this is the CSN of the \"representative\" chunk (the final chunk).\n\t * For grouped batches, clientSequenceNumber on messages is overwritten, so we track this original value here.\n\t */\n\treadonly batchStartCsn: number;\n\t/** For an empty batch (with no messages), we need to remember the empty grouped batch's sequence number */\n\treadonly emptyBatchSequenceNumber?: number;\n}\n\nfunction assertHasClientId(\n\tmessage: ISequencedDocumentMessage,\n): asserts message is ISequencedDocumentMessage & { clientId: string } {\n\tassert(\n\t\tmessage.clientId !== null,\n\t\t0xa02 /* Server-generated message should not reach RemoteMessageProcessor */,\n\t);\n}\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 * The current batch being received, with details needed to process it.\n\t *\n\t * @remarks If undefined, we are expecting the next message to start a new batch.\n\t */\n\tprivate batchInProgress: InboundBatch | undefined;\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(\n\t\tremoteMessageCopy: ISequencedDocumentMessage,\n\t\tlogLegacyCase: (codePath: string) => void,\n\t): InboundBatch | undefined {\n\t\tlet message = remoteMessageCopy;\n\n\t\tassertHasClientId(message);\n\t\tconst clientId = message.clientId;\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 undefined;\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 (batchInProgress undefined)\n\t\t\tassert(\n\t\t\t\tthis.batchInProgress === undefined,\n\t\t\t\t0x9d3 /* Grouped batch interrupting another batch */,\n\t\t\t);\n\t\t\tconst batchId = asBatchMetadata(message.metadata)?.batchId;\n\t\t\tconst groupedMessages = this.opGroupingManager.ungroupOp(message).map(unpack);\n\t\t\treturn {\n\t\t\t\tmessages: groupedMessages, // Will be [] for an empty batch\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\tclientId,\n\t\t\t\tbatchId,\n\t\t\t\t// If the batch is empty, we need to return the sequence number aside\n\t\t\t\temptyBatchSequenceNumber:\n\t\t\t\t\tgroupedMessages.length === 0 ? message.sequenceNumber : undefined,\n\t\t\t};\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, logLegacyCase);\n\n\t\tconst { batchEnded } = this.addMessageToBatch(\n\t\t\tmessage as InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t\t);\n\n\t\tif (!batchEnded) {\n\t\t\t// batch not yet complete\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst completedBatch = this.batchInProgress;\n\t\tthis.batchInProgress = undefined;\n\t\treturn completedBatch;\n\t}\n\n\t/**\n\t * Add the given message to the current batch, and indicate whether the batch is now complete.\n\t *\n\t * @returns batchEnded: true if the batch is now complete, batchEnded: false if more messages are expected\n\t */\n\tprivate addMessageToBatch(\n\t\tmessage: InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t): { batchEnded: boolean } {\n\t\tconst batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;\n\t\tif (this.batchInProgress === undefined) {\n\t\t\t// We are waiting for a new batch\n\t\t\tassert(batchMetadataFlag !== false, 0x9d5 /* 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.batchInProgress = {\n\t\t\t\t\tmessages: [message],\n\t\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t\t\tclientId: message.clientId,\n\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t};\n\n\t\t\t\treturn { batchEnded: false };\n\t\t\t}\n\n\t\t\t// Single-message batch (Since metadata flag is undefined)\n\t\t\tthis.batchInProgress = {\n\t\t\t\tmessages: [message],\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\tclientId: message.clientId,\n\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t};\n\t\t\treturn { batchEnded: true };\n\t\t}\n\t\tassert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);\n\n\t\tthis.batchInProgress.messages.push(message);\n\n\t\treturn { batchEnded: batchMetadataFlag === false };\n\t}\n}\n\n/**\n * Takes an incoming message and if the contents is a string, JSON.parse's it in place\n * @param mutableMessage - op message received\n * @param hasModernRuntimeMessageEnvelope - false if the message does not contain the modern op envelop where message.type is MessageType.Operation\n * @param logLegacyCase - callback to log when legacy op is encountered\n */\nexport function ensureContentsDeserialized(\n\tmutableMessage: ISequencedDocumentMessage,\n\thasModernRuntimeMessageEnvelope: boolean,\n\tlogLegacyCase: (codePath: string) => void,\n): void {\n\t// Currently the loader layer is parsing the contents of the message as JSON if it is a string,\n\t// so we never expect to see this case.\n\t// We intend to remove that logic from the Loader, at which point we will have it here.\n\t// Only hasModernRuntimeMessageEnvelope true will be expected to have JSON contents.\n\tlet didParseJsonContents: boolean;\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t\tdidParseJsonContents = true;\n\t} else {\n\t\tdidParseJsonContents = false;\n\t}\n\n\t// The DeltaManager parses the contents of the message as JSON if it is a string,\n\t// so we should never end up parsing it here.\n\t// Let's observe if we are wrong about this to learn about these cases.\n\tif (didParseJsonContents) {\n\t\tlogLegacyCase(\"ensureContentsDeserialized_foundJsonContents\");\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(\n\tmessage: ISequencedDocumentMessage,\n\tlogLegacyCase: (codePath: string) => void = () => {},\n): 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\tlogLegacyCase(\"unpackRuntimeMessage_contentsWithAddress\");\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":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAC7D,OAAO,EACN,WAAW,GAEX,MAAM,6CAA6C,CAAC;AAErD,OAAO,EACN,oBAAoB,GAIpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,OAAO,EAAqB,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAc,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAoD/D,SAAS,iBAAiB,CACzB,OAAkC;IAElC,MAAM,CACL,OAAO,CAAC,QAAQ,KAAK,IAAI,EACzB,KAAK,CAAC,sEAAsE,CAC5E,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,sBAAsB;IAGlC,YACkB,UAAsB,EACtB,cAA8B,EAC9B,iBAAoC;QAFpC,eAAU,GAAV,UAAU,CAAY;QACtB,mBAAc,GAAd,cAAc,CAAgB;QAC9B,sBAAiB,GAAjB,iBAAiB,CAAmB;QAL9C,oBAAe,GAAY,KAAK,CAAC;IAMtC,CAAC;IAEJ,IAAW,eAAe;QACzB,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;IAC/B,CAAC;IAEM,uBAAuB,CAAC,QAAgB;QAC9C,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACI,OAAO,CACb,iBAA4C,EAC5C,aAAyC;QAEzC,IAAI,OAAO,GAAG,iBAAiB,CAAC;QAEhC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAElC,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,MAAM,qBAAqB,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YACpE,4DAA4D;YAC5D,IAAI,CAAC,qBAAqB,CAAC,YAAY,EAAE,CAAC;gBACzC,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,yCAAyC;YACzC,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC;QACzC,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACjD,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;YAC5C,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC9C,uDAAuD;YACvD,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,MAAM,CAAC,OAAO,CAAC,CAAC;YACjB,CAAC;QACF,CAAC;QAED,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,4DAA4D;YAC5D,MAAM,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,8CAA8C,CAAC,CAAC;YACpF,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;YAC3D,MAAM,eAAe,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAE9E,OAAO;gBACN,IAAI,EAAE,WAAW;gBACjB,QAAQ,EAAE,eAAe,EAAE,gCAAgC;gBAC3D,UAAU,EAAE;oBACX,aAAa,EAAE,OAAO,CAAC,oBAAoB;oBAC3C,QAAQ;oBACR,OAAO;oBACP,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,0GAA0G;iBACrJ;gBACD,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,+BAA+B;aAC/D,CAAC;QACH,CAAC;QAED,oGAAoG;QACpG,oBAAoB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAE7C,OAAO,IAAI,CAAC,6BAA6B,CACxC,OAAyE,CACzE,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,6BAA6B,CACpC,OAAuE;QAEvE,MAAM,iBAAiB,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;QACnE,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3B,iCAAiC;YACjC,MAAM,CAAC,iBAAiB,KAAK,KAAK,EAAE,KAAK,CAAC,iCAAiC,CAAC,CAAC;YAE7E,qCAAqC;YACrC,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBAChC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;gBAC5B,OAAO;oBACN,IAAI,EAAE,sBAAsB;oBAC5B,UAAU,EAAE;wBACX,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO;wBACnD,QAAQ,EAAE,OAAO,CAAC,QAAQ;wBAC1B,aAAa,EAAE,OAAO,CAAC,oBAAoB;wBAC3C,UAAU,EAAE,OAAO;qBACnB;oBACD,WAAW,EAAE,OAAO;iBACpB,CAAC;YACH,CAAC;YAED,0DAA0D;YAC1D,OAAO;gBACN,IAAI,EAAE,WAAW;gBACjB,QAAQ,EAAE,CAAC,OAAO,CAAC;gBACnB,UAAU,EAAE;oBACX,aAAa,EAAE,OAAO,CAAC,oBAAoB;oBAC3C,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO;oBACnD,UAAU,EAAE,OAAO;iBACnB;gBACD,MAAM,EAAE,CAAC;aACT,CAAC;QACH,CAAC;QACD,MAAM,CAAC,iBAAiB,KAAK,IAAI,EAAE,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAE9E,qDAAqD;QACrD,IAAI,iBAAiB,KAAK,KAAK,EAAE,CAAC;YACjC,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC9B,CAAC;QAED,OAAO;YACN,IAAI,EAAE,kBAAkB;YACxB,WAAW,EAAE,OAAO;YACpB,QAAQ,EAAE,iBAAiB,KAAK,KAAK;SACrC,CAAC;IACH,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACzC,cAAyC,EACzC,+BAAwC,EACxC,aAAyC;IAEzC,6HAA6H;IAC7H,8EAA8E;IAC9E,4FAA4F;IAC5F,IAAI,oBAA6B,CAAC;IAClC,IAAI,OAAO,cAAc,CAAC,QAAQ,KAAK,QAAQ,IAAI,cAAc,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QACnF,cAAc,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC9D,oBAAoB,GAAG,IAAI,CAAC;IAC7B,CAAC;SAAM,CAAC;QACP,oBAAoB,GAAG,KAAK,CAAC;IAC9B,CAAC;IAED,iFAAiF;IACjF,6CAA6C;IAC7C,uEAAuE;IACvE,IAAI,oBAAoB,EAAE,CAAC;QAC1B,aAAa,CAAC,8CAA8C,CAAC,CAAC;IAC/D,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,MAAM,CAAC,OAAkC;IACjD,wFAAwF;IACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAA0C,CAAC;IAEpE,uGAAuG;IACvG,MAAM,eAAe,GAAG,OAAkD,CAAC;IAE3E,eAAe,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IACrC,eAAe,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;IAC7C,IAAI,eAAe,IAAI,QAAQ,EAAE,CAAC;QAChC,eAAwE,CAAC,aAAa;YACtF,QAAQ,CAAC,aAAa,CAAC;IACzB,CAAC;IACD,OAAO,eAAe,CAAC;AACxB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAkC,EAClC,gBAA4C,GAAG,EAAE,GAAE,CAAC;IAEpD,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,SAAS,EAAE,CAAC;QAC5C,8CAA8C;QAC9C,sDAAsD;QACtD,+BAA+B;QAC/B,8BAA8B;QAC9B,OAAO,KAAK,CAAC;IACd,CAAC;IAED,oBAAoB;IACpB,gJAAgJ;IAChJ,IACE,OAAO,CAAC,QAAkC,CAAC,OAAO,KAAK,SAAS;QAChE,OAAO,CAAC,QAA+B,CAAC,IAAI,KAAK,SAAS,EAC1D,CAAC;QACF,OAAO,CAAC,IAAI,GAAG,oBAAoB,CAAC,gBAAgB,CAAC;QACrD,aAAa,CAAC,0CAA0C,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACP,aAAa;QACb,MAAM,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC","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/** Info about the batch we learn when we process the first message */\nexport interface BatchStartInfo {\n\t/** Batch ID, if present */\n\treadonly batchId: string | undefined;\n\t/** clientId that sent this batch. Used to compute Batch ID if needed */\n\treadonly clientId: string;\n\t/**\n\t * Client Sequence Number of the Grouped Batch message, or the first message in the ungrouped batch.\n\t * Used to compute Batch ID if needed\n\t *\n\t * @remarks For chunked batches, this is the CSN of the \"representative\" chunk (the final chunk).\n\t * For grouped batches, clientSequenceNumber on messages is overwritten, so we track this original value here.\n\t */\n\treadonly batchStartCsn: number;\n\t/**\n\t * The first message in the batch, or if the batch is empty, the empty grouped batch message.\n\t * Used for accessing the sequence numbers for the (start of the) batch.\n\t *\n\t * @remarks Do not use clientSequenceNumber here, use batchStartCsn instead.\n\t */\n\treadonly keyMessage: ISequencedDocumentMessage;\n}\n\n/**\n * Result of processing the next inbound message.\n * Depending on the message and configuration of RemoteMessageProcessor, the result may be:\n * - A full batch of messages (including a single-message batch)\n * - The first message of a multi-message batch\n * - The next message in a multi-message batch\n */\nexport type InboundMessageResult =\n\t| {\n\t\t\ttype: \"fullBatch\";\n\t\t\tmessages: InboundSequencedContainerRuntimeMessage[];\n\t\t\tbatchStart: BatchStartInfo;\n\t\t\tlength: number;\n\t }\n\t| {\n\t\t\ttype: \"batchStartingMessage\";\n\t\t\tbatchStart: BatchStartInfo;\n\t\t\tnextMessage: InboundSequencedContainerRuntimeMessage;\n\t\t\tlength?: never;\n\t }\n\t| {\n\t\t\ttype: \"nextBatchMessage\";\n\t\t\tbatchEnd?: boolean;\n\t\t\tnextMessage: InboundSequencedContainerRuntimeMessage;\n\t\t\tlength?: never;\n\t };\n\nfunction assertHasClientId(\n\tmessage: ISequencedDocumentMessage,\n): asserts message is ISequencedDocumentMessage & { clientId: string } {\n\tassert(\n\t\tmessage.clientId !== null,\n\t\t0xa02 /* Server-generated message should not reach RemoteMessageProcessor */,\n\t);\n}\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\tprivate batchInProgress: boolean = false;\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(\n\t\tremoteMessageCopy: ISequencedDocumentMessage,\n\t\tlogLegacyCase: (codePath: string) => void,\n\t): InboundMessageResult | undefined {\n\t\tlet message = remoteMessageCopy;\n\n\t\tassertHasClientId(message);\n\t\tconst clientId = message.clientId;\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 undefined;\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 (batchInProgress false)\n\t\t\tassert(!this.batchInProgress, 0x9d3 /* Grouped batch interrupting another batch */);\n\t\t\tconst batchId = asBatchMetadata(message.metadata)?.batchId;\n\t\t\tconst groupedMessages = this.opGroupingManager.ungroupOp(message).map(unpack);\n\n\t\t\treturn {\n\t\t\t\ttype: \"fullBatch\",\n\t\t\t\tmessages: groupedMessages, // Will be [] for an empty batch\n\t\t\t\tbatchStart: {\n\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t\tclientId,\n\t\t\t\t\tbatchId,\n\t\t\t\t\tkeyMessage: groupedMessages[0] ?? message, // For an empty batch, this is the empty grouped batch message. Needed for sequence numbers for this batch\n\t\t\t\t},\n\t\t\t\tlength: groupedMessages.length, // Will be 0 for an empty batch\n\t\t\t};\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, logLegacyCase);\n\n\t\treturn this.getResultBasedOnBatchMetadata(\n\t\t\tmessage as InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t\t);\n\t}\n\n\t/**\n\t * Now that the message has been \"unwrapped\" as to any virtualization (grouping, compression, chunking),\n\t * inspect the batch metadata flag and determine what kind of result to return.\n\t */\n\tprivate getResultBasedOnBatchMetadata(\n\t\tmessage: InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t): InboundMessageResult {\n\t\tconst batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;\n\t\tif (!this.batchInProgress) {\n\t\t\t// We are waiting for a new batch\n\t\t\tassert(batchMetadataFlag !== false, 0x9d5 /* 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.batchInProgress = true;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"batchStartingMessage\",\n\t\t\t\t\tbatchStart: {\n\t\t\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t\t\t\tclientId: message.clientId,\n\t\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t\t\tkeyMessage: message,\n\t\t\t\t\t},\n\t\t\t\t\tnextMessage: message,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Single-message batch (Since metadata flag is undefined)\n\t\t\treturn {\n\t\t\t\ttype: \"fullBatch\",\n\t\t\t\tmessages: [message],\n\t\t\t\tbatchStart: {\n\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t\tclientId: message.clientId,\n\t\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t\t\tkeyMessage: message,\n\t\t\t\t},\n\t\t\t\tlength: 1,\n\t\t\t};\n\t\t}\n\t\tassert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);\n\n\t\t// Clear batchInProgress state if the batch is ending\n\t\tif (batchMetadataFlag === false) {\n\t\t\tthis.batchInProgress = false;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"nextBatchMessage\",\n\t\t\tnextMessage: message,\n\t\t\tbatchEnd: batchMetadataFlag === false,\n\t\t};\n\t}\n}\n\n/**\n * Takes an incoming message and if the contents is a string, JSON.parse's it in place\n * @param mutableMessage - op message received\n * @param hasModernRuntimeMessageEnvelope - false if the message does not contain the modern op envelop where message.type is MessageType.Operation\n * @param logLegacyCase - callback to log when legacy op is encountered\n */\nexport function ensureContentsDeserialized(\n\tmutableMessage: ISequencedDocumentMessage,\n\thasModernRuntimeMessageEnvelope: boolean,\n\tlogLegacyCase: (codePath: string) => void,\n): void {\n\t// This should become unconditional once (Loader LTS) DeltaManager.processInboundMessage() stops parsing content (ADO #12052)\n\t// Note: Until that change is made in the loader, this case will never be hit.\n\t// Then there will be a long time of needing both cases, until LTS catches up to the change.\n\tlet didParseJsonContents: boolean;\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t\tdidParseJsonContents = true;\n\t} else {\n\t\tdidParseJsonContents = false;\n\t}\n\n\t// The DeltaManager parses the contents of the message as JSON if it is a string,\n\t// so we should never end up parsing it here.\n\t// Let's observe if we are wrong about this to learn about these cases.\n\tif (didParseJsonContents) {\n\t\tlogLegacyCase(\"ensureContentsDeserialized_foundJsonContents\");\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(\n\tmessage: ISequencedDocumentMessage,\n\tlogLegacyCase: (codePath: string) => void = () => {},\n): 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\tlogLegacyCase(\"unpackRuntimeMessage_contentsWithAddress\");\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.2.0";
8
+ export declare const pkgVersion = "2.3.0";
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.2.0";
8
+ export const pkgVersion = "2.3.0";
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,OAAO,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.2.0\";\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,OAAO,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.3.0\";\n"]}
@@ -5,7 +5,7 @@
5
5
  import { IDisposable } from "@fluidframework/core-interfaces";
6
6
  import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
7
7
  import { type InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
8
- import { BatchId, BatchMessage, InboundBatch } from "./opLifecycle/index.js";
8
+ import { BatchId, BatchMessage, InboundMessageResult } from "./opLifecycle/index.js";
9
9
  /**
10
10
  * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the
11
11
  * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.
@@ -21,11 +21,14 @@ export interface IPendingMessage {
21
21
  sequenceNumber?: number;
22
22
  /** Info about the batch this pending message belongs to, for validation and for computing the batchId on reconnect */
23
23
  batchInfo: {
24
- /** The Batch's original clientId, from when it was first flushed to be submitted */
24
+ /**
25
+ * The Batch's original clientId, from when it was first flushed to be submitted.
26
+ * Or, a random uuid if it was never submitted (and batchStartCsn will be -1)
27
+ */
25
28
  clientId: string;
26
29
  /**
27
30
  * The Batch's original clientSequenceNumber, from when it was first flushed to be submitted
28
- * @remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)
31
+ * Or, -1 if it was never submitted (and clientId will be a random uuid)
29
32
  */
30
33
  batchStartCsn: number;
31
34
  /** length of the batch (how many runtime messages here) */
@@ -68,8 +71,6 @@ export declare class PendingStateManager implements IDisposable {
68
71
  * Sequenced local ops that are saved when stashing since pending ops may depend on them
69
72
  */
70
73
  private savedOps;
71
- /** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
72
- private negativeCounter;
73
74
  private readonly disposeOnce;
74
75
  /** Used to ensure we don't replay ops on the same connection twice */
75
76
  private clientIdFromLastReplay;
@@ -107,26 +108,35 @@ export declare class PendingStateManager implements IDisposable {
107
108
  */
108
109
  applyStashedOpsAt(seqNum?: number): Promise<void>;
109
110
  /**
110
- * Processes an inbound batch of messages - May be local or remote.
111
+ * Compares the batch ID of the incoming batch with the pending batch ID for this client.
112
+ * They should not match, as that would indicate a forked container.
113
+ * @param remoteBatchStart - BatchStartInfo for an incoming batch *NOT* submitted by this client
114
+ * @returns whether the batch IDs match
115
+ */
116
+ private remoteBatchMatchesPendingBatch;
117
+ /**
118
+ * Processes an inbound message or batch of messages - May be local or remote.
111
119
  *
112
- * @param batch - The inbound batch of messages to process. Could be local or remote.
113
- * @param local - true if we submitted this batch and expect corresponding pending messages
114
- * @returns The inbound batch's messages with localOpMetadata "zipped" in.
120
+ * @param inbound - The inbound message(s) to process, with extra info (e.g. about the start of a batch). Could be local or remote.
121
+ * @param local - true if we submitted these messages and expect corresponding pending messages
122
+ * @returns The inbound messages with localOpMetadata "zipped" in.
115
123
  *
116
- * @remarks Closes the container if:
117
- * - The batchStartCsn doesn't match for local batches
124
+ * @throws a DataProcessingError in either of these cases:
125
+ * - The pending message content doesn't match the incoming message content for any message here
126
+ * - The batch IDs *do match* but it's not local (indicates Container forking).
118
127
  */
119
- processInboundBatch(batch: InboundBatch, local: boolean): {
128
+ processInboundMessages(inbound: InboundMessageResult, local: boolean): {
120
129
  message: InboundSequencedContainerRuntimeMessage;
121
130
  localOpMetadata?: unknown;
122
131
  }[];
123
132
  /**
124
- * Processes the incoming batch from the server that was submitted by this client.
125
- * It verifies that messages are received in the right order and that the batch information is correct.
126
- * @param batch - The inbound batch (originating from this client) to correlate with the pending local state
127
- * @returns The inbound batch's messages with localOpMetadata "zipped" in.
133
+ * Processes the incoming message(s) from the server that were submitted by this client.
134
+ * It verifies that messages are received in the right order and that any batch information is correct.
135
+ * @param inbound - The inbound message(s) (originating from this client) to correlate with the pending local state
136
+ * @throws DataProcessingError if the pending message content doesn't match the incoming message content for any message here
137
+ * @returns The inbound messages with localOpMetadata "zipped" in.
128
138
  */
129
- private processPendingLocalBatch;
139
+ private processPendingLocalMessages;
130
140
  /**
131
141
  * Processes the pending local copy of message that's been ack'd by the server.
132
142
  * @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.
@@ -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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"}
@@ -7,7 +7,7 @@ import { DataProcessingError, LoggingError, extractSafePropertiesFromMessage, }
7
7
  import Deque from "double-ended-queue";
8
8
  import { v4 as uuid } from "uuid";
9
9
  import { asBatchMetadata, asEmptyBatchLocalOpMetadata } from "./metadata.js";
10
- import { generateBatchId } from "./opLifecycle/index.js";
10
+ import { getEffectiveBatchId, } from "./opLifecycle/index.js";
11
11
  function isEmptyBatchPendingMessage(message) {
12
12
  const content = JSON.parse(message.content);
13
13
  return content.type === "groupedBatch" && content.contents?.length === 0;
@@ -41,17 +41,6 @@ function withoutLocalOpMetadata(message) {
41
41
  localOpMetadata: undefined,
42
42
  };
43
43
  }
44
- /**
45
- * Get the effective batch ID for a pending message.
46
- * If the batch ID is already present in the message's op metadata, return it.
47
- * Otherwise, generate a new batch ID using the client ID and batch start CSN.
48
- * @param pendingMessage - The pending message
49
- * @returns The effective batch ID
50
- */
51
- function getEffectiveBatchId(pendingMessage) {
52
- return (asBatchMetadata(pendingMessage.opMetadata)?.batchId ??
53
- generateBatchId(pendingMessage.batchInfo.clientId, pendingMessage.batchInfo.batchStartCsn));
54
- }
55
44
  /**
56
45
  * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been
57
46
  * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed
@@ -117,8 +106,6 @@ export class PendingStateManager {
117
106
  * Sequenced local ops that are saved when stashing since pending ops may depend on them
118
107
  */
119
108
  this.savedOps = [];
120
- /** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
121
- this.negativeCounter = -1;
122
109
  this.disposeOnce = new Lazy(() => {
123
110
  this.initialMessages.clear();
124
111
  this.pendingMessages.clear();
@@ -139,14 +126,16 @@ export class PendingStateManager {
139
126
  * or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)
140
127
  */
141
128
  onFlushBatch(batch, clientSequenceNumber) {
142
- // If we're connected this is the client of the current connection,
143
- // otherwise it's the clientId that just disconnected
144
- // It's only undefined if we've NEVER connected. This is a tight corner case and we can
145
- // simply make up a unique ID in this case.
146
- const clientId = this.stateHandler.clientId() ?? uuid();
147
- // If the batch was not yet sent, we need to assign a unique batchStartCsn
148
- // Use a negative number to distinguish these from real CSNs
149
- const batchStartCsn = clientSequenceNumber ?? this.negativeCounter--;
129
+ // clientId and batchStartCsn are used for generating the batchId so we can detect container forks
130
+ // where this batch was submitted by two different clients rehydrating from the same local state.
131
+ // In the typical case where the batch was actually sent, use the clientId and clientSequenceNumber.
132
+ // In the case where the batch was not sent, use a random uuid for clientId, and -1 for clientSequenceNumber to indicate this case.
133
+ // This will guarantee uniqueness of the batchId, and is a suitable fallback since clientId/CSN is only needed if the batch was actually sent/sequenced.
134
+ const batchWasSent = clientSequenceNumber !== undefined;
135
+ const [clientId, batchStartCsn] = batchWasSent
136
+ ? [this.stateHandler.clientId(), clientSequenceNumber]
137
+ : [uuid(), -1]; // -1 will indicate not a real clientId/CSN pair
138
+ assert(clientId !== undefined, 0xa33 /* clientId (from stateHandler) could only be undefined if we've never connected, but we have a CSN so we know that's not the case */);
150
139
  for (const message of batch) {
151
140
  const { contents: content = "", referenceSequenceNumber, localOpMetadata, metadata: opMetadata, } = message;
152
141
  const pendingMessage = {
@@ -209,38 +198,66 @@ export class PendingStateManager {
209
198
  }
210
199
  }
211
200
  /**
212
- * Processes an inbound batch of messages - May be local or remote.
201
+ * Compares the batch ID of the incoming batch with the pending batch ID for this client.
202
+ * They should not match, as that would indicate a forked container.
203
+ * @param remoteBatchStart - BatchStartInfo for an incoming batch *NOT* submitted by this client
204
+ * @returns whether the batch IDs match
205
+ */
206
+ remoteBatchMatchesPendingBatch(remoteBatchStart) {
207
+ // We may have no pending changes - if so, no match, no problem.
208
+ const pendingMessage = this.pendingMessages.peekFront();
209
+ if (pendingMessage === undefined) {
210
+ return false;
211
+ }
212
+ // We must compare the effective batch IDs, since one of these ops
213
+ // may have been the original, not resubmitted, so wouldn't have its batch ID stamped yet.
214
+ const pendingBatchId = getEffectiveBatchId(pendingMessage);
215
+ const inboundBatchId = getEffectiveBatchId(remoteBatchStart);
216
+ return pendingBatchId === inboundBatchId;
217
+ }
218
+ /**
219
+ * Processes an inbound message or batch of messages - May be local or remote.
213
220
  *
214
- * @param batch - The inbound batch of messages to process. Could be local or remote.
215
- * @param local - true if we submitted this batch and expect corresponding pending messages
216
- * @returns The inbound batch's messages with localOpMetadata "zipped" in.
221
+ * @param inbound - The inbound message(s) to process, with extra info (e.g. about the start of a batch). Could be local or remote.
222
+ * @param local - true if we submitted these messages and expect corresponding pending messages
223
+ * @returns The inbound messages with localOpMetadata "zipped" in.
217
224
  *
218
- * @remarks Closes the container if:
219
- * - The batchStartCsn doesn't match for local batches
225
+ * @throws a DataProcessingError in either of these cases:
226
+ * - The pending message content doesn't match the incoming message content for any message here
227
+ * - The batch IDs *do match* but it's not local (indicates Container forking).
220
228
  */
221
- processInboundBatch(batch, local) {
229
+ processInboundMessages(inbound, local) {
222
230
  if (local) {
223
- return this.processPendingLocalBatch(batch);
231
+ return this.processPendingLocalMessages(inbound);
232
+ }
233
+ // An inbound remote batch should not match the pending batch ID for this client.
234
+ // That would indicate the container forked (two instances trying to submit the same local state)
235
+ if ("batchStart" in inbound && this.remoteBatchMatchesPendingBatch(inbound.batchStart)) {
236
+ throw DataProcessingError.create("Forked Container Error! Matching batchIds but mismatched clientId", "PendingStateManager.processInboundMessages", inbound.batchStart.keyMessage);
224
237
  }
225
238
  // No localOpMetadata for remote messages
226
- return batch.messages.map((message) => ({ message }));
239
+ const messages = inbound.type === "fullBatch" ? inbound.messages : [inbound.nextMessage];
240
+ return messages.map((message) => ({ message }));
227
241
  }
228
242
  /**
229
- * Processes the incoming batch from the server that was submitted by this client.
230
- * It verifies that messages are received in the right order and that the batch information is correct.
231
- * @param batch - The inbound batch (originating from this client) to correlate with the pending local state
232
- * @returns The inbound batch's messages with localOpMetadata "zipped" in.
243
+ * Processes the incoming message(s) from the server that were submitted by this client.
244
+ * It verifies that messages are received in the right order and that any batch information is correct.
245
+ * @param inbound - The inbound message(s) (originating from this client) to correlate with the pending local state
246
+ * @throws DataProcessingError if the pending message content doesn't match the incoming message content for any message here
247
+ * @returns The inbound messages with localOpMetadata "zipped" in.
233
248
  */
234
- processPendingLocalBatch(batch) {
235
- this.onLocalBatchBegin(batch);
249
+ processPendingLocalMessages(inbound) {
250
+ if ("batchStart" in inbound) {
251
+ this.onLocalBatchBegin(inbound.batchStart, inbound.length);
252
+ }
236
253
  // Empty batch
237
- if (batch.messages.length === 0) {
238
- assert(batch.emptyBatchSequenceNumber !== undefined, 0x9fb /* Expected sequence number for empty batch */);
239
- const localOpMetadata = this.processNextPendingMessage(batch.emptyBatchSequenceNumber);
254
+ if (inbound.length === 0) {
255
+ const localOpMetadata = this.processNextPendingMessage(inbound.batchStart.keyMessage.sequenceNumber);
240
256
  assert(asEmptyBatchLocalOpMetadata(localOpMetadata)?.emptyBatch === true, 0xa20 /* Expected empty batch marker */);
241
257
  return [];
242
258
  }
243
- return batch.messages.map((message) => ({
259
+ const messages = inbound.type === "fullBatch" ? inbound.messages : [inbound.nextMessage];
260
+ return messages.map((message) => ({
244
261
  message,
245
262
  localOpMetadata: this.processNextPendingMessage(message.sequenceNumber, message),
246
263
  }));
@@ -287,30 +304,42 @@ export class PendingStateManager {
287
304
  /**
288
305
  * Check if the incoming batch matches the batch info for the next pending message.
289
306
  */
290
- onLocalBatchBegin(batch) {
307
+ onLocalBatchBegin(batchStart, batchLength) {
291
308
  // Get the next message from the pending queue. Verify a message exists.
292
309
  const pendingMessage = this.pendingMessages.peekFront();
293
310
  assert(pendingMessage !== undefined, 0xa21 /* No pending message found as we start processing this remote batch */);
294
- // Note: This could be undefined if this batch became empty on resubmit.
295
- // In this case the next pending message is an empty batch marker.
296
- // Empty batches became empty on Resubmit, and submit them and track them in case
297
- // a different fork of this container also submitted the same batch (and it may not be empty for that fork).
298
- const firstMessage = batch.messages.length > 0 ? batch.messages[0] : undefined;
299
- const expectedPendingBatchLength = batch.messages.length === 0 ? 1 : batch.messages.length;
311
+ // If this batch became empty on resubmit, batch.messages will be empty (so firstMessage undefined)
312
+ // and the next pending message should be an empty batch marker.
313
+ // More Info: We must submit empty batches and track them in case a different fork
314
+ // of this container also submitted the same batch (and it may not be empty for that fork).
315
+ const firstMessage = batchStart.keyMessage;
316
+ // -1 length is for back compat, undefined length means we actually don't know it
317
+ const skipLengthCheck = pendingMessage.batchInfo.length === -1 || batchLength === undefined;
318
+ const expectedPendingBatchLength = batchLength === 0
319
+ ? 1 // For an empty batch, expect a singleton array with the empty batch marker
320
+ : batchLength; // Otherwise, the lengths should actually match
321
+ // Note: We don't need to use getEffectiveBatchId here, just check the explicit stamped batchID
322
+ // That logic is needed only when comparing across potential container forks.
323
+ // Furthermore, we also are comparing the batch IDs constituent data - clientId (it's local) and batchStartCsn.
324
+ const pendingBatchId = asBatchMetadata(pendingMessage.opMetadata)?.batchId;
325
+ const inboundBatchId = batchStart.batchId;
300
326
  // We expect the incoming batch to be of the same length, starting at the same clientSequenceNumber,
301
- // as the batch we originally submitted.
327
+ // as the batch we originally submitted. The batchIds should match as well, if set (or neither should be set)
302
328
  // We have another later check to compare the message contents, which we'd expect to fail if this check does,
303
- // so we don't throw here, merely log. In a later release this check may replace that one.
304
- if (pendingMessage.batchInfo.batchStartCsn !== batch.batchStartCsn ||
305
- (pendingMessage.batchInfo.length >= 0 && // -1 length is back compat and isn't suitable for this check
306
- pendingMessage.batchInfo.length !== expectedPendingBatchLength)) {
329
+ // so we don't throw here, merely log. In a later release this check may replace that one since it's cheaper.
330
+ if (pendingMessage.batchInfo.batchStartCsn !== batchStart.batchStartCsn ||
331
+ (!skipLengthCheck && pendingMessage.batchInfo.length !== expectedPendingBatchLength) ||
332
+ pendingBatchId !== inboundBatchId) {
307
333
  this.logger?.sendErrorEvent({
308
334
  eventName: "BatchInfoMismatch",
309
335
  details: {
310
336
  pendingBatchCsn: pendingMessage.batchInfo.batchStartCsn,
311
- batchStartCsn: batch.batchStartCsn,
337
+ batchStartCsn: batchStart.batchStartCsn,
312
338
  pendingBatchLength: pendingMessage.batchInfo.length,
313
- batchLength: batch.messages.length,
339
+ expectedPendingBatchLength,
340
+ batchLength,
341
+ pendingBatchId,
342
+ inboundBatchId,
314
343
  pendingMessageBatchMetadata: asBatchMetadata(pendingMessage.opMetadata)?.batch,
315
344
  messageBatchMetadata: asBatchMetadata(firstMessage?.metadata)?.batch,
316
345
  },
@@ -342,8 +371,8 @@ export class PendingStateManager {
342
371
  assert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
343
372
  // The next message starts a batch (possibly single-message), and we'll need its batchId.
344
373
  const batchId = getEffectiveBatchId(pendingMessage);
345
- // Resubmit no messages, with the batchId. Will result in another empty batch marker.
346
374
  if (asEmptyBatchLocalOpMetadata(pendingMessage.localOpMetadata)?.emptyBatch === true) {
375
+ // Resubmit no messages, with the batchId. Will result in another empty batch marker.
347
376
  this.stateHandler.reSubmitBatch([], batchId);
348
377
  continue;
349
378
  }