@fluidframework/container-runtime 2.2.0 → 2.2.2
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/container-runtime.test-files.tar +0 -0
- package/dist/containerRuntime.d.ts +8 -3
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +27 -15
- package/dist/containerRuntime.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.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +37 -17
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +44 -33
- 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 +13 -13
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +31 -27
- package/dist/pendingStateManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +8 -3
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +27 -15
- package/lib/containerRuntime.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.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +37 -17
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +44 -33
- 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 +13 -13
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +32 -28
- package/lib/pendingStateManager.js.map +1 -1
- package/package.json +19 -23
- package/src/containerRuntime.ts +40 -18
- package/src/opLifecycle/index.ts +2 -1
- package/src/opLifecycle/remoteMessageProcessor.ts +84 -55
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +42 -31
|
@@ -8,6 +8,6 @@ export { Outbox, getLongStack } from "./outbox.js";
|
|
|
8
8
|
export { OpCompressor } from "./opCompressor.js";
|
|
9
9
|
export { OpDecompressor } from "./opDecompressor.js";
|
|
10
10
|
export { OpSplitter, splitOp, isChunkedMessage } from "./opSplitter.js";
|
|
11
|
-
export { ensureContentsDeserialized,
|
|
11
|
+
export { ensureContentsDeserialized, InboundMessageResult, BatchStartInfo, RemoteMessageProcessor, unpackRuntimeMessage, } from "./remoteMessageProcessor.js";
|
|
12
12
|
export { OpGroupingManager, OpGroupingManagerConfig, isGroupedBatch, } from "./opGroupingManager.js";
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,OAAO,EACP,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACN,0BAA0B,EAC1B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,OAAO,EACP,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACN,0BAA0B,EAC1B,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACN,iBAAiB,EACjB,uBAAuB,EACvB,cAAc,GACd,MAAM,wBAAwB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/opLifecycle/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEN,YAAY,EAEZ,kBAAkB,EAClB,eAAe,GAEf,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACN,0BAA0B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/opLifecycle/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEN,YAAY,EAEZ,kBAAkB,EAClB,eAAe,GAEf,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACN,0BAA0B,EAG1B,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACN,iBAAiB,EAEjB,cAAc,GACd,MAAM,wBAAwB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport {\n\tBatchId,\n\tBatchManager,\n\tBatchSequenceNumbers,\n\testimateSocketSize,\n\tgenerateBatchId,\n\tIBatchManagerOptions,\n} from \"./batchManager.js\";\nexport { BatchMessage, IBatch, IBatchCheckpoint, IChunkedOp } from \"./definitions.js\";\nexport { Outbox, getLongStack } from \"./outbox.js\";\nexport { OpCompressor } from \"./opCompressor.js\";\nexport { OpDecompressor } from \"./opDecompressor.js\";\nexport { OpSplitter, splitOp, isChunkedMessage } from \"./opSplitter.js\";\nexport {\n\tensureContentsDeserialized,\n\tInboundMessageResult,\n\tBatchStartInfo,\n\tRemoteMessageProcessor,\n\tunpackRuntimeMessage,\n} from \"./remoteMessageProcessor.js\";\nexport {\n\tOpGroupingManager,\n\tOpGroupingManagerConfig,\n\tisGroupedBatch,\n} from \"./opGroupingManager.js\";\n"]}
|
|
@@ -7,25 +7,51 @@ import { type InboundSequencedContainerRuntimeMessage } from "../messageTypes.js
|
|
|
7
7
|
import { OpDecompressor } from "./opDecompressor.js";
|
|
8
8
|
import { OpGroupingManager } from "./opGroupingManager.js";
|
|
9
9
|
import { OpSplitter } from "./opSplitter.js";
|
|
10
|
-
/**
|
|
11
|
-
export interface
|
|
12
|
-
/** Messages in this batch */
|
|
13
|
-
readonly messages: InboundSequencedContainerRuntimeMessage[];
|
|
10
|
+
/** Info about the batch we learn when we process the first message */
|
|
11
|
+
export interface BatchStartInfo {
|
|
14
12
|
/** Batch ID, if present */
|
|
15
13
|
readonly batchId: string | undefined;
|
|
16
14
|
/** clientId that sent this batch. Used to compute Batch ID if needed */
|
|
17
15
|
readonly clientId: string;
|
|
18
16
|
/**
|
|
19
|
-
* Client Sequence Number of the first message in the batch.
|
|
17
|
+
* Client Sequence Number of the Grouped Batch message, or the first message in the ungrouped batch.
|
|
20
18
|
* Used to compute Batch ID if needed
|
|
21
19
|
*
|
|
22
20
|
* @remarks For chunked batches, this is the CSN of the "representative" chunk (the final chunk).
|
|
23
21
|
* For grouped batches, clientSequenceNumber on messages is overwritten, so we track this original value here.
|
|
24
22
|
*/
|
|
25
23
|
readonly batchStartCsn: number;
|
|
26
|
-
/**
|
|
27
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The first message in the batch, or if the batch is empty, the empty grouped batch message
|
|
26
|
+
* Used for accessing the sequence numbers for the (start of the) batch.
|
|
27
|
+
*
|
|
28
|
+
* @remarks Do not use clientSequenceNumber here, use batchStartCsn instead.
|
|
29
|
+
*/
|
|
30
|
+
readonly keyMessage: ISequencedDocumentMessage;
|
|
28
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Result of processing the next inbound message.
|
|
34
|
+
* Depending on the message and configuration of RemoteMessageProcessor, the result may be:
|
|
35
|
+
* - A full batch of messages (including a single-message batch)
|
|
36
|
+
* - The first message of a multi-message batch
|
|
37
|
+
* - The next message in a multi-message batch
|
|
38
|
+
*/
|
|
39
|
+
export type InboundMessageResult = {
|
|
40
|
+
type: "fullBatch";
|
|
41
|
+
messages: InboundSequencedContainerRuntimeMessage[];
|
|
42
|
+
batchStart: BatchStartInfo;
|
|
43
|
+
length: number;
|
|
44
|
+
} | {
|
|
45
|
+
type: "batchStartingMessage";
|
|
46
|
+
batchStart: BatchStartInfo;
|
|
47
|
+
nextMessage: InboundSequencedContainerRuntimeMessage;
|
|
48
|
+
length?: never;
|
|
49
|
+
} | {
|
|
50
|
+
type: "nextBatchMessage";
|
|
51
|
+
batchEnd?: boolean;
|
|
52
|
+
nextMessage: InboundSequencedContainerRuntimeMessage;
|
|
53
|
+
length?: never;
|
|
54
|
+
};
|
|
29
55
|
/**
|
|
30
56
|
* Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,
|
|
31
57
|
* potentially across numerous inbound ops.
|
|
@@ -36,11 +62,6 @@ export declare class RemoteMessageProcessor {
|
|
|
36
62
|
private readonly opSplitter;
|
|
37
63
|
private readonly opDecompressor;
|
|
38
64
|
private readonly opGroupingManager;
|
|
39
|
-
/**
|
|
40
|
-
* The current batch being received, with details needed to process it.
|
|
41
|
-
*
|
|
42
|
-
* @remarks If undefined, we are expecting the next message to start a new batch.
|
|
43
|
-
*/
|
|
44
65
|
private batchInProgress;
|
|
45
66
|
constructor(opSplitter: OpSplitter, opDecompressor: OpDecompressor, opGroupingManager: OpGroupingManager);
|
|
46
67
|
get partialMessages(): ReadonlyMap<string, string[]>;
|
|
@@ -64,13 +85,12 @@ export declare class RemoteMessageProcessor {
|
|
|
64
85
|
* @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch
|
|
65
86
|
* or undefined if the batch is not yet complete.
|
|
66
87
|
*/
|
|
67
|
-
process(remoteMessageCopy: ISequencedDocumentMessage, logLegacyCase: (codePath: string) => void):
|
|
88
|
+
process(remoteMessageCopy: ISequencedDocumentMessage, logLegacyCase: (codePath: string) => void): InboundMessageResult | undefined;
|
|
68
89
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* @returns batchEnded: true if the batch is now complete, batchEnded: false if more messages are expected
|
|
90
|
+
* Now that the message has been "unwrapped" as to any virtualization (grouping, compression, chunking),
|
|
91
|
+
* inspect the batch metadata flag and determine what kind of result to return.
|
|
72
92
|
*/
|
|
73
|
-
private
|
|
93
|
+
private getResultBasedOnBatchMetadata;
|
|
74
94
|
}
|
|
75
95
|
/**
|
|
76
96
|
* Takes an incoming message and if the contents is a string, JSON.parse's it in place
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remoteMessageProcessor.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAEN,yBAAyB,EACzB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAGN,KAAK,uCAAuC,EAE5C,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAkB,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAoB,MAAM,iBAAiB,CAAC;AAE/D,
|
|
1
|
+
{"version":3,"file":"remoteMessageProcessor.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAEN,yBAAyB,EACzB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAGN,KAAK,uCAAuC,EAE5C,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAkB,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAoB,MAAM,iBAAiB,CAAC;AAE/D,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC9B,2BAA2B;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC7B;IACA,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,uCAAuC,EAAE,CAAC;IACpD,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CACd,GACD;IACA,IAAI,EAAE,sBAAsB,CAAC;IAC7B,UAAU,EAAE,cAAc,CAAC;IAC3B,WAAW,EAAE,uCAAuC,CAAC;IACrD,MAAM,CAAC,EAAE,KAAK,CAAC;CACd,GACD;IACA,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,uCAAuC,CAAC;IACrD,MAAM,CAAC,EAAE,KAAK,CAAC;CACd,CAAC;AAWL;;;;;GAKG;AACH,qBAAa,sBAAsB;IAIjC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IALnC,OAAO,CAAC,eAAe,CAAkB;gBAGvB,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,cAAc,EAC9B,iBAAiB,EAAE,iBAAiB;IAGtD,IAAW,eAAe,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAE1D;IAEM,uBAAuB,CAAC,QAAQ,EAAE,MAAM;IAI/C;;;;;;;;;;;;;;;;;;OAkBG;IACI,OAAO,CACb,iBAAiB,EAAE,yBAAyB,EAC5C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GACvC,oBAAoB,GAAG,SAAS;IAuDnC;;;OAGG;IACH,OAAO,CAAC,6BAA6B;CAiDrC;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACzC,cAAc,EAAE,yBAAyB,EACzC,+BAA+B,EAAE,OAAO,EACxC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GACvC,IAAI,CAmBN;AAyBD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,yBAAyB,EAClC,aAAa,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAe,GAClD,OAAO,CAuBT"}
|
|
@@ -22,6 +22,7 @@ export class RemoteMessageProcessor {
|
|
|
22
22
|
this.opSplitter = opSplitter;
|
|
23
23
|
this.opDecompressor = opDecompressor;
|
|
24
24
|
this.opGroupingManager = opGroupingManager;
|
|
25
|
+
this.batchInProgress = false;
|
|
25
26
|
}
|
|
26
27
|
get partialMessages() {
|
|
27
28
|
return this.opSplitter.chunks;
|
|
@@ -72,62 +73,72 @@ export class RemoteMessageProcessor {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
if (isGroupedBatch(message)) {
|
|
75
|
-
// We should be awaiting a new batch (batchInProgress
|
|
76
|
-
assert(this.batchInProgress
|
|
76
|
+
// We should be awaiting a new batch (batchInProgress false)
|
|
77
|
+
assert(!this.batchInProgress, 0x9d3 /* Grouped batch interrupting another batch */);
|
|
77
78
|
const batchId = asBatchMetadata(message.metadata)?.batchId;
|
|
78
79
|
const groupedMessages = this.opGroupingManager.ungroupOp(message).map(unpack);
|
|
79
80
|
return {
|
|
81
|
+
type: "fullBatch",
|
|
80
82
|
messages: groupedMessages, // Will be [] for an empty batch
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
batchStart: {
|
|
84
|
+
batchStartCsn: message.clientSequenceNumber,
|
|
85
|
+
clientId,
|
|
86
|
+
batchId,
|
|
87
|
+
keyMessage: groupedMessages[0] ?? message, // For an empty batch, this is the empty grouped batch message. Needed for sequence numbers for this batch
|
|
88
|
+
},
|
|
89
|
+
length: groupedMessages.length, // Will be 0 for an empty batch
|
|
86
90
|
};
|
|
87
91
|
}
|
|
88
92
|
// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked
|
|
89
93
|
unpackRuntimeMessage(message, logLegacyCase);
|
|
90
|
-
|
|
91
|
-
if (!batchEnded) {
|
|
92
|
-
// batch not yet complete
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
const completedBatch = this.batchInProgress;
|
|
96
|
-
this.batchInProgress = undefined;
|
|
97
|
-
return completedBatch;
|
|
94
|
+
return this.getResultBasedOnBatchMetadata(message);
|
|
98
95
|
}
|
|
99
96
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* @returns batchEnded: true if the batch is now complete, batchEnded: false if more messages are expected
|
|
97
|
+
* Now that the message has been "unwrapped" as to any virtualization (grouping, compression, chunking),
|
|
98
|
+
* inspect the batch metadata flag and determine what kind of result to return.
|
|
103
99
|
*/
|
|
104
|
-
|
|
100
|
+
getResultBasedOnBatchMetadata(message) {
|
|
105
101
|
const batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;
|
|
106
|
-
if (this.batchInProgress
|
|
102
|
+
if (!this.batchInProgress) {
|
|
107
103
|
// We are waiting for a new batch
|
|
108
104
|
assert(batchMetadataFlag !== false, 0x9d5 /* Unexpected batch end marker */);
|
|
109
105
|
// Start of a new multi-message batch
|
|
110
106
|
if (batchMetadataFlag === true) {
|
|
111
|
-
this.batchInProgress =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
this.batchInProgress = true;
|
|
108
|
+
return {
|
|
109
|
+
type: "batchStartingMessage",
|
|
110
|
+
batchStart: {
|
|
111
|
+
batchId: asBatchMetadata(message.metadata)?.batchId,
|
|
112
|
+
clientId: message.clientId,
|
|
113
|
+
batchStartCsn: message.clientSequenceNumber,
|
|
114
|
+
keyMessage: message,
|
|
115
|
+
},
|
|
116
|
+
nextMessage: message,
|
|
116
117
|
};
|
|
117
|
-
return { batchEnded: false };
|
|
118
118
|
}
|
|
119
119
|
// Single-message batch (Since metadata flag is undefined)
|
|
120
|
-
|
|
120
|
+
return {
|
|
121
|
+
type: "fullBatch",
|
|
121
122
|
messages: [message],
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
batchStart: {
|
|
124
|
+
batchStartCsn: message.clientSequenceNumber,
|
|
125
|
+
clientId: message.clientId,
|
|
126
|
+
batchId: asBatchMetadata(message.metadata)?.batchId,
|
|
127
|
+
keyMessage: message,
|
|
128
|
+
},
|
|
129
|
+
length: 1,
|
|
125
130
|
};
|
|
126
|
-
return { batchEnded: true };
|
|
127
131
|
}
|
|
128
132
|
assert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
// Clear batchInProgress state if the batch is ending
|
|
134
|
+
if (batchMetadataFlag === false) {
|
|
135
|
+
this.batchInProgress = false;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
type: "nextBatchMessage",
|
|
139
|
+
nextMessage: message,
|
|
140
|
+
batchEnd: batchMetadataFlag === false,
|
|
141
|
+
};
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
144
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tMessageType,\n\tISequencedDocumentMessage,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport {\n\tContainerMessageType,\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype InboundSequencedRecentlyAddedContainerRuntimeMessage,\n} from \"../messageTypes.js\";\nimport { asBatchMetadata } from \"../metadata.js\";\n\nimport { OpDecompressor } from \"./opDecompressor.js\";\nimport { OpGroupingManager, isGroupedBatch } from \"./opGroupingManager.js\";\nimport { OpSplitter, isChunkedMessage } from \"./opSplitter.js\";\n\n/** Messages being received as a batch, with details needed to process the batch */\nexport interface InboundBatch {\n\t/** Messages in this batch */\n\treadonly messages: InboundSequencedContainerRuntimeMessage[];\n\t/** Batch ID, if present */\n\treadonly batchId: string | undefined;\n\t/** clientId that sent this batch. Used to compute Batch ID if needed */\n\treadonly clientId: string;\n\t/**\n\t * Client Sequence Number of the first message in the batch.\n\t * Used to compute Batch ID if needed\n\t *\n\t * @remarks For chunked batches, this is the CSN of the \"representative\" chunk (the final chunk).\n\t * For grouped batches, clientSequenceNumber on messages is overwritten, so we track this original value here.\n\t */\n\treadonly batchStartCsn: number;\n\t/** For an empty batch (with no messages), we need to remember the empty grouped batch's sequence number */\n\treadonly emptyBatchSequenceNumber?: number;\n}\n\nfunction assertHasClientId(\n\tmessage: ISequencedDocumentMessage,\n): asserts message is ISequencedDocumentMessage & { clientId: string } {\n\tassert(\n\t\tmessage.clientId !== null,\n\t\t0xa02 /* Server-generated message should not reach RemoteMessageProcessor */,\n\t);\n}\n\n/**\n * Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,\n * potentially across numerous inbound ops.\n *\n * @internal\n */\nexport class RemoteMessageProcessor {\n\t/**\n\t * The current batch being received, with details needed to process it.\n\t *\n\t * @remarks If undefined, we are expecting the next message to start a new batch.\n\t */\n\tprivate batchInProgress: InboundBatch | undefined;\n\n\tconstructor(\n\t\tprivate readonly opSplitter: OpSplitter,\n\t\tprivate readonly opDecompressor: OpDecompressor,\n\t\tprivate readonly opGroupingManager: OpGroupingManager,\n\t) {}\n\n\tpublic get partialMessages(): ReadonlyMap<string, string[]> {\n\t\treturn this.opSplitter.chunks;\n\t}\n\n\tpublic clearPartialMessagesFor(clientId: string) {\n\t\tthis.opSplitter.clearPartialChunks(clientId);\n\t}\n\n\t/**\n\t * Ungroups and Unchunks the runtime ops of a batch received over the wire\n\t * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized\n\t * (grouped, compressed, and/or chunked).\n\t * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure\n\t * depends on this object instance.\n\t * Note remoteMessageCopy.contents (and other object props) MUST not be modified,\n\t * but may be overwritten (as is the case with contents).\n\t *\n\t * Incoming messages will always have compression, chunking, and grouped batching happen in a defined order and that order cannot be changed.\n\t * When processing these messages, the order is:\n\t * 1. If chunked, process the chunk and only continue if this is a final chunk\n\t * 2. If compressed, decompress the message and store for further unrolling of the decompressed content\n\t * 3. If grouped, ungroup the message\n\t * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound\n\t *\n\t * @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch\n\t * or undefined if the batch is not yet complete.\n\t */\n\tpublic process(\n\t\tremoteMessageCopy: ISequencedDocumentMessage,\n\t\tlogLegacyCase: (codePath: string) => void,\n\t): InboundBatch | undefined {\n\t\tlet message = remoteMessageCopy;\n\n\t\tassertHasClientId(message);\n\t\tconst clientId = message.clientId;\n\n\t\tif (isChunkedMessage(message)) {\n\t\t\tconst chunkProcessingResult = this.opSplitter.processChunk(message);\n\t\t\t// Only continue further if current chunk is the final chunk\n\t\t\tif (!chunkProcessingResult.isFinalChunk) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\t// This message will always be compressed\n\t\t\tmessage = chunkProcessingResult.message;\n\t\t}\n\n\t\tif (this.opDecompressor.isCompressedMessage(message)) {\n\t\t\tthis.opDecompressor.decompressAndStore(message);\n\t\t}\n\n\t\tif (this.opDecompressor.currentlyUnrolling) {\n\t\t\tmessage = this.opDecompressor.unroll(message);\n\t\t\t// Need to unpack after unrolling if not a groupedBatch\n\t\t\tif (!isGroupedBatch(message)) {\n\t\t\t\tunpack(message);\n\t\t\t}\n\t\t}\n\n\t\tif (isGroupedBatch(message)) {\n\t\t\t// We should be awaiting a new batch (batchInProgress undefined)\n\t\t\tassert(\n\t\t\t\tthis.batchInProgress === undefined,\n\t\t\t\t0x9d3 /* Grouped batch interrupting another batch */,\n\t\t\t);\n\t\t\tconst batchId = asBatchMetadata(message.metadata)?.batchId;\n\t\t\tconst groupedMessages = this.opGroupingManager.ungroupOp(message).map(unpack);\n\t\t\treturn {\n\t\t\t\tmessages: groupedMessages, // Will be [] for an empty batch\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\tclientId,\n\t\t\t\tbatchId,\n\t\t\t\t// If the batch is empty, we need to return the sequence number aside\n\t\t\t\temptyBatchSequenceNumber:\n\t\t\t\t\tgroupedMessages.length === 0 ? message.sequenceNumber : undefined,\n\t\t\t};\n\t\t}\n\n\t\t// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked\n\t\tunpackRuntimeMessage(message, logLegacyCase);\n\n\t\tconst { batchEnded } = this.addMessageToBatch(\n\t\t\tmessage as InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t\t);\n\n\t\tif (!batchEnded) {\n\t\t\t// batch not yet complete\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst completedBatch = this.batchInProgress;\n\t\tthis.batchInProgress = undefined;\n\t\treturn completedBatch;\n\t}\n\n\t/**\n\t * Add the given message to the current batch, and indicate whether the batch is now complete.\n\t *\n\t * @returns batchEnded: true if the batch is now complete, batchEnded: false if more messages are expected\n\t */\n\tprivate addMessageToBatch(\n\t\tmessage: InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t): { batchEnded: boolean } {\n\t\tconst batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;\n\t\tif (this.batchInProgress === undefined) {\n\t\t\t// We are waiting for a new batch\n\t\t\tassert(batchMetadataFlag !== false, 0x9d5 /* Unexpected batch end marker */);\n\n\t\t\t// Start of a new multi-message batch\n\t\t\tif (batchMetadataFlag === true) {\n\t\t\t\tthis.batchInProgress = {\n\t\t\t\t\tmessages: [message],\n\t\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t\t\tclientId: message.clientId,\n\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t};\n\n\t\t\t\treturn { batchEnded: false };\n\t\t\t}\n\n\t\t\t// Single-message batch (Since metadata flag is undefined)\n\t\t\tthis.batchInProgress = {\n\t\t\t\tmessages: [message],\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\tclientId: message.clientId,\n\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t};\n\t\t\treturn { batchEnded: true };\n\t\t}\n\t\tassert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);\n\n\t\tthis.batchInProgress.messages.push(message);\n\n\t\treturn { batchEnded: batchMetadataFlag === false };\n\t}\n}\n\n/**\n * Takes an incoming message and if the contents is a string, JSON.parse's it in place\n * @param mutableMessage - op message received\n * @param hasModernRuntimeMessageEnvelope - false if the message does not contain the modern op envelop where message.type is MessageType.Operation\n * @param logLegacyCase - callback to log when legacy op is encountered\n */\nexport function ensureContentsDeserialized(\n\tmutableMessage: ISequencedDocumentMessage,\n\thasModernRuntimeMessageEnvelope: boolean,\n\tlogLegacyCase: (codePath: string) => void,\n): void {\n\t// Currently the loader layer is parsing the contents of the message as JSON if it is a string,\n\t// so we never expect to see this case.\n\t// We intend to remove that logic from the Loader, at which point we will have it here.\n\t// Only hasModernRuntimeMessageEnvelope true will be expected to have JSON contents.\n\tlet didParseJsonContents: boolean;\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t\tdidParseJsonContents = true;\n\t} else {\n\t\tdidParseJsonContents = false;\n\t}\n\n\t// The DeltaManager parses the contents of the message as JSON if it is a string,\n\t// so we should never end up parsing it here.\n\t// Let's observe if we are wrong about this to learn about these cases.\n\tif (didParseJsonContents) {\n\t\tlogLegacyCase(\"ensureContentsDeserialized_foundJsonContents\");\n\t}\n}\n\n/**\n * For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.\n *\n * The return type illustrates the assumption that the message param\n * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns\n * (but there is no runtime validation of the 'type' or 'compatDetails' values).\n */\nfunction unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {\n\t// We assume the contents is an InboundContainerRuntimeMessage (the message is \"packed\")\n\tconst contents = message.contents as InboundContainerRuntimeMessage;\n\n\t// We're going to unpack message in-place (promoting those properties of contents up to message itself)\n\tconst messageUnpacked = message as InboundSequencedContainerRuntimeMessage;\n\n\tmessageUnpacked.type = contents.type;\n\tmessageUnpacked.contents = contents.contents;\n\tif (\"compatDetails\" in contents) {\n\t\t(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =\n\t\t\tcontents.compatDetails;\n\t}\n\treturn messageUnpacked;\n}\n\n/**\n * Unpacks runtime messages.\n *\n * @remarks This API makes no promises regarding backward-compatibility. This is internal API.\n * @param message - message (as it observed in storage / service)\n * @returns whether the given message was unpacked\n *\n * @internal\n */\nexport function unpackRuntimeMessage(\n\tmessage: ISequencedDocumentMessage,\n\tlogLegacyCase: (codePath: string) => void = () => {},\n): boolean {\n\tif (message.type !== MessageType.Operation) {\n\t\t// Legacy format, but it's already \"unpacked\",\n\t\t// i.e. message.type is actually ContainerMessageType.\n\t\t// Or it's non-runtime message.\n\t\t// Nothing to do in such case.\n\t\treturn false;\n\t}\n\n\t// legacy op format?\n\t// 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.\n\tif (\n\t\t(message.contents as { address?: unknown }).address !== undefined &&\n\t\t(message.contents as { type?: unknown }).type === undefined\n\t) {\n\t\tmessage.type = ContainerMessageType.FluidDataStoreOp;\n\t\tlogLegacyCase(\"unpackRuntimeMessage_contentsWithAddress\");\n\t} else {\n\t\t// new format\n\t\tunpack(message);\n\t}\n\n\treturn true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tMessageType,\n\tISequencedDocumentMessage,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport {\n\tContainerMessageType,\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype InboundSequencedRecentlyAddedContainerRuntimeMessage,\n} from \"../messageTypes.js\";\nimport { asBatchMetadata } from \"../metadata.js\";\n\nimport { OpDecompressor } from \"./opDecompressor.js\";\nimport { OpGroupingManager, isGroupedBatch } from \"./opGroupingManager.js\";\nimport { OpSplitter, isChunkedMessage } from \"./opSplitter.js\";\n\n/** Info about the batch we learn when we process the first message */\nexport interface BatchStartInfo {\n\t/** Batch ID, if present */\n\treadonly batchId: string | undefined;\n\t/** clientId that sent this batch. Used to compute Batch ID if needed */\n\treadonly clientId: string;\n\t/**\n\t * Client Sequence Number of the Grouped Batch message, or the first message in the ungrouped batch.\n\t * Used to compute Batch ID if needed\n\t *\n\t * @remarks For chunked batches, this is the CSN of the \"representative\" chunk (the final chunk).\n\t * For grouped batches, clientSequenceNumber on messages is overwritten, so we track this original value here.\n\t */\n\treadonly batchStartCsn: number;\n\t/**\n\t * The first message in the batch, or if the batch is empty, the empty grouped batch message\n\t * Used for accessing the sequence numbers for the (start of the) batch.\n\t *\n\t * @remarks Do not use clientSequenceNumber here, use batchStartCsn instead.\n\t */\n\treadonly keyMessage: ISequencedDocumentMessage;\n}\n\n/**\n * Result of processing the next inbound message.\n * Depending on the message and configuration of RemoteMessageProcessor, the result may be:\n * - A full batch of messages (including a single-message batch)\n * - The first message of a multi-message batch\n * - The next message in a multi-message batch\n */\nexport type InboundMessageResult =\n\t| {\n\t\t\ttype: \"fullBatch\";\n\t\t\tmessages: InboundSequencedContainerRuntimeMessage[];\n\t\t\tbatchStart: BatchStartInfo;\n\t\t\tlength: number;\n\t }\n\t| {\n\t\t\ttype: \"batchStartingMessage\";\n\t\t\tbatchStart: BatchStartInfo;\n\t\t\tnextMessage: InboundSequencedContainerRuntimeMessage;\n\t\t\tlength?: never;\n\t }\n\t| {\n\t\t\ttype: \"nextBatchMessage\";\n\t\t\tbatchEnd?: boolean;\n\t\t\tnextMessage: InboundSequencedContainerRuntimeMessage;\n\t\t\tlength?: never;\n\t };\n\nfunction assertHasClientId(\n\tmessage: ISequencedDocumentMessage,\n): asserts message is ISequencedDocumentMessage & { clientId: string } {\n\tassert(\n\t\tmessage.clientId !== null,\n\t\t0xa02 /* Server-generated message should not reach RemoteMessageProcessor */,\n\t);\n}\n\n/**\n * Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,\n * potentially across numerous inbound ops.\n *\n * @internal\n */\nexport class RemoteMessageProcessor {\n\tprivate batchInProgress: boolean = false;\n\n\tconstructor(\n\t\tprivate readonly opSplitter: OpSplitter,\n\t\tprivate readonly opDecompressor: OpDecompressor,\n\t\tprivate readonly opGroupingManager: OpGroupingManager,\n\t) {}\n\n\tpublic get partialMessages(): ReadonlyMap<string, string[]> {\n\t\treturn this.opSplitter.chunks;\n\t}\n\n\tpublic clearPartialMessagesFor(clientId: string) {\n\t\tthis.opSplitter.clearPartialChunks(clientId);\n\t}\n\n\t/**\n\t * Ungroups and Unchunks the runtime ops of a batch received over the wire\n\t * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized\n\t * (grouped, compressed, and/or chunked).\n\t * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure\n\t * depends on this object instance.\n\t * Note remoteMessageCopy.contents (and other object props) MUST not be modified,\n\t * but may be overwritten (as is the case with contents).\n\t *\n\t * Incoming messages will always have compression, chunking, and grouped batching happen in a defined order and that order cannot be changed.\n\t * When processing these messages, the order is:\n\t * 1. If chunked, process the chunk and only continue if this is a final chunk\n\t * 2. If compressed, decompress the message and store for further unrolling of the decompressed content\n\t * 3. If grouped, ungroup the message\n\t * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound\n\t *\n\t * @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch\n\t * or undefined if the batch is not yet complete.\n\t */\n\tpublic process(\n\t\tremoteMessageCopy: ISequencedDocumentMessage,\n\t\tlogLegacyCase: (codePath: string) => void,\n\t): InboundMessageResult | undefined {\n\t\tlet message = remoteMessageCopy;\n\n\t\tassertHasClientId(message);\n\t\tconst clientId = message.clientId;\n\n\t\tif (isChunkedMessage(message)) {\n\t\t\tconst chunkProcessingResult = this.opSplitter.processChunk(message);\n\t\t\t// Only continue further if current chunk is the final chunk\n\t\t\tif (!chunkProcessingResult.isFinalChunk) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\t// This message will always be compressed\n\t\t\tmessage = chunkProcessingResult.message;\n\t\t}\n\n\t\tif (this.opDecompressor.isCompressedMessage(message)) {\n\t\t\tthis.opDecompressor.decompressAndStore(message);\n\t\t}\n\n\t\tif (this.opDecompressor.currentlyUnrolling) {\n\t\t\tmessage = this.opDecompressor.unroll(message);\n\t\t\t// Need to unpack after unrolling if not a groupedBatch\n\t\t\tif (!isGroupedBatch(message)) {\n\t\t\t\tunpack(message);\n\t\t\t}\n\t\t}\n\n\t\tif (isGroupedBatch(message)) {\n\t\t\t// We should be awaiting a new batch (batchInProgress false)\n\t\t\tassert(!this.batchInProgress, 0x9d3 /* Grouped batch interrupting another batch */);\n\t\t\tconst batchId = asBatchMetadata(message.metadata)?.batchId;\n\t\t\tconst groupedMessages = this.opGroupingManager.ungroupOp(message).map(unpack);\n\n\t\t\treturn {\n\t\t\t\ttype: \"fullBatch\",\n\t\t\t\tmessages: groupedMessages, // Will be [] for an empty batch\n\t\t\t\tbatchStart: {\n\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t\tclientId,\n\t\t\t\t\tbatchId,\n\t\t\t\t\tkeyMessage: groupedMessages[0] ?? message, // For an empty batch, this is the empty grouped batch message. Needed for sequence numbers for this batch\n\t\t\t\t},\n\t\t\t\tlength: groupedMessages.length, // Will be 0 for an empty batch\n\t\t\t};\n\t\t}\n\n\t\t// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked\n\t\tunpackRuntimeMessage(message, logLegacyCase);\n\n\t\treturn this.getResultBasedOnBatchMetadata(\n\t\t\tmessage as InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t\t);\n\t}\n\n\t/**\n\t * Now that the message has been \"unwrapped\" as to any virtualization (grouping, compression, chunking),\n\t * inspect the batch metadata flag and determine what kind of result to return.\n\t */\n\tprivate getResultBasedOnBatchMetadata(\n\t\tmessage: InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t): InboundMessageResult {\n\t\tconst batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;\n\t\tif (!this.batchInProgress) {\n\t\t\t// We are waiting for a new batch\n\t\t\tassert(batchMetadataFlag !== false, 0x9d5 /* Unexpected batch end marker */);\n\n\t\t\t// Start of a new multi-message batch\n\t\t\tif (batchMetadataFlag === true) {\n\t\t\t\tthis.batchInProgress = true;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"batchStartingMessage\",\n\t\t\t\t\tbatchStart: {\n\t\t\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t\t\t\tclientId: message.clientId,\n\t\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t\t\tkeyMessage: message,\n\t\t\t\t\t},\n\t\t\t\t\tnextMessage: message,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Single-message batch (Since metadata flag is undefined)\n\t\t\treturn {\n\t\t\t\ttype: \"fullBatch\",\n\t\t\t\tmessages: [message],\n\t\t\t\tbatchStart: {\n\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t\tclientId: message.clientId,\n\t\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t\t\tkeyMessage: message,\n\t\t\t\t},\n\t\t\t\tlength: 1,\n\t\t\t};\n\t\t}\n\t\tassert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);\n\n\t\t// Clear batchInProgress state if the batch is ending\n\t\tif (batchMetadataFlag === false) {\n\t\t\tthis.batchInProgress = false;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"nextBatchMessage\",\n\t\t\tnextMessage: message,\n\t\t\tbatchEnd: batchMetadataFlag === false,\n\t\t};\n\t}\n}\n\n/**\n * Takes an incoming message and if the contents is a string, JSON.parse's it in place\n * @param mutableMessage - op message received\n * @param hasModernRuntimeMessageEnvelope - false if the message does not contain the modern op envelop where message.type is MessageType.Operation\n * @param logLegacyCase - callback to log when legacy op is encountered\n */\nexport function ensureContentsDeserialized(\n\tmutableMessage: ISequencedDocumentMessage,\n\thasModernRuntimeMessageEnvelope: boolean,\n\tlogLegacyCase: (codePath: string) => void,\n): void {\n\t// Currently the loader layer is parsing the contents of the message as JSON if it is a string,\n\t// so we never expect to see this case.\n\t// We intend to remove that logic from the Loader, at which point we will have it here.\n\t// Only hasModernRuntimeMessageEnvelope true will be expected to have JSON contents.\n\tlet didParseJsonContents: boolean;\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t\tdidParseJsonContents = true;\n\t} else {\n\t\tdidParseJsonContents = false;\n\t}\n\n\t// The DeltaManager parses the contents of the message as JSON if it is a string,\n\t// so we should never end up parsing it here.\n\t// Let's observe if we are wrong about this to learn about these cases.\n\tif (didParseJsonContents) {\n\t\tlogLegacyCase(\"ensureContentsDeserialized_foundJsonContents\");\n\t}\n}\n\n/**\n * For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.\n *\n * The return type illustrates the assumption that the message param\n * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns\n * (but there is no runtime validation of the 'type' or 'compatDetails' values).\n */\nfunction unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {\n\t// We assume the contents is an InboundContainerRuntimeMessage (the message is \"packed\")\n\tconst contents = message.contents as InboundContainerRuntimeMessage;\n\n\t// We're going to unpack message in-place (promoting those properties of contents up to message itself)\n\tconst messageUnpacked = message as InboundSequencedContainerRuntimeMessage;\n\n\tmessageUnpacked.type = contents.type;\n\tmessageUnpacked.contents = contents.contents;\n\tif (\"compatDetails\" in contents) {\n\t\t(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =\n\t\t\tcontents.compatDetails;\n\t}\n\treturn messageUnpacked;\n}\n\n/**\n * Unpacks runtime messages.\n *\n * @remarks This API makes no promises regarding backward-compatibility. This is internal API.\n * @param message - message (as it observed in storage / service)\n * @returns whether the given message was unpacked\n *\n * @internal\n */\nexport function unpackRuntimeMessage(\n\tmessage: ISequencedDocumentMessage,\n\tlogLegacyCase: (codePath: string) => void = () => {},\n): boolean {\n\tif (message.type !== MessageType.Operation) {\n\t\t// Legacy format, but it's already \"unpacked\",\n\t\t// i.e. message.type is actually ContainerMessageType.\n\t\t// Or it's non-runtime message.\n\t\t// Nothing to do in such case.\n\t\treturn false;\n\t}\n\n\t// legacy op format?\n\t// 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.\n\tif (\n\t\t(message.contents as { address?: unknown }).address !== undefined &&\n\t\t(message.contents as { type?: unknown }).type === undefined\n\t) {\n\t\tmessage.type = ContainerMessageType.FluidDataStoreOp;\n\t\tlogLegacyCase(\"unpackRuntimeMessage_contentsWithAddress\");\n\t} else {\n\t\t// new format\n\t\tunpack(message);\n\t}\n\n\treturn true;\n}\n"]}
|
package/lib/packageVersion.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
|
|
6
6
|
*/
|
|
7
7
|
export declare const pkgName = "@fluidframework/container-runtime";
|
|
8
|
-
export declare const pkgVersion = "2.2.
|
|
8
|
+
export declare const pkgVersion = "2.2.2";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
package/lib/packageVersion.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.2.
|
|
1
|
+
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.2.2\";\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { IDisposable } from "@fluidframework/core-interfaces";
|
|
6
6
|
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
7
7
|
import { type InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
8
|
-
import { BatchId, BatchMessage,
|
|
8
|
+
import { BatchId, BatchMessage, InboundMessageResult } from "./opLifecycle/index.js";
|
|
9
9
|
/**
|
|
10
10
|
* This represents a message that has been submitted and is added to the pending queue when `submit` is called on the
|
|
11
11
|
* ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.
|
|
@@ -107,26 +107,26 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
107
107
|
*/
|
|
108
108
|
applyStashedOpsAt(seqNum?: number): Promise<void>;
|
|
109
109
|
/**
|
|
110
|
-
* Processes an inbound batch of messages - May be local or remote.
|
|
110
|
+
* Processes an inbound message or batch of messages - May be local or remote.
|
|
111
111
|
*
|
|
112
|
-
* @param
|
|
113
|
-
* @param local - true if we submitted
|
|
114
|
-
* @returns The inbound
|
|
112
|
+
* @param inbound - The inbound message(s) to process, with extra info (e.g. about the start of a batch). Could be local or remote.
|
|
113
|
+
* @param local - true if we submitted these messages and expect corresponding pending messages
|
|
114
|
+
* @returns The inbound messages with localOpMetadata "zipped" in.
|
|
115
115
|
*
|
|
116
|
-
* @
|
|
117
|
-
* - The batchStartCsn doesn't match for local batches
|
|
116
|
+
* @throws a DataProcessingError if the pending message content doesn't match the incoming message content (for local messages)
|
|
118
117
|
*/
|
|
119
|
-
|
|
118
|
+
processInboundMessages(inbound: InboundMessageResult, local: boolean): {
|
|
120
119
|
message: InboundSequencedContainerRuntimeMessage;
|
|
121
120
|
localOpMetadata?: unknown;
|
|
122
121
|
}[];
|
|
123
122
|
/**
|
|
124
|
-
* Processes the incoming
|
|
125
|
-
* It verifies that messages are received in the right order and that
|
|
126
|
-
* @param
|
|
127
|
-
* @
|
|
123
|
+
* Processes the incoming message(s) from the server that were submitted by this client.
|
|
124
|
+
* It verifies that messages are received in the right order and that any batch information is correct.
|
|
125
|
+
* @param inbound - The inbound message(s) (originating from this client) to correlate with the pending local state
|
|
126
|
+
* @throws DataProcessingError if the pending message content doesn't match the incoming message content for any message here
|
|
127
|
+
* @returns The inbound messages with localOpMetadata "zipped" in.
|
|
128
128
|
*/
|
|
129
|
-
private
|
|
129
|
+
private processPendingLocalMessages;
|
|
130
130
|
/**
|
|
131
131
|
* Processes the pending local copy of message that's been ack'd by the server.
|
|
132
132
|
* @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE9D,OAAO,EACN,mBAAmB,EAInB,MAAM,0CAA0C,CAAC;AAIlD,OAAO,EAEN,KAAK,uCAAuC,EAE5C,MAAM,mBAAmB,CAAC;AAE3B,OAAO,
|
|
1
|
+
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { DataProcessingError, LoggingError, extractSafePropertiesFromMessage, }
|
|
|
7
7
|
import Deque from "double-ended-queue";
|
|
8
8
|
import { v4 as uuid } from "uuid";
|
|
9
9
|
import { asBatchMetadata, asEmptyBatchLocalOpMetadata } from "./metadata.js";
|
|
10
|
-
import { generateBatchId } from "./opLifecycle/index.js";
|
|
10
|
+
import { generateBatchId, } from "./opLifecycle/index.js";
|
|
11
11
|
function isEmptyBatchPendingMessage(message) {
|
|
12
12
|
const content = JSON.parse(message.content);
|
|
13
13
|
return content.type === "groupedBatch" && content.contents?.length === 0;
|
|
@@ -209,38 +209,41 @@ export class PendingStateManager {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
/**
|
|
212
|
-
* Processes an inbound batch of messages - May be local or remote.
|
|
212
|
+
* Processes an inbound message or batch of messages - May be local or remote.
|
|
213
213
|
*
|
|
214
|
-
* @param
|
|
215
|
-
* @param local - true if we submitted
|
|
216
|
-
* @returns The inbound
|
|
214
|
+
* @param inbound - The inbound message(s) to process, with extra info (e.g. about the start of a batch). Could be local or remote.
|
|
215
|
+
* @param local - true if we submitted these messages and expect corresponding pending messages
|
|
216
|
+
* @returns The inbound messages with localOpMetadata "zipped" in.
|
|
217
217
|
*
|
|
218
|
-
* @
|
|
219
|
-
* - The batchStartCsn doesn't match for local batches
|
|
218
|
+
* @throws a DataProcessingError if the pending message content doesn't match the incoming message content (for local messages)
|
|
220
219
|
*/
|
|
221
|
-
|
|
220
|
+
processInboundMessages(inbound, local) {
|
|
222
221
|
if (local) {
|
|
223
|
-
return this.
|
|
222
|
+
return this.processPendingLocalMessages(inbound);
|
|
224
223
|
}
|
|
225
224
|
// No localOpMetadata for remote messages
|
|
226
|
-
|
|
225
|
+
const messages = inbound.type === "fullBatch" ? inbound.messages : [inbound.nextMessage];
|
|
226
|
+
return messages.map((message) => ({ message }));
|
|
227
227
|
}
|
|
228
228
|
/**
|
|
229
|
-
* Processes the incoming
|
|
230
|
-
* It verifies that messages are received in the right order and that
|
|
231
|
-
* @param
|
|
232
|
-
* @
|
|
229
|
+
* Processes the incoming message(s) from the server that were submitted by this client.
|
|
230
|
+
* It verifies that messages are received in the right order and that any batch information is correct.
|
|
231
|
+
* @param inbound - The inbound message(s) (originating from this client) to correlate with the pending local state
|
|
232
|
+
* @throws DataProcessingError if the pending message content doesn't match the incoming message content for any message here
|
|
233
|
+
* @returns The inbound messages with localOpMetadata "zipped" in.
|
|
233
234
|
*/
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
processPendingLocalMessages(inbound) {
|
|
236
|
+
if ("batchStart" in inbound) {
|
|
237
|
+
this.onLocalBatchBegin(inbound.batchStart, inbound.length);
|
|
238
|
+
}
|
|
236
239
|
// Empty batch
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
const localOpMetadata = this.processNextPendingMessage(batch.emptyBatchSequenceNumber);
|
|
240
|
+
if (inbound.length === 0) {
|
|
241
|
+
const localOpMetadata = this.processNextPendingMessage(inbound.batchStart.keyMessage.sequenceNumber);
|
|
240
242
|
assert(asEmptyBatchLocalOpMetadata(localOpMetadata)?.emptyBatch === true, 0xa20 /* Expected empty batch marker */);
|
|
241
243
|
return [];
|
|
242
244
|
}
|
|
243
|
-
|
|
245
|
+
const messages = inbound.type === "fullBatch" ? inbound.messages : [inbound.nextMessage];
|
|
246
|
+
return messages.map((message) => ({
|
|
244
247
|
message,
|
|
245
248
|
localOpMetadata: this.processNextPendingMessage(message.sequenceNumber, message),
|
|
246
249
|
}));
|
|
@@ -287,7 +290,7 @@ export class PendingStateManager {
|
|
|
287
290
|
/**
|
|
288
291
|
* Check if the incoming batch matches the batch info for the next pending message.
|
|
289
292
|
*/
|
|
290
|
-
onLocalBatchBegin(
|
|
293
|
+
onLocalBatchBegin(batchStart, batchLength) {
|
|
291
294
|
// Get the next message from the pending queue. Verify a message exists.
|
|
292
295
|
const pendingMessage = this.pendingMessages.peekFront();
|
|
293
296
|
assert(pendingMessage !== undefined, 0xa21 /* No pending message found as we start processing this remote batch */);
|
|
@@ -295,22 +298,23 @@ export class PendingStateManager {
|
|
|
295
298
|
// In this case the next pending message is an empty batch marker.
|
|
296
299
|
// Empty batches became empty on Resubmit, and submit them and track them in case
|
|
297
300
|
// a different fork of this container also submitted the same batch (and it may not be empty for that fork).
|
|
298
|
-
const firstMessage =
|
|
299
|
-
|
|
301
|
+
const firstMessage = batchStart.keyMessage;
|
|
302
|
+
// -1 length is for back compat, undefined length means we actually don't know it
|
|
303
|
+
const skipLengthCheck = pendingMessage.batchInfo.length === -1 || batchLength === undefined;
|
|
304
|
+
const expectedPendingBatchLength = batchLength === 0 ? 1 : batchLength;
|
|
300
305
|
// We expect the incoming batch to be of the same length, starting at the same clientSequenceNumber,
|
|
301
306
|
// as the batch we originally submitted.
|
|
302
307
|
// We have another later check to compare the message contents, which we'd expect to fail if this check does,
|
|
303
308
|
// so we don't throw here, merely log. In a later release this check may replace that one.
|
|
304
|
-
if (pendingMessage.batchInfo.batchStartCsn !==
|
|
305
|
-
(pendingMessage.batchInfo.length
|
|
306
|
-
pendingMessage.batchInfo.length !== expectedPendingBatchLength)) {
|
|
309
|
+
if (pendingMessage.batchInfo.batchStartCsn !== batchStart.batchStartCsn ||
|
|
310
|
+
(!skipLengthCheck && pendingMessage.batchInfo.length !== expectedPendingBatchLength)) {
|
|
307
311
|
this.logger?.sendErrorEvent({
|
|
308
312
|
eventName: "BatchInfoMismatch",
|
|
309
313
|
details: {
|
|
310
314
|
pendingBatchCsn: pendingMessage.batchInfo.batchStartCsn,
|
|
311
|
-
batchStartCsn:
|
|
315
|
+
batchStartCsn: batchStart.batchStartCsn,
|
|
312
316
|
pendingBatchLength: pendingMessage.batchInfo.length,
|
|
313
|
-
batchLength
|
|
317
|
+
batchLength,
|
|
314
318
|
pendingMessageBatchMetadata: asBatchMetadata(pendingMessage.opMetadata)?.batch,
|
|
315
319
|
messageBatchMetadata: asBatchMetadata(firstMessage?.metadata)?.batch,
|
|
316
320
|
},
|