@fluidframework/container-runtime 2.1.0-276985 → 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 +71 -18
- 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/blobManager.d.ts +10 -0
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +19 -0
- package/dist/blobManager/blobManager.js.map +1 -1
- 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 +14 -5
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +142 -98
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +4 -0
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +9 -3
- 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 +14 -8
- 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/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 +6 -6
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +18 -6
- 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 +37 -13
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +95 -45
- 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/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.map +1 -1
- package/dist/summary/summaryFormat.js +4 -1
- package/dist/summary/summaryFormat.js.map +1 -1
- package/internal.d.ts +1 -1
- package/legacy.d.ts +1 -1
- package/lib/blobManager/blobManager.d.ts +10 -0
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +19 -0
- package/lib/blobManager/blobManager.js.map +1 -1
- 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 +14 -5
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +142 -98
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +4 -0
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +10 -4
- 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 +14 -8
- 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/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 +6 -6
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +18 -6
- 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 +37 -13
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +95 -45
- 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/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.map +1 -1
- package/lib/summary/summaryFormat.js +4 -1
- package/lib/summary/summaryFormat.js.map +1 -1
- package/package.json +45 -30
- package/src/blobManager/blobManager.ts +19 -0
- package/src/channelCollection.ts +48 -11
- package/src/containerRuntime.ts +190 -132
- package/src/dataStoreContext.ts +22 -4
- package/src/gc/garbageCollection.ts +15 -10
- package/src/gc/gcDefinitions.ts +7 -2
- package/src/gc/gcHelpers.ts +18 -6
- package/src/gc/gcTelemetry.ts +20 -8
- 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 +24 -8
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +173 -74
- package/src/scheduleManager.ts +6 -2
- package/src/summary/README.md +81 -0
- package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
- package/src/summary/summaryFormat.ts +3 -1
- package/src/summary/summaryFormats.md +69 -8
- package/tsconfig.json +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
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
6
6
|
import { IDisposable } from "@fluidframework/core-interfaces";
|
|
7
7
|
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
8
|
-
import { InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
9
|
-
import
|
|
8
|
+
import { type InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
9
|
+
import { BatchId, BatchMessage } from "./opLifecycle/index.js";
|
|
10
10
|
/**
|
|
11
11
|
* This represents a message that has been submitted and is added to the pending queue when `submit` is called on the
|
|
12
12
|
* ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.
|
|
13
|
+
*
|
|
14
|
+
* @remarks This is the current serialization format for pending local state when a Container is serialized.
|
|
13
15
|
*/
|
|
14
16
|
export interface IPendingMessage {
|
|
15
17
|
type: "message";
|
|
@@ -18,7 +20,16 @@ export interface IPendingMessage {
|
|
|
18
20
|
localOpMetadata: unknown;
|
|
19
21
|
opMetadata: Record<string, unknown> | undefined;
|
|
20
22
|
sequenceNumber?: number;
|
|
21
|
-
|
|
23
|
+
/** Info needed to compute the batchId on reconnect */
|
|
24
|
+
batchIdContext: {
|
|
25
|
+
/** The Batch's original clientId, from when it was first flushed to be submitted */
|
|
26
|
+
clientId: string;
|
|
27
|
+
/**
|
|
28
|
+
* The Batch's original clientSequenceNumber, from when it was first flushed to be submitted
|
|
29
|
+
* @remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)
|
|
30
|
+
*/
|
|
31
|
+
batchStartCsn: number;
|
|
32
|
+
};
|
|
22
33
|
}
|
|
23
34
|
export interface IPendingLocalState {
|
|
24
35
|
/**
|
|
@@ -26,18 +37,14 @@ export interface IPendingLocalState {
|
|
|
26
37
|
*/
|
|
27
38
|
pendingStates: IPendingMessage[];
|
|
28
39
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
localOpMetadata: unknown;
|
|
32
|
-
opMetadata: Record<string, unknown> | undefined;
|
|
33
|
-
}
|
|
40
|
+
/** Info needed to replay/resubmit a pending message */
|
|
41
|
+
export type PendingMessageResubmitData = Pick<IPendingMessage, "content" | "localOpMetadata" | "opMetadata">;
|
|
34
42
|
export interface IRuntimeStateHandler {
|
|
35
43
|
connected(): boolean;
|
|
36
44
|
clientId(): string | undefined;
|
|
37
45
|
close(error?: ICriticalContainerError): void;
|
|
38
46
|
applyStashedOp(content: string): Promise<unknown>;
|
|
39
|
-
|
|
40
|
-
reSubmitBatch(batch: IPendingBatchMessage[]): void;
|
|
47
|
+
reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;
|
|
41
48
|
isActiveConnection: () => boolean;
|
|
42
49
|
isAttached: () => boolean;
|
|
43
50
|
}
|
|
@@ -53,16 +60,21 @@ export interface IRuntimeStateHandler {
|
|
|
53
60
|
export declare class PendingStateManager implements IDisposable {
|
|
54
61
|
private readonly stateHandler;
|
|
55
62
|
private readonly logger;
|
|
63
|
+
/** Messages that will need to be resubmitted if not ack'd before the next reconnection */
|
|
56
64
|
private readonly pendingMessages;
|
|
65
|
+
/** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */
|
|
57
66
|
private readonly initialMessages;
|
|
58
67
|
/**
|
|
59
68
|
* Sequenced local ops that are saved when stashing since pending ops may depend on them
|
|
60
69
|
*/
|
|
61
70
|
private savedOps;
|
|
71
|
+
/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
|
|
72
|
+
private negativeCounter;
|
|
62
73
|
private readonly disposeOnce;
|
|
63
74
|
private isProcessingBatch;
|
|
64
75
|
private pendingBatchBeginMessage;
|
|
65
|
-
|
|
76
|
+
/** Used to ensure we don't replay ops on the same connection twice */
|
|
77
|
+
private clientIdFromLastReplay;
|
|
66
78
|
/**
|
|
67
79
|
* The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with
|
|
68
80
|
* 'hasPendingMessages'.
|
|
@@ -80,7 +92,7 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
80
92
|
*/
|
|
81
93
|
hasPendingMessages(): boolean;
|
|
82
94
|
getLocalState(snapshotSequenceNumber?: number): IPendingLocalState;
|
|
83
|
-
constructor(stateHandler: IRuntimeStateHandler,
|
|
95
|
+
constructor(stateHandler: IRuntimeStateHandler, stashedLocalState: IPendingLocalState | undefined, logger: ITelemetryLoggerExt);
|
|
84
96
|
get disposed(): boolean;
|
|
85
97
|
readonly dispose: () => void;
|
|
86
98
|
/**
|
|
@@ -96,6 +108,16 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
96
108
|
* @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.
|
|
97
109
|
*/
|
|
98
110
|
applyStashedOpsAt(seqNum?: number): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Processes the incoming batch from the server. It verifies that messages are received in the right order and
|
|
113
|
+
* that the batch information is correct.
|
|
114
|
+
* @param batch - The batch that is being processed.
|
|
115
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch
|
|
116
|
+
*/
|
|
117
|
+
processPendingLocalBatch(batch: InboundSequencedContainerRuntimeMessage[], batchStartCsn: number): {
|
|
118
|
+
message: InboundSequencedContainerRuntimeMessage;
|
|
119
|
+
localOpMetadata: unknown;
|
|
120
|
+
}[];
|
|
99
121
|
/**
|
|
100
122
|
* Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
|
|
101
123
|
* the batch information was preserved for batch messages.
|
|
@@ -103,10 +125,12 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
103
125
|
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
104
126
|
* (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)
|
|
105
127
|
*/
|
|
106
|
-
processPendingLocalMessage
|
|
128
|
+
private processPendingLocalMessage;
|
|
107
129
|
/**
|
|
108
130
|
* This message could be the first message in batch. If so, set batch state marking the beginning of a batch.
|
|
109
131
|
* @param message - The message that is being processed.
|
|
132
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
133
|
+
* @param pendingMessage - The corresponding pendingMessage.
|
|
110
134
|
*/
|
|
111
135
|
private maybeProcessBatchBegin;
|
|
112
136
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAG9D,OAAO,EACN,mBAAmB,EAInB,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
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
|
+
import { v4 as uuid } from "uuid";
|
|
9
|
+
import { asBatchMetadata } from "./metadata.js";
|
|
10
|
+
import { generateBatchId } from "./opLifecycle/index.js";
|
|
8
11
|
import { pkgVersion } from "./packageVersion.js";
|
|
9
12
|
function buildPendingMessageContent(
|
|
10
13
|
// AnyComboFromUnion is needed need to gain access to compatDetails that
|
|
@@ -76,16 +79,19 @@ export class PendingStateManager {
|
|
|
76
79
|
],
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
|
-
constructor(stateHandler,
|
|
82
|
+
constructor(stateHandler, stashedLocalState, logger) {
|
|
80
83
|
this.stateHandler = stateHandler;
|
|
81
84
|
this.logger = logger;
|
|
85
|
+
/** Messages that will need to be resubmitted if not ack'd before the next reconnection */
|
|
82
86
|
this.pendingMessages = new Deque();
|
|
83
|
-
|
|
87
|
+
/** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */
|
|
84
88
|
this.initialMessages = new Deque();
|
|
85
89
|
/**
|
|
86
90
|
* Sequenced local ops that are saved when stashing since pending ops may depend on them
|
|
87
91
|
*/
|
|
88
92
|
this.savedOps = [];
|
|
93
|
+
/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
|
|
94
|
+
this.negativeCounter = -1;
|
|
89
95
|
this.disposeOnce = new Lazy(() => {
|
|
90
96
|
this.initialMessages.clear();
|
|
91
97
|
this.pendingMessages.clear();
|
|
@@ -93,8 +99,8 @@ export class PendingStateManager {
|
|
|
93
99
|
// Indicates whether we are processing a batch.
|
|
94
100
|
this.isProcessingBatch = false;
|
|
95
101
|
this.dispose = () => this.disposeOnce.value;
|
|
96
|
-
if (
|
|
97
|
-
this.initialMessages.push(...
|
|
102
|
+
if (stashedLocalState?.pendingStates) {
|
|
103
|
+
this.initialMessages.push(...stashedLocalState.pendingStates);
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
106
|
get disposed() {
|
|
@@ -108,6 +114,14 @@ export class PendingStateManager {
|
|
|
108
114
|
* or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)
|
|
109
115
|
*/
|
|
110
116
|
onFlushBatch(batch, clientSequenceNumber) {
|
|
117
|
+
// If we're connected this is the client of the current connection,
|
|
118
|
+
// otherwise it's the clientId that just disconnected
|
|
119
|
+
// It's only undefined if we've NEVER connected. This is a tight corner case and we can
|
|
120
|
+
// simply make up a unique ID in this case.
|
|
121
|
+
const clientId = this.stateHandler.clientId() ?? uuid();
|
|
122
|
+
// If the batch was not yet sent, we need to assign a unique batchStartCsn
|
|
123
|
+
// Use a negative number to distinguish these from real CSNs
|
|
124
|
+
const batchStartCsn = clientSequenceNumber ?? this.negativeCounter--;
|
|
111
125
|
for (const message of batch) {
|
|
112
126
|
const { contents: content = "", referenceSequenceNumber, localOpMetadata, metadata: opMetadata, } = message;
|
|
113
127
|
const pendingMessage = {
|
|
@@ -116,7 +130,8 @@ export class PendingStateManager {
|
|
|
116
130
|
content,
|
|
117
131
|
localOpMetadata,
|
|
118
132
|
opMetadata,
|
|
119
|
-
|
|
133
|
+
// Note: We only need this on the first message.
|
|
134
|
+
batchIdContext: { clientId, batchStartCsn },
|
|
120
135
|
};
|
|
121
136
|
this.pendingMessages.push(pendingMessage);
|
|
122
137
|
}
|
|
@@ -151,6 +166,7 @@ export class PendingStateManager {
|
|
|
151
166
|
else {
|
|
152
167
|
nextMessage.localOpMetadata = localOpMetadata;
|
|
153
168
|
// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
|
|
169
|
+
patchBatchIdContext(nextMessage); // Back compat
|
|
154
170
|
this.pendingMessages.push(nextMessage);
|
|
155
171
|
}
|
|
156
172
|
}
|
|
@@ -159,6 +175,18 @@ export class PendingStateManager {
|
|
|
159
175
|
}
|
|
160
176
|
}
|
|
161
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Processes the incoming batch from the server. It verifies that messages are received in the right order and
|
|
180
|
+
* that the batch information is correct.
|
|
181
|
+
* @param batch - The batch that is being processed.
|
|
182
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch
|
|
183
|
+
*/
|
|
184
|
+
processPendingLocalBatch(batch, batchStartCsn) {
|
|
185
|
+
return batch.map((message) => ({
|
|
186
|
+
message,
|
|
187
|
+
localOpMetadata: this.processPendingLocalMessage(message, batchStartCsn),
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
162
190
|
/**
|
|
163
191
|
* Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
|
|
164
192
|
* the batch information was preserved for batch messages.
|
|
@@ -167,27 +195,14 @@ export class PendingStateManager {
|
|
|
167
195
|
* (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)
|
|
168
196
|
*/
|
|
169
197
|
processPendingLocalMessage(message, batchStartCsn) {
|
|
170
|
-
// Pre-processing part - This may be the start of a batch.
|
|
171
|
-
this.maybeProcessBatchBegin(message);
|
|
172
198
|
// Get the next message from the pending queue. Verify a message exists.
|
|
173
199
|
const pendingMessage = this.pendingMessages.peekFront();
|
|
174
200
|
assert(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
|
|
201
|
+
// This may be the start of a batch.
|
|
202
|
+
this.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);
|
|
175
203
|
pendingMessage.sequenceNumber = message.sequenceNumber;
|
|
176
204
|
this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
|
|
177
205
|
this.pendingMessages.shift();
|
|
178
|
-
if (pendingMessage.batchStartCsn !== batchStartCsn) {
|
|
179
|
-
this.logger?.sendErrorEvent({
|
|
180
|
-
eventName: "BatchClientSequenceNumberMismatch",
|
|
181
|
-
details: {
|
|
182
|
-
processingBatch: !!this.pendingBatchBeginMessage,
|
|
183
|
-
pendingBatchCsn: pendingMessage.batchStartCsn,
|
|
184
|
-
batchStartCsn,
|
|
185
|
-
messageBatchMetadata: message.metadata?.batch,
|
|
186
|
-
pendingMessageBatchMetadata: pendingMessage.opMetadata?.batch,
|
|
187
|
-
},
|
|
188
|
-
messageDetails: extractSafePropertiesFromMessage(message),
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
206
|
const messageContent = buildPendingMessageContent(message);
|
|
192
207
|
// Stringified content should match
|
|
193
208
|
if (pendingMessage.content !== messageContent) {
|
|
@@ -203,8 +218,26 @@ export class PendingStateManager {
|
|
|
203
218
|
/**
|
|
204
219
|
* This message could be the first message in batch. If so, set batch state marking the beginning of a batch.
|
|
205
220
|
* @param message - The message that is being processed.
|
|
221
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
222
|
+
* @param pendingMessage - The corresponding pendingMessage.
|
|
206
223
|
*/
|
|
207
|
-
maybeProcessBatchBegin(message) {
|
|
224
|
+
maybeProcessBatchBegin(message, batchStartCsn, pendingMessage) {
|
|
225
|
+
if (!this.isProcessingBatch) {
|
|
226
|
+
// Expecting the start of a batch (maybe single-message).
|
|
227
|
+
if (pendingMessage.batchIdContext.batchStartCsn !== batchStartCsn) {
|
|
228
|
+
this.logger?.sendErrorEvent({
|
|
229
|
+
eventName: "BatchClientSequenceNumberMismatch",
|
|
230
|
+
details: {
|
|
231
|
+
processingBatch: !!this.pendingBatchBeginMessage,
|
|
232
|
+
pendingBatchCsn: pendingMessage.batchIdContext.batchStartCsn,
|
|
233
|
+
batchStartCsn,
|
|
234
|
+
messageBatchMetadata: message.metadata?.batch,
|
|
235
|
+
pendingMessageBatchMetadata: pendingMessage.opMetadata?.batch,
|
|
236
|
+
},
|
|
237
|
+
messageDetails: extractSafePropertiesFromMessage(message),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
208
241
|
// This message is the first in a batch if the "batch" property on the metadata is set to true
|
|
209
242
|
if (message.metadata?.batch) {
|
|
210
243
|
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
@@ -264,8 +297,8 @@ export class PendingStateManager {
|
|
|
264
297
|
replayPendingStates() {
|
|
265
298
|
assert(this.stateHandler.connected(), 0x172 /* "The connection state is not consistent with the runtime" */);
|
|
266
299
|
// This assert suggests we are about to send same ops twice, which will result in data loss.
|
|
267
|
-
assert(this.
|
|
268
|
-
this.
|
|
300
|
+
assert(this.clientIdFromLastReplay !== this.stateHandler.clientId(), 0x173 /* "replayPendingStates called twice for same clientId!" */);
|
|
301
|
+
this.clientIdFromLastReplay = this.stateHandler.clientId();
|
|
269
302
|
assert(this.initialMessages.isEmpty(), 0x174 /* "initial states should be empty before replaying pending" */);
|
|
270
303
|
const initialPendingMessagesCount = this.pendingMessages.length;
|
|
271
304
|
let remainingPendingMessagesCount = this.pendingMessages.length;
|
|
@@ -276,40 +309,49 @@ export class PendingStateManager {
|
|
|
276
309
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
277
310
|
let pendingMessage = this.pendingMessages.shift();
|
|
278
311
|
remainingPendingMessagesCount--;
|
|
279
|
-
|
|
312
|
+
const batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;
|
|
313
|
+
assert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
|
|
314
|
+
// The next message starts a batch (possibly single-message), and we'll need its batchId.
|
|
315
|
+
// We'll find batchId on this message if it was previously generated.
|
|
316
|
+
// Otherwise, generate it now - this is the first time resubmitting this batch.
|
|
317
|
+
const batchId = asBatchMetadata(pendingMessage.opMetadata)?.batchId ??
|
|
318
|
+
generateBatchId(pendingMessage.batchIdContext.clientId, pendingMessage.batchIdContext.batchStartCsn);
|
|
280
319
|
/**
|
|
281
|
-
* We
|
|
320
|
+
* We must preserve the distinct batches on resubmit.
|
|
282
321
|
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
283
|
-
* either receive the whole batch ack or nothing at all.
|
|
322
|
+
* either receive the whole batch ack or nothing at all. @see ScheduleManager for how this works.
|
|
284
323
|
*/
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
while (remainingPendingMessagesCount >= 0) {
|
|
290
|
-
batch.push({
|
|
324
|
+
if (batchMetadataFlag === undefined) {
|
|
325
|
+
// Single-message batch
|
|
326
|
+
this.stateHandler.reSubmitBatch([
|
|
327
|
+
{
|
|
291
328
|
content: pendingMessage.content,
|
|
292
329
|
localOpMetadata: pendingMessage.localOpMetadata,
|
|
293
330
|
opMetadata: pendingMessage.opMetadata,
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
assert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
|
|
299
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
300
|
-
pendingMessage = this.pendingMessages.shift();
|
|
301
|
-
remainingPendingMessagesCount--;
|
|
302
|
-
assert(pendingMessage.opMetadata?.batch !== true, 0x556 /* Batch start needs a corresponding batch end */);
|
|
303
|
-
}
|
|
304
|
-
this.stateHandler.reSubmitBatch(batch);
|
|
331
|
+
},
|
|
332
|
+
], batchId);
|
|
333
|
+
continue;
|
|
305
334
|
}
|
|
306
|
-
else
|
|
307
|
-
|
|
335
|
+
// else: batchMetadataFlag === true (It's a typical multi-message batch)
|
|
336
|
+
assert(remainingPendingMessagesCount > 0, 0x554 /* Last pending message cannot be a batch begin */);
|
|
337
|
+
const batch = [];
|
|
338
|
+
// check is >= because batch end may be last pending message
|
|
339
|
+
while (remainingPendingMessagesCount >= 0) {
|
|
340
|
+
batch.push({
|
|
308
341
|
content: pendingMessage.content,
|
|
309
342
|
localOpMetadata: pendingMessage.localOpMetadata,
|
|
310
343
|
opMetadata: pendingMessage.opMetadata,
|
|
311
344
|
});
|
|
345
|
+
if (pendingMessage.opMetadata?.batch === false) {
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
assert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
|
|
349
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
350
|
+
pendingMessage = this.pendingMessages.shift();
|
|
351
|
+
remainingPendingMessagesCount--;
|
|
352
|
+
assert(pendingMessage.opMetadata?.batch !== true, 0x556 /* Batch start needs a corresponding batch end */);
|
|
312
353
|
}
|
|
354
|
+
this.stateHandler.reSubmitBatch(batch, batchId);
|
|
313
355
|
}
|
|
314
356
|
// pending ops should no longer depend on previous sequenced local ops after resubmit
|
|
315
357
|
this.savedOps = [];
|
|
@@ -325,4 +367,12 @@ export class PendingStateManager {
|
|
|
325
367
|
}
|
|
326
368
|
}
|
|
327
369
|
}
|
|
370
|
+
/** For back-compat if trying to apply stashed ops that pre-date batchIdContext */
|
|
371
|
+
function patchBatchIdContext(message) {
|
|
372
|
+
const batchIdContext = message.batchIdContext;
|
|
373
|
+
if (batchIdContext === undefined) {
|
|
374
|
+
// Using uuid guarantees uniqueness, retaining existing behavior
|
|
375
|
+
message.batchIdContext = { clientId: uuid(), batchStartCsn: -1 };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
328
378
|
//# sourceMappingURL=pendingStateManager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.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 { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert, Lazy } from \"@fluidframework/core-utils/internal\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tITelemetryLoggerExt,\n\tDataProcessingError,\n\tLoggingError,\n\textractSafePropertiesFromMessage,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport Deque from \"double-ended-queue\";\n\nimport { InboundSequencedContainerRuntimeMessage } from \"./messageTypes.js\";\nimport { IBatchMetadata } from \"./metadata.js\";\nimport type { BatchMessage } from \"./opLifecycle/index.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n */\nexport interface IPendingMessage {\n\ttype: \"message\";\n\treferenceSequenceNumber: number;\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n\tsequenceNumber?: number;\n\tbatchStartCsn?: number;\n}\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingMessage[];\n}\n\nexport interface IPendingBatchMessage {\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tclose(error?: ICriticalContainerError): void;\n\tapplyStashedOp(content: string): Promise<unknown>;\n\treSubmit(message: IPendingBatchMessage): void;\n\treSubmitBatch(batch: IPendingBatchMessage[]): void;\n\tisActiveConnection: () => boolean;\n\tisAttached: () => boolean;\n}\n\n/** Union of keys of T */\ntype KeysOfUnion<T extends object> = T extends T ? keyof T : never;\n/** *Partial* type all possible combinations of properties and values of union T.\n * This loosens typing allowing access to all possible properties without\n * narrowing.\n */\ntype AnyComboFromUnion<T extends object> = { [P in KeysOfUnion<T>]?: T[P] };\n\nfunction buildPendingMessageContent(\n\t// AnyComboFromUnion is needed need to gain access to compatDetails that\n\t// is only defined for some cases.\n\tmessage: AnyComboFromUnion<InboundSequencedContainerRuntimeMessage>,\n): string {\n\t// IMPORTANT: Order matters here, this must match the order of the properties used\n\t// when submitting the message.\n\tconst { type, contents, compatDetails } = message;\n\t// Any properties that are not defined, won't be emitted by stringify.\n\treturn JSON.stringify({ type, contents, compatDetails });\n}\n\nfunction withoutLocalOpMetadata(message: IPendingMessage): IPendingMessage {\n\treturn {\n\t\t...message,\n\t\tlocalOpMetadata: undefined,\n\t};\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n\tprivate readonly pendingMessages = new Deque<IPendingMessage>();\n\t// This queue represents already acked messages.\n\tprivate readonly initialMessages = new Deque<IPendingMessage>();\n\n\t/**\n\t * Sequenced local ops that are saved when stashing since pending ops may depend on them\n\t */\n\tprivate savedOps: IPendingMessage[] = [];\n\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\t// Indicates whether we are processing a batch.\n\tprivate isProcessingBatch: boolean = false;\n\n\t// This stores the first message in the batch that we are processing. This is used to verify that we get\n\t// the correct batch metadata.\n\tprivate pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n\tprivate clientId: string | undefined;\n\n\t/**\n\t * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with\n\t * 'hasPendingMessages'.\n\t */\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length + this.initialMessages.length;\n\t}\n\n\t/**\n\t * The minimumPendingMessageSequenceNumber is the minimum of the first pending message and the first initial message.\n\t *\n\t * We need this so that we can properly keep local data and maintain the correct sequence window.\n\t */\n\tpublic get minimumPendingMessageSequenceNumber(): number | undefined {\n\t\treturn this.pendingMessages.peekFront()?.referenceSequenceNumber;\n\t}\n\n\t/**\n\t * Called to check if there are any pending messages in the pending message queue.\n\t * @returns A boolean indicating whether there are messages or not.\n\t */\n\tpublic hasPendingMessages(): boolean {\n\t\treturn this.pendingMessagesCount !== 0;\n\t}\n\n\tpublic getLocalState(snapshotSequenceNumber?: number): IPendingLocalState {\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x2e9 /* \"Must call getLocalState() after applying initial states\" */,\n\t\t);\n\t\t// Using snapshot sequence number to filter ops older than our latest snapshot.\n\t\t// Such ops should not be declared in pending/stashed state. Snapshot seq num will not\n\t\t// be available when the container is not attached. Therefore, no filtering is needed.\n\t\tconst newSavedOps = [...this.savedOps].filter((message) => {\n\t\t\tassert(\n\t\t\t\tmessage.sequenceNumber !== undefined,\n\t\t\t\t0x97c /* saved op should already have a sequence number */,\n\t\t\t);\n\t\t\treturn message.sequenceNumber > (snapshotSequenceNumber ?? 0);\n\t\t});\n\t\tthis.pendingMessages.toArray().forEach((message) => {\n\t\t\tif (\n\t\t\t\tsnapshotSequenceNumber !== undefined &&\n\t\t\t\tmessage.referenceSequenceNumber < snapshotSequenceNumber\n\t\t\t) {\n\t\t\t\tthrow new LoggingError(\"trying to stash ops older than our latest snapshot\");\n\t\t\t}\n\t\t});\n\t\treturn {\n\t\t\tpendingStates: [\n\t\t\t\t...newSavedOps,\n\t\t\t\t...this.pendingMessages.toArray().map(withoutLocalOpMetadata),\n\t\t\t],\n\t\t};\n\t}\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tinitialLocalState: IPendingLocalState | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt | undefined,\n\t) {\n\t\tif (initialLocalState?.pendingStates) {\n\t\t\tthis.initialMessages.push(...initialLocalState.pendingStates);\n\t\t}\n\t}\n\n\tpublic get disposed() {\n\t\treturn this.disposeOnce.evaluated;\n\t}\n\tpublic readonly dispose = () => this.disposeOnce.value;\n\n\t/**\n\t * The given batch has been flushed, and needs to be tracked locally until the corresponding\n\t * acks are processed, to ensure it is successfully sent.\n\t * @param batch - The batch that was flushed\n\t * @param clientSequenceNumber - The CSN of the first message in the batch,\n\t * or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)\n\t */\n\tpublic onFlushBatch(batch: BatchMessage[], clientSequenceNumber: number | undefined) {\n\t\tfor (const message of batch) {\n\t\t\tconst {\n\t\t\t\tcontents: content = \"\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\tmetadata: opMetadata,\n\t\t\t} = message;\n\t\t\tconst pendingMessage: IPendingMessage = {\n\t\t\t\ttype: \"message\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tcontent,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\topMetadata,\n\t\t\t\tbatchStartCsn: clientSequenceNumber,\n\t\t\t};\n\t\t\tthis.pendingMessages.push(pendingMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n\t * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n\t */\n\tpublic async applyStashedOpsAt(seqNum?: number) {\n\t\t// apply stashed ops at sequence number\n\t\twhile (!this.initialMessages.isEmpty()) {\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tconst peekMessage = this.initialMessages.peekFront()!;\n\t\t\t\tif (peekMessage.referenceSequenceNumber > seqNum) {\n\t\t\t\t\tbreak; // nothing left to do at this sequence number\n\t\t\t\t}\n\t\t\t\tif (peekMessage.referenceSequenceNumber < seqNum) {\n\t\t\t\t\tthrow new Error(\"loaded from snapshot too recent to apply stashed ops\");\n\t\t\t\t}\n\t\t\t}\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.shift()!;\n\t\t\ttry {\n\t\t\t\t// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n\t\t\t\tconst localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);\n\t\t\t\tif (!this.stateHandler.isAttached()) {\n\t\t\t\t\tif (localOpMetadata !== undefined) {\n\t\t\t\t\t\tthrow new Error(\"Local Op Metadata must be undefined when not attached\");\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n\t * the batch information was preserved for batch messages.\n\t * @param message - The message that got ack'd and needs to be processed.\n\t * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)\n\t * (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)\n\t */\n\tpublic processPendingLocalMessage(\n\t\tmessage: InboundSequencedContainerRuntimeMessage,\n\t\tbatchStartCsn: number,\n\t): unknown {\n\t\t// Pre-processing part - This may be the start of a batch.\n\t\tthis.maybeProcessBatchBegin(message);\n\t\t// Get the next message from the pending queue. Verify a message exists.\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0x169 /* \"No pending message found for this remote message\" */,\n\t\t);\n\t\tpendingMessage.sequenceNumber = message.sequenceNumber;\n\t\tthis.savedOps.push(withoutLocalOpMetadata(pendingMessage));\n\n\t\tthis.pendingMessages.shift();\n\n\t\tif (pendingMessage.batchStartCsn !== batchStartCsn) {\n\t\t\tthis.logger?.sendErrorEvent({\n\t\t\t\teventName: \"BatchClientSequenceNumberMismatch\",\n\t\t\t\tdetails: {\n\t\t\t\t\tprocessingBatch: !!this.pendingBatchBeginMessage,\n\t\t\t\t\tpendingBatchCsn: pendingMessage.batchStartCsn,\n\t\t\t\t\tbatchStartCsn,\n\t\t\t\t\tmessageBatchMetadata: (message.metadata as any)?.batch,\n\t\t\t\t\tpendingMessageBatchMetadata: (pendingMessage.opMetadata as any)?.batch,\n\t\t\t\t},\n\t\t\t\tmessageDetails: extractSafePropertiesFromMessage(message),\n\t\t\t});\n\t\t}\n\n\t\tconst messageContent = buildPendingMessageContent(message);\n\n\t\t// Stringified content should match\n\t\tif (pendingMessage.content !== messageContent) {\n\t\t\tthis.stateHandler.close(\n\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\"pending local message content mismatch\",\n\t\t\t\t\t\"unexpectedAckReceived\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\texpectedMessageType: JSON.parse(pendingMessage.content).type,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Post-processing part - If we are processing a batch then this could be the last message in the batch.\n\t\tthis.maybeProcessBatchEnd(message);\n\n\t\treturn pendingMessage.localOpMetadata;\n\t}\n\n\t/**\n\t * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n\t * @param message - The message that is being processed.\n\t */\n\tprivate maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\n\t\t// This message is the first in a batch if the \"batch\" property on the metadata is set to true\n\t\tif ((message.metadata as IBatchMetadata | undefined)?.batch) {\n\t\t\t// We should not already be processing a batch and there should be no pending batch begin message.\n\t\t\tassert(\n\t\t\t\t!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n\t\t\t\t0x16b /* \"The pending batch state indicates we are already processing a batch\" */,\n\t\t\t);\n\n\t\t\t// Set the pending batch state indicating we have started processing a batch.\n\t\t\tthis.pendingBatchBeginMessage = message;\n\t\t\tthis.isProcessingBatch = true;\n\t\t}\n\t}\n\n\t/**\n\t * This message could be the last message in batch. If so, clear batch state since the batch is complete.\n\t * @param message - The message that is being processed.\n\t */\n\tprivate maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n\t\tif (!this.isProcessingBatch) {\n\t\t\treturn;\n\t\t}\n\n\t\t// There should be a pending batch begin message.\n\t\tassert(\n\t\t\tthis.pendingBatchBeginMessage !== undefined,\n\t\t\t0x16d /* \"There is no pending batch begin message\" */,\n\t\t);\n\n\t\tconst batchEndMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\t\tif (this.pendingMessages.isEmpty() || batchEndMetadata === false) {\n\t\t\t// Get the batch begin metadata from the first message in the batch.\n\t\t\tconst batchBeginMetadata = (\n\t\t\t\tthis.pendingBatchBeginMessage.metadata as IBatchMetadata | undefined\n\t\t\t)?.batch;\n\n\t\t\t// There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n\t\t\t// are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n\t\t\tif (this.pendingBatchBeginMessage === message) {\n\t\t\t\tassert(\n\t\t\t\t\tbatchBeginMetadata === undefined,\n\t\t\t\t\t0x16e /* \"Batch with single message should not have batch metadata\" */,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tif (batchBeginMetadata !== true || batchEndMetadata !== false) {\n\t\t\t\t\tthis.stateHandler.close(\n\t\t\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\t\t\"Pending batch inconsistency\", // Formerly known as asserts 0x16f and 0x170\n\t\t\t\t\t\t\t\"processPendingLocalMessage\",\n\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\t\t\t\t\tthis.pendingBatchBeginMessage.clientId === null\n\t\t\t\t\t\t\t\t\t\t? \"null\"\n\t\t\t\t\t\t\t\t\t\t: this.pendingBatchBeginMessage.clientId,\n\t\t\t\t\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t\t\t\t\t\thasBatchStart: batchBeginMetadata === true,\n\t\t\t\t\t\t\t\thasBatchEnd: batchEndMetadata === false,\n\t\t\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t\t\t\tpendingMessagesCount: this.pendingMessagesCount,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Clear the pending batch state now that we have processed the entire batch.\n\t\t\tthis.pendingBatchBeginMessage = undefined;\n\t\t\tthis.isProcessingBatch = false;\n\t\t}\n\t}\n\n\t/**\n\t * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n\t * states in its queue. This includes triggering resubmission of unacked ops.\n\t * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)\n\t */\n\tpublic replayPendingStates() {\n\t\tassert(\n\t\t\tthis.stateHandler.connected(),\n\t\t\t0x172 /* \"The connection state is not consistent with the runtime\" */,\n\t\t);\n\n\t\t// This assert suggests we are about to send same ops twice, which will result in data loss.\n\t\tassert(\n\t\t\tthis.clientId !== this.stateHandler.clientId(),\n\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t);\n\t\tthis.clientId = this.stateHandler.clientId();\n\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x174 /* \"initial states should be empty before replaying pending\" */,\n\t\t);\n\n\t\tconst initialPendingMessagesCount = this.pendingMessages.length;\n\t\tlet remainingPendingMessagesCount = this.pendingMessages.length;\n\n\t\t// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n\t\t// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n\t\t// which must not be replayed.\n\t\twhile (remainingPendingMessagesCount > 0) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tlet pendingMessage = this.pendingMessages.shift()!;\n\t\t\tremainingPendingMessagesCount--;\n\t\t\tassert(\n\t\t\t\tpendingMessage.opMetadata?.batch !== false,\n\t\t\t\t0x41b /* We cannot process batches in chunks */,\n\t\t\t);\n\n\t\t\t/**\n\t\t\t * We want to ensure grouped messages get processed in a batch.\n\t\t\t * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n\t\t\t * either receive the whole batch ack or nothing at all.\n\t\t\t */\n\t\t\tif (pendingMessage.opMetadata?.batch) {\n\t\t\t\tassert(\n\t\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t\t);\n\n\t\t\t\tconst batch: IPendingBatchMessage[] = [];\n\n\t\t\t\t// check is >= because batch end may be last pending message\n\t\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\t\tbatch.push({\n\t\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\t\tassert(\n\t\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(batch);\n\t\t\t} else {\n\t\t\t\tthis.stateHandler.reSubmit({\n\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// pending ops should no longer depend on previous sequenced local ops after resubmit\n\t\tthis.savedOps = [];\n\n\t\t// We replayPendingStates on read connections too - we expect these to get nack'd though, and to then reconnect\n\t\t// on a write connection and replay again. This filters out the replay that happens on the read connection so\n\t\t// we only see the replays on write connections (that have a chance to go through).\n\t\tif (this.stateHandler.isActiveConnection()) {\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"PendingStatesReplayed\",\n\t\t\t\tcount: initialPendingMessagesCount,\n\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.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 { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert, Lazy } from \"@fluidframework/core-utils/internal\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tITelemetryLoggerExt,\n\tDataProcessingError,\n\tLoggingError,\n\textractSafePropertiesFromMessage,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport Deque from \"double-ended-queue\";\nimport { v4 as uuid } from \"uuid\";\n\nimport { type InboundSequencedContainerRuntimeMessage } from \"./messageTypes.js\";\nimport { asBatchMetadata, IBatchMetadata } from \"./metadata.js\";\nimport { BatchId, BatchMessage, generateBatchId } from \"./opLifecycle/index.js\";\nimport { pkgVersion } from \"./packageVersion.js\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n *\n * @remarks This is the current serialization format for pending local state when a Container is serialized.\n */\nexport interface IPendingMessage {\n\ttype: \"message\";\n\treferenceSequenceNumber: number;\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n\tsequenceNumber?: number;\n\t/** Info needed to compute the batchId on reconnect */\n\tbatchIdContext: {\n\t\t/** The Batch's original clientId, from when it was first flushed to be submitted */\n\t\tclientId: string;\n\t\t/**\n\t\t * The Batch's original clientSequenceNumber, from when it was first flushed to be submitted\n\t\t *\t@remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)\n\t\t */\n\t\tbatchStartCsn: number;\n\t};\n}\n\ntype Patch<T, U> = U & Omit<T, keyof U>;\n\n/** First version of the type (pre-dates batchIdContext) */\ntype IPendingMessageV0 = Patch<IPendingMessage, { batchIdContext?: undefined }>;\n\n/**\n * Union of all supported schemas for when applying stashed ops\n *\n * @remarks When the format changes, this type should update to reflect all possible schemas.\n */\ntype IPendingMessageFromStash = IPendingMessageV0 | IPendingMessage;\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingMessage[];\n}\n\n/** Info needed to replay/resubmit a pending message */\nexport type PendingMessageResubmitData = Pick<\n\tIPendingMessage,\n\t\"content\" | \"localOpMetadata\" | \"opMetadata\"\n>;\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tclose(error?: ICriticalContainerError): void;\n\tapplyStashedOp(content: string): Promise<unknown>;\n\treSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;\n\tisActiveConnection: () => boolean;\n\tisAttached: () => boolean;\n}\n\n/** Union of keys of T */\ntype KeysOfUnion<T extends object> = T extends T ? keyof T : never;\n/** *Partial* type all possible combinations of properties and values of union T.\n * This loosens typing allowing access to all possible properties without\n * narrowing.\n */\ntype AnyComboFromUnion<T extends object> = { [P in KeysOfUnion<T>]?: T[P] };\n\nfunction buildPendingMessageContent(\n\t// AnyComboFromUnion is needed need to gain access to compatDetails that\n\t// is only defined for some cases.\n\tmessage: AnyComboFromUnion<InboundSequencedContainerRuntimeMessage>,\n): string {\n\t// IMPORTANT: Order matters here, this must match the order of the properties used\n\t// when submitting the message.\n\tconst { type, contents, compatDetails } = message;\n\t// Any properties that are not defined, won't be emitted by stringify.\n\treturn JSON.stringify({ type, contents, compatDetails });\n}\n\nfunction withoutLocalOpMetadata(message: IPendingMessage): IPendingMessage {\n\treturn {\n\t\t...message,\n\t\tlocalOpMetadata: undefined,\n\t};\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n\t/** Messages that will need to be resubmitted if not ack'd before the next reconnection */\n\tprivate readonly pendingMessages = new Deque<IPendingMessage>();\n\t/** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */\n\tprivate readonly initialMessages = new Deque<IPendingMessageFromStash>();\n\n\t/**\n\t * Sequenced local ops that are saved when stashing since pending ops may depend on them\n\t */\n\tprivate savedOps: IPendingMessage[] = [];\n\n\t/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */\n\tprivate negativeCounter: number = -1;\n\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\t// Indicates whether we are processing a batch.\n\tprivate isProcessingBatch: boolean = false;\n\n\t// This stores the first message in the batch that we are processing. This is used to verify that we get\n\t// the correct batch metadata.\n\tprivate pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n\t/** Used to ensure we don't replay ops on the same connection twice */\n\tprivate clientIdFromLastReplay: string | undefined;\n\n\t/**\n\t * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with\n\t * 'hasPendingMessages'.\n\t */\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length + this.initialMessages.length;\n\t}\n\n\t/**\n\t * The minimumPendingMessageSequenceNumber is the minimum of the first pending message and the first initial message.\n\t *\n\t * We need this so that we can properly keep local data and maintain the correct sequence window.\n\t */\n\tpublic get minimumPendingMessageSequenceNumber(): number | undefined {\n\t\treturn this.pendingMessages.peekFront()?.referenceSequenceNumber;\n\t}\n\n\t/**\n\t * Called to check if there are any pending messages in the pending message queue.\n\t * @returns A boolean indicating whether there are messages or not.\n\t */\n\tpublic hasPendingMessages(): boolean {\n\t\treturn this.pendingMessagesCount !== 0;\n\t}\n\n\tpublic getLocalState(snapshotSequenceNumber?: number): IPendingLocalState {\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x2e9 /* \"Must call getLocalState() after applying initial states\" */,\n\t\t);\n\t\t// Using snapshot sequence number to filter ops older than our latest snapshot.\n\t\t// Such ops should not be declared in pending/stashed state. Snapshot seq num will not\n\t\t// be available when the container is not attached. Therefore, no filtering is needed.\n\t\tconst newSavedOps = [...this.savedOps].filter((message) => {\n\t\t\tassert(\n\t\t\t\tmessage.sequenceNumber !== undefined,\n\t\t\t\t0x97c /* saved op should already have a sequence number */,\n\t\t\t);\n\t\t\treturn message.sequenceNumber > (snapshotSequenceNumber ?? 0);\n\t\t});\n\t\tthis.pendingMessages.toArray().forEach((message) => {\n\t\t\tif (\n\t\t\t\tsnapshotSequenceNumber !== undefined &&\n\t\t\t\tmessage.referenceSequenceNumber < snapshotSequenceNumber\n\t\t\t) {\n\t\t\t\tthrow new LoggingError(\"trying to stash ops older than our latest snapshot\");\n\t\t\t}\n\t\t});\n\t\treturn {\n\t\t\tpendingStates: [\n\t\t\t\t...newSavedOps,\n\t\t\t\t...this.pendingMessages.toArray().map(withoutLocalOpMetadata),\n\t\t\t],\n\t\t};\n\t}\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tstashedLocalState: IPendingLocalState | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t) {\n\t\tif (stashedLocalState?.pendingStates) {\n\t\t\tthis.initialMessages.push(...stashedLocalState.pendingStates);\n\t\t}\n\t}\n\n\tpublic get disposed() {\n\t\treturn this.disposeOnce.evaluated;\n\t}\n\tpublic readonly dispose = () => this.disposeOnce.value;\n\n\t/**\n\t * The given batch has been flushed, and needs to be tracked locally until the corresponding\n\t * acks are processed, to ensure it is successfully sent.\n\t * @param batch - The batch that was flushed\n\t * @param clientSequenceNumber - The CSN of the first message in the batch,\n\t * or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)\n\t */\n\tpublic onFlushBatch(batch: BatchMessage[], clientSequenceNumber: number | undefined) {\n\t\t// If we're connected this is the client of the current connection,\n\t\t// otherwise it's the clientId that just disconnected\n\t\t// It's only undefined if we've NEVER connected. This is a tight corner case and we can\n\t\t// simply make up a unique ID in this case.\n\t\tconst clientId = this.stateHandler.clientId() ?? uuid();\n\n\t\t// If the batch was not yet sent, we need to assign a unique batchStartCsn\n\t\t// Use a negative number to distinguish these from real CSNs\n\t\tconst batchStartCsn = clientSequenceNumber ?? this.negativeCounter--;\n\n\t\tfor (const message of batch) {\n\t\t\tconst {\n\t\t\t\tcontents: content = \"\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\tmetadata: opMetadata,\n\t\t\t} = message;\n\t\t\tconst pendingMessage: IPendingMessage = {\n\t\t\t\ttype: \"message\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tcontent,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\topMetadata,\n\t\t\t\t// Note: We only need this on the first message.\n\t\t\t\tbatchIdContext: { clientId, batchStartCsn },\n\t\t\t};\n\t\t\tthis.pendingMessages.push(pendingMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n\t * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n\t */\n\tpublic async applyStashedOpsAt(seqNum?: number) {\n\t\t// apply stashed ops at sequence number\n\t\twhile (!this.initialMessages.isEmpty()) {\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tconst peekMessage = this.initialMessages.peekFront()!;\n\t\t\t\tif (peekMessage.referenceSequenceNumber > seqNum) {\n\t\t\t\t\tbreak; // nothing left to do at this sequence number\n\t\t\t\t}\n\t\t\t\tif (peekMessage.referenceSequenceNumber < seqNum) {\n\t\t\t\t\tthrow new Error(\"loaded from snapshot too recent to apply stashed ops\");\n\t\t\t\t}\n\t\t\t}\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.shift()!;\n\t\t\ttry {\n\t\t\t\t// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n\t\t\t\tconst localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);\n\t\t\t\tif (!this.stateHandler.isAttached()) {\n\t\t\t\t\tif (localOpMetadata !== undefined) {\n\t\t\t\t\t\tthrow new Error(\"Local Op Metadata must be undefined when not attached\");\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t\t\tpatchBatchIdContext(nextMessage); // Back compat\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processes the incoming batch from the server. It verifies that messages are received in the right order and\n\t * that the batch information is correct.\n\t * @param batch - The batch that is being processed.\n\t * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch\n\t */\n\tpublic processPendingLocalBatch(\n\t\tbatch: InboundSequencedContainerRuntimeMessage[],\n\t\tbatchStartCsn: number,\n\t): {\n\t\tmessage: InboundSequencedContainerRuntimeMessage;\n\t\tlocalOpMetadata: unknown;\n\t}[] {\n\t\treturn batch.map((message) => ({\n\t\t\tmessage,\n\t\t\tlocalOpMetadata: this.processPendingLocalMessage(message, batchStartCsn),\n\t\t}));\n\t}\n\n\t/**\n\t * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n\t * the batch information was preserved for batch messages.\n\t * @param message - The message that got ack'd and needs to be processed.\n\t * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)\n\t * (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)\n\t */\n\tprivate processPendingLocalMessage(\n\t\tmessage: InboundSequencedContainerRuntimeMessage,\n\t\tbatchStartCsn: number,\n\t): unknown {\n\t\t// Get the next message from the pending queue. Verify a message exists.\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0x169 /* \"No pending message found for this remote message\" */,\n\t\t);\n\n\t\t// This may be the start of a batch.\n\t\tthis.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);\n\n\t\tpendingMessage.sequenceNumber = message.sequenceNumber;\n\t\tthis.savedOps.push(withoutLocalOpMetadata(pendingMessage));\n\n\t\tthis.pendingMessages.shift();\n\n\t\tconst messageContent = buildPendingMessageContent(message);\n\n\t\t// Stringified content should match\n\t\tif (pendingMessage.content !== messageContent) {\n\t\t\tthis.stateHandler.close(\n\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\"pending local message content mismatch\",\n\t\t\t\t\t\"unexpectedAckReceived\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\texpectedMessageType: JSON.parse(pendingMessage.content).type,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Post-processing part - If we are processing a batch then this could be the last message in the batch.\n\t\tthis.maybeProcessBatchEnd(message);\n\n\t\treturn pendingMessage.localOpMetadata;\n\t}\n\n\t/**\n\t * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n\t * @param message - The message that is being processed.\n\t * @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)\n\t * @param pendingMessage - The corresponding pendingMessage.\n\t */\n\tprivate maybeProcessBatchBegin(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tbatchStartCsn: number,\n\t\tpendingMessage: IPendingMessage,\n\t) {\n\t\tif (!this.isProcessingBatch) {\n\t\t\t// Expecting the start of a batch (maybe single-message).\n\t\t\tif (pendingMessage.batchIdContext.batchStartCsn !== batchStartCsn) {\n\t\t\t\tthis.logger?.sendErrorEvent({\n\t\t\t\t\teventName: \"BatchClientSequenceNumberMismatch\",\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tprocessingBatch: !!this.pendingBatchBeginMessage,\n\t\t\t\t\t\tpendingBatchCsn: pendingMessage.batchIdContext.batchStartCsn,\n\t\t\t\t\t\tbatchStartCsn,\n\t\t\t\t\t\tmessageBatchMetadata: (message.metadata as any)?.batch,\n\t\t\t\t\t\tpendingMessageBatchMetadata: (pendingMessage.opMetadata as any)?.batch,\n\t\t\t\t\t},\n\t\t\t\t\tmessageDetails: extractSafePropertiesFromMessage(message),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// This message is the first in a batch if the \"batch\" property on the metadata is set to true\n\t\tif ((message.metadata as IBatchMetadata | undefined)?.batch) {\n\t\t\t// We should not already be processing a batch and there should be no pending batch begin message.\n\t\t\tassert(\n\t\t\t\t!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n\t\t\t\t0x16b /* \"The pending batch state indicates we are already processing a batch\" */,\n\t\t\t);\n\n\t\t\t// Set the pending batch state indicating we have started processing a batch.\n\t\t\tthis.pendingBatchBeginMessage = message;\n\t\t\tthis.isProcessingBatch = true;\n\t\t}\n\t}\n\n\t/**\n\t * This message could be the last message in batch. If so, clear batch state since the batch is complete.\n\t * @param message - The message that is being processed.\n\t */\n\tprivate maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n\t\tif (!this.isProcessingBatch) {\n\t\t\treturn;\n\t\t}\n\n\t\t// There should be a pending batch begin message.\n\t\tassert(\n\t\t\tthis.pendingBatchBeginMessage !== undefined,\n\t\t\t0x16d /* \"There is no pending batch begin message\" */,\n\t\t);\n\n\t\tconst batchEndMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\t\tif (this.pendingMessages.isEmpty() || batchEndMetadata === false) {\n\t\t\t// Get the batch begin metadata from the first message in the batch.\n\t\t\tconst batchBeginMetadata = (\n\t\t\t\tthis.pendingBatchBeginMessage.metadata as IBatchMetadata | undefined\n\t\t\t)?.batch;\n\n\t\t\t// There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n\t\t\t// are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n\t\t\tif (this.pendingBatchBeginMessage === message) {\n\t\t\t\tassert(\n\t\t\t\t\tbatchBeginMetadata === undefined,\n\t\t\t\t\t0x16e /* \"Batch with single message should not have batch metadata\" */,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tif (batchBeginMetadata !== true || batchEndMetadata !== false) {\n\t\t\t\t\tthis.stateHandler.close(\n\t\t\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\t\t\"Pending batch inconsistency\", // Formerly known as asserts 0x16f and 0x170\n\t\t\t\t\t\t\t\"processPendingLocalMessage\",\n\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\t\t\t\t\tthis.pendingBatchBeginMessage.clientId === null\n\t\t\t\t\t\t\t\t\t\t? \"null\"\n\t\t\t\t\t\t\t\t\t\t: this.pendingBatchBeginMessage.clientId,\n\t\t\t\t\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t\t\t\t\t\thasBatchStart: batchBeginMetadata === true,\n\t\t\t\t\t\t\t\thasBatchEnd: batchEndMetadata === false,\n\t\t\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t\t\t\tpendingMessagesCount: this.pendingMessagesCount,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Clear the pending batch state now that we have processed the entire batch.\n\t\t\tthis.pendingBatchBeginMessage = undefined;\n\t\t\tthis.isProcessingBatch = false;\n\t\t}\n\t}\n\n\t/**\n\t * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n\t * states in its queue. This includes triggering resubmission of unacked ops.\n\t * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)\n\t */\n\tpublic replayPendingStates() {\n\t\tassert(\n\t\t\tthis.stateHandler.connected(),\n\t\t\t0x172 /* \"The connection state is not consistent with the runtime\" */,\n\t\t);\n\n\t\t// This assert suggests we are about to send same ops twice, which will result in data loss.\n\t\tassert(\n\t\t\tthis.clientIdFromLastReplay !== this.stateHandler.clientId(),\n\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t);\n\t\tthis.clientIdFromLastReplay = this.stateHandler.clientId();\n\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x174 /* \"initial states should be empty before replaying pending\" */,\n\t\t);\n\n\t\tconst initialPendingMessagesCount = this.pendingMessages.length;\n\t\tlet remainingPendingMessagesCount = this.pendingMessages.length;\n\n\t\t// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n\t\t// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n\t\t// which must not be replayed.\n\t\twhile (remainingPendingMessagesCount > 0) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tlet pendingMessage = this.pendingMessages.shift()!;\n\t\t\tremainingPendingMessagesCount--;\n\n\t\t\tconst batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;\n\t\t\tassert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);\n\n\t\t\t// The next message starts a batch (possibly single-message), and we'll need its batchId.\n\t\t\t// We'll find batchId on this message if it was previously generated.\n\t\t\t// Otherwise, generate it now - this is the first time resubmitting this batch.\n\t\t\tconst batchId =\n\t\t\t\tasBatchMetadata(pendingMessage.opMetadata)?.batchId ??\n\t\t\t\tgenerateBatchId(\n\t\t\t\t\tpendingMessage.batchIdContext.clientId,\n\t\t\t\t\tpendingMessage.batchIdContext.batchStartCsn,\n\t\t\t\t);\n\n\t\t\t/**\n\t\t\t * We must preserve the distinct batches on resubmit.\n\t\t\t * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n\t\t\t * either receive the whole batch ack or nothing at all. @see ScheduleManager for how this works.\n\t\t\t */\n\t\t\tif (batchMetadataFlag === undefined) {\n\t\t\t\t// Single-message batch\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tbatchId,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// else: batchMetadataFlag === true (It's a typical multi-message batch)\n\n\t\t\tassert(\n\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t);\n\n\t\t\tconst batch: PendingMessageResubmitData[] = [];\n\n\t\t\t// check is >= because batch end may be last pending message\n\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\tbatch.push({\n\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t});\n\n\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\tassert(\n\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.stateHandler.reSubmitBatch(batch, batchId);\n\t\t}\n\n\t\t// pending ops should no longer depend on previous sequenced local ops after resubmit\n\t\tthis.savedOps = [];\n\n\t\t// We replayPendingStates on read connections too - we expect these to get nack'd though, and to then reconnect\n\t\t// on a write connection and replay again. This filters out the replay that happens on the read connection so\n\t\t// we only see the replays on write connections (that have a chance to go through).\n\t\tif (this.stateHandler.isActiveConnection()) {\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"PendingStatesReplayed\",\n\t\t\t\tcount: initialPendingMessagesCount,\n\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t});\n\t\t}\n\t}\n}\n\n/** For back-compat if trying to apply stashed ops that pre-date batchIdContext */\nfunction patchBatchIdContext(\n\tmessage: IPendingMessageFromStash,\n): asserts message is IPendingMessage {\n\tconst batchIdContext: IPendingMessageFromStash[\"batchIdContext\"] = message.batchIdContext;\n\tif (batchIdContext === undefined) {\n\t\t// Using uuid guarantees uniqueness, retaining existing behavior\n\t\tmessage.batchIdContext = { clientId: uuid(), batchStartCsn: -1 };\n\t}\n}\n"]}
|
package/lib/public.d.ts
CHANGED
package/lib/scheduleManager.js
CHANGED
|
@@ -80,6 +80,8 @@ class ScheduleManagerCore {
|
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
// First message will have the batch flag set to true if doing a batched send
|
|
83
|
+
// Non null asserting because of the length check above
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
83
85
|
const firstMessageMetadata = messages[0].metadata;
|
|
84
86
|
if (!firstMessageMetadata?.batch) {
|
|
85
87
|
return;
|
|
@@ -90,6 +92,8 @@ class ScheduleManagerCore {
|
|
|
90
92
|
return;
|
|
91
93
|
}
|
|
92
94
|
// Set the batch flag to false on the last message to indicate the end of the send batch
|
|
95
|
+
// Non null asserting here because of the length check at the start of the function
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
93
97
|
const lastMessage = messages[messages.length - 1];
|
|
94
98
|
// TODO: It's not clear if this shallow clone is required, as opposed to just setting "batch" to false.
|
|
95
99
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|