@fluidframework/container-runtime 2.0.0-internal.2.1.1 → 2.0.0-internal.2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -1
- 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 +39 -40
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +115 -278
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +3 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +21 -3
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +8 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +26 -13
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +15 -17
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +92 -106
- 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 +8 -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} +25 -10
- 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/opLifecycle/opCompressor.d.ts +18 -0
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
- package/dist/opLifecycle/opCompressor.js +53 -0
- package/dist/opLifecycle/opCompressor.js.map +1 -0
- package/dist/opLifecycle/opDecompressor.d.ts +20 -0
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/dist/opLifecycle/opDecompressor.js +72 -0
- 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 +39 -40
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +113 -275
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +3 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +23 -5
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +8 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +28 -15
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +15 -17
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +72 -86
- 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} +25 -10
- 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/opLifecycle/opCompressor.d.ts +18 -0
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
- package/lib/opLifecycle/opCompressor.js +49 -0
- package/lib/opLifecycle/opCompressor.js.map +1 -0
- package/lib/opLifecycle/opDecompressor.d.ts +20 -0
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/lib/opLifecycle/opDecompressor.js +68 -0
- 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 +35 -21
- package/prettier.config.cjs +8 -0
- package/src/blobManager.ts +74 -19
- package/src/containerRuntime.ts +144 -341
- package/src/dataStoreContext.ts +33 -5
- package/src/dataStores.ts +32 -16
- package/src/garbageCollection.ts +106 -82
- package/src/garbageCollectionConstants.ts +35 -0
- package/src/gcSweepReadyUsageDetection.ts +1 -1
- package/src/index.ts +6 -4
- package/src/{batchManager.ts → opLifecycle/batchManager.ts} +41 -23
- package/src/opLifecycle/definitions.ts +44 -0
- package/src/opLifecycle/index.ts +17 -0
- package/src/opLifecycle/opCompressor.ts +64 -0
- package/src/opLifecycle/opDecompressor.ts +84 -0
- 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 -36
- package/dist/batchManager.d.ts.map +0 -1
- package/dist/batchManager.js.map +0 -1
- package/lib/batchManager.d.ts +0 -36
- package/lib/batchManager.d.ts.map +0 -1
- package/lib/batchManager.js.map +0 -1
|
@@ -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,21 +16,23 @@ export {
|
|
|
17
16
|
IRootSummaryTreeWithStats,
|
|
18
17
|
isRuntimeMessage,
|
|
19
18
|
RuntimeMessage,
|
|
20
|
-
unpackRuntimeMessage,
|
|
21
19
|
agentSchedulerId,
|
|
22
20
|
ContainerRuntime,
|
|
23
21
|
RuntimeHeaders,
|
|
24
22
|
ISummaryConfiguration,
|
|
25
23
|
DefaultSummaryConfiguration,
|
|
26
24
|
ICompressionRuntimeOptions,
|
|
25
|
+
CompressionAlgorithms,
|
|
27
26
|
} from "./containerRuntime";
|
|
28
27
|
export { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
28
|
+
export {
|
|
29
|
+
IGCStats,
|
|
30
|
+
} from "./garbageCollection";
|
|
29
31
|
export {
|
|
30
32
|
gcBlobPrefix,
|
|
31
33
|
gcTombstoneBlobKey,
|
|
32
34
|
gcTreeKey,
|
|
33
|
-
|
|
34
|
-
} from "./garbageCollection";
|
|
35
|
+
} from "./garbageCollectionConstants";
|
|
35
36
|
export {
|
|
36
37
|
IPendingFlush,
|
|
37
38
|
IPendingLocalState,
|
|
@@ -81,3 +82,4 @@ export {
|
|
|
81
82
|
SummaryCollection,
|
|
82
83
|
} from "./summaryCollection";
|
|
83
84
|
export { ICancellableSummarizerController, neverCancelledSummaryToken } from "./runWhileConnectedCoordinator";
|
|
85
|
+
export { IChunkedOp, unpackRuntimeMessage } from "./opLifecycle";
|
|
@@ -3,17 +3,14 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { ICompressionRuntimeOptions } from "../containerRuntime";
|
|
7
|
+
import { BatchMessage, IBatch, IBatchCheckpoint } from "./definitions";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
deserializedContent: ContainerRuntimeMessage;
|
|
15
|
-
referenceSequenceNumber: number;
|
|
16
|
-
};
|
|
9
|
+
export interface IBatchManagerOptions {
|
|
10
|
+
readonly hardLimit: number;
|
|
11
|
+
readonly softLimit?: number;
|
|
12
|
+
readonly compressionOptions?: ICompressionRuntimeOptions;
|
|
13
|
+
}
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
16
|
* Helper class that manages partial batch & rollback.
|
|
@@ -23,15 +20,12 @@ export class BatchManager {
|
|
|
23
20
|
private batchContentSize = 0;
|
|
24
21
|
|
|
25
22
|
public get length() { return this.pendingBatch.length; }
|
|
26
|
-
public get
|
|
23
|
+
public get contentSizeInBytes() { return this.batchContentSize; }
|
|
27
24
|
|
|
28
|
-
constructor(
|
|
29
|
-
private readonly hardLimit: number,
|
|
30
|
-
public readonly softLimit?: number,
|
|
31
|
-
) { }
|
|
25
|
+
constructor(public readonly options: IBatchManagerOptions) { }
|
|
32
26
|
|
|
33
27
|
public push(message: BatchMessage): boolean {
|
|
34
|
-
const contentSize = this.batchContentSize + message.contents
|
|
28
|
+
const contentSize = this.batchContentSize + (message.contents?.length ?? 0);
|
|
35
29
|
const opCount = this.pendingBatch.length;
|
|
36
30
|
|
|
37
31
|
// Attempt to estimate batch size, aka socket message size.
|
|
@@ -44,11 +38,15 @@ export class BatchManager {
|
|
|
44
38
|
// If we were provided soft limit, check for exceeding it.
|
|
45
39
|
// But only if we have any ops, as the intention here is to flush existing ops (on exceeding this limit)
|
|
46
40
|
// and start over. That's not an option if we have no ops.
|
|
47
|
-
|
|
41
|
+
// If compression is enabled, the soft and hard limit are ignored and the message will be pushed anyways.
|
|
42
|
+
// Cases where the message is still too large will be handled by the maxConsecutiveReconnects path.
|
|
43
|
+
if (this.options.softLimit !== undefined
|
|
44
|
+
&& this.length > 0
|
|
45
|
+
&& socketMessageSize >= this.options.softLimit) {
|
|
48
46
|
return false;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
if (socketMessageSize >= this.
|
|
49
|
+
if (socketMessageSize >= this.options.hardLimit) {
|
|
52
50
|
return false;
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -59,24 +57,29 @@ export class BatchManager {
|
|
|
59
57
|
|
|
60
58
|
public get empty() { return this.pendingBatch.length === 0; }
|
|
61
59
|
|
|
62
|
-
public popBatch() {
|
|
63
|
-
const batch =
|
|
60
|
+
public popBatch(): IBatch {
|
|
61
|
+
const batch: IBatch = {
|
|
62
|
+
content: this.pendingBatch,
|
|
63
|
+
contentSizeInBytes: this.batchContentSize,
|
|
64
|
+
};
|
|
65
|
+
|
|
64
66
|
this.pendingBatch = [];
|
|
65
67
|
this.batchContentSize = 0;
|
|
66
|
-
|
|
68
|
+
|
|
69
|
+
return addBatchMetadata(batch);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/**
|
|
70
73
|
* Capture the pending state at this point
|
|
71
74
|
*/
|
|
72
|
-
public checkpoint() {
|
|
75
|
+
public checkpoint(): IBatchCheckpoint {
|
|
73
76
|
const startPoint = this.pendingBatch.length;
|
|
74
77
|
return {
|
|
75
78
|
rollback: (process: (message: BatchMessage) => void) => {
|
|
76
79
|
for (let i = this.pendingBatch.length; i > startPoint;) {
|
|
77
80
|
i--;
|
|
78
81
|
const message = this.pendingBatch[i];
|
|
79
|
-
this.batchContentSize -= message.contents
|
|
82
|
+
this.batchContentSize -= message.contents?.length ?? 0;
|
|
80
83
|
process(message);
|
|
81
84
|
}
|
|
82
85
|
|
|
@@ -85,3 +88,18 @@ export class BatchManager {
|
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
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";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
+
import { IsoBuffer } from "@fluidframework/common-utils";
|
|
8
|
+
import { ChildLogger } from "@fluidframework/telemetry-utils";
|
|
9
|
+
import { compress } from "lz4js";
|
|
10
|
+
import { CompressionAlgorithms, ContainerRuntimeMessage } from "../containerRuntime";
|
|
11
|
+
import { IBatch, BatchMessage } from "./definitions";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Compresses batches of ops. It generates a single compressed op that contains
|
|
15
|
+
* the contents of each op in the batch. It then submits empty ops for each original
|
|
16
|
+
* op to reserve sequence numbers.
|
|
17
|
+
*/
|
|
18
|
+
export class OpCompressor {
|
|
19
|
+
private readonly logger;
|
|
20
|
+
private compressedBatchCount = 0;
|
|
21
|
+
|
|
22
|
+
constructor(logger: ITelemetryLogger) {
|
|
23
|
+
this.logger = ChildLogger.create(logger, "OpCompressor");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public compressBatch(batch: IBatch): IBatch {
|
|
27
|
+
const messages: BatchMessage[] = [];
|
|
28
|
+
this.compressedBatchCount++;
|
|
29
|
+
const contentToCompress: ContainerRuntimeMessage[] = [];
|
|
30
|
+
for (const message of batch.content) {
|
|
31
|
+
contentToCompress.push(message.deserializedContent);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const compressionStart = Date.now();
|
|
35
|
+
const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(contentToCompress));
|
|
36
|
+
const compressedContents = compress(contentsAsBuffer);
|
|
37
|
+
const compressedContent = IsoBuffer.from(compressedContents).toString("base64");
|
|
38
|
+
const duration = Date.now() - compressionStart;
|
|
39
|
+
|
|
40
|
+
if (batch.contentSizeInBytes > 200000 || this.compressedBatchCount % 25) {
|
|
41
|
+
this.logger.sendPerformanceEvent({
|
|
42
|
+
eventName: "CompressedBatch",
|
|
43
|
+
duration,
|
|
44
|
+
sizeBeforeCompression: batch.contentSizeInBytes,
|
|
45
|
+
sizeAfterCompression: compressedContent.length,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
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
|
+
});
|
|
54
|
+
|
|
55
|
+
for (const message of batch.content.slice(1)) {
|
|
56
|
+
messages.push({ ...message, contents: undefined });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
contentSizeInBytes: compressedContent.length,
|
|
61
|
+
content: messages,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { decompress } from "lz4js";
|
|
7
|
+
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
8
|
+
import { assert, IsoBuffer, Uint8ArrayToString } from "@fluidframework/common-utils";
|
|
9
|
+
import { CompressionAlgorithms } from "../containerRuntime";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* State machine that "unrolls" contents of compressed batches of ops after decompressing them.
|
|
13
|
+
* This class relies on some implicit contracts defined below:
|
|
14
|
+
* 1. A compressed batch will have its first message with batch metadata set to true and compressed set to true
|
|
15
|
+
* 2. Messages in the middle of a compressed batch will have neither batch metadata nor the compression property set
|
|
16
|
+
* 3. The final message of a batch will have batch metadata set to false
|
|
17
|
+
* 4. An individually compressed op will have undefined batch metadata and compression set to true
|
|
18
|
+
*/
|
|
19
|
+
export class OpDecompressor {
|
|
20
|
+
private activeBatch = false;
|
|
21
|
+
private rootMessageContents: any | undefined;
|
|
22
|
+
private processedCount = 0;
|
|
23
|
+
|
|
24
|
+
public processMessage(message: ISequencedDocumentMessage): ISequencedDocumentMessage {
|
|
25
|
+
// We're checking for compression = true or top level compression property so
|
|
26
|
+
// that we can enable compression without waiting on all ordering services
|
|
27
|
+
// to pick up protocol change. Eventually only the top level property should
|
|
28
|
+
// be used.
|
|
29
|
+
if (message.metadata?.batch === true
|
|
30
|
+
&& (message.metadata?.compressed || message.compression !== undefined)) {
|
|
31
|
+
// Beginning of a compressed batch
|
|
32
|
+
assert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);
|
|
33
|
+
if (message.compression) {
|
|
34
|
+
// lz4 is the only supported compression algorithm for now
|
|
35
|
+
assert(message.compression === CompressionAlgorithms.lz4,
|
|
36
|
+
0x4b9 /* lz4 is currently the only supported compression algorithm */);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.activeBatch = true;
|
|
40
|
+
|
|
41
|
+
const contents = IsoBuffer.from(message.contents.packedContents, "base64");
|
|
42
|
+
const decompressedMessage = decompress(contents);
|
|
43
|
+
const intoString = Uint8ArrayToString(decompressedMessage);
|
|
44
|
+
const asObj = JSON.parse(intoString);
|
|
45
|
+
this.rootMessageContents = asObj;
|
|
46
|
+
|
|
47
|
+
return { ...message, contents: this.rootMessageContents[this.processedCount++] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.rootMessageContents !== undefined && message.metadata?.batch === undefined && this.activeBatch) {
|
|
51
|
+
// Continuation of compressed batch
|
|
52
|
+
return { ...message, contents: this.rootMessageContents[this.processedCount++] };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (this.rootMessageContents !== undefined && message.metadata?.batch === false) {
|
|
56
|
+
// End of compressed batch
|
|
57
|
+
const returnMessage = {
|
|
58
|
+
...message,
|
|
59
|
+
contents: this.rootMessageContents[this.processedCount++]
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
this.activeBatch = false;
|
|
63
|
+
this.rootMessageContents = undefined;
|
|
64
|
+
this.processedCount = 0;
|
|
65
|
+
|
|
66
|
+
return returnMessage;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (message.metadata?.batch === undefined &&
|
|
70
|
+
(message.metadata?.compressed || message.compression === CompressionAlgorithms.lz4)) {
|
|
71
|
+
// Single compressed message
|
|
72
|
+
assert(this.activeBatch === false, 0x4ba /* shouldn't receive compressed message in middle of a batch */);
|
|
73
|
+
|
|
74
|
+
const contents = IsoBuffer.from(message.contents.packedContents, "base64");
|
|
75
|
+
const decompressedMessage = decompress(contents);
|
|
76
|
+
const intoString = new TextDecoder().decode(decompressedMessage);
|
|
77
|
+
const asObj = JSON.parse(intoString);
|
|
78
|
+
|
|
79
|
+
return { ...message, contents: asObj[0] };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return message;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -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
|
+
}
|