@fluidframework/container-runtime 2.0.0-dev.2.2.0.111723 → 2.0.0-dev.2.3.0.115467

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 (161) hide show
  1. package/dist/blobManager.d.ts +20 -5
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +57 -15
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +16 -33
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +71 -219
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.js +2 -2
  10. package/dist/dataStoreContext.js.map +1 -1
  11. package/dist/dataStores.d.ts.map +1 -1
  12. package/dist/dataStores.js +2 -1
  13. package/dist/dataStores.js.map +1 -1
  14. package/dist/garbageCollection.d.ts +7 -16
  15. package/dist/garbageCollection.d.ts.map +1 -1
  16. package/dist/garbageCollection.js +41 -61
  17. package/dist/garbageCollection.js.map +1 -1
  18. package/dist/garbageCollectionConstants.d.ts +19 -0
  19. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  20. package/dist/garbageCollectionConstants.js +34 -0
  21. package/dist/garbageCollectionConstants.js.map +1 -0
  22. package/dist/gcSweepReadyUsageDetection.js +2 -2
  23. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  24. package/dist/index.d.ts +4 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +7 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/opLifecycle/batchManager.d.ts +30 -0
  29. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  30. package/dist/{batchManager.js → opLifecycle/batchManager.js} +17 -17
  31. package/dist/opLifecycle/batchManager.js.map +1 -0
  32. package/dist/opLifecycle/definitions.d.ts +40 -0
  33. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  34. package/dist/opLifecycle/definitions.js +7 -0
  35. package/dist/opLifecycle/definitions.js.map +1 -0
  36. package/dist/opLifecycle/index.d.ts +12 -0
  37. package/dist/opLifecycle/index.d.ts.map +1 -0
  38. package/dist/opLifecycle/index.js +21 -0
  39. package/dist/opLifecycle/index.js.map +1 -0
  40. package/dist/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +2 -2
  41. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  42. package/dist/{opCompressor.js → opLifecycle/opCompressor.js} +16 -13
  43. package/dist/opLifecycle/opCompressor.js.map +1 -0
  44. package/dist/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +0 -0
  45. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  46. package/dist/{opDecompressor.js → opLifecycle/opDecompressor.js} +5 -5
  47. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  48. package/dist/opLifecycle/opSplitter.d.ts +17 -0
  49. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  50. package/dist/opLifecycle/opSplitter.js +61 -0
  51. package/dist/opLifecycle/opSplitter.js.map +1 -0
  52. package/dist/opLifecycle/outbox.d.ts +47 -0
  53. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  54. package/dist/opLifecycle/outbox.js +153 -0
  55. package/dist/opLifecycle/outbox.js.map +1 -0
  56. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  57. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  58. package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
  59. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  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/summaryFormat.js +2 -2
  64. package/dist/summaryFormat.js.map +1 -1
  65. package/lib/blobManager.d.ts +20 -5
  66. package/lib/blobManager.d.ts.map +1 -1
  67. package/lib/blobManager.js +59 -17
  68. package/lib/blobManager.js.map +1 -1
  69. package/lib/containerRuntime.d.ts +16 -33
  70. package/lib/containerRuntime.d.ts.map +1 -1
  71. package/lib/containerRuntime.js +68 -215
  72. package/lib/containerRuntime.js.map +1 -1
  73. package/lib/dataStoreContext.js +1 -1
  74. package/lib/dataStoreContext.js.map +1 -1
  75. package/lib/dataStores.d.ts.map +1 -1
  76. package/lib/dataStores.js +2 -1
  77. package/lib/dataStores.js.map +1 -1
  78. package/lib/garbageCollection.d.ts +7 -16
  79. package/lib/garbageCollection.d.ts.map +1 -1
  80. package/lib/garbageCollection.js +19 -39
  81. package/lib/garbageCollection.js.map +1 -1
  82. package/lib/garbageCollectionConstants.d.ts +19 -0
  83. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  84. package/lib/garbageCollectionConstants.js +31 -0
  85. package/lib/garbageCollectionConstants.js.map +1 -0
  86. package/lib/gcSweepReadyUsageDetection.js +1 -1
  87. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  88. package/lib/index.d.ts +4 -2
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +3 -2
  91. package/lib/index.js.map +1 -1
  92. package/lib/opLifecycle/batchManager.d.ts +30 -0
  93. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  94. package/lib/{batchManager.js → opLifecycle/batchManager.js} +17 -17
  95. package/lib/opLifecycle/batchManager.js.map +1 -0
  96. package/lib/opLifecycle/definitions.d.ts +40 -0
  97. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  98. package/lib/opLifecycle/definitions.js +6 -0
  99. package/lib/opLifecycle/definitions.js.map +1 -0
  100. package/lib/opLifecycle/index.d.ts +12 -0
  101. package/lib/opLifecycle/index.d.ts.map +1 -0
  102. package/lib/opLifecycle/index.js +11 -0
  103. package/lib/opLifecycle/index.js.map +1 -0
  104. package/lib/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +2 -2
  105. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  106. package/lib/{opCompressor.js → opLifecycle/opCompressor.js} +16 -13
  107. package/lib/opLifecycle/opCompressor.js.map +1 -0
  108. package/lib/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +0 -0
  109. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  110. package/lib/{opDecompressor.js → opLifecycle/opDecompressor.js} +4 -4
  111. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  112. package/lib/opLifecycle/opSplitter.d.ts +17 -0
  113. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  114. package/lib/opLifecycle/opSplitter.js +57 -0
  115. package/lib/opLifecycle/opSplitter.js.map +1 -0
  116. package/lib/opLifecycle/outbox.d.ts +47 -0
  117. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  118. package/lib/opLifecycle/outbox.js +149 -0
  119. package/lib/opLifecycle/outbox.js.map +1 -0
  120. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  121. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  122. package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
  123. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  124. package/lib/packageVersion.d.ts +1 -1
  125. package/lib/packageVersion.js +1 -1
  126. package/lib/packageVersion.js.map +1 -1
  127. package/lib/summaryFormat.js +1 -1
  128. package/lib/summaryFormat.js.map +1 -1
  129. package/package.json +21 -34
  130. package/src/blobManager.ts +74 -19
  131. package/src/containerRuntime.ts +91 -278
  132. package/src/dataStoreContext.ts +1 -1
  133. package/src/dataStores.ts +2 -1
  134. package/src/garbageCollection.ts +33 -43
  135. package/src/garbageCollectionConstants.ts +35 -0
  136. package/src/gcSweepReadyUsageDetection.ts +1 -1
  137. package/src/index.ts +5 -4
  138. package/src/{batchManager.ts → opLifecycle/batchManager.ts} +30 -33
  139. package/src/opLifecycle/definitions.ts +44 -0
  140. package/src/opLifecycle/index.ts +17 -0
  141. package/src/{opCompressor.ts → opLifecycle/opCompressor.ts} +21 -16
  142. package/src/{opDecompressor.ts → opLifecycle/opDecompressor.ts} +8 -6
  143. package/src/opLifecycle/opSplitter.ts +78 -0
  144. package/src/opLifecycle/outbox.ts +204 -0
  145. package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
  146. package/src/packageVersion.ts +1 -1
  147. package/src/summaryFormat.ts +1 -1
  148. package/dist/batchManager.d.ts +0 -42
  149. package/dist/batchManager.d.ts.map +0 -1
  150. package/dist/batchManager.js.map +0 -1
  151. package/dist/opCompressor.d.ts.map +0 -1
  152. package/dist/opCompressor.js.map +0 -1
  153. package/dist/opDecompressor.d.ts.map +0 -1
  154. package/dist/opDecompressor.js.map +0 -1
  155. package/lib/batchManager.d.ts +0 -42
  156. package/lib/batchManager.d.ts.map +0 -1
  157. package/lib/batchManager.js.map +0 -1
  158. package/lib/opCompressor.d.ts.map +0 -1
  159. package/lib/opCompressor.js.map +0 -1
  160. package/lib/opDecompressor.d.ts.map +0 -1
  161. package/lib/opDecompressor.js.map +0 -1
