@fluidframework/container-runtime 2.0.0-internal.2.2.1 → 2.0.0-internal.2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/.eslintrc.js +19 -8
  2. package/dist/batchTracker.d.ts +1 -2
  3. package/dist/batchTracker.d.ts.map +1 -1
  4. package/dist/batchTracker.js.map +1 -1
  5. package/dist/blobManager.d.ts +45 -34
  6. package/dist/blobManager.d.ts.map +1 -1
  7. package/dist/blobManager.js +135 -102
  8. package/dist/blobManager.js.map +1 -1
  9. package/dist/containerRuntime.d.ts +54 -8
  10. package/dist/containerRuntime.d.ts.map +1 -1
  11. package/dist/containerRuntime.js +143 -72
  12. package/dist/containerRuntime.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +1 -1
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +6 -8
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +12 -9
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +41 -35
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/garbageCollection.d.ts +41 -20
  22. package/dist/garbageCollection.d.ts.map +1 -1
  23. package/dist/garbageCollection.js +205 -150
  24. package/dist/garbageCollection.js.map +1 -1
  25. package/dist/garbageCollectionConstants.d.ts +7 -3
  26. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  27. package/dist/garbageCollectionConstants.js +10 -8
  28. package/dist/garbageCollectionConstants.js.map +1 -1
  29. package/dist/garbageCollectionTombstoneUtils.d.ts +14 -0
  30. package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -0
  31. package/dist/garbageCollectionTombstoneUtils.js +23 -0
  32. package/dist/garbageCollectionTombstoneUtils.js.map +1 -0
  33. package/dist/index.d.ts +1 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +3 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/opLifecycle/batchManager.d.ts +13 -1
  38. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  39. package/dist/opLifecycle/batchManager.js +35 -1
  40. package/dist/opLifecycle/batchManager.js.map +1 -1
  41. package/dist/opLifecycle/definitions.d.ts +25 -1
  42. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  43. package/dist/opLifecycle/definitions.js.map +1 -1
  44. package/dist/opLifecycle/index.d.ts +2 -2
  45. package/dist/opLifecycle/index.d.ts.map +1 -1
  46. package/dist/opLifecycle/index.js +2 -1
  47. package/dist/opLifecycle/index.js.map +1 -1
  48. package/dist/opLifecycle/opCompressor.d.ts +1 -1
  49. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  50. package/dist/opLifecycle/opCompressor.js +24 -10
  51. package/dist/opLifecycle/opCompressor.js.map +1 -1
  52. package/dist/opLifecycle/opDecompressor.d.ts +2 -1
  53. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  54. package/dist/opLifecycle/opDecompressor.js +30 -17
  55. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  56. package/dist/opLifecycle/opSplitter.d.ts +34 -2
  57. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  58. package/dist/opLifecycle/opSplitter.js +114 -5
  59. package/dist/opLifecycle/opSplitter.js.map +1 -1
  60. package/dist/opLifecycle/outbox.d.ts +5 -0
  61. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  62. package/dist/opLifecycle/outbox.js +24 -14
  63. package/dist/opLifecycle/outbox.js.map +1 -1
  64. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  65. package/dist/opLifecycle/remoteMessageProcessor.js +17 -2
  66. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  67. package/dist/packageVersion.d.ts +1 -1
  68. package/dist/packageVersion.js +1 -1
  69. package/dist/packageVersion.js.map +1 -1
  70. package/dist/runningSummarizer.d.ts.map +1 -1
  71. package/dist/runningSummarizer.js +0 -1
  72. package/dist/runningSummarizer.js.map +1 -1
  73. package/dist/scheduleManager.d.ts +0 -1
  74. package/dist/scheduleManager.d.ts.map +1 -1
  75. package/dist/scheduleManager.js +9 -20
  76. package/dist/scheduleManager.js.map +1 -1
  77. package/dist/summarizer.d.ts +0 -1
  78. package/dist/summarizer.d.ts.map +1 -1
  79. package/dist/summarizer.js +2 -1
  80. package/dist/summarizer.js.map +1 -1
  81. package/dist/summarizerTypes.d.ts +1 -0
  82. package/dist/summarizerTypes.d.ts.map +1 -1
  83. package/dist/summarizerTypes.js.map +1 -1
  84. package/dist/summaryFormat.d.ts.map +1 -1
  85. package/dist/summaryFormat.js +1 -2
  86. package/dist/summaryFormat.js.map +1 -1
  87. package/lib/batchTracker.d.ts +1 -2
  88. package/lib/batchTracker.d.ts.map +1 -1
  89. package/lib/batchTracker.js.map +1 -1
  90. package/lib/blobManager.d.ts +45 -34
  91. package/lib/blobManager.d.ts.map +1 -1
  92. package/lib/blobManager.js +137 -104
  93. package/lib/blobManager.js.map +1 -1
  94. package/lib/containerRuntime.d.ts +54 -8
  95. package/lib/containerRuntime.d.ts.map +1 -1
  96. package/lib/containerRuntime.js +140 -69
  97. package/lib/containerRuntime.js.map +1 -1
  98. package/lib/dataStoreContext.d.ts +1 -1
  99. package/lib/dataStoreContext.d.ts.map +1 -1
  100. package/lib/dataStoreContext.js +7 -9
  101. package/lib/dataStoreContext.js.map +1 -1
  102. package/lib/dataStores.d.ts +12 -9
  103. package/lib/dataStores.d.ts.map +1 -1
  104. package/lib/dataStores.js +44 -38
  105. package/lib/dataStores.js.map +1 -1
  106. package/lib/garbageCollection.d.ts +41 -20
  107. package/lib/garbageCollection.d.ts.map +1 -1
  108. package/lib/garbageCollection.js +201 -146
  109. package/lib/garbageCollection.js.map +1 -1
  110. package/lib/garbageCollectionConstants.d.ts +7 -3
  111. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  112. package/lib/garbageCollectionConstants.js +9 -7
  113. package/lib/garbageCollectionConstants.js.map +1 -1
  114. package/lib/garbageCollectionTombstoneUtils.d.ts +14 -0
  115. package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -0
  116. package/lib/garbageCollectionTombstoneUtils.js +19 -0
  117. package/lib/garbageCollectionTombstoneUtils.js.map +1 -0
  118. package/lib/index.d.ts +1 -2
  119. package/lib/index.d.ts.map +1 -1
  120. package/lib/index.js +1 -2
  121. package/lib/index.js.map +1 -1
  122. package/lib/opLifecycle/batchManager.d.ts +13 -1
  123. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  124. package/lib/opLifecycle/batchManager.js +35 -1
  125. package/lib/opLifecycle/batchManager.js.map +1 -1
  126. package/lib/opLifecycle/definitions.d.ts +25 -1
  127. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  128. package/lib/opLifecycle/definitions.js.map +1 -1
  129. package/lib/opLifecycle/index.d.ts +2 -2
  130. package/lib/opLifecycle/index.d.ts.map +1 -1
  131. package/lib/opLifecycle/index.js +1 -1
  132. package/lib/opLifecycle/index.js.map +1 -1
  133. package/lib/opLifecycle/opCompressor.d.ts +1 -1
  134. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  135. package/lib/opLifecycle/opCompressor.js +24 -10
  136. package/lib/opLifecycle/opCompressor.js.map +1 -1
  137. package/lib/opLifecycle/opDecompressor.d.ts +2 -1
  138. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  139. package/lib/opLifecycle/opDecompressor.js +30 -17
  140. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  141. package/lib/opLifecycle/opSplitter.d.ts +34 -2
  142. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  143. package/lib/opLifecycle/opSplitter.js +112 -4
  144. package/lib/opLifecycle/opSplitter.js.map +1 -1
  145. package/lib/opLifecycle/outbox.d.ts +5 -0
  146. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  147. package/lib/opLifecycle/outbox.js +24 -14
  148. package/lib/opLifecycle/outbox.js.map +1 -1
  149. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  150. package/lib/opLifecycle/remoteMessageProcessor.js +17 -2
  151. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  152. package/lib/packageVersion.d.ts +1 -1
  153. package/lib/packageVersion.js +1 -1
  154. package/lib/packageVersion.js.map +1 -1
  155. package/lib/runningSummarizer.d.ts.map +1 -1
  156. package/lib/runningSummarizer.js +0 -1
  157. package/lib/runningSummarizer.js.map +1 -1
  158. package/lib/scheduleManager.d.ts +0 -1
  159. package/lib/scheduleManager.d.ts.map +1 -1
  160. package/lib/scheduleManager.js +9 -20
  161. package/lib/scheduleManager.js.map +1 -1
  162. package/lib/summarizer.d.ts +0 -1
  163. package/lib/summarizer.d.ts.map +1 -1
  164. package/lib/summarizer.js +2 -1
  165. package/lib/summarizer.js.map +1 -1
  166. package/lib/summarizerTypes.d.ts +1 -0
  167. package/lib/summarizerTypes.d.ts.map +1 -1
  168. package/lib/summarizerTypes.js.map +1 -1
  169. package/lib/summaryFormat.d.ts.map +1 -1
  170. package/lib/summaryFormat.js +1 -2
  171. package/lib/summaryFormat.js.map +1 -1
  172. package/package.json +20 -19
  173. package/src/batchTracker.ts +1 -1
  174. package/src/blobManager.ts +159 -111
  175. package/src/containerRuntime.ts +202 -73
  176. package/src/dataStoreContext.ts +15 -16
  177. package/src/dataStores.ts +61 -45
  178. package/src/garbageCollection.ts +258 -183
  179. package/src/garbageCollectionConstants.ts +10 -7
  180. package/src/garbageCollectionTombstoneUtils.ts +28 -0
  181. package/src/index.ts +2 -5
  182. package/src/opLifecycle/batchManager.ts +59 -1
  183. package/src/opLifecycle/definitions.ts +27 -1
  184. package/src/opLifecycle/index.ts +2 -1
  185. package/src/opLifecycle/opCompressor.ts +29 -12
  186. package/src/opLifecycle/opDecompressor.ts +39 -18
  187. package/src/opLifecycle/opSplitter.ts +141 -7
  188. package/src/opLifecycle/outbox.ts +32 -16
  189. package/src/opLifecycle/remoteMessageProcessor.ts +19 -3
  190. package/src/packageVersion.ts +1 -1
  191. package/src/runningSummarizer.ts +0 -1
  192. package/src/scheduleManager.ts +19 -30
  193. package/src/summarizer.ts +1 -1
  194. package/src/summarizerTypes.ts +1 -0
  195. package/src/summaryFormat.ts +1 -2
