@fluidframework/container-runtime 2.0.0-internal.5.2.0 → 2.0.0-internal.5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/blobManager.d.ts +5 -2
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +51 -22
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +8 -1
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +4 -15
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +12 -7
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +4 -3
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.d.ts.map +1 -1
- package/dist/dataStoreContexts.js +2 -1
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStores.d.ts +1 -2
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +2 -1
- package/dist/dataStores.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +4 -3
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +1 -1
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/id-compressor/uuidUtilities.d.ts +0 -2
- package/dist/id-compressor/uuidUtilities.d.ts.map +1 -1
- package/dist/id-compressor/uuidUtilities.js +1 -3
- package/dist/id-compressor/uuidUtilities.js.map +1 -1
- package/dist/metadata.d.ts +18 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +7 -0
- package/dist/metadata.js.map +1 -0
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +14 -8
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +2 -6
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +2 -0
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +5 -1
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +45 -27
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +3 -1
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +1 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +13 -5
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +8 -2
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js +3 -2
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/dist/summary/summarizerTypes.d.ts +2 -1
- package/dist/summary/summarizerTypes.d.ts.map +1 -1
- package/dist/summary/summarizerTypes.js.map +1 -1
- package/dist/summary/summaryCollection.d.ts.map +1 -1
- package/dist/summary/summaryCollection.js +4 -0
- package/dist/summary/summaryCollection.js.map +1 -1
- package/dist/summary/summaryGenerator.d.ts +1 -1
- package/dist/summary/summaryGenerator.d.ts.map +1 -1
- package/dist/summary/summaryGenerator.js.map +1 -1
- package/lib/blobManager.d.ts +5 -2
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +51 -22
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +8 -1
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +4 -15
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +12 -7
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +4 -3
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.d.ts.map +1 -1
- package/lib/dataStoreContexts.js +2 -1
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStores.d.ts +1 -2
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +2 -1
- package/lib/dataStores.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +2 -1
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +1 -1
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/id-compressor/uuidUtilities.d.ts +0 -2
- package/lib/id-compressor/uuidUtilities.d.ts.map +1 -1
- package/lib/id-compressor/uuidUtilities.js +1 -3
- package/lib/id-compressor/uuidUtilities.js.map +1 -1
- package/lib/metadata.d.ts +18 -0
- package/lib/metadata.d.ts.map +1 -0
- package/lib/metadata.js +6 -0
- package/lib/metadata.js.map +1 -0
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +14 -8
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +2 -6
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +2 -0
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +5 -1
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +45 -27
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +3 -1
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +1 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +13 -5
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +8 -2
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js +2 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/summary/summarizerTypes.d.ts +2 -1
- package/lib/summary/summarizerTypes.d.ts.map +1 -1
- package/lib/summary/summarizerTypes.js.map +1 -1
- package/lib/summary/summaryCollection.d.ts.map +1 -1
- package/lib/summary/summaryCollection.js +4 -0
- package/lib/summary/summaryCollection.js.map +1 -1
- package/lib/summary/summaryGenerator.d.ts +1 -1
- package/lib/summary/summaryGenerator.d.ts.map +1 -1
- package/lib/summary/summaryGenerator.js.map +1 -1
- package/package.json +19 -18
- package/src/blobManager.ts +68 -27
- package/src/connectionTelemetry.ts +10 -1
- package/src/containerRuntime.ts +14 -25
- package/src/dataStoreContext.ts +5 -4
- package/src/dataStoreContexts.ts +2 -1
- package/src/dataStores.ts +8 -3
- package/src/gc/garbageCollection.ts +2 -1
- package/src/gc/gcTelemetry.ts +1 -1
- package/src/id-compressor/uuidUtilities.ts +1 -4
- package/src/metadata.ts +19 -0
- package/src/opLifecycle/README.md +20 -0
- package/src/opLifecycle/opDecompressor.ts +41 -13
- package/src/opLifecycle/opGroupingManager.ts +14 -7
- package/src/opLifecycle/opSplitter.ts +3 -1
- package/src/opLifecycle/outbox.ts +61 -38
- package/src/opLifecycle/remoteMessageProcessor.ts +5 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +20 -9
- package/src/scheduleManager.ts +15 -6
- package/src/summary/summarizerNode/summarizerNode.ts +1 -1
- package/src/summary/summarizerNode/summarizerNodeWithGc.ts +2 -1
- package/src/summary/summarizerTypes.ts +2 -1
- package/src/summary/summaryCollection.ts +6 -2
- package/src/summary/summaryGenerator.ts +1 -1
package/src/dataStores.ts
CHANGED
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
|
|
7
6
|
import {
|
|
8
7
|
DataCorruptionError,
|
|
9
8
|
extractSafePropertiesFromMessage,
|
|
10
9
|
} from "@fluidframework/container-utils";
|
|
11
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ITelemetryBaseLogger,
|
|
12
|
+
IDisposable,
|
|
13
|
+
IFluidHandle,
|
|
14
|
+
IRequest,
|
|
15
|
+
} from "@fluidframework/core-interfaces";
|
|
12
16
|
import { FluidObjectHandle } from "@fluidframework/datastore";
|
|
13
17
|
import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
|
|
14
18
|
import {
|
|
@@ -46,7 +50,8 @@ import {
|
|
|
46
50
|
} from "@fluidframework/telemetry-utils";
|
|
47
51
|
import { AttachState } from "@fluidframework/container-definitions";
|
|
48
52
|
import { buildSnapshotTree } from "@fluidframework/driver-utils";
|
|
49
|
-
import { assert
|
|
53
|
+
import { assert } from "@fluidframework/common-utils";
|
|
54
|
+
import { Lazy } from "@fluidframework/core-utils";
|
|
50
55
|
import { v4 as uuid } from "uuid";
|
|
51
56
|
import { DataStoreContexts } from "./dataStoreContexts";
|
|
52
57
|
import {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { Timer } from "@fluidframework/common-utils";
|
|
7
|
+
import { LazyPromise } from "@fluidframework/core-utils";
|
|
7
8
|
import { ClientSessionExpiredError, DataProcessingError } from "@fluidframework/container-utils";
|
|
8
9
|
import { IRequestHeader } from "@fluidframework/core-interfaces";
|
|
9
10
|
import {
|
package/src/gc/gcTelemetry.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ITelemetryGenericEvent } from "@fluidframework/
|
|
6
|
+
import { ITelemetryGenericEvent } from "@fluidframework/core-interfaces";
|
|
7
7
|
import { IGarbageCollectionData } from "@fluidframework/runtime-definitions";
|
|
8
8
|
import { packagePathToTelemetryProperty } from "@fluidframework/runtime-utils";
|
|
9
9
|
import {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { assert } from "@fluidframework/common-utils";
|
|
7
7
|
import { StableId, UuidString } from "@fluidframework/runtime-definitions";
|
|
8
|
-
import { v4
|
|
8
|
+
import { v4 } from "uuid";
|
|
9
9
|
|
|
10
10
|
const hexadecimalCharCodes = Array.from("09afAF").map((c) => c.charCodeAt(0)) as [
|
|
11
11
|
zero: number,
|
|
@@ -24,9 +24,6 @@ function isHexadecimalCharacter(charCode: number): boolean {
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/** The null (lowest/all-zeros) UUID */
|
|
28
|
-
export const nilUuid = assertIsUuidString(NIL);
|
|
29
|
-
|
|
30
27
|
/**
|
|
31
28
|
* Asserts that the given string is a UUID
|
|
32
29
|
*/
|
package/src/metadata.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Batching makes assumptions about what might be on the metadata. This interface codifies those assumptions, but does not validate them.
|
|
8
|
+
*/
|
|
9
|
+
export interface IBatchMetadata {
|
|
10
|
+
batch?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Blob handling makes assumptions about what might be on the metadata. This interface codifies those assumptions, but does not validate them.
|
|
15
|
+
*/
|
|
16
|
+
export interface IBlobMetadata {
|
|
17
|
+
blobId?: string;
|
|
18
|
+
localId?: string;
|
|
19
|
+
}
|
|
@@ -51,6 +51,26 @@ and verifying that the following expectation changes won't have any effects:
|
|
|
51
51
|
- client sequence numbers on batch messages can only be used to order messages with the same sequenceNumber
|
|
52
52
|
- requires all ops to be processed by runtime layer (version "2.0.0-internal.1.2.0" or later https://github.com/microsoft/FluidFramework/pull/11832)
|
|
53
53
|
|
|
54
|
+
Grouped batching may become problematic for batches which contain reentrant ops. This is the case when changes are made to a DDS inside a DDS 'onChanged' event handler. This means that the reentrant op will have a different reference sequence number than the rest of the ops in the batch, resulting in a different view of the state of the data model.
|
|
55
|
+
|
|
56
|
+
Therefore, when grouped batching is enabled, all batches with reentrant ops are rebased to the current reference sequence number and resubmitted to the data stores so that all ops are in agreement about the state of the data model and ensure eventual consistency.
|
|
57
|
+
|
|
58
|
+
### How to enable
|
|
59
|
+
|
|
60
|
+
**This feature is disabled by default, currently considered experimental and not ready for production usage.**
|
|
61
|
+
|
|
62
|
+
If all prerequisites in the previous section are met, enabling the feature can be done via the `IContainerRuntimeOptions` as following:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
const runtimeOptions: IContainerRuntimeOptions = {
|
|
66
|
+
(...)
|
|
67
|
+
enableGroupedBatching: true,
|
|
68
|
+
(...)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
In case of emergency grouped batching can be disabled at runtime, using feature gates. If `"Fluid.ContainerRuntime.DisableGroupedBatching"` is set to `true`, it will disable grouped batching if enabled from `IContainerRuntimeOptions` in the code.
|
|
73
|
+
|
|
54
74
|
## Chunking for compression
|
|
55
75
|
|
|
56
76
|
**Op chunking for compression targets payloads which exceed the max batch size after compression.** So, only payloads which are already compressed. By default, the feature is enabled.
|
|
@@ -8,8 +8,16 @@ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"
|
|
|
8
8
|
import { assert, IsoBuffer, Uint8ArrayToString } from "@fluidframework/common-utils";
|
|
9
9
|
import { ChildLogger, ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
|
|
10
10
|
import { CompressionAlgorithms } from "../containerRuntime";
|
|
11
|
+
import { IBatchMetadata } from "../metadata";
|
|
11
12
|
import { IMessageProcessingResult } from "./definitions";
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Compression makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.
|
|
16
|
+
*/
|
|
17
|
+
interface IPackedContentsContents {
|
|
18
|
+
packedContents: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* State machine that "unrolls" contents of compressed batches of ops after decompressing them.
|
|
15
23
|
* This class relies on some implicit contracts defined below:
|
|
@@ -34,7 +42,10 @@ export class OpDecompressor {
|
|
|
34
42
|
0x511 /* Only lz4 compression is supported */,
|
|
35
43
|
);
|
|
36
44
|
|
|
37
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
(message.metadata as IBatchMetadata | undefined)?.batch === true &&
|
|
47
|
+
this.isCompressed(message)
|
|
48
|
+
) {
|
|
38
49
|
// Beginning of a compressed batch
|
|
39
50
|
assert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);
|
|
40
51
|
if (message.compression) {
|
|
@@ -47,7 +58,10 @@ export class OpDecompressor {
|
|
|
47
58
|
|
|
48
59
|
this.activeBatch = true;
|
|
49
60
|
|
|
50
|
-
const contents = IsoBuffer.from(
|
|
61
|
+
const contents = IsoBuffer.from(
|
|
62
|
+
(message.contents as IPackedContentsContents).packedContents,
|
|
63
|
+
"base64",
|
|
64
|
+
);
|
|
51
65
|
const decompressedMessage = decompress(contents);
|
|
52
66
|
const intoString = Uint8ArrayToString(decompressedMessage);
|
|
53
67
|
const asObj = JSON.parse(intoString);
|
|
@@ -61,7 +75,7 @@ export class OpDecompressor {
|
|
|
61
75
|
|
|
62
76
|
if (
|
|
63
77
|
this.rootMessageContents !== undefined &&
|
|
64
|
-
message.metadata?.batch === undefined &&
|
|
78
|
+
(message.metadata as IBatchMetadata | undefined)?.batch === undefined &&
|
|
65
79
|
this.activeBatch
|
|
66
80
|
) {
|
|
67
81
|
assert(message.contents === undefined, 0x512 /* Expecting empty message */);
|
|
@@ -73,7 +87,10 @@ export class OpDecompressor {
|
|
|
73
87
|
};
|
|
74
88
|
}
|
|
75
89
|
|
|
76
|
-
if (
|
|
90
|
+
if (
|
|
91
|
+
this.rootMessageContents !== undefined &&
|
|
92
|
+
(message.metadata as IBatchMetadata | undefined)?.batch === false
|
|
93
|
+
) {
|
|
77
94
|
// End of compressed batch
|
|
78
95
|
const returnMessage = newMessage(
|
|
79
96
|
message,
|
|
@@ -90,14 +107,20 @@ export class OpDecompressor {
|
|
|
90
107
|
};
|
|
91
108
|
}
|
|
92
109
|
|
|
93
|
-
if (
|
|
110
|
+
if (
|
|
111
|
+
(message.metadata as IBatchMetadata | undefined)?.batch === undefined &&
|
|
112
|
+
this.isCompressed(message)
|
|
113
|
+
) {
|
|
94
114
|
// Single compressed message
|
|
95
115
|
assert(
|
|
96
116
|
this.activeBatch === false,
|
|
97
117
|
0x4ba /* shouldn't receive compressed message in middle of a batch */,
|
|
98
118
|
);
|
|
99
119
|
|
|
100
|
-
const contents = IsoBuffer.from(
|
|
120
|
+
const contents = IsoBuffer.from(
|
|
121
|
+
(message.contents as IPackedContentsContents).packedContents,
|
|
122
|
+
"base64",
|
|
123
|
+
);
|
|
101
124
|
const decompressedMessage = decompress(contents);
|
|
102
125
|
const intoString = new TextDecoder().decode(decompressedMessage);
|
|
103
126
|
const asObj = JSON.parse(intoString);
|
|
@@ -135,16 +158,19 @@ export class OpDecompressor {
|
|
|
135
158
|
message.contents !== null &&
|
|
136
159
|
typeof message.contents === "object" &&
|
|
137
160
|
Object.keys(message.contents).length === 1 &&
|
|
138
|
-
message.contents
|
|
139
|
-
|
|
140
|
-
message.contents.packedContents.length > 0 &&
|
|
141
|
-
IsoBuffer.from(
|
|
142
|
-
message.contents.packedContents
|
|
161
|
+
typeof (message.contents as { packedContents?: unknown }).packedContents ===
|
|
162
|
+
"string" &&
|
|
163
|
+
(message.contents as IPackedContentsContents).packedContents.length > 0 &&
|
|
164
|
+
IsoBuffer.from(
|
|
165
|
+
(message.contents as IPackedContentsContents).packedContents,
|
|
166
|
+
"base64",
|
|
167
|
+
).toString("base64") ===
|
|
168
|
+
(message.contents as IPackedContentsContents).packedContents
|
|
143
169
|
) {
|
|
144
170
|
this.logger.sendTelemetryEvent({
|
|
145
171
|
eventName: "LegacyCompression",
|
|
146
172
|
type: message.type,
|
|
147
|
-
batch: message.metadata?.batch,
|
|
173
|
+
batch: (message.metadata as IBatchMetadata | undefined)?.batch,
|
|
148
174
|
});
|
|
149
175
|
return true;
|
|
150
176
|
}
|
|
@@ -164,5 +190,7 @@ const newMessage = (
|
|
|
164
190
|
...originalMessage,
|
|
165
191
|
contents,
|
|
166
192
|
compression: undefined,
|
|
167
|
-
|
|
193
|
+
// TODO: It should already be the case that we're not modifying any metadata, not clear if/why this shallow clone should be required.
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
195
|
+
metadata: { ...(originalMessage.metadata as any) },
|
|
168
196
|
});
|
|
@@ -8,6 +8,14 @@ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"
|
|
|
8
8
|
import { ContainerMessageType } from "..";
|
|
9
9
|
import { IBatch } from "./definitions";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Grouping makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.
|
|
13
|
+
*/
|
|
14
|
+
interface IGroupedBatchMessageContents {
|
|
15
|
+
type: typeof OpGroupingManager.groupedBatchOp;
|
|
16
|
+
contents: IGroupedMessage[];
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
interface IGroupedMessage {
|
|
12
20
|
contents?: unknown;
|
|
13
21
|
metadata?: Record<string, unknown>;
|
|
@@ -15,7 +23,7 @@ interface IGroupedMessage {
|
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
export class OpGroupingManager {
|
|
18
|
-
static groupedBatchOp = "groupedBatch";
|
|
26
|
+
static readonly groupedBatchOp = "groupedBatch";
|
|
19
27
|
|
|
20
28
|
constructor(private readonly groupedBatchingEnabled: boolean) {}
|
|
21
29
|
|
|
@@ -25,10 +33,6 @@ export class OpGroupingManager {
|
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
for (const message of batch.content) {
|
|
28
|
-
// Blob attaches cannot be grouped (grouped batching would hide metadata)
|
|
29
|
-
if (message.type === ContainerMessageType.BlobAttach) {
|
|
30
|
-
return batch;
|
|
31
|
-
}
|
|
32
36
|
if (message.metadata) {
|
|
33
37
|
const keys = Object.keys(message.metadata);
|
|
34
38
|
assert(keys.length < 2, 0x5dd /* cannot group ops with metadata */);
|
|
@@ -64,11 +68,14 @@ export class OpGroupingManager {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
public ungroupOp(op: ISequencedDocumentMessage): ISequencedDocumentMessage[] {
|
|
67
|
-
if (
|
|
71
|
+
if (
|
|
72
|
+
(op.contents as { type?: unknown } | undefined)?.type !==
|
|
73
|
+
OpGroupingManager.groupedBatchOp
|
|
74
|
+
) {
|
|
68
75
|
return [op];
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
const messages = op.contents
|
|
78
|
+
const messages = (op.contents as IGroupedBatchMessageContents).contents;
|
|
72
79
|
let fakeCsn = 1;
|
|
73
80
|
return messages.map((subMessage) => ({
|
|
74
81
|
...op,
|
|
@@ -52,7 +52,9 @@ export class OpSplitter {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
57
|
+
const clientId = message.clientId as string;
|
|
56
58
|
const chunkedContent = message.contents as IChunkedOp;
|
|
57
59
|
this.addChunk(clientId, chunkedContent, message);
|
|
58
60
|
|
|
@@ -31,7 +31,7 @@ export interface IOutboxConfig {
|
|
|
31
31
|
// The maximum size of a batch that we can send over the wire.
|
|
32
32
|
readonly maxBatchSizeInBytes: number;
|
|
33
33
|
readonly disablePartialFlush: boolean;
|
|
34
|
-
readonly
|
|
34
|
+
readonly enableGroupedBatching: boolean;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export interface IOutboxParameters {
|
|
@@ -85,6 +85,7 @@ export class Outbox {
|
|
|
85
85
|
private readonly mc: MonitoringContext;
|
|
86
86
|
private readonly attachFlowBatch: BatchManager;
|
|
87
87
|
private readonly mainBatch: BatchManager;
|
|
88
|
+
private readonly blobAttachBatch: BatchManager;
|
|
88
89
|
private readonly defaultAttachFlowSoftLimitInBytes = 320 * 1024;
|
|
89
90
|
private batchRebasesToReport = 5;
|
|
90
91
|
private rebasing = false;
|
|
@@ -109,10 +110,15 @@ export class Outbox {
|
|
|
109
110
|
|
|
110
111
|
this.attachFlowBatch = new BatchManager({ hardLimit, softLimit });
|
|
111
112
|
this.mainBatch = new BatchManager({ hardLimit });
|
|
113
|
+
this.blobAttachBatch = new BatchManager({ hardLimit });
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
public get isEmpty(): boolean {
|
|
115
|
-
return
|
|
117
|
+
return (
|
|
118
|
+
this.attachFlowBatch.length === 0 &&
|
|
119
|
+
this.mainBatch.length === 0 &&
|
|
120
|
+
this.blobAttachBatch.length === 0
|
|
121
|
+
);
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
/**
|
|
@@ -124,9 +130,11 @@ export class Outbox {
|
|
|
124
130
|
private maybeFlushPartialBatch() {
|
|
125
131
|
const mainBatchSeqNums = this.mainBatch.sequenceNumbers;
|
|
126
132
|
const attachFlowBatchSeqNums = this.attachFlowBatch.sequenceNumbers;
|
|
133
|
+
const blobAttachSeqNums = this.blobAttachBatch.sequenceNumbers;
|
|
127
134
|
assert(
|
|
128
135
|
this.params.config.disablePartialFlush ||
|
|
129
|
-
sequenceNumbersMatch(mainBatchSeqNums, attachFlowBatchSeqNums)
|
|
136
|
+
(sequenceNumbersMatch(mainBatchSeqNums, attachFlowBatchSeqNums) &&
|
|
137
|
+
sequenceNumbersMatch(mainBatchSeqNums, blobAttachSeqNums)),
|
|
130
138
|
0x58d /* Reference sequence numbers from both batches must be in sync */,
|
|
131
139
|
);
|
|
132
140
|
|
|
@@ -134,7 +142,8 @@ export class Outbox {
|
|
|
134
142
|
|
|
135
143
|
if (
|
|
136
144
|
sequenceNumbersMatch(mainBatchSeqNums, currentSequenceNumbers) &&
|
|
137
|
-
sequenceNumbersMatch(attachFlowBatchSeqNums, currentSequenceNumbers)
|
|
145
|
+
sequenceNumbersMatch(attachFlowBatchSeqNums, currentSequenceNumbers) &&
|
|
146
|
+
sequenceNumbersMatch(blobAttachSeqNums, currentSequenceNumbers)
|
|
138
147
|
) {
|
|
139
148
|
// The reference sequence numbers are stable, there is nothing to do
|
|
140
149
|
return;
|
|
@@ -149,6 +158,8 @@ export class Outbox {
|
|
|
149
158
|
mainClientSequenceNumber: mainBatchSeqNums.clientSequenceNumber,
|
|
150
159
|
attachReferenceSequenceNumber: attachFlowBatchSeqNums.referenceSequenceNumber,
|
|
151
160
|
attachClientSequenceNumber: attachFlowBatchSeqNums.clientSequenceNumber,
|
|
161
|
+
blobAttachReferenceSequenceNumber: blobAttachSeqNums.referenceSequenceNumber,
|
|
162
|
+
blobAttachClientSequenceNumber: blobAttachSeqNums.clientSequenceNumber,
|
|
152
163
|
currentReferenceSequenceNumber: currentSequenceNumbers.referenceSequenceNumber,
|
|
153
164
|
currentClientSequenceNumber: currentSequenceNumbers.clientSequenceNumber,
|
|
154
165
|
},
|
|
@@ -164,20 +175,7 @@ export class Outbox {
|
|
|
164
175
|
public submit(message: BatchMessage) {
|
|
165
176
|
this.maybeFlushPartialBatch();
|
|
166
177
|
|
|
167
|
-
|
|
168
|
-
!this.mainBatch.push(
|
|
169
|
-
message,
|
|
170
|
-
this.isContextReentrant(),
|
|
171
|
-
this.params.getCurrentSequenceNumbers().clientSequenceNumber,
|
|
172
|
-
)
|
|
173
|
-
) {
|
|
174
|
-
throw new GenericError("BatchTooLarge", /* error */ undefined, {
|
|
175
|
-
opSize: message.contents?.length ?? 0,
|
|
176
|
-
batchSize: this.mainBatch.contentSizeInBytes,
|
|
177
|
-
count: this.mainBatch.length,
|
|
178
|
-
limit: this.mainBatch.options.hardLimit,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
178
|
+
this.addMessageToBatchManager(this.mainBatch, message);
|
|
181
179
|
}
|
|
182
180
|
|
|
183
181
|
public submitAttach(message: BatchMessage) {
|
|
@@ -194,20 +192,8 @@ export class Outbox {
|
|
|
194
192
|
// when queue is not empty.
|
|
195
193
|
// Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
|
|
196
194
|
this.flushInternal(this.attachFlowBatch);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
message,
|
|
200
|
-
this.isContextReentrant(),
|
|
201
|
-
this.params.getCurrentSequenceNumbers().clientSequenceNumber,
|
|
202
|
-
)
|
|
203
|
-
) {
|
|
204
|
-
throw new GenericError("BatchTooLarge", /* error */ undefined, {
|
|
205
|
-
opSize: message.contents?.length ?? 0,
|
|
206
|
-
batchSize: this.attachFlowBatch.contentSizeInBytes,
|
|
207
|
-
count: this.attachFlowBatch.length,
|
|
208
|
-
limit: this.attachFlowBatch.options.hardLimit,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
195
|
+
|
|
196
|
+
this.addMessageToBatchManager(this.attachFlowBatch, message);
|
|
211
197
|
}
|
|
212
198
|
|
|
213
199
|
// If compression is enabled, we will always successfully receive
|
|
@@ -223,6 +209,41 @@ export class Outbox {
|
|
|
223
209
|
}
|
|
224
210
|
}
|
|
225
211
|
|
|
212
|
+
public submitBlobAttach(message: BatchMessage) {
|
|
213
|
+
this.maybeFlushPartialBatch();
|
|
214
|
+
|
|
215
|
+
this.addMessageToBatchManager(this.blobAttachBatch, message);
|
|
216
|
+
|
|
217
|
+
// If compression is enabled, we will always successfully receive
|
|
218
|
+
// blobAttach ops and compress then send them at the next JS turn, regardless
|
|
219
|
+
// of the overall size of the accumulated ops in the batch.
|
|
220
|
+
// However, it is more efficient to flush these ops faster, preferably
|
|
221
|
+
// after they reach a size which would benefit from compression.
|
|
222
|
+
if (
|
|
223
|
+
this.blobAttachBatch.contentSizeInBytes >=
|
|
224
|
+
this.params.config.compressionOptions.minimumBatchSizeInBytes
|
|
225
|
+
) {
|
|
226
|
+
this.flushInternal(this.blobAttachBatch);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private addMessageToBatchManager(batchManager: BatchManager, message: BatchMessage) {
|
|
231
|
+
if (
|
|
232
|
+
!batchManager.push(
|
|
233
|
+
message,
|
|
234
|
+
this.isContextReentrant(),
|
|
235
|
+
this.params.getCurrentSequenceNumbers().clientSequenceNumber,
|
|
236
|
+
)
|
|
237
|
+
) {
|
|
238
|
+
throw new GenericError("BatchTooLarge", /* error */ undefined, {
|
|
239
|
+
opSize: message.contents?.length ?? 0,
|
|
240
|
+
batchSize: batchManager.contentSizeInBytes,
|
|
241
|
+
count: batchManager.length,
|
|
242
|
+
limit: batchManager.options.hardLimit,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
226
247
|
public flush() {
|
|
227
248
|
if (this.isContextReentrant()) {
|
|
228
249
|
const error = new UsageError("Flushing is not supported inside DDS event handlers");
|
|
@@ -235,16 +256,17 @@ export class Outbox {
|
|
|
235
256
|
|
|
236
257
|
private flushAll() {
|
|
237
258
|
this.flushInternal(this.attachFlowBatch);
|
|
259
|
+
this.flushInternal(this.blobAttachBatch, true /* disableGroupedBatching */);
|
|
238
260
|
this.flushInternal(this.mainBatch);
|
|
239
261
|
}
|
|
240
262
|
|
|
241
|
-
private flushInternal(batchManager: BatchManager) {
|
|
263
|
+
private flushInternal(batchManager: BatchManager, disableGroupedBatching: boolean = false) {
|
|
242
264
|
if (batchManager.empty) {
|
|
243
265
|
return;
|
|
244
266
|
}
|
|
245
267
|
|
|
246
268
|
const rawBatch = batchManager.popBatch();
|
|
247
|
-
if (rawBatch.hasReentrantOps === true && this.params.config.
|
|
269
|
+
if (rawBatch.hasReentrantOps === true && this.params.config.enableGroupedBatching) {
|
|
248
270
|
assert(!this.rebasing, 0x6fa /* A rebased batch should never have reentrant ops */);
|
|
249
271
|
// If a batch contains reentrant ops (ops created as a result from processing another op)
|
|
250
272
|
// it needs to be rebased so that we can ensure consistent reference sequence numbers
|
|
@@ -253,7 +275,7 @@ export class Outbox {
|
|
|
253
275
|
return;
|
|
254
276
|
}
|
|
255
277
|
|
|
256
|
-
const processedBatch = this.compressBatch(rawBatch);
|
|
278
|
+
const processedBatch = this.compressBatch(rawBatch, disableGroupedBatching);
|
|
257
279
|
this.sendBatch(processedBatch);
|
|
258
280
|
|
|
259
281
|
this.persistBatch(rawBatch.content);
|
|
@@ -298,7 +320,7 @@ export class Outbox {
|
|
|
298
320
|
return this.params.opReentrancy() && !this.rebasing;
|
|
299
321
|
}
|
|
300
322
|
|
|
301
|
-
private compressBatch(batch: IBatch): IBatch {
|
|
323
|
+
private compressBatch(batch: IBatch, disableGroupedBatching: boolean): IBatch {
|
|
302
324
|
if (
|
|
303
325
|
batch.content.length === 0 ||
|
|
304
326
|
this.params.config.compressionOptions === undefined ||
|
|
@@ -307,11 +329,11 @@ export class Outbox {
|
|
|
307
329
|
this.params.containerContext.submitBatchFn === undefined
|
|
308
330
|
) {
|
|
309
331
|
// Nothing to do if the batch is empty or if compression is disabled or not supported, or if we don't need to compress
|
|
310
|
-
return this.params.groupingManager.groupBatch(batch);
|
|
332
|
+
return disableGroupedBatching ? batch : this.params.groupingManager.groupBatch(batch);
|
|
311
333
|
}
|
|
312
334
|
|
|
313
335
|
const compressedBatch = this.params.compressor.compressBatch(
|
|
314
|
-
this.params.groupingManager.groupBatch(batch),
|
|
336
|
+
disableGroupedBatching ? batch : this.params.groupingManager.groupBatch(batch),
|
|
315
337
|
);
|
|
316
338
|
|
|
317
339
|
if (this.params.splitter.isBatchChunkingEnabled) {
|
|
@@ -413,6 +435,7 @@ export class Outbox {
|
|
|
413
435
|
return {
|
|
414
436
|
mainBatch: this.mainBatch.checkpoint(),
|
|
415
437
|
attachFlowBatch: this.attachFlowBatch.checkpoint(),
|
|
438
|
+
blobAttachBatch: this.blobAttachBatch.checkpoint(),
|
|
416
439
|
};
|
|
417
440
|
}
|
|
418
441
|
}
|
|
@@ -120,7 +120,11 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolea
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// legacy op format?
|
|
123
|
-
if
|
|
123
|
+
// TODO: Unsure if this is a real format we should be concerned with. There doesn't appear to be anything prepared to handle the address member.
|
|
124
|
+
if (
|
|
125
|
+
(message.contents as { address?: unknown }).address !== undefined &&
|
|
126
|
+
(message.contents as { type?: unknown }).type === undefined
|
|
127
|
+
) {
|
|
124
128
|
message.type = ContainerMessageType.FluidDataStoreOp;
|
|
125
129
|
} else {
|
|
126
130
|
// new format
|
package/src/packageVersion.ts
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IDisposable } from "@fluidframework/
|
|
7
|
-
import { assert
|
|
6
|
+
import { IDisposable } from "@fluidframework/common-definitions";
|
|
7
|
+
import { assert } from "@fluidframework/common-utils";
|
|
8
8
|
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
9
9
|
import { DataProcessingError } from "@fluidframework/container-utils";
|
|
10
|
+
import { Lazy } from "@fluidframework/core-utils";
|
|
10
11
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
11
12
|
import Deque from "double-ended-queue";
|
|
12
13
|
import { ContainerMessageType } from "./containerRuntime";
|
|
13
14
|
import { pkgVersion } from "./packageVersion";
|
|
15
|
+
import { IBatchMetadata } from "./metadata";
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* ! TODO: Remove this interface in "2.0.0-internal.7.0.0" once we only read IPendingMessageNew
|
|
@@ -209,9 +211,13 @@ export class PendingStateManager implements IDisposable {
|
|
|
209
211
|
}
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
try {
|
|
215
|
+
// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
|
|
216
|
+
const localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);
|
|
217
|
+
nextMessage.localOpMetadata = localOpMetadata;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw DataProcessingError.wrapIfUnrecognized(error, "applyStashedOp", nextMessage);
|
|
220
|
+
}
|
|
215
221
|
|
|
216
222
|
// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
|
|
217
223
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -264,7 +270,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
264
270
|
*/
|
|
265
271
|
private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {
|
|
266
272
|
// This message is the first in a batch if the "batch" property on the metadata is set to true
|
|
267
|
-
if (message.metadata?.batch) {
|
|
273
|
+
if ((message.metadata as IBatchMetadata | undefined)?.batch) {
|
|
268
274
|
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
269
275
|
assert(
|
|
270
276
|
!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,
|
|
@@ -292,10 +298,12 @@ export class PendingStateManager implements IDisposable {
|
|
|
292
298
|
0x16d /* "There is no pending batch begin message" */,
|
|
293
299
|
);
|
|
294
300
|
|
|
295
|
-
const batchEndMetadata = message.metadata?.batch;
|
|
301
|
+
const batchEndMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;
|
|
296
302
|
if (this.pendingMessages.isEmpty() || batchEndMetadata === false) {
|
|
297
303
|
// Get the batch begin metadata from the first message in the batch.
|
|
298
|
-
const batchBeginMetadata =
|
|
304
|
+
const batchBeginMetadata = (
|
|
305
|
+
this.pendingBatchBeginMessage.metadata as IBatchMetadata | undefined
|
|
306
|
+
)?.batch;
|
|
299
307
|
|
|
300
308
|
// There could be just a single message in the batch. If so, it should not have any batch metadata. If there
|
|
301
309
|
// are multiple messages in the batch, verify that we got the correct batch begin and end metadata.
|
|
@@ -313,7 +321,10 @@ export class PendingStateManager implements IDisposable {
|
|
|
313
321
|
message,
|
|
314
322
|
{
|
|
315
323
|
runtimeVersion: pkgVersion,
|
|
316
|
-
batchClientId:
|
|
324
|
+
batchClientId:
|
|
325
|
+
this.pendingBatchBeginMessage.clientId === null
|
|
326
|
+
? "null"
|
|
327
|
+
: this.pendingBatchBeginMessage.clientId,
|
|
317
328
|
clientId: this.stateHandler.clientId(),
|
|
318
329
|
hasBatchStart: batchBeginMetadata === true,
|
|
319
330
|
hasBatchEnd: batchEndMetadata === false,
|
package/src/scheduleManager.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "@fluidframework/container-utils";
|
|
16
16
|
import { DeltaScheduler } from "./deltaScheduler";
|
|
17
17
|
import { pkgVersion } from "./packageVersion";
|
|
18
|
+
import { IBatchMetadata } from "./metadata";
|
|
18
19
|
|
|
19
20
|
type IRuntimeMessageMetadata =
|
|
20
21
|
| undefined
|
|
@@ -61,7 +62,9 @@ export class ScheduleManager {
|
|
|
61
62
|
this.deltaScheduler.batchBegin(message);
|
|
62
63
|
|
|
63
64
|
const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
|
|
64
|
-
this
|
|
65
|
+
// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
67
|
+
this.batchClientId = batch ? (message.clientId as string) : undefined;
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -127,7 +130,9 @@ class ScheduleManagerCore {
|
|
|
127
130
|
|
|
128
131
|
// Set the batch flag to false on the last message to indicate the end of the send batch
|
|
129
132
|
const lastMessage = messages[messages.length - 1];
|
|
130
|
-
|
|
133
|
+
// TODO: It's not clear if this shallow clone is required, as opposed to just setting "batch" to false.
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
135
|
+
lastMessage.metadata = { ...(lastMessage.metadata as any), batch: false };
|
|
131
136
|
});
|
|
132
137
|
|
|
133
138
|
// Listen for updates and peek at the inbound
|
|
@@ -185,7 +190,7 @@ class ScheduleManagerCore {
|
|
|
185
190
|
{
|
|
186
191
|
type: message.type,
|
|
187
192
|
contentType: typeof message.contents,
|
|
188
|
-
batch: message.metadata?.batch,
|
|
193
|
+
batch: (message.metadata as IBatchMetadata | undefined)?.batch,
|
|
189
194
|
compression: message.compression,
|
|
190
195
|
pauseSeqNum: this.pauseSequenceNumber,
|
|
191
196
|
},
|
|
@@ -263,7 +268,8 @@ class ScheduleManagerCore {
|
|
|
263
268
|
message,
|
|
264
269
|
{
|
|
265
270
|
runtimeVersion: pkgVersion,
|
|
266
|
-
batchClientId:
|
|
271
|
+
batchClientId:
|
|
272
|
+
this.currentBatchClientId === null ? "null" : this.currentBatchClientId,
|
|
267
273
|
pauseSequenceNumber: this.pauseSequenceNumber,
|
|
268
274
|
localBatch: this.currentBatchClientId === this.getClientId(),
|
|
269
275
|
messageType: message.type,
|
|
@@ -297,7 +303,8 @@ class ScheduleManagerCore {
|
|
|
297
303
|
) {
|
|
298
304
|
throw new DataCorruptionError("OpBatchIncomplete", {
|
|
299
305
|
runtimeVersion: pkgVersion,
|
|
300
|
-
batchClientId:
|
|
306
|
+
batchClientId:
|
|
307
|
+
this.currentBatchClientId === null ? "null" : this.currentBatchClientId,
|
|
301
308
|
pauseSequenceNumber: this.pauseSequenceNumber,
|
|
302
309
|
localBatch: this.currentBatchClientId === this.getClientId(),
|
|
303
310
|
localMessage: message.clientId === this.getClientId(),
|
|
@@ -321,7 +328,9 @@ class ScheduleManagerCore {
|
|
|
321
328
|
0x29f /* "we should be processing ops when there is no active batch" */,
|
|
322
329
|
);
|
|
323
330
|
this.pauseSequenceNumber = message.sequenceNumber;
|
|
324
|
-
this
|
|
331
|
+
// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
|
|
332
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
333
|
+
this.currentBatchClientId = message.clientId as string;
|
|
325
334
|
// Start of the batch
|
|
326
335
|
// Only pause processing if queue has no other ops!
|
|
327
336
|
// If there are any other ops in the queue, processing will be stopped when they are processed!
|