@fluidframework/container-runtime 2.1.0-276326 → 2.1.0-281041
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/README.md +74 -21
- package/api-extractor/api-extractor.current.json +5 -0
- package/api-extractor/api-extractor.legacy.json +1 -1
- package/api-extractor.json +1 -1
- package/api-report/container-runtime.legacy.public.api.md +9 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
- package/dist/blobManager/blobManager.d.ts.map +1 -0
- package/dist/{blobManager.js → blobManager/blobManager.js} +42 -83
- package/dist/blobManager/blobManager.js.map +1 -0
- package/dist/blobManager/blobManagerSnapSum.d.ts +30 -0
- package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -0
- package/dist/blobManager/blobManagerSnapSum.js +82 -0
- package/dist/blobManager/blobManagerSnapSum.js.map +1 -0
- package/dist/blobManager/index.d.ts +7 -0
- package/dist/blobManager/index.d.ts.map +1 -0
- package/dist/blobManager/index.js +16 -0
- package/dist/blobManager/index.js.map +1 -0
- package/dist/channelCollection.d.ts +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +40 -8
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +15 -10
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +199 -162
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +5 -0
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +16 -5
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +16 -10
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +4 -2
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +12 -0
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +3 -2
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +6 -6
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -1
- package/dist/metadata.d.ts +7 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +6 -0
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +8 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +37 -16
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +1 -1
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +12 -8
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +14 -11
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +11 -6
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +22 -6
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +43 -21
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +22 -6
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +59 -9
- 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 +39 -13
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +98 -33
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/scheduleManager.js +4 -0
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/index.d.ts +1 -1
- package/dist/summary/index.d.ts.map +1 -1
- package/dist/summary/index.js +1 -2
- package/dist/summary/index.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -0
- package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts +0 -1
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js +7 -4
- package/dist/summary/summaryFormat.js.map +1 -1
- package/internal.d.ts +1 -1
- package/legacy.d.ts +1 -1
- package/lib/{blobManager.d.ts → blobManager/blobManager.d.ts} +19 -29
- package/lib/blobManager/blobManager.d.ts.map +1 -0
- package/lib/{blobManager.js → blobManager/blobManager.js} +40 -83
- package/lib/blobManager/blobManager.js.map +1 -0
- package/lib/blobManager/blobManagerSnapSum.d.ts +30 -0
- package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -0
- package/lib/blobManager/blobManagerSnapSum.js +75 -0
- package/lib/blobManager/blobManagerSnapSum.js.map +1 -0
- package/lib/blobManager/index.d.ts +7 -0
- package/lib/blobManager/index.d.ts.map +1 -0
- package/lib/blobManager/index.js +7 -0
- package/lib/blobManager/index.js.map +1 -0
- package/lib/channelCollection.d.ts +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +40 -8
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +15 -10
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +149 -112
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +5 -0
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +17 -6
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +16 -10
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +4 -2
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +12 -0
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +3 -2
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +6 -6
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -1
- package/lib/metadata.d.ts +7 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js +4 -1
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +8 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +35 -15
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +1 -1
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +12 -8
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +14 -11
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +11 -6
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +22 -6
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +44 -22
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +22 -6
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +57 -7
- 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 +39 -13
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +99 -34
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/scheduleManager.js +4 -0
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/index.d.ts +1 -1
- package/lib/summary/index.d.ts.map +1 -1
- package/lib/summary/index.js +1 -1
- package/lib/summary/index.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -0
- package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts +0 -1
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryFormat.js +5 -2
- package/lib/summary/summaryFormat.js.map +1 -1
- package/package.json +49 -34
- package/src/{blobManager.ts → blobManager/blobManager.ts} +57 -123
- package/src/blobManager/blobManagerSnapSum.ts +133 -0
- package/src/blobManager/index.ts +19 -0
- package/src/channelCollection.ts +48 -11
- package/src/containerRuntime.ts +213 -158
- package/src/dataStoreContext.ts +30 -6
- package/src/gc/garbageCollection.ts +17 -12
- package/src/gc/gcDefinitions.ts +7 -2
- package/src/gc/gcHelpers.ts +18 -6
- package/src/gc/gcTelemetry.ts +20 -8
- package/src/index.ts +1 -1
- package/src/metadata.ts +11 -1
- package/src/opLifecycle/README.md +0 -8
- package/src/opLifecycle/batchManager.ts +46 -16
- package/src/opLifecycle/definitions.ts +1 -1
- package/src/opLifecycle/index.ts +8 -1
- package/src/opLifecycle/opCompressor.ts +12 -8
- package/src/opLifecycle/opGroupingManager.ts +14 -11
- package/src/opLifecycle/opSplitter.ts +10 -6
- package/src/opLifecycle/outbox.ts +64 -26
- package/src/opLifecycle/remoteMessageProcessor.ts +84 -11
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +177 -60
- package/src/scheduleManager.ts +6 -2
- package/src/summary/README.md +81 -0
- package/src/summary/index.ts +0 -1
- package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
- package/src/summary/summaryFormat.ts +4 -2
- package/src/summary/summaryFormats.md +69 -8
- package/tsconfig.json +0 -1
- package/dist/blobManager.d.ts.map +0 -1
- package/dist/blobManager.js.map +0 -1
- package/lib/blobManager.d.ts.map +0 -1
- package/lib/blobManager.js.map +0 -1
- package/src/summary/images/appTree.png +0 -0
- package/src/summary/images/protocolAndAppTree.png +0 -0
- package/src/summary/images/summaryTree.png +0 -0
|
@@ -59,27 +59,28 @@ export class OpGroupingManager {
|
|
|
59
59
|
public groupBatch(batch: IBatch): IBatch<[BatchMessage]> {
|
|
60
60
|
assert(this.shouldGroup(batch), 0x946 /* cannot group the provided batch */);
|
|
61
61
|
|
|
62
|
-
if (batch.
|
|
62
|
+
if (batch.messages.length >= 1000) {
|
|
63
63
|
this.logger.sendTelemetryEvent({
|
|
64
64
|
eventName: "GroupLargeBatch",
|
|
65
|
-
length: batch.
|
|
65
|
+
length: batch.messages.length,
|
|
66
66
|
threshold: this.config.opCountThreshold,
|
|
67
67
|
reentrant: batch.hasReentrantOps,
|
|
68
|
-
|
|
68
|
+
// Non null asserting here because of the length check above
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
70
|
+
referenceSequenceNumber: batch.messages[0]!.referenceSequenceNumber,
|
|
69
71
|
});
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
for (const message of batch.
|
|
74
|
+
for (const message of batch.messages) {
|
|
73
75
|
if (message.metadata) {
|
|
74
|
-
const
|
|
75
|
-
assert(keys.length
|
|
76
|
-
assert(keys.length === 0 || keys[0] === "batch", 0x5de /* unexpected op metadata */);
|
|
76
|
+
const { batch: _batch, batchId, ...rest } = message.metadata;
|
|
77
|
+
assert(Object.keys(rest).length === 0, 0x5dd /* cannot group ops with metadata */);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
const serializedContent = JSON.stringify({
|
|
81
82
|
type: OpGroupingManager.groupedBatchOp,
|
|
82
|
-
contents: batch.
|
|
83
|
+
contents: batch.messages.map<IGroupedMessage>((message) => ({
|
|
83
84
|
contents: message.contents === undefined ? undefined : JSON.parse(message.contents),
|
|
84
85
|
metadata: message.metadata,
|
|
85
86
|
compression: message.compression,
|
|
@@ -88,10 +89,12 @@ export class OpGroupingManager {
|
|
|
88
89
|
|
|
89
90
|
const groupedBatch: IBatch<[BatchMessage]> = {
|
|
90
91
|
...batch,
|
|
91
|
-
|
|
92
|
+
messages: [
|
|
92
93
|
{
|
|
93
94
|
metadata: undefined,
|
|
94
|
-
|
|
95
|
+
// TODO why are we non null asserting here?
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
97
|
+
referenceSequenceNumber: batch.messages[0]!.referenceSequenceNumber,
|
|
95
98
|
contents: serializedContent,
|
|
96
99
|
},
|
|
97
100
|
],
|
|
@@ -118,7 +121,7 @@ export class OpGroupingManager {
|
|
|
118
121
|
// Grouped batching must be enabled
|
|
119
122
|
this.config.groupedBatchingEnabled &&
|
|
120
123
|
// The number of ops in the batch must surpass the configured threshold
|
|
121
|
-
batch.
|
|
124
|
+
batch.messages.length >= this.config.opCountThreshold &&
|
|
122
125
|
// Support for reentrant batches must be explicitly enabled
|
|
123
126
|
(this.config.reentrantBatchGroupingEnabled || batch.hasReentrantOps !== true)
|
|
124
127
|
);
|
|
@@ -121,7 +121,7 @@ export class OpSplitter {
|
|
|
121
121
|
public splitFirstBatchMessage(batch: IBatch): IBatch {
|
|
122
122
|
assert(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);
|
|
123
123
|
assert(
|
|
124
|
-
batch.contentSizeInBytes > 0 && batch.
|
|
124
|
+
batch.contentSizeInBytes > 0 && batch.messages.length > 0,
|
|
125
125
|
0x514 /* Batch needs to be non-empty */,
|
|
126
126
|
);
|
|
127
127
|
assert(
|
|
@@ -134,13 +134,15 @@ export class OpSplitter {
|
|
|
134
134
|
0x516 /* Chunk size needs to be smaller than the max batch size */,
|
|
135
135
|
);
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
// Non null asserting here because of the length check above
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
139
|
+
const firstMessage = batch.messages[0]!; // we expect this to be the large compressed op, which needs to be split
|
|
138
140
|
assert(
|
|
139
141
|
(firstMessage.contents?.length ?? 0) >= this.chunkSizeInBytes,
|
|
140
142
|
0x518 /* First message in the batch needs to be chunkable */,
|
|
141
143
|
);
|
|
142
144
|
|
|
143
|
-
const restOfMessages = batch.
|
|
145
|
+
const restOfMessages = batch.messages.slice(1); // we expect these to be empty ops, created to reserve sequence numbers
|
|
144
146
|
const socketSize = estimateSocketSize(batch);
|
|
145
147
|
const chunks = splitOp(
|
|
146
148
|
firstMessage,
|
|
@@ -163,7 +165,9 @@ export class OpSplitter {
|
|
|
163
165
|
// The last chunk will be part of the new batch and needs to
|
|
164
166
|
// preserve the batch metadata of the original batch
|
|
165
167
|
const lastChunk = chunkToBatchMessage(
|
|
166
|
-
|
|
168
|
+
// Non null asserting here because of the length assert above
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
170
|
+
chunks[chunks.length - 1]!,
|
|
167
171
|
batch.referenceSequenceNumber,
|
|
168
172
|
{ batch: firstMessage.metadata?.batch },
|
|
169
173
|
);
|
|
@@ -171,7 +175,7 @@ export class OpSplitter {
|
|
|
171
175
|
this.logger.sendPerformanceEvent({
|
|
172
176
|
// Used to be "Chunked compressed batch"
|
|
173
177
|
eventName: "CompressedChunkedBatch",
|
|
174
|
-
length: batch.
|
|
178
|
+
length: batch.messages.length,
|
|
175
179
|
sizeInBytes: batch.contentSizeInBytes,
|
|
176
180
|
chunks: chunks.length,
|
|
177
181
|
chunkSizeInBytes: this.chunkSizeInBytes,
|
|
@@ -179,7 +183,7 @@ export class OpSplitter {
|
|
|
179
183
|
});
|
|
180
184
|
|
|
181
185
|
return {
|
|
182
|
-
|
|
186
|
+
messages: [lastChunk, ...restOfMessages],
|
|
183
187
|
contentSizeInBytes: lastChunk.contents?.length ?? 0,
|
|
184
188
|
referenceSequenceNumber: batch.referenceSequenceNumber,
|
|
185
189
|
};
|
|
@@ -9,19 +9,20 @@ import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
|
9
9
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
10
10
|
import {
|
|
11
11
|
GenericError,
|
|
12
|
-
MonitoringContext,
|
|
13
12
|
UsageError,
|
|
14
|
-
|
|
13
|
+
createChildLogger,
|
|
14
|
+
type ITelemetryLoggerExt,
|
|
15
15
|
} from "@fluidframework/telemetry-utils/internal";
|
|
16
16
|
|
|
17
17
|
import { ICompressionRuntimeOptions } from "../containerRuntime.js";
|
|
18
|
-
import {
|
|
18
|
+
import { PendingMessageResubmitData, PendingStateManager } from "../pendingStateManager.js";
|
|
19
19
|
|
|
20
20
|
import {
|
|
21
21
|
BatchManager,
|
|
22
22
|
BatchSequenceNumbers,
|
|
23
23
|
estimateSocketSize,
|
|
24
24
|
sequenceNumbersMatch,
|
|
25
|
+
type BatchId,
|
|
25
26
|
} from "./batchManager.js";
|
|
26
27
|
import { BatchMessage, IBatch, IBatchCheckpoint } from "./definitions.js";
|
|
27
28
|
import { OpCompressor } from "./opCompressor.js";
|
|
@@ -48,7 +49,7 @@ export interface IOutboxParameters {
|
|
|
48
49
|
readonly logger: ITelemetryBaseLogger;
|
|
49
50
|
readonly groupingManager: OpGroupingManager;
|
|
50
51
|
readonly getCurrentSequenceNumbers: () => BatchSequenceNumbers;
|
|
51
|
-
readonly reSubmit: (message:
|
|
52
|
+
readonly reSubmit: (message: PendingMessageResubmitData) => void;
|
|
52
53
|
readonly opReentrancy: () => boolean;
|
|
53
54
|
readonly closeContainer: (error?: ICriticalContainerError) => void;
|
|
54
55
|
}
|
|
@@ -87,8 +88,15 @@ export function getLongStack<T>(action: () => T, length: number = 50): T {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
/**
|
|
92
|
+
* The Outbox collects messages submitted by the ContainerRuntime into a batch,
|
|
93
|
+
* and then flushes the batch when requested.
|
|
94
|
+
*
|
|
95
|
+
* @remarks There are actually multiple independent batches (some are for a specific message type),
|
|
96
|
+
* to support slight variation in semantics for each batch (e.g. support for rebasing or grouping).
|
|
97
|
+
*/
|
|
90
98
|
export class Outbox {
|
|
91
|
-
private readonly
|
|
99
|
+
private readonly logger: ITelemetryLoggerExt;
|
|
92
100
|
private readonly mainBatch: BatchManager;
|
|
93
101
|
private readonly blobAttachBatch: BatchManager;
|
|
94
102
|
private readonly idAllocationBatch: BatchManager;
|
|
@@ -105,7 +113,8 @@ export class Outbox {
|
|
|
105
113
|
private mismatchedOpsReported = 0;
|
|
106
114
|
|
|
107
115
|
constructor(private readonly params: IOutboxParameters) {
|
|
108
|
-
this.
|
|
116
|
+
this.logger = createChildLogger({ logger: params.logger, namespace: "Outbox" });
|
|
117
|
+
|
|
109
118
|
const isCompressionEnabled =
|
|
110
119
|
this.params.config.compressionOptions.minimumBatchSizeInBytes !==
|
|
111
120
|
Number.POSITIVE_INFINITY;
|
|
@@ -158,7 +167,7 @@ export class Outbox {
|
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
if (++this.mismatchedOpsReported <= this.maxMismatchedOpsToReport) {
|
|
161
|
-
this.
|
|
170
|
+
this.logger.sendTelemetryEvent(
|
|
162
171
|
{
|
|
163
172
|
category: this.params.config.disablePartialFlush ? "error" : "generic",
|
|
164
173
|
eventName: "ReferenceSequenceNumberMismatch",
|
|
@@ -225,28 +234,48 @@ export class Outbox {
|
|
|
225
234
|
}
|
|
226
235
|
}
|
|
227
236
|
|
|
228
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Flush all the batches to the ordering service.
|
|
239
|
+
* This method is expected to be called at the end of a batch.
|
|
240
|
+
* @param resubmittingBatchId - If defined, indicates this is a resubmission of a batch
|
|
241
|
+
* with the given Batch ID, which must be preserved
|
|
242
|
+
*/
|
|
243
|
+
public flush(resubmittingBatchId?: BatchId) {
|
|
229
244
|
if (this.isContextReentrant()) {
|
|
230
245
|
const error = new UsageError("Flushing is not supported inside DDS event handlers");
|
|
231
246
|
this.params.closeContainer(error);
|
|
232
247
|
throw error;
|
|
233
248
|
}
|
|
234
249
|
|
|
235
|
-
this.flushAll();
|
|
250
|
+
this.flushAll(resubmittingBatchId);
|
|
236
251
|
}
|
|
237
252
|
|
|
238
|
-
private flushAll() {
|
|
253
|
+
private flushAll(resubmittingBatchId?: BatchId) {
|
|
254
|
+
// Don't use resubmittingBatchId for idAllocationBatch.
|
|
255
|
+
// ID Allocation messages are not directly resubmitted so we don't want to reuse the batch ID.
|
|
239
256
|
this.flushInternal(this.idAllocationBatch);
|
|
240
|
-
this.flushInternal(
|
|
241
|
-
|
|
257
|
+
this.flushInternal(
|
|
258
|
+
this.blobAttachBatch,
|
|
259
|
+
true /* disableGroupedBatching */,
|
|
260
|
+
resubmittingBatchId,
|
|
261
|
+
);
|
|
262
|
+
this.flushInternal(
|
|
263
|
+
this.mainBatch,
|
|
264
|
+
false /* disableGroupedBatching */,
|
|
265
|
+
resubmittingBatchId,
|
|
266
|
+
);
|
|
242
267
|
}
|
|
243
268
|
|
|
244
|
-
private flushInternal(
|
|
269
|
+
private flushInternal(
|
|
270
|
+
batchManager: BatchManager,
|
|
271
|
+
disableGroupedBatching: boolean = false,
|
|
272
|
+
resubmittingBatchId?: BatchId,
|
|
273
|
+
) {
|
|
245
274
|
if (batchManager.empty) {
|
|
246
275
|
return;
|
|
247
276
|
}
|
|
248
277
|
|
|
249
|
-
const rawBatch = batchManager.popBatch();
|
|
278
|
+
const rawBatch = batchManager.popBatch(resubmittingBatchId);
|
|
250
279
|
const shouldGroup =
|
|
251
280
|
!disableGroupedBatching && this.params.groupingManager.shouldGroup(rawBatch);
|
|
252
281
|
if (batchManager.options.canRebase && rawBatch.hasReentrantOps === true && shouldGroup) {
|
|
@@ -267,9 +296,13 @@ export class Outbox {
|
|
|
267
296
|
shouldGroup ? this.params.groupingManager.groupBatch(rawBatch) : rawBatch,
|
|
268
297
|
);
|
|
269
298
|
clientSequenceNumber = this.sendBatch(processedBatch);
|
|
299
|
+
assert(
|
|
300
|
+
clientSequenceNumber === undefined || clientSequenceNumber >= 0,
|
|
301
|
+
"unexpected negative clientSequenceNumber (empty batch should yield undefined)",
|
|
302
|
+
);
|
|
270
303
|
}
|
|
271
304
|
|
|
272
|
-
this.params.pendingStateManager.onFlushBatch(rawBatch.
|
|
305
|
+
this.params.pendingStateManager.onFlushBatch(rawBatch.messages, clientSequenceNumber);
|
|
273
306
|
}
|
|
274
307
|
|
|
275
308
|
/**
|
|
@@ -283,7 +316,7 @@ export class Outbox {
|
|
|
283
316
|
assert(batchManager.options.canRebase, 0x9a7 /* BatchManager does not support rebase */);
|
|
284
317
|
|
|
285
318
|
this.rebasing = true;
|
|
286
|
-
for (const message of rawBatch.
|
|
319
|
+
for (const message of rawBatch.messages) {
|
|
287
320
|
this.params.reSubmit({
|
|
288
321
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
289
322
|
content: message.contents!,
|
|
@@ -293,10 +326,10 @@ export class Outbox {
|
|
|
293
326
|
}
|
|
294
327
|
|
|
295
328
|
if (this.batchRebasesToReport > 0) {
|
|
296
|
-
this.
|
|
329
|
+
this.logger.sendTelemetryEvent(
|
|
297
330
|
{
|
|
298
331
|
eventName: "BatchRebase",
|
|
299
|
-
length: rawBatch.
|
|
332
|
+
length: rawBatch.messages.length,
|
|
300
333
|
referenceSequenceNumber: rawBatch.referenceSequenceNumber,
|
|
301
334
|
},
|
|
302
335
|
new UsageError("BatchRebase"),
|
|
@@ -323,7 +356,7 @@ export class Outbox {
|
|
|
323
356
|
*/
|
|
324
357
|
private compressBatch(batch: IBatch): IBatch {
|
|
325
358
|
if (
|
|
326
|
-
batch.
|
|
359
|
+
batch.messages.length === 0 ||
|
|
327
360
|
this.params.config.compressionOptions === undefined ||
|
|
328
361
|
this.params.config.compressionOptions.minimumBatchSizeInBytes >
|
|
329
362
|
batch.contentSizeInBytes ||
|
|
@@ -345,7 +378,7 @@ export class Outbox {
|
|
|
345
378
|
throw new GenericError("BatchTooLarge", /* error */ undefined, {
|
|
346
379
|
batchSize: batch.contentSizeInBytes,
|
|
347
380
|
compressedBatchSize: compressedBatch.contentSizeInBytes,
|
|
348
|
-
count: compressedBatch.
|
|
381
|
+
count: compressedBatch.messages.length,
|
|
349
382
|
limit: this.params.config.maxBatchSizeInBytes,
|
|
350
383
|
chunkingEnabled: this.params.splitter.isBatchChunkingEnabled,
|
|
351
384
|
compressionOptions: JSON.stringify(this.params.config.compressionOptions),
|
|
@@ -363,16 +396,16 @@ export class Outbox {
|
|
|
363
396
|
* @returns the clientSequenceNumber of the start of the batch, or undefined if nothing was sent
|
|
364
397
|
*/
|
|
365
398
|
private sendBatch(batch: IBatch) {
|
|
366
|
-
const length = batch.
|
|
399
|
+
const length = batch.messages.length;
|
|
367
400
|
if (length === 0) {
|
|
368
401
|
return undefined; // Nothing submitted
|
|
369
402
|
}
|
|
370
403
|
|
|
371
404
|
const socketSize = estimateSocketSize(batch);
|
|
372
405
|
if (socketSize >= this.params.config.maxBatchSizeInBytes) {
|
|
373
|
-
this.
|
|
406
|
+
this.logger.sendPerformanceEvent({
|
|
374
407
|
eventName: "LargeBatch",
|
|
375
|
-
length: batch.
|
|
408
|
+
length: batch.messages.length,
|
|
376
409
|
sizeInBytes: batch.contentSizeInBytes,
|
|
377
410
|
socketSize,
|
|
378
411
|
});
|
|
@@ -383,7 +416,9 @@ export class Outbox {
|
|
|
383
416
|
// Legacy path - supporting old loader versions. Can be removed only when LTS moves above
|
|
384
417
|
// version that has support for batches (submitBatchFn)
|
|
385
418
|
assert(
|
|
386
|
-
|
|
419
|
+
// Non null asserting here because of the length check above
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
421
|
+
batch.messages[0]!.compression === undefined,
|
|
387
422
|
0x5a6 /* Compression should not have happened if the loader does not support it */,
|
|
388
423
|
);
|
|
389
424
|
|
|
@@ -391,7 +426,7 @@ export class Outbox {
|
|
|
391
426
|
} else {
|
|
392
427
|
assert(batch.referenceSequenceNumber !== undefined, 0x58e /* Batch must not be empty */);
|
|
393
428
|
clientSequenceNumber = this.params.submitBatchFn(
|
|
394
|
-
batch.
|
|
429
|
+
batch.messages.map<IBatchMessage>((message) => ({
|
|
395
430
|
contents: message.contents,
|
|
396
431
|
metadata: message.metadata,
|
|
397
432
|
compression: message.compression,
|
|
@@ -407,7 +442,10 @@ export class Outbox {
|
|
|
407
442
|
return clientSequenceNumber;
|
|
408
443
|
}
|
|
409
444
|
|
|
410
|
-
|
|
445
|
+
/**
|
|
446
|
+
* @returns A checkpoint object per batch that facilitates iterating over the batch messages when rolling back.
|
|
447
|
+
*/
|
|
448
|
+
public getBatchCheckpoints() {
|
|
411
449
|
// This variable is declared with a specific type so that we have a standard import of the IBatchCheckpoint type.
|
|
412
450
|
// When the type is inferred, the generated .d.ts uses a dynamic import which doesn't resolve.
|
|
413
451
|
const mainBatch: IBatchCheckpoint = this.mainBatch.checkpoint();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { assert } from "@fluidframework/core-utils/internal";
|
|
6
7
|
import {
|
|
7
8
|
MessageType,
|
|
8
9
|
ISequencedDocumentMessage,
|
|
@@ -12,9 +13,9 @@ import {
|
|
|
12
13
|
ContainerMessageType,
|
|
13
14
|
type InboundContainerRuntimeMessage,
|
|
14
15
|
type InboundSequencedContainerRuntimeMessage,
|
|
15
|
-
type InboundSequencedContainerRuntimeMessageOrSystemMessage,
|
|
16
16
|
type InboundSequencedRecentlyAddedContainerRuntimeMessage,
|
|
17
17
|
} from "../messageTypes.js";
|
|
18
|
+
import { asBatchMetadata } from "../metadata.js";
|
|
18
19
|
|
|
19
20
|
import { OpDecompressor } from "./opDecompressor.js";
|
|
20
21
|
import { OpGroupingManager, isGroupedBatch } from "./opGroupingManager.js";
|
|
@@ -27,6 +28,15 @@ import { OpSplitter, isChunkedMessage } from "./opSplitter.js";
|
|
|
27
28
|
* @internal
|
|
28
29
|
*/
|
|
29
30
|
export class RemoteMessageProcessor {
|
|
31
|
+
/**
|
|
32
|
+
* Client Sequence Number of the first message in the current batch being processed.
|
|
33
|
+
* If undefined, we are expecting the next message to start a new batch.
|
|
34
|
+
*
|
|
35
|
+
* @remarks For chunked batches, this is the CSN of the "representative" chunk (the final chunk)
|
|
36
|
+
*/
|
|
37
|
+
private batchStartCsn: number | undefined;
|
|
38
|
+
private readonly processorBatch: InboundSequencedContainerRuntimeMessage[] = [];
|
|
39
|
+
|
|
30
40
|
constructor(
|
|
31
41
|
private readonly opSplitter: OpSplitter,
|
|
32
42
|
private readonly opDecompressor: OpDecompressor,
|
|
@@ -42,7 +52,7 @@ export class RemoteMessageProcessor {
|
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
/**
|
|
45
|
-
* Ungroups and Unchunks the runtime ops
|
|
55
|
+
* Ungroups and Unchunks the runtime ops of a batch received over the wire
|
|
46
56
|
* @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized
|
|
47
57
|
* (grouped, compressed, and/or chunked).
|
|
48
58
|
* Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure
|
|
@@ -57,21 +67,24 @@ export class RemoteMessageProcessor {
|
|
|
57
67
|
* 3. If grouped, ungroup the message
|
|
58
68
|
* For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound
|
|
59
69
|
*
|
|
60
|
-
* @returns the unchunked, decompressed, ungrouped, unpacked
|
|
61
|
-
*
|
|
62
|
-
* a singleton array [remoteMessageCopy] is returned
|
|
70
|
+
* @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch
|
|
71
|
+
* or undefined if the batch is not yet complete.
|
|
63
72
|
*/
|
|
64
|
-
public process(
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
public process(remoteMessageCopy: ISequencedDocumentMessage):
|
|
74
|
+
| {
|
|
75
|
+
messages: InboundSequencedContainerRuntimeMessage[];
|
|
76
|
+
batchStartCsn: number;
|
|
77
|
+
}
|
|
78
|
+
| undefined {
|
|
67
79
|
let message = remoteMessageCopy;
|
|
80
|
+
|
|
68
81
|
ensureContentsDeserialized(message);
|
|
69
82
|
|
|
70
83
|
if (isChunkedMessage(message)) {
|
|
71
84
|
const chunkProcessingResult = this.opSplitter.processChunk(message);
|
|
72
85
|
// Only continue further if current chunk is the final chunk
|
|
73
86
|
if (!chunkProcessingResult.isFinalChunk) {
|
|
74
|
-
return
|
|
87
|
+
return;
|
|
75
88
|
}
|
|
76
89
|
// This message will always be compressed
|
|
77
90
|
message = chunkProcessingResult.message;
|
|
@@ -90,12 +103,72 @@ export class RemoteMessageProcessor {
|
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
if (isGroupedBatch(message)) {
|
|
93
|
-
|
|
106
|
+
// We should be awaiting a new batch (batchStartCsn undefined)
|
|
107
|
+
assert(this.batchStartCsn === undefined, "Grouped batch interrupting another batch");
|
|
108
|
+
assert(
|
|
109
|
+
this.processorBatch.length === 0,
|
|
110
|
+
"Processor batch should be empty on grouped batch",
|
|
111
|
+
);
|
|
112
|
+
return {
|
|
113
|
+
messages: this.opGroupingManager.ungroupOp(message).map(unpack),
|
|
114
|
+
batchStartCsn: message.clientSequenceNumber,
|
|
115
|
+
};
|
|
94
116
|
}
|
|
95
117
|
|
|
118
|
+
const batchStartCsn = this.getAndUpdateBatchStartCsn(message);
|
|
119
|
+
|
|
96
120
|
// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked
|
|
97
121
|
unpackRuntimeMessage(message);
|
|
98
|
-
|
|
122
|
+
this.processorBatch.push(message as InboundSequencedContainerRuntimeMessage);
|
|
123
|
+
|
|
124
|
+
// this.batchStartCsn is undefined only if we have processed all messages in the batch.
|
|
125
|
+
// If it's still defined, we're still in the middle of a batch, so we return nothing, letting
|
|
126
|
+
// containerRuntime know that we're waiting for more messages to complete the batch.
|
|
127
|
+
if (this.batchStartCsn !== undefined) {
|
|
128
|
+
// batch not yet complete
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const messages = [...this.processorBatch];
|
|
133
|
+
this.processorBatch.length = 0;
|
|
134
|
+
return {
|
|
135
|
+
messages,
|
|
136
|
+
batchStartCsn,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Based on pre-existing batch tracking info and the current message's batch metadata,
|
|
142
|
+
* this will return the starting CSN for this message's batch, and will also update
|
|
143
|
+
* the batch tracking info (this.batchStartCsn) based on whether we're still mid-batch.
|
|
144
|
+
*/
|
|
145
|
+
private getAndUpdateBatchStartCsn(message: ISequencedDocumentMessage): number {
|
|
146
|
+
const batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;
|
|
147
|
+
if (this.batchStartCsn === undefined) {
|
|
148
|
+
// We are waiting for a new batch
|
|
149
|
+
assert(batchMetadataFlag !== false, "Unexpected batch end marker");
|
|
150
|
+
|
|
151
|
+
// Start of a new multi-message batch
|
|
152
|
+
if (batchMetadataFlag === true) {
|
|
153
|
+
this.batchStartCsn = message.clientSequenceNumber;
|
|
154
|
+
return this.batchStartCsn;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Single-message batch (Since metadata flag is undefined)
|
|
158
|
+
// IMPORTANT: Leave this.batchStartCsn undefined, we're ready for the next batch now.
|
|
159
|
+
return message.clientSequenceNumber;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// We are in the middle or end of an existing multi-message batch. Return the current batchStartCsn
|
|
163
|
+
const batchStartCsn = this.batchStartCsn;
|
|
164
|
+
|
|
165
|
+
assert(batchMetadataFlag !== true, "Unexpected batch start marker");
|
|
166
|
+
if (batchMetadataFlag === false) {
|
|
167
|
+
// Batch end? Then get ready for the next batch to start
|
|
168
|
+
this.batchStartCsn = undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return batchStartCsn;
|
|
99
172
|
}
|
|
100
173
|
}
|
|
101
174
|
|
package/src/packageVersion.ts
CHANGED