@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.
- package/dist/blobManager.d.ts +20 -5
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +57 -15
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +16 -33
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +71 -219
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.js +2 -2
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +2 -1
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +7 -16
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +41 -61
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +19 -0
- package/dist/garbageCollectionConstants.d.ts.map +1 -0
- package/dist/garbageCollectionConstants.js +34 -0
- package/dist/garbageCollectionConstants.js.map +1 -0
- package/dist/gcSweepReadyUsageDetection.js +2 -2
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +30 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -0
- package/dist/{batchManager.js → opLifecycle/batchManager.js} +17 -17
- package/dist/opLifecycle/batchManager.js.map +1 -0
- package/dist/opLifecycle/definitions.d.ts +40 -0
- package/dist/opLifecycle/definitions.d.ts.map +1 -0
- package/dist/opLifecycle/definitions.js +7 -0
- package/dist/opLifecycle/definitions.js.map +1 -0
- package/dist/opLifecycle/index.d.ts +12 -0
- package/dist/opLifecycle/index.d.ts.map +1 -0
- package/dist/opLifecycle/index.js +21 -0
- package/dist/opLifecycle/index.js.map +1 -0
- package/dist/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +2 -2
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
- package/dist/{opCompressor.js → opLifecycle/opCompressor.js} +16 -13
- package/dist/opLifecycle/opCompressor.js.map +1 -0
- package/dist/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +0 -0
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/dist/{opDecompressor.js → opLifecycle/opDecompressor.js} +5 -5
- package/dist/opLifecycle/opDecompressor.js.map +1 -0
- package/dist/opLifecycle/opSplitter.d.ts +17 -0
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
- package/dist/opLifecycle/opSplitter.js +61 -0
- package/dist/opLifecycle/opSplitter.js.map +1 -0
- package/dist/opLifecycle/outbox.d.ts +47 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -0
- package/dist/opLifecycle/outbox.js +153 -0
- package/dist/opLifecycle/outbox.js.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/summaryFormat.js +2 -2
- package/dist/summaryFormat.js.map +1 -1
- package/lib/blobManager.d.ts +20 -5
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +59 -17
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +16 -33
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +68 -215
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.js +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +2 -1
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +7 -16
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +19 -39
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +19 -0
- package/lib/garbageCollectionConstants.d.ts.map +1 -0
- package/lib/garbageCollectionConstants.js +31 -0
- package/lib/garbageCollectionConstants.js.map +1 -0
- package/lib/gcSweepReadyUsageDetection.js +1 -1
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +4 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -2
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +30 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -0
- package/lib/{batchManager.js → opLifecycle/batchManager.js} +17 -17
- package/lib/opLifecycle/batchManager.js.map +1 -0
- package/lib/opLifecycle/definitions.d.ts +40 -0
- package/lib/opLifecycle/definitions.d.ts.map +1 -0
- package/lib/opLifecycle/definitions.js +6 -0
- package/lib/opLifecycle/definitions.js.map +1 -0
- package/lib/opLifecycle/index.d.ts +12 -0
- package/lib/opLifecycle/index.d.ts.map +1 -0
- package/lib/opLifecycle/index.js +11 -0
- package/lib/opLifecycle/index.js.map +1 -0
- package/lib/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +2 -2
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
- package/lib/{opCompressor.js → opLifecycle/opCompressor.js} +16 -13
- package/lib/opLifecycle/opCompressor.js.map +1 -0
- package/lib/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +0 -0
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/lib/{opDecompressor.js → opLifecycle/opDecompressor.js} +4 -4
- package/lib/opLifecycle/opDecompressor.js.map +1 -0
- package/lib/opLifecycle/opSplitter.d.ts +17 -0
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
- package/lib/opLifecycle/opSplitter.js +57 -0
- package/lib/opLifecycle/opSplitter.js.map +1 -0
- package/lib/opLifecycle/outbox.d.ts +47 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -0
- package/lib/opLifecycle/outbox.js +149 -0
- package/lib/opLifecycle/outbox.js.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/summaryFormat.js +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/package.json +21 -34
- package/src/blobManager.ts +74 -19
- package/src/containerRuntime.ts +91 -278
- package/src/dataStoreContext.ts +1 -1
- package/src/dataStores.ts +2 -1
- package/src/garbageCollection.ts +33 -43
- package/src/garbageCollectionConstants.ts +35 -0
- package/src/gcSweepReadyUsageDetection.ts +1 -1
- package/src/index.ts +5 -4
- package/src/{batchManager.ts → opLifecycle/batchManager.ts} +30 -33
- package/src/opLifecycle/definitions.ts +44 -0
- package/src/opLifecycle/index.ts +17 -0
- package/src/{opCompressor.ts → opLifecycle/opCompressor.ts} +21 -16
- package/src/{opDecompressor.ts → opLifecycle/opDecompressor.ts} +8 -6
- package/src/opLifecycle/opSplitter.ts +78 -0
- package/src/opLifecycle/outbox.ts +204 -0
- package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
- package/src/packageVersion.ts +1 -1
- package/src/summaryFormat.ts +1 -1
- package/dist/batchManager.d.ts +0 -42
- package/dist/batchManager.d.ts.map +0 -1
- package/dist/batchManager.js.map +0 -1
- package/dist/opCompressor.d.ts.map +0 -1
- package/dist/opCompressor.js.map +0 -1
- package/dist/opDecompressor.d.ts.map +0 -1
- package/dist/opDecompressor.js.map +0 -1
- package/lib/batchManager.d.ts +0 -42
- package/lib/batchManager.d.ts.map +0 -1
- package/lib/batchManager.js.map +0 -1
- package/lib/opCompressor.d.ts.map +0 -1
- package/lib/opCompressor.js.map +0 -1
- package/lib/opDecompressor.d.ts.map +0 -1
- package/lib/opDecompressor.js.map +0 -1
package/src/garbageCollection.ts
CHANGED
|
@@ -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.
|
|
1254
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
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
|
-
|
|
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 {
|
|
7
|
-
import {
|
|
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
|
|
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
|
|
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 =
|
|
77
|
-
|
|
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
|
-
|
|
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 {
|
|
11
|
-
import {
|
|
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:
|
|
27
|
-
const
|
|
26
|
+
public compressBatch(batch: IBatch): IBatch {
|
|
27
|
+
const messages: BatchMessage[] = [];
|
|
28
28
|
this.compressedBatchCount++;
|
|
29
|
-
const
|
|
30
|
-
for (const message of batch) {
|
|
31
|
-
|
|
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(
|
|
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 (
|
|
40
|
+
if (batch.contentSizeInBytes > 200000 || this.compressedBatchCount % 25) {
|
|
41
41
|
this.logger.sendPerformanceEvent({
|
|
42
42
|
eventName: "CompressedBatch",
|
|
43
43
|
duration,
|
|
44
|
-
sizeBeforeCompression:
|
|
44
|
+
sizeBeforeCompression: batch.contentSizeInBytes,
|
|
45
45
|
sizeAfterCompression: compressedContent.length,
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
+
for (const message of batch.content.slice(1)) {
|
|
56
|
+
messages.push({ ...message, contents: undefined });
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
return
|
|
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,
|
|
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
|
-
|
|
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 = {
|
|
58
|
-
|
|
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,
|
|
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
|
+
}
|