@@ -45,6 +45,21 @@ import {
45
45
 
46
46
  import { IGCRuntimeOptions, RuntimeHeaders } from "./containerRuntime";
47
47
  import { getSummaryForDatastores } from "./dataStores";
48
+ import {
49
+ defaultInactiveTimeoutMs,
50
+ defaultSessionExpiryDurationMs,
51
+ disableSweepLogKey,
52
+ disableTombstoneKey,
53
+ gcBlobPrefix,
54
+ gcTestModeKey,
55
+ gcTombstoneBlobKey,
56
+ gcTreeKey,
57
+ oneDayMs,
58
+ runGCKey,
59
+ runSessionExpiryKey,
60
+ runSweepKey,
61
+ trackGCStateKey
62
+ } from "./garbageCollectionConstants";
48
63
  import { SweepReadyUsageDetectionHandler } from "./gcSweepReadyUsageDetection";
49
64
  import {
50
65
  getGCVersion,
@@ -60,36 +75,6 @@ import {
60
75
  /** This is the current version of garbage collection. */
61
76
  const GCVersion = 1;
62
77
 
63
- // The key for the GC tree in summary.
64
- export const gcTreeKey = "gc";
65
- // They prefix for GC blobs in the GC tree in summary.
66
- export const gcBlobPrefix = "__gc";
67
- // The key for tombstone blob in the GC tree in summary.
68
- export const gcTombstoneBlobKey = "__tombstones";
69
-
70
- // Feature gate key to turn GC on / off.
71
- export const runGCKey = "Fluid.GarbageCollection.RunGC";
72
- // Feature gate key to turn GC sweep on / off.
73
- export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
74
- // Feature gate key to turn GC test mode on / off.
75
- export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
76
- // Feature gate key to expire a session after a set period of time.
77
- export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
78
- // Feature gate key to write the gc blob as a handle if the data is the same.
79
- export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
80
- // Feature gate key to turn GC sweep log off.
81
- export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
82
- // Feature gate key to disable the tombstone feature, i.e., tombstone information is not read / written into summary.
83
- export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
84
- // Feature gate to enable throwing an error when tombstone object is used.
85
- export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
86
-
87
- // One day in milliseconds.
88
- export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
89
-
90
- export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
91
- export const defaultSessionExpiryDurationMs = 30 * oneDayMs; // 30 days
92
-
93
78
  /** The statistics of the system state after a garbage collection run. */
94
79
  export interface IGCStats {
95
80
  /** The number of nodes in the container. */
@@ -1250,8 +1235,13 @@ export class GarbageCollector implements IGarbageCollector {
1250
1235
 
1251
1236
  /**
1252
1237
  * Since GC runs periodically, the GC data that is generated only tells us the state of the world at that point in
1253
- * time. It's possible that nodes transition from `unreferenced -> referenced -> unreferenced` between two runs. The
1254
- * unreferenced timestamp of such nodes needs to be reset as they may have been accessed when they were referenced.
1238
+ * time. There can be nodes that were referenced in between two runs and their unreferenced state needs to be
1239
+ * updated. For example, in the following scenarios not updating the unreferenced timestamp can lead to deletion of
1240
+ * these objects while there can be in-memory referenced to it:
1241
+ * 1. A node transitions from `unreferenced -> referenced -> unreferenced` between two runs. When the reference is
1242
+ * added, the object may have been accessed and in-memory reference to it added.
1243
+ * 2. A reference is added from one unreferenced node to one or more unreferenced nodes. Even though the node[s] were
1244
+ * unreferenced, they could have been accessed and in-memory reference to them added.
1255
1245
  *
1256
1246
  * This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
1257
1247
  * If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
@@ -1292,41 +1282,41 @@ export class GarbageCollector implements IGarbageCollector {
1292
1282
  * run, and then add the references since last run.
1293
1283
  *
1294
1284
  * Note on why we need to combine the data from previous run, current run and all references in between -
1295
- *
1296
1285
  * 1. We need data from last run because some of its references may have been deleted since then. If those
1297
- * references added new outbound references before getting deleted, we need to detect them.
1286
+ * references added new outbound references before they were deleted, we need to detect them.
1298
1287
  *
1299
1288
  * 2. We need new outbound references since last run because some of them may have been deleted later. If those
1300
- * references added new outbound references before getting deleted, we need to detect them.
1289
+ * references added new outbound references before they were deleted, we need to detect them.
1301
1290
  *
1302
1291
  * 3. We need data from the current run because currently we may not detect when DDSes are referenced:
1303
- *
1304
- * - We don't require DDSes handles to be stored in a referenced DDS. For this, we need GC at DDS level
1305
- * which is tracked by https://github.com/microsoft/FluidFramework/issues/8470.
1306
- *
1292
+ * - We don't require DDSes handles to be stored in a referenced DDS.
1307
1293
  * - A new data store may have "root" DDSes already created and we don't detect them today.
1308
1294
  */
1309
1295
  const gcDataSuperSet = concatGarbageCollectionData(this.previousGCDataFromLastRun, currentGCData);
1296
+ const newOutboundRoutesSinceLastRun: string[] = [];
1310
1297
  this.newReferencesSinceLastRun.forEach((outboundRoutes: string[], sourceNodeId: string) => {
1311
1298
  if (gcDataSuperSet.gcNodes[sourceNodeId] === undefined) {
1312
1299
  gcDataSuperSet.gcNodes[sourceNodeId] = outboundRoutes;
1313
1300
  } else {
1314
1301
  gcDataSuperSet.gcNodes[sourceNodeId].push(...outboundRoutes);
1315
1302
  }
1303
+ newOutboundRoutesSinceLastRun.push(...outboundRoutes);
1316
1304
  });
1317
1305
 
1318
1306
  /**
1319
- * Run GC on the above reference graph to find all nodes that are referenced. For each one, if they are
1307
+ * Run GC on the above reference graph starting with root and all new outbound routes. This will generate a
1308
+ * list of all nodes that could have been referenced since the last run. If any of these nodes are unreferenced,
1320
1309
  * unreferenced, stop tracking them and remove from unreferenced list.
1321
- * Some of these nodes may be unreferenced now and if so, the current run will add unreferenced state for them.
1310
+ * Note that some of these nodes may be unreferenced now and if so, the current run will mark them as
1311
+ * unreferenced and add unreferenced state.
1322
1312
  */
1323
- const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"]);
1313
+ const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/", ...newOutboundRoutesSinceLastRun]);
1324
1314
  for (const nodeId of gcResult.referencedNodeIds) {
1325
1315
  const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
1326
1316
  if (nodeStateTracker !== undefined) {
1327
1317
  // Stop tracking so as to clear out any running timers.
1328
1318
  nodeStateTracker.stopTracking();
1329
- // Delete the node as we don't need to track it any more.
1319
+ // Delete the unreferenced state as we don't need to track it any more.
1330
1320
  this.unreferencedNodesState.delete(nodeId);
1331
1321
  }
1332
1322
  }
@@ -0,0 +1,35 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+
7
+ // The key for the GC tree in summary.
8
+ export const gcTreeKey = "gc";
9
+ // They prefix for GC blobs in the GC tree in summary.
10
+ export const gcBlobPrefix = "__gc";
11
+ // The key for tombstone blob in the GC tree in summary.
12
+ export const gcTombstoneBlobKey = "__tombstones";
13
+
14
+ // Feature gate key to turn GC on / off.
15
+ export const runGCKey = "Fluid.GarbageCollection.RunGC";
16
+ // Feature gate key to turn GC sweep on / off.
17
+ export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
18
+ // Feature gate key to turn GC test mode on / off.
19
+ export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
20
+ // Feature gate key to expire a session after a set period of time.
21
+ export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
22
+ // Feature gate key to write the gc blob as a handle if the data is the same.
23
+ export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
24
+ // Feature gate key to turn GC sweep log off.
25
+ export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
26
+ // Feature gate key to disable the tombstone feature, i.e., tombstone information is not read / written into summary.
27
+ export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
28
+ // Feature gate to enable throwing an error when tombstone object is used.
29
+ export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
30
+
31
+ // One day in milliseconds.
32
+ export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
33
+
34
+ export const defaultInactiveTimeoutMs = 7 * oneDayMs; // 7 days
35
+ export const defaultSessionExpiryDurationMs = 30 * oneDayMs; // 30 days
@@ -11,7 +11,7 @@ import {
11
11
  LoggingError,
12
12
  MonitoringContext,
13
13
  } from "@fluidframework/telemetry-utils";
14
- import { oneDayMs } from "./garbageCollection";
14
+ import { oneDayMs } from "./garbageCollectionConstants";
15
15
 
16
16
  /**
17
17
  * Feature Gate Key -
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@
5
5
 
6
6
  export {
7
7
  ContainerMessageType,
8
- IChunkedOp,
9
8
  ContainerRuntimeMessage,
10
9
  IGCRuntimeOptions,
11
10
  ISummaryRuntimeOptions,
@@ -17,7 +16,6 @@ export {
17
16
  IRootSummaryTreeWithStats,
18
17
  isRuntimeMessage,
19
18
  RuntimeMessage,
20
- unpackRuntimeMessage,
21
19
  agentSchedulerId,
22
20
  ContainerRuntime,
23
21
  RuntimeHeaders,
@@ -27,12 +25,14 @@ export {
27
25
  CompressionAlgorithms,
28
26
  } from "./containerRuntime";
29
27
  export { FluidDataStoreRegistry } from "./dataStoreRegistry";
28
+ export {
29
+ IGCStats,
30
+ } from "./garbageCollection";
30
31
  export {
31
32
  gcBlobPrefix,
32
33
  gcTombstoneBlobKey,
33
34
  gcTreeKey,
34
- IGCStats,
35
- } from "./garbageCollection";
35
+ } from "./garbageCollectionConstants";
36
36
  export {
37
37
  IPendingFlush,
38
38
  IPendingLocalState,
@@ -82,3 +82,4 @@ export {
82
82
  SummaryCollection,
83
83
  } from "./summaryCollection";
84
84
  export { ICancellableSummarizerController, neverCancelledSummaryToken } from "./runWhileConnectedCoordinator";
85
+ export { IChunkedOp, unpackRuntimeMessage } from "./opLifecycle";
@@ -3,19 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
- import { IBatchMessage } from "@fluidframework/container-definitions";
8
- import { ContainerRuntimeMessage, ICompressionRuntimeOptions } from "./containerRuntime";
9
- import { OpCompressor } from "./opCompressor";
10
-
11
- /**
12
- * Message type used by BatchManager
13
- */
14
- export type BatchMessage = IBatchMessage & {
15
- localOpMetadata: unknown;
16
- deserializedContent: ContainerRuntimeMessage;
17
- referenceSequenceNumber: number;
18
- };
6
+ import { ICompressionRuntimeOptions } from "../containerRuntime";
7
+ import { BatchMessage, IBatch, IBatchCheckpoint } from "./definitions";
19
8
 
20
9
  export interface IBatchManagerOptions {
21
10
  readonly hardLimit: number;
@@ -27,15 +16,13 @@ export interface IBatchManagerOptions {
27
16
  * Helper class that manages partial batch & rollback.
28
17
  */
29
18
  export class BatchManager {
30
- private readonly opCompressor: OpCompressor;
31
- private pendingBatch: BatchMessage [] = [];
19
+ private pendingBatch: BatchMessage[] = [];
32
20
  private batchContentSize = 0;
33
21
 
34
22
  public get length() { return this.pendingBatch.length; }
23
+ public get contentSizeInBytes() { return this.batchContentSize; }
35
24
 
36
- constructor(public readonly logger: ITelemetryLogger, public readonly options: IBatchManagerOptions) {
37
- this.opCompressor = new OpCompressor(logger);
38
- }
25
+ constructor(public readonly options: IBatchManagerOptions) { }
39
26
 
40
27
  public push(message: BatchMessage): boolean {
41
28
  const contentSize = this.batchContentSize + (message.contents?.length ?? 0);
@@ -55,13 +42,11 @@ export class BatchManager {
55
42
  // Cases where the message is still too large will be handled by the maxConsecutiveReconnects path.
56
43
  if (this.options.softLimit !== undefined
57
44
  && this.length > 0
58
- && socketMessageSize >= this.options.softLimit
59
- && Infinity === (this.options.compressionOptions?.minimumBatchSizeInBytes ?? Infinity)) {
45
+ && socketMessageSize >= this.options.softLimit) {
60
46
  return false;
61
47
  }
62
48
 
63
- if (socketMessageSize >= this.options.hardLimit
64
- && Infinity === (this.options.compressionOptions?.minimumBatchSizeInBytes ?? Infinity)) {
49
+ if (socketMessageSize >= this.options.hardLimit) {
65
50
  return false;
66
51
  }
67
52
 
@@ -72,25 +57,22 @@ export class BatchManager {
72
57
 
73
58
  public get empty() { return this.pendingBatch.length === 0; }
74
59
 
75
- public popBatch() {
76
- const batch = this.pendingBatch;
77
- const size = this.batchContentSize;
60
+ public popBatch(): IBatch {
61
+ const batch: IBatch = {
62
+ content: this.pendingBatch,
63
+ contentSizeInBytes: this.batchContentSize,
64
+ };
65
+
78
66
  this.pendingBatch = [];
79
67
  this.batchContentSize = 0;
80
68
 
81
- if (batch.length > 0
82
- && this.options.compressionOptions !== undefined
83
- && this.options.compressionOptions.minimumBatchSizeInBytes < size) {
84
- return this.opCompressor.compressBatch(batch, size);
85
- }
86
-
87
- return batch;
69
+ return addBatchMetadata(batch);
88
70
  }
89
71
 
90
72
  /**
91
73
  * Capture the pending state at this point
92
74
  */
93
- public checkpoint() {
75
+ public checkpoint(): IBatchCheckpoint {
94
76
  const startPoint = this.pendingBatch.length;
95
77
  return {
96
78
  rollback: (process: (message: BatchMessage) => void) => {
@@ -106,3 +88,18 @@ export class BatchManager {
106
88
  };
107
89
  }
108
90
  }
91
+
92
+ const addBatchMetadata = (batch: IBatch): IBatch => {
93
+ if (batch.content.length > 1) {
94
+ batch.content[0].metadata = {
95
+ ...batch.content[0].metadata,
96
+ batch: true
97
+ };
98
+ batch.content[batch.content.length - 1].metadata = {
99
+ ...batch.content[batch.content.length - 1].metadata,
100
+ batch: false
101
+ };
102
+ }
103
+
104
+ return batch;
105
+ };
@@ -0,0 +1,44 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { IBatchMessage } from "@fluidframework/container-definitions";
7
+ import { MessageType } from "@fluidframework/protocol-definitions";
8
+ import { CompressionAlgorithms, ContainerMessageType, ContainerRuntimeMessage } from "..";
9
+
10
+ /**
11
+ * Batch message type used internally by the runtime
12
+ */
13
+ export type BatchMessage = IBatchMessage & {
14
+ localOpMetadata: unknown;
15
+ deserializedContent: ContainerRuntimeMessage;
16
+ referenceSequenceNumber: number;
17
+ compression?: CompressionAlgorithms;
18
+ };
19
+
20
+ /**
21
+ * Batch interface used internally by the runtime.
22
+ */
23
+ export interface IBatch {
24
+ /**
25
+ * Sum of the in-memory content sizes of all messages in the batch.
26
+ * If the batch is compressed, this number reflects the post-compression size.
27
+ */
28
+ readonly contentSizeInBytes: number;
29
+ /**
30
+ * All the messages in the batch
31
+ */
32
+ readonly content: BatchMessage[];
33
+ }
34
+
35
+ export interface IBatchCheckpoint {
36
+ rollback: (action: (message: BatchMessage) => void) => void;
37
+ }
38
+
39
+ export interface IChunkedOp {
40
+ chunkId: number;
41
+ totalChunks: number;
42
+ contents: string;
43
+ originalType: MessageType | ContainerMessageType;
44
+ }
@@ -0,0 +1,17 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export { BatchManager } from "./batchManager";
7
+ export {
8
+ BatchMessage,
9
+ IBatch,
10
+ IBatchCheckpoint,
11
+ IChunkedOp,
12
+ } from "./definitions";
13
+ export { Outbox } from "./outbox";
14
+ export { OpCompressor } from "./opCompressor";
15
+ export { OpDecompressor } from "./opDecompressor";
16
+ export { OpSplitter } from "./opSplitter";
17
+ export { RemoteMessageProcessor, unpackRuntimeMessage } from "./remoteMessageProcessor";
@@ -7,8 +7,8 @@ import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { IsoBuffer } from "@fluidframework/common-utils";
8
8
  import { ChildLogger } from "@fluidframework/telemetry-utils";
9
9
  import { compress } from "lz4js";
10
- import { BatchMessage } from "./batchManager";
11
- import { CompressionAlgorithms, ContainerRuntimeMessage } from "./containerRuntime";
10
+ import { CompressionAlgorithms, ContainerRuntimeMessage } from "../containerRuntime";
11
+ import { IBatch, BatchMessage } from "./definitions";
12
12
 
13
13
  /**
14
14
  * Compresses batches of ops. It generates a single compressed op that contains
@@ -23,37 +23,42 @@ export class OpCompressor {
23
23
  this.logger = ChildLogger.create(logger, "OpCompressor");
24
24
  }
25
25
 
26
- public compressBatch(batch: BatchMessage[], originalLength: number): BatchMessage[] {
27
- const batchToSend: BatchMessage[] = [];
26
+ public compressBatch(batch: IBatch): IBatch {
27
+ const messages: BatchMessage[] = [];
28
28
  this.compressedBatchCount++;
29
- const batchedContents: ContainerRuntimeMessage[] = [];
30
- for (const message of batch) {
31
- batchedContents.push(message.deserializedContent);
29
+ const contentToCompress: ContainerRuntimeMessage[] = [];
30
+ for (const message of batch.content) {
31
+ contentToCompress.push(message.deserializedContent);
32
32
  }
33
33
 
34
34
  const compressionStart = Date.now();
35
- const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(batchedContents));
35
+ const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(contentToCompress));
36
36
  const compressedContents = compress(contentsAsBuffer);
37
37
  const compressedContent = IsoBuffer.from(compressedContents).toString("base64");
38
38
  const duration = Date.now() - compressionStart;
39
39
 
40
- if (originalLength > 200000 || this.compressedBatchCount % 100) {
40
+ if (batch.contentSizeInBytes > 200000 || this.compressedBatchCount % 25) {
41
41
  this.logger.sendPerformanceEvent({
42
42
  eventName: "CompressedBatch",
43
43
  duration,
44
- sizeBeforeCompression: originalLength,
44
+ sizeBeforeCompression: batch.contentSizeInBytes,
45
45
  sizeAfterCompression: compressedContent.length,
46
46
  });
47
47
  }
48
48
 
49
- batchToSend.push({ ...batch[0], contents: JSON.stringify({ packedContents: compressedContent }),
50
- metadata: { ...batch[0].metadata, compressed: true },
51
- compression: CompressionAlgorithms.lz4 });
49
+ messages.push({
50
+ ...batch.content[0], contents: JSON.stringify({ packedContents: compressedContent }),
51
+ metadata: { ...batch.content[0].metadata, compressed: true },
52
+ compression: CompressionAlgorithms.lz4,
53
+ });
52
54
 
53
- for (const message of batch.slice(1)) {
54
- batchToSend.push({ ...message, contents: undefined });
55
+ for (const message of batch.content.slice(1)) {
56
+ messages.push({ ...message, contents: undefined });
55
57
  }
56
58
 
57
- return batchToSend;
59
+ return {
60
+ contentSizeInBytes: compressedContent.length,
61
+ content: messages,
62
+ };
58
63
  }
59
64
  }
@@ -6,7 +6,7 @@
6
6
  import { decompress } from "lz4js";
7
7
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
8
  import { assert, IsoBuffer, Uint8ArrayToString } from "@fluidframework/common-utils";
9
- import { CompressionAlgorithms } from ".";
9
+ import { CompressionAlgorithms } from "../containerRuntime";
10
10
 
11
11
  /**
12
12
  * State machine that "unrolls" contents of compressed batches of ops after decompressing them.
@@ -29,11 +29,11 @@ export class OpDecompressor {
29
29
  if (message.metadata?.batch === true
30
30
  && (message.metadata?.compressed || message.compression !== undefined)) {
31
31
  // Beginning of a compressed batch
32
- assert(this.activeBatch === false, "shouldn't have multiple active batches");
32
+ assert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);
33
33
  if (message.compression) {
34
34
  // lz4 is the only supported compression algorithm for now
35
35
  assert(message.compression === CompressionAlgorithms.lz4,
36
- "lz4 is currently the only supported compression algorithm");
36
+ 0x4b9 /* lz4 is currently the only supported compression algorithm */);
37
37
  }
38
38
 
39
39
  this.activeBatch = true;
@@ -54,8 +54,10 @@ export class OpDecompressor {
54
54
 
55
55
  if (this.rootMessageContents !== undefined && message.metadata?.batch === false) {
56
56
  // End of compressed batch
57
- const returnMessage = { ...message,
58
- contents: this.rootMessageContents[this.processedCount++] };
57
+ const returnMessage = {
58
+ ...message,
59
+ contents: this.rootMessageContents[this.processedCount++]
60
+ };
59
61
 
60
62
  this.activeBatch = false;
61
63
  this.rootMessageContents = undefined;
@@ -67,7 +69,7 @@ export class OpDecompressor {
67
69
  if (message.metadata?.batch === undefined &&
68
70
  (message.metadata?.compressed || message.compression === CompressionAlgorithms.lz4)) {
69
71
  // Single compressed message
70
- assert(this.activeBatch === false, "shouldn't receive compressed message in middle of a batch");
72
+ assert(this.activeBatch === false, 0x4ba /* shouldn't receive compressed message in middle of a batch */);
71
73
 
72
74
  const contents = IsoBuffer.from(message.contents.packedContents, "base64");
73
75
  const decompressedMessage = decompress(contents);
@@ -0,0 +1,78 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { DataCorruptionError, extractSafePropertiesFromMessage } from "@fluidframework/container-utils";
7
+ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
+ import { ContainerMessageType } from "../containerRuntime";
9
+ import { IChunkedOp } from "./definitions";
10
+
11
+ /**
12
+ * Responsible for creating and reconstructing chunked messages.
13
+ */
14
+ export class OpSplitter {
15
+ // Local copy of incomplete received chunks.
16
+ private readonly chunkMap: Map<string, string[]>;
17
+
18
+ constructor(chunks: [string, string[]][]) {
19
+ this.chunkMap = new Map<string, string[]>(chunks);
20
+ }
21
+
22
+ public get chunks(): ReadonlyMap<string, string[]> {
23
+ return this.chunkMap;
24
+ }
25
+
26
+ public processRemoteMessage(message: ISequencedDocumentMessage): ISequencedDocumentMessage {
27
+ if (message.type !== ContainerMessageType.ChunkedOp) {
28
+ return message;
29
+ }
30
+
31
+ const clientId = message.clientId;
32
+ const chunkedContent = message.contents as IChunkedOp;
33
+ this.addChunk(clientId, chunkedContent, message);
34
+
35
+ if (chunkedContent.chunkId < chunkedContent.totalChunks) {
36
+ // We are processing the op in chunks but haven't reached
37
+ // the last chunk yet in order to reconstruct the original op
38
+ return message;
39
+ }
40
+
41
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
42
+ const serializedContent = this.chunkMap.get(clientId)!.join("");
43
+ this.clearPartialChunks(clientId);
44
+
45
+ const newMessage = { ...message };
46
+ newMessage.contents = serializedContent === "" ? undefined : JSON.parse(serializedContent);
47
+ newMessage.type = chunkedContent.originalType;
48
+ return newMessage;
49
+ }
50
+
51
+ public clearPartialChunks(clientId: string) {
52
+ if (this.chunkMap.has(clientId)) {
53
+ this.chunkMap.delete(clientId);
54
+ }
55
+ }
56
+
57
+ private addChunk(clientId: string, chunkedContent: IChunkedOp, originalMessage: ISequencedDocumentMessage) {
58
+ let map = this.chunkMap.get(clientId);
59
+ if (map === undefined) {
60
+ map = [];
61
+ this.chunkMap.set(clientId, map);
62
+ }
63
+
64
+ if (chunkedContent.chunkId !== map.length + 1) {
65
+ // We are expecting the chunks to be processed sequentially, in the same order as they are sent.
66
+ // Therefore, the chunkId of the incoming op needs to match the length of the array (1-based indexing)
67
+ // holding the existing chunks for that particular clientId.
68
+ throw new DataCorruptionError("Chunk Id mismatch", {
69
+ ...extractSafePropertiesFromMessage(originalMessage),
70
+ chunkMapLength: map.length,
71
+ chunkId: chunkedContent.chunkId,
72
+ totalChunks: chunkedContent.totalChunks,
73
+ });
74
+ }
75
+
76
+ map.push(chunkedContent.contents);
77
+ }
78
+ }