@fluidframework/container-runtime 2.1.1 → 2.2.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 +30 -0
- package/README.md +2 -2
- package/api-report/container-runtime.legacy.alpha.api.md +4 -3
- package/container-runtime.test-files.tar +0 -0
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +9 -0
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +0 -14
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +2 -12
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +34 -6
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +181 -90
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +9 -18
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +40 -78
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +0 -6
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +23 -66
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +11 -34
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +9 -52
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js +3 -23
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +2 -6
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +4 -8
- package/dist/gc/gcSummaryStateTracker.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +1 -9
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +3 -25
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/index.d.ts +2 -2
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +2 -7
- package/dist/gc/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +6 -5
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/metadata.d.ts +9 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +6 -1
- package/dist/metadata.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/opGroupingManager.d.ts +8 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +34 -2
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +1 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +20 -0
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +38 -19
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +67 -43
- 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 +33 -22
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +148 -105
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +5 -1
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +9 -0
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +0 -14
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +2 -11
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +34 -6
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +181 -90
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +9 -18
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +27 -65
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +0 -6
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +25 -68
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +12 -35
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +9 -52
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js +2 -22
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +2 -6
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +4 -8
- package/lib/gc/gcSummaryStateTracker.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +1 -9
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +3 -24
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/index.d.ts +2 -2
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +2 -2
- package/lib/gc/index.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +6 -5
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/metadata.d.ts +9 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js +4 -0
- package/lib/metadata.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/opGroupingManager.d.ts +8 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +34 -2
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +1 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +20 -0
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +38 -19
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +67 -43
- 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 +33 -22
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +149 -106
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +5 -1
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/package.json +21 -21
- package/src/batchTracker.ts +4 -2
- package/src/blobManager/blobManager.ts +9 -0
- package/src/channelCollection.ts +2 -11
- package/src/containerRuntime.ts +216 -121
- package/src/dataStoreContext.ts +29 -93
- package/src/gc/garbageCollection.ts +26 -79
- package/src/gc/gcConfigs.ts +12 -45
- package/src/gc/gcDefinitions.ts +10 -55
- package/src/gc/gcHelpers.ts +10 -8
- package/src/gc/gcSummaryStateTracker.ts +6 -9
- package/src/gc/gcTelemetry.ts +3 -38
- package/src/gc/index.ts +2 -6
- package/src/index.ts +0 -1
- package/src/messageTypes.ts +12 -11
- package/src/metadata.ts +16 -2
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/opGroupingManager.ts +42 -3
- package/src/opLifecycle/outbox.ts +30 -0
- package/src/opLifecycle/remoteMessageProcessor.ts +110 -56
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +209 -168
- package/src/summary/README.md +31 -28
- package/src/summary/summarizerNode/summarizerNode.ts +6 -1
- package/src/summary/summarizerNode/summarizerNodeWithGc.ts +20 -43
- package/src/summary/summaryFormats.md +25 -22
|
@@ -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.
|
|
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\";\n"]}
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
6
5
|
import { IDisposable } from "@fluidframework/core-interfaces";
|
|
7
6
|
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
8
|
-
import { InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
9
|
-
import { BatchId, BatchMessage } from "./opLifecycle/index.js";
|
|
7
|
+
import { type InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
8
|
+
import { BatchId, BatchMessage, InboundBatch } from "./opLifecycle/index.js";
|
|
10
9
|
/**
|
|
11
10
|
* This represents a message that has been submitted and is added to the pending queue when `submit` is called on the
|
|
12
11
|
* ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.
|
|
@@ -20,8 +19,8 @@ export interface IPendingMessage {
|
|
|
20
19
|
localOpMetadata: unknown;
|
|
21
20
|
opMetadata: Record<string, unknown> | undefined;
|
|
22
21
|
sequenceNumber?: number;
|
|
23
|
-
/** Info
|
|
24
|
-
|
|
22
|
+
/** Info about the batch this pending message belongs to, for validation and for computing the batchId on reconnect */
|
|
23
|
+
batchInfo: {
|
|
25
24
|
/** The Batch's original clientId, from when it was first flushed to be submitted */
|
|
26
25
|
clientId: string;
|
|
27
26
|
/**
|
|
@@ -29,6 +28,8 @@ export interface IPendingMessage {
|
|
|
29
28
|
* @remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)
|
|
30
29
|
*/
|
|
31
30
|
batchStartCsn: number;
|
|
31
|
+
/** length of the batch (how many runtime messages here) */
|
|
32
|
+
length: number;
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
export interface IPendingLocalState {
|
|
@@ -42,7 +43,6 @@ export type PendingMessageResubmitData = Pick<IPendingMessage, "content" | "loca
|
|
|
42
43
|
export interface IRuntimeStateHandler {
|
|
43
44
|
connected(): boolean;
|
|
44
45
|
clientId(): string | undefined;
|
|
45
|
-
close(error?: ICriticalContainerError): void;
|
|
46
46
|
applyStashedOp(content: string): Promise<unknown>;
|
|
47
47
|
reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;
|
|
48
48
|
isActiveConnection: () => boolean;
|
|
@@ -71,8 +71,6 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
71
71
|
/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
|
|
72
72
|
private negativeCounter;
|
|
73
73
|
private readonly disposeOnce;
|
|
74
|
-
private isProcessingBatch;
|
|
75
|
-
private pendingBatchBeginMessage;
|
|
76
74
|
/** Used to ensure we don't replay ops on the same connection twice */
|
|
77
75
|
private clientIdFromLastReplay;
|
|
78
76
|
/**
|
|
@@ -109,25 +107,38 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
109
107
|
*/
|
|
110
108
|
applyStashedOpsAt(seqNum?: number): Promise<void>;
|
|
111
109
|
/**
|
|
112
|
-
* Processes
|
|
113
|
-
*
|
|
114
|
-
* @param
|
|
115
|
-
* @param
|
|
116
|
-
*
|
|
110
|
+
* Processes an inbound batch of messages - May be local or remote.
|
|
111
|
+
*
|
|
112
|
+
* @param batch - The inbound batch of messages to process. Could be local or remote.
|
|
113
|
+
* @param local - true if we submitted this batch and expect corresponding pending messages
|
|
114
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
115
|
+
*
|
|
116
|
+
* @remarks Closes the container if:
|
|
117
|
+
* - The batchStartCsn doesn't match for local batches
|
|
118
|
+
*/
|
|
119
|
+
processInboundBatch(batch: InboundBatch, local: boolean): {
|
|
120
|
+
message: InboundSequencedContainerRuntimeMessage;
|
|
121
|
+
localOpMetadata?: unknown;
|
|
122
|
+
}[];
|
|
123
|
+
/**
|
|
124
|
+
* Processes the incoming batch from the server that was submitted by this client.
|
|
125
|
+
* It verifies that messages are received in the right order and that the batch information is correct.
|
|
126
|
+
* @param batch - The inbound batch (originating from this client) to correlate with the pending local state
|
|
127
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
117
128
|
*/
|
|
118
|
-
|
|
129
|
+
private processPendingLocalBatch;
|
|
119
130
|
/**
|
|
120
|
-
*
|
|
121
|
-
* @param
|
|
122
|
-
* @param
|
|
123
|
-
* @
|
|
131
|
+
* Processes the pending local copy of message that's been ack'd by the server.
|
|
132
|
+
* @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.
|
|
133
|
+
* @param message - [optional] The entire incoming message, for comparing contents with the pending message for extra validation.
|
|
134
|
+
* @throws DataProcessingError if the pending message content doesn't match the incoming message content.
|
|
135
|
+
* @returns - The localOpMetadata of the next pending message, to be sent to whoever submitted the original message.
|
|
124
136
|
*/
|
|
125
|
-
private
|
|
137
|
+
private processNextPendingMessage;
|
|
126
138
|
/**
|
|
127
|
-
*
|
|
128
|
-
* @param message - The message that is being processed.
|
|
139
|
+
* Check if the incoming batch matches the batch info for the next pending message.
|
|
129
140
|
*/
|
|
130
|
-
private
|
|
141
|
+
private onLocalBatchBegin;
|
|
131
142
|
/**
|
|
132
143
|
* Called when the Container's connection state changes. If the Container gets connected, it replays all the pending
|
|
133
144
|
* states in its queue. This includes triggering resubmission of unacked ops.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,25 +6,52 @@ import { assert, Lazy } from "@fluidframework/core-utils/internal";
|
|
|
6
6
|
import { DataProcessingError, LoggingError, extractSafePropertiesFromMessage, } from "@fluidframework/telemetry-utils/internal";
|
|
7
7
|
import Deque from "double-ended-queue";
|
|
8
8
|
import { v4 as uuid } from "uuid";
|
|
9
|
-
import { asBatchMetadata } from "./metadata.js";
|
|
9
|
+
import { asBatchMetadata, asEmptyBatchLocalOpMetadata } from "./metadata.js";
|
|
10
10
|
import { generateBatchId } from "./opLifecycle/index.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
message) {
|
|
11
|
+
function isEmptyBatchPendingMessage(message) {
|
|
12
|
+
const content = JSON.parse(message.content);
|
|
13
|
+
return content.type === "groupedBatch" && content.contents?.length === 0;
|
|
14
|
+
}
|
|
15
|
+
function buildPendingMessageContent(message) {
|
|
16
16
|
// IMPORTANT: Order matters here, this must match the order of the properties used
|
|
17
17
|
// when submitting the message.
|
|
18
18
|
const { type, contents, compatDetails } = message;
|
|
19
19
|
// Any properties that are not defined, won't be emitted by stringify.
|
|
20
20
|
return JSON.stringify({ type, contents, compatDetails });
|
|
21
21
|
}
|
|
22
|
+
function typesOfKeys(obj) {
|
|
23
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
24
|
+
acc[key] = typeof obj[key];
|
|
25
|
+
return acc;
|
|
26
|
+
}, {});
|
|
27
|
+
}
|
|
28
|
+
function scrubAndStringify(message) {
|
|
29
|
+
// Scrub the whole object in case there are unexpected keys
|
|
30
|
+
const scrubbed = typesOfKeys(message);
|
|
31
|
+
// For these known/expected keys, we can either drill in (for contents)
|
|
32
|
+
// or just use the value as-is (since it's not personal info)
|
|
33
|
+
scrubbed.contents = message.contents && typesOfKeys(message.contents);
|
|
34
|
+
scrubbed.compatDetails = message.compatDetails;
|
|
35
|
+
scrubbed.type = message.type;
|
|
36
|
+
return JSON.stringify(scrubbed);
|
|
37
|
+
}
|
|
22
38
|
function withoutLocalOpMetadata(message) {
|
|
23
39
|
return {
|
|
24
40
|
...message,
|
|
25
41
|
localOpMetadata: undefined,
|
|
26
42
|
};
|
|
27
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the effective batch ID for a pending message.
|
|
46
|
+
* If the batch ID is already present in the message's op metadata, return it.
|
|
47
|
+
* Otherwise, generate a new batch ID using the client ID and batch start CSN.
|
|
48
|
+
* @param pendingMessage - The pending message
|
|
49
|
+
* @returns The effective batch ID
|
|
50
|
+
*/
|
|
51
|
+
function getEffectiveBatchId(pendingMessage) {
|
|
52
|
+
return (asBatchMetadata(pendingMessage.opMetadata)?.batchId ??
|
|
53
|
+
generateBatchId(pendingMessage.batchInfo.clientId, pendingMessage.batchInfo.batchStartCsn));
|
|
54
|
+
}
|
|
28
55
|
/**
|
|
29
56
|
* PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been
|
|
30
57
|
* acknowledged by the server. It also maintains the batch information for both automatically and manually flushed
|
|
@@ -96,8 +123,6 @@ export class PendingStateManager {
|
|
|
96
123
|
this.initialMessages.clear();
|
|
97
124
|
this.pendingMessages.clear();
|
|
98
125
|
});
|
|
99
|
-
// Indicates whether we are processing a batch.
|
|
100
|
-
this.isProcessingBatch = false;
|
|
101
126
|
this.dispose = () => this.disposeOnce.value;
|
|
102
127
|
if (stashedLocalState?.pendingStates) {
|
|
103
128
|
this.initialMessages.push(...stashedLocalState.pendingStates);
|
|
@@ -130,8 +155,8 @@ export class PendingStateManager {
|
|
|
130
155
|
content,
|
|
131
156
|
localOpMetadata,
|
|
132
157
|
opMetadata,
|
|
133
|
-
// Note: We only
|
|
134
|
-
|
|
158
|
+
// Note: We only will read this off the first message, but put it on all for simplicity
|
|
159
|
+
batchInfo: { clientId, batchStartCsn, length: batch.length },
|
|
135
160
|
};
|
|
136
161
|
this.pendingMessages.push(pendingMessage);
|
|
137
162
|
}
|
|
@@ -155,7 +180,15 @@ export class PendingStateManager {
|
|
|
155
180
|
}
|
|
156
181
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
157
182
|
const nextMessage = this.initialMessages.shift();
|
|
183
|
+
// Nothing to apply if the message is an empty batch.
|
|
184
|
+
// We still need to track it for resubmission.
|
|
158
185
|
try {
|
|
186
|
+
if (isEmptyBatchPendingMessage(nextMessage)) {
|
|
187
|
+
nextMessage.localOpMetadata = { emptyBatch: true }; // equivalent to applyStashedOp for empty batch
|
|
188
|
+
patchbatchInfo(nextMessage); // Back compat
|
|
189
|
+
this.pendingMessages.push(nextMessage);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
159
192
|
// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
|
|
160
193
|
const localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);
|
|
161
194
|
if (!this.stateHandler.isAttached()) {
|
|
@@ -166,7 +199,7 @@ export class PendingStateManager {
|
|
|
166
199
|
else {
|
|
167
200
|
nextMessage.localOpMetadata = localOpMetadata;
|
|
168
201
|
// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
|
|
169
|
-
|
|
202
|
+
patchbatchInfo(nextMessage); // Back compat
|
|
170
203
|
this.pendingMessages.push(nextMessage);
|
|
171
204
|
}
|
|
172
205
|
}
|
|
@@ -176,105 +209,113 @@ export class PendingStateManager {
|
|
|
176
209
|
}
|
|
177
210
|
}
|
|
178
211
|
/**
|
|
179
|
-
* Processes
|
|
180
|
-
*
|
|
181
|
-
* @param
|
|
182
|
-
* @param
|
|
183
|
-
*
|
|
212
|
+
* Processes an inbound batch of messages - May be local or remote.
|
|
213
|
+
*
|
|
214
|
+
* @param batch - The inbound batch of messages to process. Could be local or remote.
|
|
215
|
+
* @param local - true if we submitted this batch and expect corresponding pending messages
|
|
216
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
217
|
+
*
|
|
218
|
+
* @remarks Closes the container if:
|
|
219
|
+
* - The batchStartCsn doesn't match for local batches
|
|
184
220
|
*/
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
assert(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
|
|
189
|
-
// This may be the start of a batch.
|
|
190
|
-
this.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);
|
|
191
|
-
pendingMessage.sequenceNumber = message.sequenceNumber;
|
|
192
|
-
this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
|
|
193
|
-
this.pendingMessages.shift();
|
|
194
|
-
const messageContent = buildPendingMessageContent(message);
|
|
195
|
-
// Stringified content should match
|
|
196
|
-
if (pendingMessage.content !== messageContent) {
|
|
197
|
-
this.stateHandler.close(DataProcessingError.create("pending local message content mismatch", "unexpectedAckReceived", message, {
|
|
198
|
-
expectedMessageType: JSON.parse(pendingMessage.content).type,
|
|
199
|
-
}));
|
|
200
|
-
return;
|
|
221
|
+
processInboundBatch(batch, local) {
|
|
222
|
+
if (local) {
|
|
223
|
+
return this.processPendingLocalBatch(batch);
|
|
201
224
|
}
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
return pendingMessage.localOpMetadata;
|
|
225
|
+
// No localOpMetadata for remote messages
|
|
226
|
+
return batch.messages.map((message) => ({ message }));
|
|
205
227
|
}
|
|
206
228
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* @param
|
|
210
|
-
* @
|
|
229
|
+
* Processes the incoming batch from the server that was submitted by this client.
|
|
230
|
+
* It verifies that messages are received in the right order and that the batch information is correct.
|
|
231
|
+
* @param batch - The inbound batch (originating from this client) to correlate with the pending local state
|
|
232
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
211
233
|
*/
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
234
|
+
processPendingLocalBatch(batch) {
|
|
235
|
+
this.onLocalBatchBegin(batch);
|
|
236
|
+
// Empty batch
|
|
237
|
+
if (batch.messages.length === 0) {
|
|
238
|
+
assert(batch.emptyBatchSequenceNumber !== undefined, 0x9fb /* Expected sequence number for empty batch */);
|
|
239
|
+
const localOpMetadata = this.processNextPendingMessage(batch.emptyBatchSequenceNumber);
|
|
240
|
+
assert(asEmptyBatchLocalOpMetadata(localOpMetadata)?.emptyBatch === true, 0xa20 /* Expected empty batch marker */);
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
return batch.messages.map((message) => ({
|
|
244
|
+
message,
|
|
245
|
+
localOpMetadata: this.processNextPendingMessage(message.sequenceNumber, message),
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Processes the pending local copy of message that's been ack'd by the server.
|
|
250
|
+
* @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.
|
|
251
|
+
* @param message - [optional] The entire incoming message, for comparing contents with the pending message for extra validation.
|
|
252
|
+
* @throws DataProcessingError if the pending message content doesn't match the incoming message content.
|
|
253
|
+
* @returns - The localOpMetadata of the next pending message, to be sent to whoever submitted the original message.
|
|
254
|
+
*/
|
|
255
|
+
processNextPendingMessage(sequenceNumber, message) {
|
|
256
|
+
const pendingMessage = this.pendingMessages.peekFront();
|
|
257
|
+
assert(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
|
|
258
|
+
pendingMessage.sequenceNumber = sequenceNumber;
|
|
259
|
+
this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
|
|
260
|
+
this.pendingMessages.shift();
|
|
261
|
+
// message is undefined in the Empty Batch case,
|
|
262
|
+
// because we don't have an incoming message to compare and pendingMessage is just a placeholder anyway.
|
|
263
|
+
if (message !== undefined) {
|
|
264
|
+
const messageContent = buildPendingMessageContent(message);
|
|
265
|
+
// Stringified content should match
|
|
266
|
+
if (pendingMessage.content !== messageContent) {
|
|
267
|
+
const pendingContentObj = JSON.parse(pendingMessage.content);
|
|
268
|
+
const incomingContentObj = JSON.parse(messageContent);
|
|
269
|
+
const contentsMatch = pendingContentObj.contents === incomingContentObj.contents ||
|
|
270
|
+
(pendingContentObj.contents !== undefined &&
|
|
271
|
+
incomingContentObj.contents !== undefined &&
|
|
272
|
+
JSON.stringify(pendingContentObj.contents) ===
|
|
273
|
+
JSON.stringify(incomingContentObj.contents));
|
|
274
|
+
this.logger.sendErrorEvent({
|
|
275
|
+
eventName: "unexpectedAckReceived",
|
|
218
276
|
details: {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
messageBatchMetadata: message.metadata?.batch,
|
|
223
|
-
pendingMessageBatchMetadata: pendingMessage.opMetadata?.batch,
|
|
277
|
+
pendingContentScrubbed: scrubAndStringify(pendingContentObj),
|
|
278
|
+
incomingContentScrubbed: scrubAndStringify(incomingContentObj),
|
|
279
|
+
contentsMatch,
|
|
224
280
|
},
|
|
225
|
-
messageDetails: extractSafePropertiesFromMessage(message),
|
|
226
281
|
});
|
|
282
|
+
throw DataProcessingError.create("pending local message content mismatch", "unexpectedAckReceived", message);
|
|
227
283
|
}
|
|
228
284
|
}
|
|
229
|
-
|
|
230
|
-
if (message.metadata?.batch) {
|
|
231
|
-
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
232
|
-
assert(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined, 0x16b /* "The pending batch state indicates we are already processing a batch" */);
|
|
233
|
-
// Set the pending batch state indicating we have started processing a batch.
|
|
234
|
-
this.pendingBatchBeginMessage = message;
|
|
235
|
-
this.isProcessingBatch = true;
|
|
236
|
-
}
|
|
285
|
+
return pendingMessage.localOpMetadata;
|
|
237
286
|
}
|
|
238
287
|
/**
|
|
239
|
-
*
|
|
240
|
-
* @param message - The message that is being processed.
|
|
288
|
+
* Check if the incoming batch matches the batch info for the next pending message.
|
|
241
289
|
*/
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
pendingMessagesCount: this.pendingMessagesCount,
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// Clear the pending batch state now that we have processed the entire batch.
|
|
276
|
-
this.pendingBatchBeginMessage = undefined;
|
|
277
|
-
this.isProcessingBatch = false;
|
|
290
|
+
onLocalBatchBegin(batch) {
|
|
291
|
+
// Get the next message from the pending queue. Verify a message exists.
|
|
292
|
+
const pendingMessage = this.pendingMessages.peekFront();
|
|
293
|
+
assert(pendingMessage !== undefined, 0xa21 /* No pending message found as we start processing this remote batch */);
|
|
294
|
+
// Note: This could be undefined if this batch became empty on resubmit.
|
|
295
|
+
// In this case the next pending message is an empty batch marker.
|
|
296
|
+
// Empty batches became empty on Resubmit, and submit them and track them in case
|
|
297
|
+
// a different fork of this container also submitted the same batch (and it may not be empty for that fork).
|
|
298
|
+
const firstMessage = batch.messages.length > 0 ? batch.messages[0] : undefined;
|
|
299
|
+
const expectedPendingBatchLength = batch.messages.length === 0 ? 1 : batch.messages.length;
|
|
300
|
+
// We expect the incoming batch to be of the same length, starting at the same clientSequenceNumber,
|
|
301
|
+
// as the batch we originally submitted.
|
|
302
|
+
// We have another later check to compare the message contents, which we'd expect to fail if this check does,
|
|
303
|
+
// so we don't throw here, merely log. In a later release this check may replace that one.
|
|
304
|
+
if (pendingMessage.batchInfo.batchStartCsn !== batch.batchStartCsn ||
|
|
305
|
+
(pendingMessage.batchInfo.length >= 0 && // -1 length is back compat and isn't suitable for this check
|
|
306
|
+
pendingMessage.batchInfo.length !== expectedPendingBatchLength)) {
|
|
307
|
+
this.logger?.sendErrorEvent({
|
|
308
|
+
eventName: "BatchInfoMismatch",
|
|
309
|
+
details: {
|
|
310
|
+
pendingBatchCsn: pendingMessage.batchInfo.batchStartCsn,
|
|
311
|
+
batchStartCsn: batch.batchStartCsn,
|
|
312
|
+
pendingBatchLength: pendingMessage.batchInfo.length,
|
|
313
|
+
batchLength: batch.messages.length,
|
|
314
|
+
pendingMessageBatchMetadata: asBatchMetadata(pendingMessage.opMetadata)?.batch,
|
|
315
|
+
messageBatchMetadata: asBatchMetadata(firstMessage?.metadata)?.batch,
|
|
316
|
+
},
|
|
317
|
+
messageDetails: firstMessage && extractSafePropertiesFromMessage(firstMessage),
|
|
318
|
+
});
|
|
278
319
|
}
|
|
279
320
|
}
|
|
280
321
|
/**
|
|
@@ -300,10 +341,12 @@ export class PendingStateManager {
|
|
|
300
341
|
const batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;
|
|
301
342
|
assert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
|
|
302
343
|
// The next message starts a batch (possibly single-message), and we'll need its batchId.
|
|
303
|
-
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
344
|
+
const batchId = getEffectiveBatchId(pendingMessage);
|
|
345
|
+
// Resubmit no messages, with the batchId. Will result in another empty batch marker.
|
|
346
|
+
if (asEmptyBatchLocalOpMetadata(pendingMessage.localOpMetadata)?.emptyBatch === true) {
|
|
347
|
+
this.stateHandler.reSubmitBatch([], batchId);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
307
350
|
/**
|
|
308
351
|
* We must preserve the distinct batches on resubmit.
|
|
309
352
|
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
@@ -355,12 +398,12 @@ export class PendingStateManager {
|
|
|
355
398
|
}
|
|
356
399
|
}
|
|
357
400
|
}
|
|
358
|
-
/** For back-compat if trying to apply stashed ops that pre-date
|
|
359
|
-
function
|
|
360
|
-
const
|
|
361
|
-
if (
|
|
401
|
+
/** For back-compat if trying to apply stashed ops that pre-date batchInfo */
|
|
402
|
+
function patchbatchInfo(message) {
|
|
403
|
+
const batchInfo = message.batchInfo;
|
|
404
|
+
if (batchInfo === undefined) {
|
|
362
405
|
// Using uuid guarantees uniqueness, retaining existing behavior
|
|
363
|
-
message.
|
|
406
|
+
message.batchInfo = { clientId: uuid(), batchStartCsn: -1, length: -1 };
|
|
364
407
|
}
|
|
365
408
|
}
|
|
366
409
|
//# sourceMappingURL=pendingStateManager.js.map
|