@@ -22,13 +22,9 @@ class OpDecompressor {
22
22
  this.processedCount = 0;
23
23
  }
24
24
  processMessage(message) {
25
- var _a, _b, _c, _d, _e, _f;
26
- // We're checking for compression = true or top level compression property so
27
- // that we can enable compression without waiting on all ordering services
28
- // to pick up protocol change. Eventually only the top level property should
29
- // be used.
30
- if (((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.batch) === true
31
- && (((_b = message.metadata) === null || _b === void 0 ? void 0 : _b.compressed) || message.compression !== undefined)) {
25
+ var _a, _b, _c, _d;
26
+ (0, common_utils_1.assert)(message.compression === undefined || message.compression === containerRuntime_1.CompressionAlgorithms.lz4, 0x511 /* Only lz4 compression is supported */);
27
+ if (((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.batch) === true && message.compression === containerRuntime_1.CompressionAlgorithms.lz4) {
32
28
  // Beginning of a compressed batch
33
29
  (0, common_utils_1.assert)(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);
34
30
  if (message.compression) {
@@ -41,32 +37,49 @@ class OpDecompressor {
41
37
  const intoString = (0, common_utils_1.Uint8ArrayToString)(decompressedMessage);
42
38
  const asObj = JSON.parse(intoString);
43
39
  this.rootMessageContents = asObj;
44
- return Object.assign(Object.assign({}, message), { contents: this.rootMessageContents[this.processedCount++] });
40
+ return {
41
+ message: newMessage(message, this.rootMessageContents[this.processedCount++]),
42
+ state: "Accepted",
43
+ };
45
44
  }
46
- if (this.rootMessageContents !== undefined && ((_c = message.metadata) === null || _c === void 0 ? void 0 : _c.batch) === undefined && this.activeBatch) {
45
+ if (this.rootMessageContents !== undefined && ((_b = message.metadata) === null || _b === void 0 ? void 0 : _b.batch) === undefined && this.activeBatch) {
46
+ (0, common_utils_1.assert)(message.contents === undefined, 0x512 /* Expecting empty message */);
47
47
  // Continuation of compressed batch
48
- return Object.assign(Object.assign({}, message), { contents: this.rootMessageContents[this.processedCount++] });
48
+ return {
49
+ message: newMessage(message, this.rootMessageContents[this.processedCount++]),
50
+ state: "Accepted",
51
+ };
49
52
  }
50
- if (this.rootMessageContents !== undefined && ((_d = message.metadata) === null || _d === void 0 ? void 0 : _d.batch) === false) {
53
+ if (this.rootMessageContents !== undefined && ((_c = message.metadata) === null || _c === void 0 ? void 0 : _c.batch) === false) {
51
54
  // End of compressed batch
52
- const returnMessage = Object.assign(Object.assign({}, message), { contents: this.rootMessageContents[this.processedCount++] });
55
+ const returnMessage = newMessage(message, this.rootMessageContents[this.processedCount++]);
53
56
  this.activeBatch = false;
54
57
  this.rootMessageContents = undefined;
55
58
  this.processedCount = 0;
56
- return returnMessage;
59
+ return {
60
+ message: returnMessage,
61
+ state: "Processed",
62
+ };
57
63
  }
58
- if (((_e = message.metadata) === null || _e === void 0 ? void 0 : _e.batch) === undefined &&
59
- (((_f = message.metadata) === null || _f === void 0 ? void 0 : _f.compressed) || message.compression === containerRuntime_1.CompressionAlgorithms.lz4)) {
64
+ if (((_d = message.metadata) === null || _d === void 0 ? void 0 : _d.batch) === undefined && message.compression === containerRuntime_1.CompressionAlgorithms.lz4) {
60
65
  // Single compressed message
61
66
  (0, common_utils_1.assert)(this.activeBatch === false, 0x4ba /* shouldn't receive compressed message in middle of a batch */);
62
67
  const contents = common_utils_1.IsoBuffer.from(message.contents.packedContents, "base64");
63
68
  const decompressedMessage = (0, lz4js_1.decompress)(contents);
64
69
  const intoString = new TextDecoder().decode(decompressedMessage);
65
70
  const asObj = JSON.parse(intoString);
66
- return Object.assign(Object.assign({}, message), { contents: asObj[0] });
71
+ return {
72
+ message: newMessage(message, asObj[0]),
73
+ state: "Processed",
74
+ };
67
75
  }
68
- return message;
76
+ return {
77
+ message,
78
+ state: "Skipped",
79
+ };
69
80
  }
70
81
  }
71
82
  exports.OpDecompressor = OpDecompressor;
83
+ // We should not be mutating the input message nor its metadata
84
+ const newMessage = (originalMessage, contents) => (Object.assign(Object.assign({}, originalMessage), { contents, compression: undefined, metadata: Object.assign({}, originalMessage.metadata) }));
72
85
  //# sourceMappingURL=opDecompressor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"opDecompressor.js","sourceRoot":"","sources":["../../src/opLifecycle/opDecompressor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { decompress } from \"lz4js\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { assert, IsoBuffer, Uint8ArrayToString } from \"@fluidframework/common-utils\";\nimport { CompressionAlgorithms } from \"../containerRuntime\";\n\n/**\n * State machine that \"unrolls\" contents of compressed batches of ops after decompressing them.\n * This class relies on some implicit contracts defined below:\n * 1. A compressed batch will have its first message with batch metadata set to true and compressed set to true\n * 2. Messages in the middle of a compressed batch will have neither batch metadata nor the compression property set\n * 3. The final message of a batch will have batch metadata set to false\n * 4. An individually compressed op will have undefined batch metadata and compression set to true\n */\nexport class OpDecompressor {\n private activeBatch = false;\n private rootMessageContents: any | undefined;\n private processedCount = 0;\n\n public processMessage(message: ISequencedDocumentMessage): ISequencedDocumentMessage {\n // We're checking for compression = true or top level compression property so\n // that we can enable compression without waiting on all ordering services\n // to pick up protocol change. Eventually only the top level property should\n // be used.\n if (message.metadata?.batch === true\n && (message.metadata?.compressed || message.compression !== undefined)) {\n // Beginning of a compressed batch\n assert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);\n if (message.compression) {\n // lz4 is the only supported compression algorithm for now\n assert(message.compression === CompressionAlgorithms.lz4,\n 0x4b9 /* lz4 is currently the only supported compression algorithm */);\n }\n\n this.activeBatch = true;\n\n const contents = IsoBuffer.from(message.contents.packedContents, \"base64\");\n const decompressedMessage = decompress(contents);\n const intoString = Uint8ArrayToString(decompressedMessage);\n const asObj = JSON.parse(intoString);\n this.rootMessageContents = asObj;\n\n return { ...message, contents: this.rootMessageContents[this.processedCount++] };\n }\n\n if (this.rootMessageContents !== undefined && message.metadata?.batch === undefined && this.activeBatch) {\n // Continuation of compressed batch\n return { ...message, contents: this.rootMessageContents[this.processedCount++] };\n }\n\n if (this.rootMessageContents !== undefined && message.metadata?.batch === false) {\n // End of compressed batch\n const returnMessage = {\n ...message,\n contents: this.rootMessageContents[this.processedCount++]\n };\n\n this.activeBatch = false;\n this.rootMessageContents = undefined;\n this.processedCount = 0;\n\n return returnMessage;\n }\n\n if (message.metadata?.batch === undefined &&\n (message.metadata?.compressed || message.compression === CompressionAlgorithms.lz4)) {\n // Single compressed message\n assert(this.activeBatch === false, 0x4ba /* shouldn't receive compressed message in middle of a batch */);\n\n const contents = IsoBuffer.from(message.contents.packedContents, \"base64\");\n const decompressedMessage = decompress(contents);\n const intoString = new TextDecoder().decode(decompressedMessage);\n const asObj = JSON.parse(intoString);\n\n return { ...message, contents: asObj[0] };\n }\n\n return message;\n }\n}\n"]}
1
+ {"version":3,"file":"opDecompressor.js","sourceRoot":"","sources":["../../src/opLifecycle/opDecompressor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { decompress } from \"lz4js\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { assert, IsoBuffer, Uint8ArrayToString } from \"@fluidframework/common-utils\";\nimport { CompressionAlgorithms } from \"../containerRuntime\";\nimport { IMessageProcessingResult } from \"./definitions\";\n\n/**\n * State machine that \"unrolls\" contents of compressed batches of ops after decompressing them.\n * This class relies on some implicit contracts defined below:\n * 1. A compressed batch will have its first message with batch metadata set to true and compressed set to true\n * 2. Messages in the middle of a compressed batch will have neither batch metadata nor the compression property set\n * 3. The final message of a batch will have batch metadata set to false\n * 4. An individually compressed op will have undefined batch metadata and compression set to true\n */\nexport class OpDecompressor {\n private activeBatch = false;\n private rootMessageContents: any | undefined;\n private processedCount = 0;\n\n public processMessage(message: ISequencedDocumentMessage): IMessageProcessingResult {\n assert(\n message.compression === undefined || message.compression === CompressionAlgorithms.lz4,\n 0x511 /* Only lz4 compression is supported */);\n\n if (message.metadata?.batch === true && message.compression === CompressionAlgorithms.lz4) {\n // Beginning of a compressed batch\n assert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);\n if (message.compression) {\n // lz4 is the only supported compression algorithm for now\n assert(message.compression === CompressionAlgorithms.lz4,\n 0x4b9 /* lz4 is currently the only supported compression algorithm */);\n }\n\n this.activeBatch = true;\n\n const contents = IsoBuffer.from(message.contents.packedContents, \"base64\");\n const decompressedMessage = decompress(contents);\n const intoString = Uint8ArrayToString(decompressedMessage);\n const asObj = JSON.parse(intoString);\n this.rootMessageContents = asObj;\n\n return {\n message: newMessage(message, this.rootMessageContents[this.processedCount++]),\n state: \"Accepted\",\n };\n }\n\n if (this.rootMessageContents !== undefined && message.metadata?.batch === undefined && this.activeBatch) {\n assert(message.contents === undefined, 0x512 /* Expecting empty message */);\n\n // Continuation of compressed batch\n return {\n message: newMessage(message, this.rootMessageContents[this.processedCount++]),\n state: \"Accepted\",\n };\n }\n\n if (this.rootMessageContents !== undefined && message.metadata?.batch === false) {\n // End of compressed batch\n const returnMessage = newMessage(message, this.rootMessageContents[this.processedCount++]);\n\n this.activeBatch = false;\n this.rootMessageContents = undefined;\n this.processedCount = 0;\n\n return {\n message: returnMessage,\n state: \"Processed\",\n };\n }\n\n if (message.metadata?.batch === undefined && message.compression === CompressionAlgorithms.lz4) {\n // Single compressed message\n assert(this.activeBatch === false, 0x4ba /* shouldn't receive compressed message in middle of a batch */);\n\n const contents = IsoBuffer.from(message.contents.packedContents, \"base64\");\n const decompressedMessage = decompress(contents);\n const intoString = new TextDecoder().decode(decompressedMessage);\n const asObj = JSON.parse(intoString);\n\n return {\n message: newMessage(message, asObj[0]),\n state: \"Processed\",\n };\n }\n\n return {\n message,\n state: \"Skipped\",\n };\n }\n}\n\n// We should not be mutating the input message nor its metadata\nconst newMessage = (originalMessage: ISequencedDocumentMessage, contents: any): ISequencedDocumentMessage => ({\n ...originalMessage,\n contents,\n compression: undefined,\n metadata: { ...originalMessage.metadata },\n});\n"]}
@@ -2,16 +2,48 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { ITelemetryLogger } from "@fluidframework/common-definitions";
6
+ import { IBatchMessage } from "@fluidframework/container-definitions";
5
7
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
+ import { BatchMessage, IBatch, IChunkedOp, IMessageProcessingResult } from "./definitions";
6
9
  /**
7
10
  * Responsible for creating and reconstructing chunked messages.
8
11
  */
9
12
  export declare class OpSplitter {
13
+ private readonly submitBatchFn;
14
+ private readonly chunkSizeInBytes;
15
+ private readonly maxBatchSizeInBytes;
10
16
  private readonly chunkMap;
11
- constructor(chunks: [string, string[]][]);
17
+ private readonly logger;
18
+ constructor(chunks: [string, string[]][], submitBatchFn: ((batch: IBatchMessage[]) => number) | undefined, chunkSizeInBytes: number, maxBatchSizeInBytes: number, logger: ITelemetryLogger);
19
+ get isBatchChunkingEnabled(): boolean;
12
20
  get chunks(): ReadonlyMap<string, string[]>;
13
- processRemoteMessage(message: ISequencedDocumentMessage): ISequencedDocumentMessage;
21
+ processRemoteMessage(message: ISequencedDocumentMessage): IMessageProcessingResult;
14
22
  clearPartialChunks(clientId: string): void;
15
23
  private addChunk;
24
+ /**
25
+ * Splits the first op of a compressed batch in chunks, sends the chunks separately and
26
+ * returns a new batch composed of the last chunk and the rest of the ops in the original batch.
27
+ *
28
+ * A compressed batch is formed by one large op at the first position, followed by a series of placeholder ops
29
+ * which are used in order to reserve the sequence numbers for when the first op gets unrolled into the original
30
+ * uncompressed ops at ingestion in the runtime.
31
+ *
32
+ * If the first op is too large, it can be chunked (split into smaller op) which can be sent individually over the wire
33
+ * and accumulate at ingestion, until the last op in the chunk is processed, when the original op is unrolled.
34
+ *
35
+ * This method will send the first N - 1 chunks separately and use the last chunk as the first message in the result batch
36
+ * and then appends the original placeholder ops. This will ensure that the batch semantics of the original (non-compressed) batch
37
+ * are preserved, as the original chunked op will be unrolled by the runtime when the first message in the batch is processed
38
+ * (as it is the last chunk).
39
+ *
40
+ * To illustrate, if the input is `[largeOp, emptyOp, emptyOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.
41
+ * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4, emptyOp, emptyOp]` will be returned.
42
+ *
43
+ * @param batch - the compressed batch which needs to be processed
44
+ * @returns A new adjusted batch which can be sent over the wire
45
+ */
46
+ splitCompressedBatch(batch: IBatch): IBatch;
16
47
  }
48
+ export declare const splitOp: (op: BatchMessage, chunkSizeInBytes: number) => IChunkedOp[];
17
49
  //# sourceMappingURL=opSplitter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"opSplitter.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opSplitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAIjF;;GAEG;AACH,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;gBAErC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE;IAIxC,IAAW,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAEjD;IAEM,oBAAoB,CAAC,OAAO,EAAE,yBAAyB,GAAG,yBAAyB;IAyBnF,kBAAkB,CAAC,QAAQ,EAAE,MAAM;IAM1C,OAAO,CAAC,QAAQ;CAqBnB"}
1
+ {"version":3,"file":"opSplitter.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opSplitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAEtE,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAEtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAGjF,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAE3F;;GAEG;AACH,qBAAa,UAAU;IAOf,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAPxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAGpB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,EACX,aAAa,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,MAAM,CAAC,GAAG,SAAS,EAC/D,gBAAgB,EAAE,MAAM,EACxB,mBAAmB,EAAE,MAAM,EAC5C,MAAM,EAAE,gBAAgB;IAM5B,IAAW,sBAAsB,IAAI,OAAO,CAE3C;IAED,IAAW,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAEjD;IAEM,oBAAoB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB;IAoClF,kBAAkB,CAAC,QAAQ,EAAE,MAAM;IAM1C,OAAO,CAAC,QAAQ;IAsBhB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACI,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAuCrD;AAiBD,eAAO,MAAM,OAAO,OAAQ,YAAY,oBAAoB,MAAM,KAAG,UAAU,EA4B9E,CAAC"}
@@ -4,22 +4,34 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.OpSplitter = void 0;
7
+ exports.splitOp = exports.OpSplitter = void 0;
8
+ const common_utils_1 = require("@fluidframework/common-utils");
8
9
  const container_utils_1 = require("@fluidframework/container-utils");
10
+ const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
9
11
  const containerRuntime_1 = require("../containerRuntime");
10
12
  /**
11
13
  * Responsible for creating and reconstructing chunked messages.
12
14
  */
13
15
  class OpSplitter {
14
- constructor(chunks) {
16
+ constructor(chunks, submitBatchFn, chunkSizeInBytes, maxBatchSizeInBytes, logger) {
17
+ this.submitBatchFn = submitBatchFn;
18
+ this.chunkSizeInBytes = chunkSizeInBytes;
19
+ this.maxBatchSizeInBytes = maxBatchSizeInBytes;
15
20
  this.chunkMap = new Map(chunks);
21
+ this.logger = telemetry_utils_1.ChildLogger.create(logger, "OpSplitter");
22
+ }
23
+ get isBatchChunkingEnabled() {
24
+ return this.chunkSizeInBytes < Number.POSITIVE_INFINITY && this.submitBatchFn !== undefined;
16
25
  }
17
26
  get chunks() {
18
27
  return this.chunkMap;
19
28
  }
20
29
  processRemoteMessage(message) {
21
30
  if (message.type !== containerRuntime_1.ContainerMessageType.ChunkedOp) {
22
- return message;
31
+ return {
32
+ message,
33
+ state: "Skipped",
34
+ };
23
35
  }
24
36
  const clientId = message.clientId;
25
37
  const chunkedContent = message.contents;
@@ -27,7 +39,10 @@ class OpSplitter {
27
39
  if (chunkedContent.chunkId < chunkedContent.totalChunks) {
28
40
  // We are processing the op in chunks but haven't reached
29
41
  // the last chunk yet in order to reconstruct the original op
30
- return message;
42
+ return {
43
+ message,
44
+ state: "Accepted",
45
+ };
31
46
  }
32
47
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
33
48
  const serializedContent = this.chunkMap.get(clientId).join("");
@@ -35,7 +50,12 @@ class OpSplitter {
35
50
  const newMessage = Object.assign({}, message);
36
51
  newMessage.contents = serializedContent === "" ? undefined : JSON.parse(serializedContent);
37
52
  newMessage.type = chunkedContent.originalType;
38
- return newMessage;
53
+ newMessage.metadata = chunkedContent.originalMetadata;
54
+ newMessage.compression = chunkedContent.originalCompression;
55
+ return {
56
+ message: newMessage,
57
+ state: "Processed",
58
+ };
39
59
  }
40
60
  clearPartialChunks(clientId) {
41
61
  if (this.chunkMap.has(clientId)) {
@@ -56,6 +76,95 @@ class OpSplitter {
56
76
  }
57
77
  map.push(chunkedContent.contents);
58
78
  }
79
+ /**
80
+ * Splits the first op of a compressed batch in chunks, sends the chunks separately and
81
+ * returns a new batch composed of the last chunk and the rest of the ops in the original batch.
82
+ *
83
+ * A compressed batch is formed by one large op at the first position, followed by a series of placeholder ops
84
+ * which are used in order to reserve the sequence numbers for when the first op gets unrolled into the original
85
+ * uncompressed ops at ingestion in the runtime.
86
+ *
87
+ * If the first op is too large, it can be chunked (split into smaller op) which can be sent individually over the wire
88
+ * and accumulate at ingestion, until the last op in the chunk is processed, when the original op is unrolled.
89
+ *
90
+ * This method will send the first N - 1 chunks separately and use the last chunk as the first message in the result batch
91
+ * and then appends the original placeholder ops. This will ensure that the batch semantics of the original (non-compressed) batch
92
+ * are preserved, as the original chunked op will be unrolled by the runtime when the first message in the batch is processed
93
+ * (as it is the last chunk).
94
+ *
95
+ * To illustrate, if the input is `[largeOp, emptyOp, emptyOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.
96
+ * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4, emptyOp, emptyOp]` will be returned.
97
+ *
98
+ * @param batch - the compressed batch which needs to be processed
99
+ * @returns A new adjusted batch which can be sent over the wire
100
+ */
101
+ splitCompressedBatch(batch) {
102
+ var _a, _b, _c, _d, _e, _f;
103
+ (0, common_utils_1.assert)(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);
104
+ (0, common_utils_1.assert)(batch.contentSizeInBytes > 0 && batch.content.length > 0, 0x514 /* Batch needs to be non-empty */);
105
+ (0, common_utils_1.assert)(this.chunkSizeInBytes !== 0, 0x515 /* Chunk size needs to be non-zero */);
106
+ (0, common_utils_1.assert)(this.chunkSizeInBytes < this.maxBatchSizeInBytes, 0x516 /* Chunk size needs to be smaller than the max batch size */);
107
+ const firstMessage = batch.content[0]; // we expect this to be the large compressed op, which needs to be split
108
+ (0, common_utils_1.assert)(((_a = firstMessage.metadata) === null || _a === void 0 ? void 0 : _a.compressed) === true || firstMessage.compression !== undefined, 0x517 /* Batch needs to be compressed */);
109
+ (0, common_utils_1.assert)(((_c = (_b = firstMessage.contents) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) >= this.chunkSizeInBytes, 0x518 /* First message in the batch needs to be chunkable */);
110
+ const restOfMessages = batch.content.slice(1); // we expect these to be empty ops, created to reserve sequence numbers
111
+ const chunks = (0, exports.splitOp)(firstMessage, this.chunkSizeInBytes);
112
+ (0, common_utils_1.assert)(this.submitBatchFn !== undefined, 0x519 /* We don't support old loaders */);
113
+ // Send the first N-1 chunks immediately
114
+ for (const chunk of chunks.slice(0, -1)) {
115
+ this.submitBatchFn([chunkToBatchMessage(chunk, firstMessage.referenceSequenceNumber)]);
116
+ }
117
+ // The last chunk will be part of the new batch and needs to
118
+ // preserve the batch metadata of the original batch
119
+ const lastChunk = chunkToBatchMessage(chunks[chunks.length - 1], firstMessage.referenceSequenceNumber, { batch: (_d = firstMessage.metadata) === null || _d === void 0 ? void 0 : _d.batch });
120
+ this.logger.sendPerformanceEvent({
121
+ eventName: "Chunked compressed batch",
122
+ length: batch.content.length,
123
+ sizeInBytes: batch.contentSizeInBytes,
124
+ chunks: chunks.length,
125
+ chunkSizeInBytes: this.chunkSizeInBytes,
126
+ });
127
+ return {
128
+ content: [lastChunk, ...restOfMessages],
129
+ contentSizeInBytes: (_f = (_e = lastChunk.contents) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0,
130
+ };
131
+ }
59
132
  }
60
133
  exports.OpSplitter = OpSplitter;
134
+ const chunkToBatchMessage = (chunk, referenceSequenceNumber, metadata = undefined) => {
135
+ const payload = { type: containerRuntime_1.ContainerMessageType.ChunkedOp, contents: chunk };
136
+ return {
137
+ contents: JSON.stringify(payload),
138
+ deserializedContent: payload,
139
+ metadata,
140
+ localOpMetadata: undefined,
141
+ referenceSequenceNumber,
142
+ };
143
+ };
144
+ const splitOp = (op, chunkSizeInBytes) => {
145
+ const chunks = [];
146
+ (0, common_utils_1.assert)(op.contents !== undefined && op.contents !== null, 0x51a /* We should have something to chunk */);
147
+ const contentLength = op.contents.length;
148
+ const chunkN = Math.floor((contentLength - 1) / chunkSizeInBytes) + 1;
149
+ let offset = 0;
150
+ for (let i = 1; i <= chunkN; i++) {
151
+ const chunk = {
152
+ chunkId: i,
153
+ contents: op.contents.substr(offset, chunkSizeInBytes),
154
+ originalType: op.deserializedContent.type,
155
+ totalChunks: chunkN,
156
+ };
157
+ if (i === chunkN) {
158
+ // We don't need to port these to all the chunks,
159
+ // as we rebuild the original op when we process the
160
+ // last chunk, therefore it is the only one that needs it.
161
+ chunk.originalMetadata = op.metadata;
162
+ chunk.originalCompression = op.compression;
163
+ }
164
+ chunks.push(chunk);
165
+ offset += chunkSizeInBytes;
166
+ }
167
+ return chunks;
168
+ };
169
+ exports.splitOp = splitOp;
61
170
  //# sourceMappingURL=opSplitter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"opSplitter.js","sourceRoot":"","sources":["../../src/opLifecycle/opSplitter.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,qEAAwG;AAExG,0DAA2D;AAG3D;;GAEG;AACH,MAAa,UAAU;IAInB,YAAY,MAA4B;QACpC,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAmB,MAAM,CAAC,CAAC;IACtD,CAAC;IAED,IAAW,MAAM;QACb,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAEM,oBAAoB,CAAC,OAAkC;QAC1D,IAAI,OAAO,CAAC,IAAI,KAAK,uCAAoB,CAAC,SAAS,EAAE;YACjD,OAAO,OAAO,CAAC;SAClB;QAED,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,MAAM,cAAc,GAAG,OAAO,CAAC,QAAsB,CAAC;QACtD,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;QAEjD,IAAI,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE;YACrD,yDAAyD;YACzD,6DAA6D;YAC7D,OAAO,OAAO,CAAC;SAClB;QAED,oEAAoE;QACpE,MAAM,iBAAiB,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAElC,MAAM,UAAU,qBAAQ,OAAO,CAAE,CAAC;QAClC,UAAU,CAAC,QAAQ,GAAG,iBAAiB,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC3F,UAAU,CAAC,IAAI,GAAG,cAAc,CAAC,YAAY,CAAC;QAC9C,OAAO,UAAU,CAAC;IACtB,CAAC;IAEM,kBAAkB,CAAC,QAAgB;QACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;YAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;SAClC;IACL,CAAC;IAEO,QAAQ,CAAC,QAAgB,EAAE,cAA0B,EAAE,eAA0C;QACrG,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,GAAG,KAAK,SAAS,EAAE;YACnB,GAAG,GAAG,EAAE,CAAC;YACT,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;SACpC;QAED,IAAI,cAAc,CAAC,OAAO,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE;YAC3C,gGAAgG;YAChG,sGAAsG;YACtG,4DAA4D;YAC5D,MAAM,IAAI,qCAAmB,CAAC,mBAAmB,kCAC1C,IAAA,kDAAgC,EAAC,eAAe,CAAC,KACpD,cAAc,EAAE,GAAG,CAAC,MAAM,EAC1B,OAAO,EAAE,cAAc,CAAC,OAAO,EAC/B,WAAW,EAAE,cAAc,CAAC,WAAW,IACzC,CAAC;SACN;QAED,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;CACJ;AAhED,gCAgEC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { DataCorruptionError, extractSafePropertiesFromMessage } from \"@fluidframework/container-utils\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { ContainerMessageType } from \"../containerRuntime\";\nimport { IChunkedOp } from \"./definitions\";\n\n/**\n * Responsible for creating and reconstructing chunked messages.\n */\nexport class OpSplitter {\n // Local copy of incomplete received chunks.\n private readonly chunkMap: Map<string, string[]>;\n\n constructor(chunks: [string, string[]][]) {\n this.chunkMap = new Map<string, string[]>(chunks);\n }\n\n public get chunks(): ReadonlyMap<string, string[]> {\n return this.chunkMap;\n }\n\n public processRemoteMessage(message: ISequencedDocumentMessage): ISequencedDocumentMessage {\n if (message.type !== ContainerMessageType.ChunkedOp) {\n return message;\n }\n\n const clientId = message.clientId;\n const chunkedContent = message.contents as IChunkedOp;\n this.addChunk(clientId, chunkedContent, message);\n\n if (chunkedContent.chunkId < chunkedContent.totalChunks) {\n // We are processing the op in chunks but haven't reached\n // the last chunk yet in order to reconstruct the original op\n return message;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const serializedContent = this.chunkMap.get(clientId)!.join(\"\");\n this.clearPartialChunks(clientId);\n\n const newMessage = { ...message };\n newMessage.contents = serializedContent === \"\" ? undefined : JSON.parse(serializedContent);\n newMessage.type = chunkedContent.originalType;\n return newMessage;\n }\n\n public clearPartialChunks(clientId: string) {\n if (this.chunkMap.has(clientId)) {\n this.chunkMap.delete(clientId);\n }\n }\n\n private addChunk(clientId: string, chunkedContent: IChunkedOp, originalMessage: ISequencedDocumentMessage) {\n let map = this.chunkMap.get(clientId);\n if (map === undefined) {\n map = [];\n this.chunkMap.set(clientId, map);\n }\n\n if (chunkedContent.chunkId !== map.length + 1) {\n // We are expecting the chunks to be processed sequentially, in the same order as they are sent.\n // Therefore, the chunkId of the incoming op needs to match the length of the array (1-based indexing)\n // holding the existing chunks for that particular clientId.\n throw new DataCorruptionError(\"Chunk Id mismatch\", {\n ...extractSafePropertiesFromMessage(originalMessage),\n chunkMapLength: map.length,\n chunkId: chunkedContent.chunkId,\n totalChunks: chunkedContent.totalChunks,\n });\n }\n\n map.push(chunkedContent.contents);\n }\n}\n"]}
1
+ {"version":3,"file":"opSplitter.js","sourceRoot":"","sources":["../../src/opLifecycle/opSplitter.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 { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IBatchMessage } from \"@fluidframework/container-definitions\";\nimport { DataCorruptionError, extractSafePropertiesFromMessage } from \"@fluidframework/container-utils\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { ContainerMessageType, ContainerRuntimeMessage } from \"../containerRuntime\";\nimport { BatchMessage, IBatch, IChunkedOp, IMessageProcessingResult } from \"./definitions\";\n\n/**\n * Responsible for creating and reconstructing chunked messages.\n */\nexport class OpSplitter {\n // Local copy of incomplete received chunks.\n private readonly chunkMap: Map<string, string[]>;\n private readonly logger;\n\n constructor(\n chunks: [string, string[]][],\n private readonly submitBatchFn: ((batch: IBatchMessage[]) => number) | undefined,\n private readonly chunkSizeInBytes: number,\n private readonly maxBatchSizeInBytes: number,\n logger: ITelemetryLogger,\n ) {\n this.chunkMap = new Map<string, string[]>(chunks);\n this.logger = ChildLogger.create(logger, \"OpSplitter\");\n }\n\n public get isBatchChunkingEnabled(): boolean {\n return this.chunkSizeInBytes < Number.POSITIVE_INFINITY && this.submitBatchFn !== undefined;\n }\n\n public get chunks(): ReadonlyMap<string, string[]> {\n return this.chunkMap;\n }\n\n public processRemoteMessage(message: ISequencedDocumentMessage): IMessageProcessingResult {\n if (message.type !== ContainerMessageType.ChunkedOp) {\n return {\n message,\n state: \"Skipped\",\n };\n }\n\n const clientId = message.clientId;\n const chunkedContent = message.contents as IChunkedOp;\n this.addChunk(clientId, chunkedContent, message);\n\n if (chunkedContent.chunkId < chunkedContent.totalChunks) {\n // We are processing the op in chunks but haven't reached\n // the last chunk yet in order to reconstruct the original op\n return {\n message,\n state: \"Accepted\",\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const serializedContent = this.chunkMap.get(clientId)!.join(\"\");\n this.clearPartialChunks(clientId);\n\n const newMessage = { ...message };\n newMessage.contents = serializedContent === \"\" ? undefined : JSON.parse(serializedContent);\n newMessage.type = chunkedContent.originalType;\n newMessage.metadata = chunkedContent.originalMetadata;\n newMessage.compression = chunkedContent.originalCompression;\n return {\n message: newMessage,\n state: \"Processed\",\n };\n }\n\n public clearPartialChunks(clientId: string) {\n if (this.chunkMap.has(clientId)) {\n this.chunkMap.delete(clientId);\n }\n }\n\n private addChunk(clientId: string, chunkedContent: IChunkedOp, originalMessage: ISequencedDocumentMessage) {\n let map = this.chunkMap.get(clientId);\n if (map === undefined) {\n map = [];\n this.chunkMap.set(clientId, map);\n }\n\n if (chunkedContent.chunkId !== map.length + 1) {\n // We are expecting the chunks to be processed sequentially, in the same order as they are sent.\n // Therefore, the chunkId of the incoming op needs to match the length of the array (1-based indexing)\n // holding the existing chunks for that particular clientId.\n throw new DataCorruptionError(\"Chunk Id mismatch\", {\n ...extractSafePropertiesFromMessage(originalMessage),\n chunkMapLength: map.length,\n chunkId: chunkedContent.chunkId,\n totalChunks: chunkedContent.totalChunks,\n });\n }\n\n map.push(chunkedContent.contents);\n }\n\n /**\n * Splits the first op of a compressed batch in chunks, sends the chunks separately and\n * returns a new batch composed of the last chunk and the rest of the ops in the original batch.\n *\n * A compressed batch is formed by one large op at the first position, followed by a series of placeholder ops\n * which are used in order to reserve the sequence numbers for when the first op gets unrolled into the original\n * uncompressed ops at ingestion in the runtime.\n *\n * If the first op is too large, it can be chunked (split into smaller op) which can be sent individually over the wire\n * and accumulate at ingestion, until the last op in the chunk is processed, when the original op is unrolled.\n *\n * This method will send the first N - 1 chunks separately and use the last chunk as the first message in the result batch\n * and then appends the original placeholder ops. This will ensure that the batch semantics of the original (non-compressed) batch\n * are preserved, as the original chunked op will be unrolled by the runtime when the first message in the batch is processed\n * (as it is the last chunk).\n *\n * To illustrate, if the input is `[largeOp, emptyOp, emptyOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.\n * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4, emptyOp, emptyOp]` will be returned.\n *\n * @param batch - the compressed batch which needs to be processed\n * @returns A new adjusted batch which can be sent over the wire\n */\n public splitCompressedBatch(batch: IBatch): IBatch {\n assert(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);\n assert(batch.contentSizeInBytes > 0 && batch.content.length > 0, 0x514 /* Batch needs to be non-empty */);\n assert(this.chunkSizeInBytes !== 0, 0x515 /* Chunk size needs to be non-zero */);\n assert(this.chunkSizeInBytes < this.maxBatchSizeInBytes, 0x516 /* Chunk size needs to be smaller than the max batch size */);\n\n const firstMessage = batch.content[0]; // we expect this to be the large compressed op, which needs to be split\n assert(firstMessage.metadata?.compressed === true || firstMessage.compression !== undefined, 0x517 /* Batch needs to be compressed */);\n assert((firstMessage.contents?.length ?? 0) >= this.chunkSizeInBytes, 0x518 /* First message in the batch needs to be chunkable */);\n\n const restOfMessages = batch.content.slice(1); // we expect these to be empty ops, created to reserve sequence numbers\n const chunks = splitOp(firstMessage, this.chunkSizeInBytes);\n\n assert(this.submitBatchFn !== undefined, 0x519 /* We don't support old loaders */);\n // Send the first N-1 chunks immediately\n for (const chunk of chunks.slice(0, -1)) {\n this.submitBatchFn([chunkToBatchMessage(chunk, firstMessage.referenceSequenceNumber)]);\n }\n\n // The last chunk will be part of the new batch and needs to\n // preserve the batch metadata of the original batch\n const lastChunk = chunkToBatchMessage(\n chunks[chunks.length - 1],\n firstMessage.referenceSequenceNumber,\n { batch: firstMessage.metadata?.batch });\n\n this.logger.sendPerformanceEvent({\n eventName: \"Chunked compressed batch\",\n length: batch.content.length,\n sizeInBytes: batch.contentSizeInBytes,\n chunks: chunks.length,\n chunkSizeInBytes: this.chunkSizeInBytes,\n });\n\n return {\n content: [lastChunk, ...restOfMessages],\n contentSizeInBytes: lastChunk.contents?.length ?? 0,\n };\n }\n}\n\nconst chunkToBatchMessage = (\n chunk: IChunkedOp,\n referenceSequenceNumber: number,\n metadata: Record<string, unknown> | undefined = undefined,\n): BatchMessage => {\n const payload: ContainerRuntimeMessage = { type: ContainerMessageType.ChunkedOp, contents: chunk };\n return {\n contents: JSON.stringify(payload),\n deserializedContent: payload,\n metadata,\n localOpMetadata: undefined,\n referenceSequenceNumber,\n };\n}\n\nexport const splitOp = (op: BatchMessage, chunkSizeInBytes: number): IChunkedOp[] => {\n const chunks: IChunkedOp[] = [];\n assert(op.contents !== undefined && op.contents !== null, 0x51a /* We should have something to chunk */);\n\n const contentLength = op.contents.length;\n const chunkN = Math.floor((contentLength - 1) / chunkSizeInBytes) + 1;\n let offset = 0;\n for (let i = 1; i <= chunkN; i++) {\n const chunk: IChunkedOp = {\n chunkId: i,\n contents: op.contents.substr(offset, chunkSizeInBytes),\n originalType: op.deserializedContent.type,\n totalChunks: chunkN,\n }\n\n if (i === chunkN) {\n // We don't need to port these to all the chunks,\n // as we rebuild the original op when we process the\n // last chunk, therefore it is the only one that needs it.\n chunk.originalMetadata = op.metadata;\n chunk.originalCompression = op.compression;\n }\n\n chunks.push(chunk);\n offset += chunkSizeInBytes;\n }\n\n return chunks;\n};\n"]}
@@ -2,14 +2,17 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { ITelemetryLogger } from "@fluidframework/common-definitions";
5
6
  import { IContainerContext } from "@fluidframework/container-definitions";
6
7
  import { ICompressionRuntimeOptions } from "../containerRuntime";
7
8
  import { PendingStateManager } from "../pendingStateManager";
8
9
  import { BatchMessage } from "./definitions";
9
10
  import { OpCompressor } from "./opCompressor";
11
+ import { OpSplitter } from "./opSplitter";
10
12
  export interface IOutboxConfig {
11
13
  readonly compressionOptions: ICompressionRuntimeOptions;
12
14
  readonly maxBatchSizeInBytes: number;
15
+ readonly enableOpReentryCheck?: boolean;
13
16
  }
14
17
  export interface IOutboxParameters {
15
18
  readonly shouldSend: () => boolean;
@@ -17,6 +20,8 @@ export interface IOutboxParameters {
17
20
  readonly containerContext: IContainerContext;
18
21
  readonly config: IOutboxConfig;
19
22
  readonly compressor: OpCompressor;
23
+ readonly splitter: OpSplitter;
24
+ readonly logger: ITelemetryLogger;
20
25
  }
21
26
  export declare class Outbox {
22
27
  private readonly params;
@@ -1 +1 @@
1
- {"version":3,"file":"outbox.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/outbox.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAG1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAU,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;IAExD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;IACnC,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClD,QAAQ,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC;CACrC;AAED,qBAAa,MAAM;IAKH,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAe;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,iCAAiC,CAAa;gBAElC,MAAM,EAAE,iBAAiB;IAetD,IAAW,OAAO,IAAI,OAAO,CAE5B;IAEM,MAAM,CAAC,OAAO,EAAE,YAAY;IAa5B,YAAY,CAAC,OAAO,EAAE,YAAY;IA4BlC,KAAK;IAKZ,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IAyBrB;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAuCjB,OAAO,CAAC,YAAY;IAoBb,UAAU;;;;CAMpB"}
1
+ {"version":3,"file":"outbox.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/outbox.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAEtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAG1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAU,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;IAExD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAC3C;AAED,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;IACnC,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClD,QAAQ,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;CACrC;AAED,qBAAa,MAAM;IAKH,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAe;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,iCAAiC,CAAa;gBAElC,MAAM,EAAE,iBAAiB;IAiBtD,IAAW,OAAO,IAAI,OAAO,CAE5B;IAEM,MAAM,CAAC,OAAO,EAAE,YAAY;IAa5B,YAAY,CAAC,OAAO,EAAE,YAAY;IA4BlC,KAAK;IAKZ,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IA8BrB;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IA2CjB,OAAO,CAAC,YAAY;IAoBb,UAAU;;;;CAMpB"}
@@ -21,10 +21,12 @@ class Outbox {
21
21
  this.attachFlowBatch = new batchManager_1.BatchManager({
22
22
  hardLimit,
23
23
  softLimit,
24
- });
24
+ enableOpReentryCheck: params.config.enableOpReentryCheck,
25
+ }, params.logger);
25
26
  this.mainBatch = new batchManager_1.BatchManager({
26
- hardLimit
27
- });
27
+ hardLimit,
28
+ enableOpReentryCheck: params.config.enableOpReentryCheck,
29
+ }, params.logger);
28
30
  }
29
31
  get isEmpty() {
30
32
  return this.attachFlowBatch.length === 0 && this.mainBatch.length === 0;
@@ -82,17 +84,21 @@ class Outbox {
82
84
  return batch;
83
85
  }
84
86
  const compressedBatch = this.params.compressor.compressBatch(batch);
85
- if (compressedBatch.contentSizeInBytes > this.params.config.maxBatchSizeInBytes) {
86
- throw new container_utils_1.GenericError("BatchTooLarge",
87
- /* error */ undefined, {
88
- opSize: batch.contentSizeInBytes,
89
- count: batch.content.length,
90
- limit: this.params.config.maxBatchSizeInBytes,
91
- compressed: true,
92
- });
87
+ if (compressedBatch.contentSizeInBytes <= this.params.config.maxBatchSizeInBytes) {
88
+ // If we don't reach the maximum supported size of a batch, it can safely be sent as is
89
+ return compressedBatch;
93
90
  }
94
- // If we don't reach the maximum supported size of a batch, it safe to be sent as is
95
- return compressedBatch;
91
+ if (this.params.splitter.isBatchChunkingEnabled) {
92
+ return this.params.splitter.splitCompressedBatch(compressedBatch);
93
+ }
94
+ // If we've reached this point, the runtime would attempt to send a batch larger than the allowed size
95
+ throw new container_utils_1.GenericError("BatchTooLarge",
96
+ /* error */ undefined, {
97
+ opSize: batch.contentSizeInBytes,
98
+ count: batch.content.length,
99
+ limit: this.params.config.maxBatchSizeInBytes,
100
+ compressed: true,
101
+ });
96
102
  }
97
103
  /**
98
104
  * Sends the batch object to the container context to be sent over the wire.
@@ -125,7 +131,11 @@ class Outbox {
125
131
  }
126
132
  else {
127
133
  // returns clientSequenceNumber of last message in a batch
128
- clientSequenceNumber = this.params.containerContext.submitBatchFn(batch.content.map((message) => ({ contents: message.contents, metadata: message.metadata })));
134
+ clientSequenceNumber = this.params.containerContext.submitBatchFn(batch.content.map((message) => ({
135
+ contents: message.contents,
136
+ metadata: message.metadata,
137
+ compression: message.compression,
138
+ })));
129
139
  }
130
140
  // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
131
141
  clientSequenceNumber -= length - 1;
@@ -1 +1 @@
1
- {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../src/opLifecycle/outbox.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/common-utils\";\nimport { IContainerContext } from \"@fluidframework/container-definitions\";\nimport { GenericError } from \"@fluidframework/container-utils\";\nimport { MessageType } from \"@fluidframework/protocol-definitions\";\nimport { ICompressionRuntimeOptions } from \"../containerRuntime\";\nimport { PendingStateManager } from \"../pendingStateManager\";\nimport { BatchManager } from \"./batchManager\";\nimport { BatchMessage, IBatch } from \"./definitions\";\nimport { OpCompressor } from \"./opCompressor\";\n\nexport interface IOutboxConfig {\n readonly compressionOptions: ICompressionRuntimeOptions;\n // The maximum size of a batch that we can send over the wire.\n readonly maxBatchSizeInBytes: number;\n};\n\nexport interface IOutboxParameters {\n readonly shouldSend: () => boolean,\n readonly pendingStateManager: PendingStateManager,\n readonly containerContext: IContainerContext,\n readonly config: IOutboxConfig,\n readonly compressor: OpCompressor;\n}\n\nexport class Outbox {\n private readonly attachFlowBatch: BatchManager;\n private readonly mainBatch: BatchManager;\n private readonly defaultAttachFlowSoftLimitInBytes = 64 * 1024;\n\n constructor(private readonly params: IOutboxParameters) {\n const isCompressionEnabled = this.params.config.compressionOptions.minimumBatchSizeInBytes !== Number.POSITIVE_INFINITY;\n // We need to allow infinite size batches if we enable compression\n const hardLimit = isCompressionEnabled ? Infinity : this.params.config.maxBatchSizeInBytes;\n const softLimit = isCompressionEnabled ? Infinity : this.defaultAttachFlowSoftLimitInBytes;\n\n this.attachFlowBatch = new BatchManager({\n hardLimit,\n softLimit,\n });\n this.mainBatch = new BatchManager({\n hardLimit\n });\n }\n\n public get isEmpty(): boolean {\n return this.attachFlowBatch.length === 0 && this.mainBatch.length === 0;\n }\n\n public submit(message: BatchMessage) {\n if (!this.mainBatch.push(message)) {\n throw new GenericError(\n \"BatchTooLarge\",\n /* error */ undefined,\n {\n opSize: (message.contents?.length) ?? 0,\n count: this.mainBatch.length,\n limit: this.mainBatch.options.hardLimit,\n });\n }\n }\n\n public submitAttach(message: BatchMessage) {\n if (!this.attachFlowBatch.push(message)) {\n // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged\n // when queue is not empty.\n // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit\n this.flushInternal(this.attachFlowBatch.popBatch());\n if (!this.attachFlowBatch.push(message)) {\n throw new GenericError(\n \"BatchTooLarge\",\n /* error */ undefined,\n {\n opSize: (message.contents?.length) ?? 0,\n count: this.attachFlowBatch.length,\n limit: this.attachFlowBatch.options.hardLimit,\n });\n }\n }\n\n // If compression is enabled, we will always successfully receive\n // attach ops and compress then send them at the next JS turn, regardless\n // of the overall size of the accumulated ops in the batch.\n // However, it is more efficient to flush these ops faster, preferably\n // after they reach a size which would benefit from compression.\n if (this.attachFlowBatch.contentSizeInBytes >= this.params.config.compressionOptions.minimumBatchSizeInBytes) {\n this.flushInternal(this.attachFlowBatch.popBatch());\n }\n }\n\n public flush() {\n this.flushInternal(this.attachFlowBatch.popBatch());\n this.flushInternal(this.mainBatch.popBatch());\n }\n\n private flushInternal(rawBatch: IBatch) {\n const processedBatch = this.compressBatch(rawBatch);\n const clientSequenceNumber = this.sendBatch(processedBatch);\n\n this.persistBatch(clientSequenceNumber, rawBatch.content);\n }\n\n private compressBatch(batch: IBatch): IBatch {\n if (batch.content.length === 0\n || this.params.config.compressionOptions === undefined\n || this.params.config.compressionOptions.minimumBatchSizeInBytes > batch.contentSizeInBytes) {\n // Nothing to do if the batch is empty or if compression is disabled or if we don't need to compress\n return batch;\n }\n\n const compressedBatch = this.params.compressor.compressBatch(batch);\n if (compressedBatch.contentSizeInBytes > this.params.config.maxBatchSizeInBytes) {\n throw new GenericError(\n \"BatchTooLarge\",\n /* error */ undefined,\n {\n opSize: batch.contentSizeInBytes,\n count: batch.content.length,\n limit: this.params.config.maxBatchSizeInBytes,\n compressed: true,\n });\n }\n\n // If we don't reach the maximum supported size of a batch, it safe to be sent as is\n return compressedBatch;\n }\n\n /**\n * Sends the batch object to the container context to be sent over the wire.\n *\n * @param batch - batch to be sent\n * @returns the client sequence number of the last batched op which was sent and\n * -1 if there are no ops or the container cannot send ops.\n */\n private sendBatch(batch: IBatch): number {\n let clientSequenceNumber: number = -1;\n const length = batch.content.length;\n\n // Did we disconnect in the middle of turn-based batch?\n // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.\n if (length === 0 || !this.params.shouldSend()) {\n return clientSequenceNumber;\n }\n\n if (this.params.containerContext.submitBatchFn === undefined) {\n // Legacy path - supporting old loader versions. Can be removed only when LTS moves above\n // version that has support for batches (submitBatchFn)\n for (const message of batch.content) {\n // Legacy path doesn't support compressed payloads and will submit uncompressed payload anyways\n if (message.metadata?.compressed) {\n delete message.metadata.compressed;\n }\n\n clientSequenceNumber = this.params.containerContext.submitFn(\n MessageType.Operation,\n message.deserializedContent,\n true, // batch\n message.metadata);\n }\n\n this.params.containerContext.deltaManager.flush();\n } else {\n // returns clientSequenceNumber of last message in a batch\n clientSequenceNumber = this.params.containerContext.submitBatchFn(\n batch.content.map((message) => ({ contents: message.contents, metadata: message.metadata })));\n }\n\n // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.\n clientSequenceNumber -= length - 1;\n assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);\n return clientSequenceNumber;\n }\n\n private persistBatch(initialClientSequenceNumber: number, batch: BatchMessage[]) {\n let clientSequenceNumber = initialClientSequenceNumber;\n // Let the PendingStateManager know that a message was submitted.\n // In future, need to shift toward keeping batch as a whole!\n for (const message of batch) {\n this.params.pendingStateManager.onSubmitMessage(\n message.deserializedContent.type,\n clientSequenceNumber,\n message.referenceSequenceNumber,\n message.deserializedContent.contents,\n message.localOpMetadata,\n message.metadata,\n );\n\n clientSequenceNumber++;\n }\n\n this.params.pendingStateManager.onFlush();\n }\n\n public checkpoint() {\n return {\n mainBatch: this.mainBatch.checkpoint(),\n attachFlowBatch: this.attachFlowBatch.checkpoint(),\n };\n }\n}\n"]}
1
+ {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../src/opLifecycle/outbox.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 { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IContainerContext } from \"@fluidframework/container-definitions\";\nimport { GenericError } from \"@fluidframework/container-utils\";\nimport { MessageType } from \"@fluidframework/protocol-definitions\";\nimport { ICompressionRuntimeOptions } from \"../containerRuntime\";\nimport { PendingStateManager } from \"../pendingStateManager\";\nimport { BatchManager } from \"./batchManager\";\nimport { BatchMessage, IBatch } from \"./definitions\";\nimport { OpCompressor } from \"./opCompressor\";\nimport { OpSplitter } from \"./opSplitter\";\n\nexport interface IOutboxConfig {\n readonly compressionOptions: ICompressionRuntimeOptions;\n // The maximum size of a batch that we can send over the wire.\n readonly maxBatchSizeInBytes: number;\n readonly enableOpReentryCheck?: boolean;\n};\n\nexport interface IOutboxParameters {\n readonly shouldSend: () => boolean,\n readonly pendingStateManager: PendingStateManager,\n readonly containerContext: IContainerContext,\n readonly config: IOutboxConfig,\n readonly compressor: OpCompressor;\n readonly splitter: OpSplitter;\n readonly logger: ITelemetryLogger;\n}\n\nexport class Outbox {\n private readonly attachFlowBatch: BatchManager;\n private readonly mainBatch: BatchManager;\n private readonly defaultAttachFlowSoftLimitInBytes = 64 * 1024;\n\n constructor(private readonly params: IOutboxParameters) {\n const isCompressionEnabled = this.params.config.compressionOptions.minimumBatchSizeInBytes !== Number.POSITIVE_INFINITY;\n // We need to allow infinite size batches if we enable compression\n const hardLimit = isCompressionEnabled ? Infinity : this.params.config.maxBatchSizeInBytes;\n const softLimit = isCompressionEnabled ? Infinity : this.defaultAttachFlowSoftLimitInBytes;\n\n this.attachFlowBatch = new BatchManager({\n hardLimit,\n softLimit,\n enableOpReentryCheck: params.config.enableOpReentryCheck,\n }, params.logger);\n this.mainBatch = new BatchManager({\n hardLimit,\n enableOpReentryCheck: params.config.enableOpReentryCheck,\n }, params.logger);\n }\n\n public get isEmpty(): boolean {\n return this.attachFlowBatch.length === 0 && this.mainBatch.length === 0;\n }\n\n public submit(message: BatchMessage) {\n if (!this.mainBatch.push(message)) {\n throw new GenericError(\n \"BatchTooLarge\",\n /* error */ undefined,\n {\n opSize: (message.contents?.length) ?? 0,\n count: this.mainBatch.length,\n limit: this.mainBatch.options.hardLimit,\n });\n }\n }\n\n public submitAttach(message: BatchMessage) {\n if (!this.attachFlowBatch.push(message)) {\n // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged\n // when queue is not empty.\n // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit\n this.flushInternal(this.attachFlowBatch.popBatch());\n if (!this.attachFlowBatch.push(message)) {\n throw new GenericError(\n \"BatchTooLarge\",\n /* error */ undefined,\n {\n opSize: (message.contents?.length) ?? 0,\n count: this.attachFlowBatch.length,\n limit: this.attachFlowBatch.options.hardLimit,\n });\n }\n }\n\n // If compression is enabled, we will always successfully receive\n // attach ops and compress then send them at the next JS turn, regardless\n // of the overall size of the accumulated ops in the batch.\n // However, it is more efficient to flush these ops faster, preferably\n // after they reach a size which would benefit from compression.\n if (this.attachFlowBatch.contentSizeInBytes >= this.params.config.compressionOptions.minimumBatchSizeInBytes) {\n this.flushInternal(this.attachFlowBatch.popBatch());\n }\n }\n\n public flush() {\n this.flushInternal(this.attachFlowBatch.popBatch());\n this.flushInternal(this.mainBatch.popBatch());\n }\n\n private flushInternal(rawBatch: IBatch) {\n const processedBatch = this.compressBatch(rawBatch);\n const clientSequenceNumber = this.sendBatch(processedBatch);\n\n this.persistBatch(clientSequenceNumber, rawBatch.content);\n }\n\n private compressBatch(batch: IBatch): IBatch {\n if (batch.content.length === 0\n || this.params.config.compressionOptions === undefined\n || this.params.config.compressionOptions.minimumBatchSizeInBytes > batch.contentSizeInBytes) {\n // Nothing to do if the batch is empty or if compression is disabled or if we don't need to compress\n return batch;\n }\n\n const compressedBatch = this.params.compressor.compressBatch(batch);\n if (compressedBatch.contentSizeInBytes <= this.params.config.maxBatchSizeInBytes) {\n // If we don't reach the maximum supported size of a batch, it can safely be sent as is\n return compressedBatch;\n }\n\n if (this.params.splitter.isBatchChunkingEnabled) {\n return this.params.splitter.splitCompressedBatch(compressedBatch);\n }\n\n // If we've reached this point, the runtime would attempt to send a batch larger than the allowed size\n throw new GenericError(\n \"BatchTooLarge\",\n /* error */ undefined,\n {\n opSize: batch.contentSizeInBytes,\n count: batch.content.length,\n limit: this.params.config.maxBatchSizeInBytes,\n compressed: true,\n });\n }\n\n /**\n * Sends the batch object to the container context to be sent over the wire.\n *\n * @param batch - batch to be sent\n * @returns the client sequence number of the last batched op which was sent and\n * -1 if there are no ops or the container cannot send ops.\n */\n private sendBatch(batch: IBatch): number {\n let clientSequenceNumber: number = -1;\n const length = batch.content.length;\n\n // Did we disconnect in the middle of turn-based batch?\n // If so, do nothing, as pending state manager will resubmit it correctly on reconnect.\n if (length === 0 || !this.params.shouldSend()) {\n return clientSequenceNumber;\n }\n\n if (this.params.containerContext.submitBatchFn === undefined) {\n // Legacy path - supporting old loader versions. Can be removed only when LTS moves above\n // version that has support for batches (submitBatchFn)\n for (const message of batch.content) {\n // Legacy path doesn't support compressed payloads and will submit uncompressed payload anyways\n if (message.metadata?.compressed) {\n delete message.metadata.compressed;\n }\n\n clientSequenceNumber = this.params.containerContext.submitFn(\n MessageType.Operation,\n message.deserializedContent,\n true, // batch\n message.metadata);\n }\n\n this.params.containerContext.deltaManager.flush();\n } else {\n // returns clientSequenceNumber of last message in a batch\n clientSequenceNumber = this.params.containerContext.submitBatchFn(\n batch.content.map((message) => ({\n contents: message.contents,\n metadata: message.metadata,\n compression: message.compression,\n })));\n }\n\n // Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.\n clientSequenceNumber -= length - 1;\n assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);\n return clientSequenceNumber;\n }\n\n private persistBatch(initialClientSequenceNumber: number, batch: BatchMessage[]) {\n let clientSequenceNumber = initialClientSequenceNumber;\n // Let the PendingStateManager know that a message was submitted.\n // In future, need to shift toward keeping batch as a whole!\n for (const message of batch) {\n this.params.pendingStateManager.onSubmitMessage(\n message.deserializedContent.type,\n clientSequenceNumber,\n message.referenceSequenceNumber,\n message.deserializedContent.contents,\n message.localOpMetadata,\n message.metadata,\n );\n\n clientSequenceNumber++;\n }\n\n this.params.pendingStateManager.onFlush();\n }\n\n public checkpoint() {\n return {\n mainBatch: this.mainBatch.checkpoint(),\n attachFlowBatch: this.attachFlowBatch.checkpoint(),\n };\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"remoteMessageProcessor.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,yBAAyB,EAAe,MAAM,sCAAsC,CAAC;AAE9F,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,qBAAa,sBAAsB;IAE3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc;gBADd,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,cAAc;IAGnD,IAAW,eAAe,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAE1D;IAEM,uBAAuB,CAAC,QAAQ,EAAE,MAAM;IAIxC,OAAO,CAAC,aAAa,EAAE,yBAAyB,GAAG,yBAAyB;CAStF;AA6BD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAkBhF"}
1
+ {"version":3,"file":"remoteMessageProcessor.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,yBAAyB,EAAe,MAAM,sCAAsC,CAAC;AAE9F,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,qBAAa,sBAAsB;IAE3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc;gBADd,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,cAAc;IAGnD,IAAW,eAAe,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAE1D;IAEM,uBAAuB,CAAC,QAAQ,EAAE,MAAM;IAIxC,OAAO,CAAC,aAAa,EAAE,yBAAyB,GAAG,yBAAyB;CAyBtF;AA6BD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAkBhF"}
@@ -20,9 +20,24 @@ class RemoteMessageProcessor {
20
20
  }
21
21
  process(remoteMessage) {
22
22
  let message = copy(remoteMessage);
23
- message = this.opDecompressor.processMessage(message);
23
+ message = this.opDecompressor.processMessage(message).message;
24
24
  unpackRuntimeMessage(message);
25
- message = this.opSplitter.processRemoteMessage(message);
25
+ const chunkProcessingResult = this.opSplitter.processRemoteMessage(message);
26
+ message = chunkProcessingResult.message;
27
+ if (chunkProcessingResult.state !== "Processed") {
28
+ // If the message is not chunked or if the splitter is still rebuilding the original message,
29
+ // there is no need to continue processing
30
+ return message;
31
+ }
32
+ const decompressionAfterChunking = this.opDecompressor.processMessage(message);
33
+ message = decompressionAfterChunking.message;
34
+ if (decompressionAfterChunking.state === "Skipped") {
35
+ // After chunking, if the original message was not compressed,
36
+ // there is no need to continue processing
37
+ return message;
38
+ }
39
+ // The message needs to be unpacked after chunking + decompression
40
+ unpack(message);
26
41
  return message;
27
42
  }
28
43
  }
@@ -1 +1 @@
1
- {"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+EAA8F;AAC9F,0DAAoF;AAIpF,MAAa,sBAAsB;IAC/B,YACqB,UAAsB,EACtB,cAA8B;QAD9B,eAAU,GAAV,UAAU,CAAY;QACtB,mBAAc,GAAd,cAAc,CAAgB;IAC/C,CAAC;IAEL,IAAW,eAAe;QACtB,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;IAClC,CAAC;IAEM,uBAAuB,CAAC,QAAgB;QAC3C,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC;IAEM,OAAO,CAAC,aAAwC;QACnD,IAAI,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;QAElC,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACtD,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAExD,OAAO,OAAO,CAAC;IACnB,CAAC;CACJ;AAvBD,wDAuBC;AAED,MAAM,IAAI,GAAG,CAAC,aAAwC,EAA6B,EAAE;IACjF,qEAAqE;IACrE,qEAAqE;IACrE,4FAA4F;IAC5F,wCAAwC;IACxC,MAAM,OAAO,qBAAQ,aAAa,CAAE,CAAC;IAErC,iGAAiG;IACjG,+GAA+G;IAC/G,qDAAqD;IACrD,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,EAAE,EAAE;QACjE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;KACnD;IAED,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,GAAG,CAAC,OAAkC,EAAE,EAAE;IAClD,MAAM,aAAa,GAAG,OAAO,CAAC,QAAmC,CAAC;IAClE,OAAO,CAAC,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC;IAClC,OAAO,CAAC,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC;AAC9C,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAgB,oBAAoB,CAAC,OAAkC;IACnE,IAAI,OAAO,CAAC,IAAI,KAAK,kCAAW,CAAC,SAAS,EAAE;QACxC,8CAA8C;QAC9C,sDAAsD;QACtD,+BAA+B;QAC/B,8BAA8B;QAC9B,OAAO,KAAK,CAAC;KAChB;IAED,oBAAoB;IACpB,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE;QAC/E,OAAO,CAAC,IAAI,GAAG,uCAAoB,CAAC,gBAAgB,CAAC;KACxD;SAAM;QACH,aAAa;QACb,MAAM,CAAC,OAAO,CAAC,CAAC;KACnB;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAlBD,oDAkBC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ISequencedDocumentMessage, MessageType } from \"@fluidframework/protocol-definitions\";\nimport { ContainerMessageType, ContainerRuntimeMessage } from \"../containerRuntime\";\nimport { OpDecompressor } from \"./opDecompressor\";\nimport { OpSplitter } from \"./opSplitter\";\n\nexport class RemoteMessageProcessor {\n constructor(\n private readonly opSplitter: OpSplitter,\n private readonly opDecompressor: OpDecompressor,\n ) { }\n\n public get partialMessages(): ReadonlyMap<string, string[]> {\n return this.opSplitter.chunks;\n }\n\n public clearPartialMessagesFor(clientId: string) {\n this.opSplitter.clearPartialChunks(clientId);\n }\n\n public process(remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage {\n let message = copy(remoteMessage);\n\n message = this.opDecompressor.processMessage(message);\n unpackRuntimeMessage(message);\n message = this.opSplitter.processRemoteMessage(message);\n\n return message;\n }\n}\n\nconst copy = (remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage => {\n // Do shallow copy of message, as the processing flow will modify it.\n // There might be multiple container instances receiving same message\n // We do not need to make deep copy, as each layer will just replace message.content itself,\n // but would not modify contents details\n const message = { ...remoteMessage };\n\n // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!\n // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.\n // Old ops may contain empty string (I assume noops).\n if (typeof message.contents === \"string\" && message.contents !== \"\") {\n message.contents = JSON.parse(message.contents);\n }\n\n return message;\n};\n\n/**\n * For a given message, it moves the nested contents and type on level up.\n *\n */\nconst unpack = (message: ISequencedDocumentMessage) => {\n const innerContents = message.contents as ContainerRuntimeMessage;\n message.type = innerContents.type;\n message.contents = innerContents.contents;\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 unpacked runtime message\n *\n * @internal\n */\nexport function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolean {\n if (message.type !== MessageType.Operation) {\n // Legacy format, but it's already \"unpacked\",\n // i.e. message.type is actually ContainerMessageType.\n // Or it's non-runtime message.\n // Nothing to do in such case.\n return false;\n }\n\n // legacy op format?\n if (message.contents.address !== undefined && message.contents.type === undefined) {\n message.type = ContainerMessageType.FluidDataStoreOp;\n } else {\n // new format\n unpack(message);\n }\n\n return true;\n}\n"]}
1
+ {"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+EAA8F;AAC9F,0DAAoF;AAIpF,MAAa,sBAAsB;IAC/B,YACqB,UAAsB,EACtB,cAA8B;QAD9B,eAAU,GAAV,UAAU,CAAY;QACtB,mBAAc,GAAd,cAAc,CAAgB;IAC/C,CAAC;IAEL,IAAW,eAAe;QACtB,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;IAClC,CAAC;IAEM,uBAAuB,CAAC,QAAgB;QAC3C,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC;IAEM,OAAO,CAAC,aAAwC;QACnD,IAAI,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;QAClC,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;QAC9D,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAE9B,MAAM,qBAAqB,GAAG,IAAI,CAAC,UAAU,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC5E,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC;QACxC,IAAI,qBAAqB,CAAC,KAAK,KAAK,WAAW,EAAE;YAC7C,6FAA6F;YAC7F,0CAA0C;YAC1C,OAAO,OAAO,CAAC;SAClB;QAED,MAAM,0BAA0B,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAC/E,OAAO,GAAG,0BAA0B,CAAC,OAAO,CAAC;QAC7C,IAAI,0BAA0B,CAAC,KAAK,KAAK,SAAS,EAAE;YAChD,8DAA8D;YAC9D,0CAA0C;YAC1C,OAAO,OAAO,CAAC;SAClB;QAED,kEAAkE;QAClE,MAAM,CAAC,OAAO,CAAC,CAAC;QAChB,OAAO,OAAO,CAAC;IACnB,CAAC;CACJ;AAvCD,wDAuCC;AAED,MAAM,IAAI,GAAG,CAAC,aAAwC,EAA6B,EAAE;IACjF,qEAAqE;IACrE,qEAAqE;IACrE,4FAA4F;IAC5F,wCAAwC;IACxC,MAAM,OAAO,qBAAQ,aAAa,CAAE,CAAC;IAErC,iGAAiG;IACjG,+GAA+G;IAC/G,qDAAqD;IACrD,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,EAAE,EAAE;QACjE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;KACnD;IAED,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,GAAG,CAAC,OAAkC,EAAE,EAAE;IAClD,MAAM,aAAa,GAAG,OAAO,CAAC,QAAmC,CAAC;IAClE,OAAO,CAAC,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC;IAClC,OAAO,CAAC,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC;AAC9C,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAgB,oBAAoB,CAAC,OAAkC;IACnE,IAAI,OAAO,CAAC,IAAI,KAAK,kCAAW,CAAC,SAAS,EAAE;QACxC,8CAA8C;QAC9C,sDAAsD;QACtD,+BAA+B;QAC/B,8BAA8B;QAC9B,OAAO,KAAK,CAAC;KAChB;IAED,oBAAoB;IACpB,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE;QAC/E,OAAO,CAAC,IAAI,GAAG,uCAAoB,CAAC,gBAAgB,CAAC;KACxD;SAAM;QACH,aAAa;QACb,MAAM,CAAC,OAAO,CAAC,CAAC;KACnB;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAlBD,oDAkBC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ISequencedDocumentMessage, MessageType } from \"@fluidframework/protocol-definitions\";\nimport { ContainerMessageType, ContainerRuntimeMessage } from \"../containerRuntime\";\nimport { OpDecompressor } from \"./opDecompressor\";\nimport { OpSplitter } from \"./opSplitter\";\n\nexport class RemoteMessageProcessor {\n constructor(\n private readonly opSplitter: OpSplitter,\n private readonly opDecompressor: OpDecompressor,\n ) { }\n\n public get partialMessages(): ReadonlyMap<string, string[]> {\n return this.opSplitter.chunks;\n }\n\n public clearPartialMessagesFor(clientId: string) {\n this.opSplitter.clearPartialChunks(clientId);\n }\n\n public process(remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage {\n let message = copy(remoteMessage);\n message = this.opDecompressor.processMessage(message).message;\n unpackRuntimeMessage(message);\n\n const chunkProcessingResult = this.opSplitter.processRemoteMessage(message);\n message = chunkProcessingResult.message;\n if (chunkProcessingResult.state !== \"Processed\") {\n // If the message is not chunked or if the splitter is still rebuilding the original message,\n // there is no need to continue processing\n return message;\n }\n\n const decompressionAfterChunking = this.opDecompressor.processMessage(message);\n message = decompressionAfterChunking.message;\n if (decompressionAfterChunking.state === \"Skipped\") {\n // After chunking, if the original message was not compressed,\n // there is no need to continue processing\n return message;\n }\n\n // The message needs to be unpacked after chunking + decompression\n unpack(message);\n return message;\n }\n}\n\nconst copy = (remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage => {\n // Do shallow copy of message, as the processing flow will modify it.\n // There might be multiple container instances receiving same message\n // We do not need to make deep copy, as each layer will just replace message.content itself,\n // but would not modify contents details\n const message = { ...remoteMessage };\n\n // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!\n // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.\n // Old ops may contain empty string (I assume noops).\n if (typeof message.contents === \"string\" && message.contents !== \"\") {\n message.contents = JSON.parse(message.contents);\n }\n\n return message;\n};\n\n/**\n * For a given message, it moves the nested contents and type on level up.\n *\n */\nconst unpack = (message: ISequencedDocumentMessage) => {\n const innerContents = message.contents as ContainerRuntimeMessage;\n message.type = innerContents.type;\n message.contents = innerContents.contents;\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 unpacked runtime message\n *\n * @internal\n */\nexport function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolean {\n if (message.type !== MessageType.Operation) {\n // Legacy format, but it's already \"unpacked\",\n // i.e. message.type is actually ContainerMessageType.\n // Or it's non-runtime message.\n // Nothing to do in such case.\n return false;\n }\n\n // legacy op format?\n if (message.contents.address !== undefined && message.contents.type === undefined) {\n message.type = ContainerMessageType.FluidDataStoreOp;\n } else {\n // new format\n unpack(message);\n }\n\n return true;\n}\n"]}