@fluidframework/container-runtime 2.0.0-internal.2.4.0 → 2.0.0-internal.3.0.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/dist/containerRuntime.d.ts +45 -42
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +87 -38
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +1 -0
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +7 -2
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +0 -1
- package/dist/opLifecycle/outbox.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 +4 -13
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +130 -160
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summarizerClientElection.d.ts +1 -2
- package/dist/summarizerClientElection.d.ts.map +1 -1
- package/dist/summarizerClientElection.js +3 -30
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summarizerTypes.d.ts +0 -4
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/lib/containerRuntime.d.ts +45 -42
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +87 -38
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +1 -0
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +7 -2
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +0 -1
- package/lib/opLifecycle/outbox.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 +4 -13
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +130 -160
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summarizerClientElection.d.ts +1 -2
- package/lib/summarizerClientElection.d.ts.map +1 -1
- package/lib/summarizerClientElection.js +3 -30
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summarizerTypes.d.ts +0 -4
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/package.json +34 -16
- package/src/containerRuntime.ts +116 -84
- package/src/dataStoreContext.ts +12 -6
- package/src/opLifecycle/outbox.ts +0 -2
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +146 -187
- package/src/summarizerClientElection.ts +1 -30
- package/src/summarizerTypes.ts +0 -4
|
@@ -17,38 +17,74 @@ import { pkgVersion } from "./packageVersion";
|
|
|
17
17
|
*/
|
|
18
18
|
export class PendingStateManager {
|
|
19
19
|
constructor(stateHandler, initialLocalState) {
|
|
20
|
-
var _a;
|
|
20
|
+
var _a, _b;
|
|
21
21
|
this.stateHandler = stateHandler;
|
|
22
|
-
this.
|
|
22
|
+
this.pendingMessages = new Deque();
|
|
23
|
+
this.initialMessages = new Deque();
|
|
23
24
|
this.disposeOnce = new Lazy(() => {
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
25
|
+
this.initialMessages.clear();
|
|
26
|
+
this.pendingMessages.clear();
|
|
26
27
|
});
|
|
27
|
-
// Maintains the count of messages that are currently unacked.
|
|
28
|
-
this._pendingMessagesCount = 0;
|
|
29
28
|
// Indicates whether we are processing a batch.
|
|
30
29
|
this.isProcessingBatch = false;
|
|
31
30
|
this.dispose = () => this.disposeOnce.value;
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Convert old local state format to the new format
|
|
33
|
+
* The old format contained "flush" messages as the indicator of batch ends
|
|
34
|
+
* The new format instead uses batch metadata on the last message to indicate batch ends
|
|
35
|
+
* ! TODO: Remove this conversion in "2.0.0-internal.4.0.0" as rollback from future version will be new format
|
|
36
|
+
* AB#2496 tracks removal
|
|
37
|
+
*/
|
|
38
|
+
if (initialLocalState === null || initialLocalState === void 0 ? void 0 : initialLocalState.pendingStates) {
|
|
39
|
+
const pendingStates = initialLocalState === null || initialLocalState === void 0 ? void 0 : initialLocalState.pendingStates;
|
|
40
|
+
let currentlyBatching = false;
|
|
41
|
+
for (let i = 0; i < pendingStates.length; i++) {
|
|
42
|
+
const initialState = pendingStates[i];
|
|
43
|
+
// Skip over "flush" messages
|
|
44
|
+
if (initialState.type === "message") {
|
|
45
|
+
if ((_a = initialState.opMetadata) === null || _a === void 0 ? void 0 : _a.batch) {
|
|
46
|
+
currentlyBatching = true;
|
|
47
|
+
}
|
|
48
|
+
else if (((_b = initialState.opMetadata) === null || _b === void 0 ? void 0 : _b.batch) === false) {
|
|
49
|
+
currentlyBatching = false;
|
|
50
|
+
}
|
|
51
|
+
else if (
|
|
52
|
+
// End of batch if we are currently batching and this is last message or next message is flush
|
|
53
|
+
currentlyBatching
|
|
54
|
+
&& (i === pendingStates.length - 1 || pendingStates[i + 1].type === "flush")) {
|
|
55
|
+
currentlyBatching = false;
|
|
56
|
+
initialState.opMetadata = Object.assign(Object.assign({}, initialState.opMetadata), { batch: false });
|
|
57
|
+
}
|
|
58
|
+
this.initialMessages.push(initialState);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
33
62
|
}
|
|
34
63
|
get pendingMessagesCount() {
|
|
35
|
-
return this.
|
|
64
|
+
return this.pendingMessages.length;
|
|
36
65
|
}
|
|
37
66
|
/**
|
|
38
|
-
* Called to check if there are any pending messages in the pending
|
|
67
|
+
* Called to check if there are any pending messages in the pending message queue.
|
|
39
68
|
* @returns A boolean indicating whether there are messages or not.
|
|
40
69
|
*/
|
|
41
70
|
hasPendingMessages() {
|
|
42
|
-
return this.
|
|
71
|
+
return !this.pendingMessages.isEmpty() || !this.initialMessages.isEmpty();
|
|
43
72
|
}
|
|
44
73
|
getLocalState() {
|
|
45
|
-
assert(this.
|
|
46
|
-
if (this.
|
|
74
|
+
assert(this.initialMessages.isEmpty(), 0x2e9 /* "Must call getLocalState() after applying initial states" */);
|
|
75
|
+
if (!this.pendingMessages.isEmpty()) {
|
|
47
76
|
return {
|
|
48
|
-
pendingStates: this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
pendingStates: this.pendingMessages.toArray().reduce((arr, message) => {
|
|
78
|
+
var _a;
|
|
79
|
+
// delete localOpMetadata since it may not be serializable
|
|
80
|
+
// and will be regenerated by applyStashedOp()
|
|
81
|
+
arr.push(Object.assign(Object.assign({}, message), { localOpMetadata: undefined }));
|
|
82
|
+
// TODO: Remove in 2.0.0-internal.4.0.0 (AB#2496)
|
|
83
|
+
if (((_a = message.opMetadata) === null || _a === void 0 ? void 0 : _a.batch) === false) {
|
|
84
|
+
arr.push({ type: "flush" });
|
|
85
|
+
}
|
|
86
|
+
return arr;
|
|
87
|
+
}, new Array()),
|
|
52
88
|
};
|
|
53
89
|
}
|
|
54
90
|
}
|
|
@@ -71,20 +107,7 @@ export class PendingStateManager {
|
|
|
71
107
|
localOpMetadata,
|
|
72
108
|
opMetadata,
|
|
73
109
|
};
|
|
74
|
-
this.
|
|
75
|
-
this._pendingMessagesCount++;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Called when flush() is called on the ContainerRuntime to manually flush messages.
|
|
79
|
-
*/
|
|
80
|
-
onFlush() {
|
|
81
|
-
// If the previous state is not a message, flush is a no-op.
|
|
82
|
-
const previousState = this.pendingStates.peekBack();
|
|
83
|
-
if ((previousState === null || previousState === void 0 ? void 0 : previousState.type) !== "message") {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
// An explicit flush is interesting and is tracked only if there are messages sent in TurnBased mode.
|
|
87
|
-
this.pendingStates.push({ type: "flush" });
|
|
110
|
+
this.pendingMessages.push(pendingMessage);
|
|
88
111
|
}
|
|
89
112
|
/**
|
|
90
113
|
* Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted
|
|
@@ -92,29 +115,23 @@ export class PendingStateManager {
|
|
|
92
115
|
*/
|
|
93
116
|
async applyStashedOpsAt(seqNum) {
|
|
94
117
|
// apply stashed ops at sequence number
|
|
95
|
-
while (!this.
|
|
118
|
+
while (!this.initialMessages.isEmpty()) {
|
|
96
119
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
throw new Error("loaded from snapshot too recent to apply stashed ops");
|
|
105
|
-
}
|
|
120
|
+
const nextMessage = this.initialMessages.peekFront();
|
|
121
|
+
if (seqNum !== undefined) {
|
|
122
|
+
if (nextMessage.referenceSequenceNumber > seqNum) {
|
|
123
|
+
break; // nothing left to do at this sequence number
|
|
124
|
+
}
|
|
125
|
+
if (nextMessage.referenceSequenceNumber < seqNum) {
|
|
126
|
+
throw new Error("loaded from snapshot too recent to apply stashed ops");
|
|
106
127
|
}
|
|
107
|
-
// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
|
|
108
|
-
const localOpMetadata = await this.stateHandler.applyStashedOp(nextState.messageType, nextState.content);
|
|
109
|
-
nextState.localOpMetadata = localOpMetadata;
|
|
110
128
|
}
|
|
111
|
-
//
|
|
129
|
+
// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
|
|
130
|
+
const localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.messageType, nextMessage.content);
|
|
131
|
+
nextMessage.localOpMetadata = localOpMetadata;
|
|
132
|
+
// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
|
|
112
133
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
113
|
-
|
|
114
|
-
this.pendingStates.push(firstPendingState);
|
|
115
|
-
if (firstPendingState.type === "message") {
|
|
116
|
-
this._pendingMessagesCount++;
|
|
117
|
-
}
|
|
134
|
+
this.pendingMessages.push(this.initialMessages.shift());
|
|
118
135
|
}
|
|
119
136
|
}
|
|
120
137
|
/**
|
|
@@ -125,23 +142,21 @@ export class PendingStateManager {
|
|
|
125
142
|
processPendingLocalMessage(message) {
|
|
126
143
|
// Pre-processing part - This may be the start of a batch.
|
|
127
144
|
this.maybeProcessBatchBegin(message);
|
|
128
|
-
// Get the next
|
|
129
|
-
const
|
|
130
|
-
assert(
|
|
131
|
-
this.
|
|
145
|
+
// Get the next message from the pending queue. Verify a message exists.
|
|
146
|
+
const pendingMessage = this.pendingMessages.peekFront();
|
|
147
|
+
assert(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
|
|
148
|
+
this.pendingMessages.shift();
|
|
132
149
|
// Processing part - Verify that there has been no data corruption.
|
|
133
150
|
// The clientSequenceNumber of the incoming message must match that of the pending message.
|
|
134
|
-
if (
|
|
151
|
+
if (pendingMessage.clientSequenceNumber !== message.clientSequenceNumber) {
|
|
135
152
|
// Close the container because this could indicate data corruption.
|
|
136
|
-
const error = DataProcessingError.create("pending local message clientSequenceNumber mismatch", "unexpectedAckReceived", message, { expectedClientSequenceNumber:
|
|
153
|
+
const error = DataProcessingError.create("pending local message clientSequenceNumber mismatch", "unexpectedAckReceived", message, { expectedClientSequenceNumber: pendingMessage.clientSequenceNumber });
|
|
137
154
|
this.stateHandler.close(error);
|
|
138
155
|
return;
|
|
139
156
|
}
|
|
140
|
-
this._pendingMessagesCount--;
|
|
141
|
-
assert(this._pendingMessagesCount >= 0, 0x3d6 /* positive */);
|
|
142
157
|
// Post-processing part - If we are processing a batch then this could be the last message in the batch.
|
|
143
158
|
this.maybeProcessBatchEnd(message);
|
|
144
|
-
return
|
|
159
|
+
return pendingMessage.localOpMetadata;
|
|
145
160
|
}
|
|
146
161
|
/**
|
|
147
162
|
* This message could be the first message in batch. If so, set batch state marking the beginning of a batch.
|
|
@@ -149,20 +164,6 @@ export class PendingStateManager {
|
|
|
149
164
|
*/
|
|
150
165
|
maybeProcessBatchBegin(message) {
|
|
151
166
|
var _a;
|
|
152
|
-
/**
|
|
153
|
-
* We are checking if the next message is the start of a batch. It can happen in the following scenarios:
|
|
154
|
-
*
|
|
155
|
-
* 1. The FlushMode was set to TurnBased before this message was sent.
|
|
156
|
-
*
|
|
157
|
-
* 2. The FlushMode was already TurnBased and a flush was called before this message was sent. This essentially
|
|
158
|
-
* means that the flush marked the end of a previous batch and beginning of a new batch.
|
|
159
|
-
*
|
|
160
|
-
* Keep reading pending states from the queue until we encounter a message. It's possible that the FlushMode was
|
|
161
|
-
* updated a bunch of times without sending any messages.
|
|
162
|
-
*/
|
|
163
|
-
while (this.peekNextPendingState().type !== "message") {
|
|
164
|
-
this.pendingStates.shift();
|
|
165
|
-
}
|
|
166
167
|
// This message is the first in a batch if the "batch" property on the metadata is set to true
|
|
167
168
|
if ((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.batch) {
|
|
168
169
|
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
@@ -181,48 +182,36 @@ export class PendingStateManager {
|
|
|
181
182
|
if (!this.isProcessingBatch) {
|
|
182
183
|
return;
|
|
183
184
|
}
|
|
184
|
-
const nextPendingState = this.peekNextPendingState();
|
|
185
|
-
if (nextPendingState.type === "message") {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
185
|
// There should be a pending batch begin message.
|
|
189
186
|
assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* "There is no pending batch begin message" */);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
187
|
+
const batchEndMetadata = (_a = message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
188
|
+
if (this.pendingMessages.isEmpty() || batchEndMetadata === false) {
|
|
189
|
+
// Get the batch begin metadata from the first message in the batch.
|
|
190
|
+
const batchBeginMetadata = (_b = this.pendingBatchBeginMessage.metadata) === null || _b === void 0 ? void 0 : _b.batch;
|
|
191
|
+
// There could be just a single message in the batch. If so, it should not have any batch metadata. If there
|
|
192
|
+
// are multiple messages in the batch, verify that we got the correct batch begin and end metadata.
|
|
193
|
+
if (this.pendingBatchBeginMessage === message) {
|
|
194
|
+
assert(batchBeginMetadata === undefined, 0x16e /* "Batch with single message should not have batch metadata" */);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
if (batchBeginMetadata !== true || batchEndMetadata !== false) {
|
|
198
|
+
this.stateHandler.close(DataProcessingError.create("Pending batch inconsistency", // Formerly known as asserts 0x16f and 0x170
|
|
199
|
+
"processPendingLocalMessage", message, {
|
|
200
|
+
runtimeVersion: pkgVersion,
|
|
201
|
+
batchClientId: this.pendingBatchBeginMessage.clientId,
|
|
202
|
+
clientId: this.stateHandler.clientId(),
|
|
203
|
+
hasBatchStart: batchBeginMetadata === true,
|
|
204
|
+
hasBatchEnd: batchEndMetadata === false,
|
|
205
|
+
messageType: message.type,
|
|
206
|
+
batchStartSequenceNumber: this.pendingBatchBeginMessage.clientSequenceNumber,
|
|
207
|
+
pendingMessagesCount: this.pendingMessagesCount,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
213
210
|
}
|
|
211
|
+
// Clear the pending batch state now that we have processed the entire batch.
|
|
212
|
+
this.pendingBatchBeginMessage = undefined;
|
|
213
|
+
this.isProcessingBatch = false;
|
|
214
214
|
}
|
|
215
|
-
// Clear the pending batch state now that we have processed the entire batch.
|
|
216
|
-
this.pendingBatchBeginMessage = undefined;
|
|
217
|
-
this.isProcessingBatch = false;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Returns the next pending state from the pending state queue.
|
|
221
|
-
*/
|
|
222
|
-
peekNextPendingState() {
|
|
223
|
-
const nextPendingState = this.pendingStates.peekFront();
|
|
224
|
-
assert(!!nextPendingState, 0x171 /* "No pending state found for the remote message" */);
|
|
225
|
-
return nextPendingState;
|
|
226
215
|
}
|
|
227
216
|
/**
|
|
228
217
|
* Called when the Container's connection state changes. If the Container gets connected, it replays all the pending
|
|
@@ -234,63 +223,44 @@ export class PendingStateManager {
|
|
|
234
223
|
// This assert suggests we are about to send same ops twice, which will result in data loss.
|
|
235
224
|
assert(this.clientId !== this.stateHandler.clientId(), 0x173 /* "replayPendingStates called twice for same clientId!" */);
|
|
236
225
|
this.clientId = this.stateHandler.clientId();
|
|
237
|
-
assert(this.
|
|
238
|
-
let
|
|
239
|
-
if (
|
|
226
|
+
assert(this.initialMessages.isEmpty(), 0x174 /* "initial states should be empty before replaying pending" */);
|
|
227
|
+
let pendingMessagesCount = this.pendingMessages.length;
|
|
228
|
+
if (pendingMessagesCount === 0) {
|
|
240
229
|
return;
|
|
241
230
|
}
|
|
242
|
-
//
|
|
243
|
-
this._pendingMessagesCount = 0;
|
|
244
|
-
const messageBatchQueue = new Deque();
|
|
245
|
-
// Process exactly `pendingStatesCount` items in the queue as it represents the number of states that were
|
|
231
|
+
// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were
|
|
246
232
|
// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue
|
|
247
233
|
// which must not be replayed.
|
|
248
|
-
while (
|
|
234
|
+
while (pendingMessagesCount > 0) {
|
|
249
235
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.stateHandler.reSubmit(
|
|
236
|
+
let pendingMessage = this.pendingMessages.shift();
|
|
237
|
+
pendingMessagesCount--;
|
|
238
|
+
assert(((_a = pendingMessage.opMetadata) === null || _a === void 0 ? void 0 : _a.batch) !== false, 0x41b /* We cannot process batches in chunks */);
|
|
239
|
+
/**
|
|
240
|
+
* We want to ensure grouped messages get processed in a batch.
|
|
241
|
+
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
242
|
+
* either receive the whole batch ack or nothing at all.
|
|
243
|
+
*/
|
|
244
|
+
if ((_b = pendingMessage.opMetadata) === null || _b === void 0 ? void 0 : _b.batch) {
|
|
245
|
+
assert(pendingMessagesCount > 0, 0x554 /* Last pending message cannot be a batch begin */);
|
|
246
|
+
this.stateHandler.orderSequentially(() => {
|
|
247
|
+
var _a, _b;
|
|
248
|
+
while (pendingMessagesCount >= 0) { // check is >= because batch end may be last pending message
|
|
249
|
+
this.stateHandler.reSubmit(pendingMessage.messageType, pendingMessage.content, pendingMessage.localOpMetadata, pendingMessage.opMetadata);
|
|
250
|
+
if (((_a = pendingMessage.opMetadata) === null || _a === void 0 ? void 0 : _a.batch) === false) {
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
assert(pendingMessagesCount > 0, 0x555 /* No batch end found */);
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
255
|
+
pendingMessage = this.pendingMessages.shift();
|
|
256
|
+
pendingMessagesCount--;
|
|
257
|
+
assert(((_b = pendingMessage.opMetadata) === null || _b === void 0 ? void 0 : _b.batch) !== true, 0x556 /* Batch start needs a corresponding batch end */);
|
|
264
258
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
* We can't rely on the "batch" property in the message metadata as it gets
|
|
270
|
-
* updated elsewhere and it is not the same object instance that gets updated.
|
|
271
|
-
*/
|
|
272
|
-
if (messageBatchQueue.length > 0) {
|
|
273
|
-
this.stateHandler.orderSequentially(() => {
|
|
274
|
-
while (messageBatchQueue.length > 0) {
|
|
275
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
276
|
-
const message = messageBatchQueue.dequeue();
|
|
277
|
-
this.stateHandler.reSubmit(message.messageType, message.content, message.localOpMetadata, message.opMetadata);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
assert(messageBatchQueue.length === 0, 0x41c /* cannot flush in the middle of a batch */);
|
|
282
|
-
this.stateHandler.flush();
|
|
283
|
-
break;
|
|
284
|
-
default:
|
|
285
|
-
break;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
this.stateHandler.reSubmit(pendingMessage.messageType, pendingMessage.content, pendingMessage.localOpMetadata, pendingMessage.opMetadata);
|
|
286
263
|
}
|
|
287
|
-
pendingStatesCount--;
|
|
288
|
-
}
|
|
289
|
-
// There are some cases where ops are stashed but not flushed. We need to ensure they are resubmitted
|
|
290
|
-
while (messageBatchQueue.length > 0) {
|
|
291
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
292
|
-
const message = messageBatchQueue.dequeue();
|
|
293
|
-
this.stateHandler.reSubmit(message.messageType, message.content, message.localOpMetadata, message.opMetadata);
|
|
294
264
|
}
|
|
295
265
|
}
|
|
296
266
|
}
|
|
@@ -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 { IDisposable } from \"@fluidframework/common-definitions\";\nimport { assert, Lazy } from \"@fluidframework/common-utils\";\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { DataProcessingError } from \"@fluidframework/container-utils\";\nimport {\n ISequencedDocumentMessage,\n} from \"@fluidframework/protocol-definitions\";\nimport Deque from \"double-ended-queue\";\nimport { ContainerMessageType } from \"./containerRuntime\";\nimport { pkgVersion } from \"./packageVersion\";\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 type: \"message\";\n messageType: ContainerMessageType;\n clientSequenceNumber: number;\n referenceSequenceNumber: number;\n content: any;\n localOpMetadata: unknown;\n opMetadata: Record<string, unknown> | undefined;\n}\n\n/**\n * This represents an explicit flush call and is added to the pending queue when flush is called on the ContainerRuntime\n * to flush pending messages.\n */\nexport interface IPendingFlush {\n type: \"flush\";\n}\n\nexport type IPendingState = IPendingMessage | IPendingFlush;\n\nexport interface IPendingLocalState {\n /**\n * list of pending states, including ops and batch information\n */\n pendingStates: IPendingState[];\n}\n\nexport interface IRuntimeStateHandler{\n connected(): boolean;\n clientId(): string | undefined;\n close(error?: ICriticalContainerError): void;\n applyStashedOp: (type: ContainerMessageType, content: ISequencedDocumentMessage) => Promise<unknown>;\n flush(): void;\n reSubmit(\n type: ContainerMessageType,\n content: any,\n localOpMetadata: unknown,\n opMetadata: Record<string, unknown> | undefined): void;\n rollback(\n type: ContainerMessageType,\n content: any,\n localOpMetadata: unknown): void;\n orderSequentially(callback: () => void): void;\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 private readonly pendingStates = new Deque<IPendingState>();\n private readonly initialStates: Deque<IPendingState>;\n private readonly disposeOnce = new Lazy<void>(() => {\n this.initialStates.clear();\n this.pendingStates.clear();\n });\n\n // Maintains the count of messages that are currently unacked.\n private _pendingMessagesCount: number = 0;\n public get pendingMessagesCount(): number {\n return this._pendingMessagesCount;\n }\n\n // Indicates whether we are processing a batch.\n private isProcessingBatch: boolean = false;\n\n // This stores the first message in the batch that we are processing. This is used to verify that we get\n // the correct batch metadata.\n private pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n private clientId: string | undefined;\n\n /**\n * Called to check if there are any pending messages in the pending state queue.\n * @returns A boolean indicating whether there are messages or not.\n */\n public hasPendingMessages(): boolean {\n return this._pendingMessagesCount !== 0 || !this.initialStates.isEmpty();\n }\n\n public getLocalState(): IPendingLocalState | undefined {\n assert(this.initialStates.isEmpty(), 0x2e9 /* \"Must call getLocalState() after applying initial states\" */);\n if (this.hasPendingMessages()) {\n return {\n pendingStates: this.pendingStates.toArray().map(\n // delete localOpMetadata since it may not be serializable\n // and will be regenerated by applyStashedOp()\n (state) => state.type === \"message\" ? { ...state, localOpMetadata: undefined } : state),\n };\n }\n }\n\n constructor(\n private readonly stateHandler: IRuntimeStateHandler,\n initialLocalState: IPendingLocalState | undefined,\n ) {\n this.initialStates = new Deque<IPendingState>(initialLocalState?.pendingStates ?? []);\n }\n\n public get disposed() { return this.disposeOnce.evaluated; }\n public readonly dispose = () => this.disposeOnce.value;\n\n /**\n * Called when a message is submitted locally. Adds the message and the associated details to the pending state\n * queue.\n * @param type - The container message type.\n * @param clientSequenceNumber - The clientSequenceNumber associated with the message.\n * @param content - The message content.\n * @param localOpMetadata - The local metadata associated with the message.\n */\n public onSubmitMessage(\n type: ContainerMessageType,\n clientSequenceNumber: number,\n referenceSequenceNumber: number,\n content: any,\n localOpMetadata: unknown,\n opMetadata: Record<string, unknown> | undefined,\n ) {\n const pendingMessage: IPendingMessage = {\n type: \"message\",\n messageType: type,\n clientSequenceNumber,\n referenceSequenceNumber,\n content,\n localOpMetadata,\n opMetadata,\n };\n\n this.pendingStates.push(pendingMessage);\n\n this._pendingMessagesCount++;\n }\n\n /**\n * Called when flush() is called on the ContainerRuntime to manually flush messages.\n */\n public onFlush() {\n // If the previous state is not a message, flush is a no-op.\n const previousState = this.pendingStates.peekBack();\n if (previousState?.type !== \"message\") {\n return;\n }\n\n // An explicit flush is interesting and is tracked only if there are messages sent in TurnBased mode.\n this.pendingStates.push({ type: \"flush\" });\n }\n\n /**\n * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n */\n public async applyStashedOpsAt(seqNum?: number) {\n // apply stashed ops at sequence number\n while (!this.initialStates.isEmpty()) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const nextState = this.initialStates.peekFront()!;\n if (nextState.type === \"message\") {\n if (seqNum !== undefined) {\n if (nextState.referenceSequenceNumber > seqNum) {\n break; // nothing left to do at this sequence number\n } else if (nextState.referenceSequenceNumber < seqNum) {\n throw new Error(\"loaded from snapshot too recent to apply stashed ops\");\n }\n }\n\n // applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n const localOpMetadata =\n await this.stateHandler.applyStashedOp(nextState.messageType, nextState.content);\n nextState.localOpMetadata = localOpMetadata;\n }\n\n // then we push onto pendingStates which will cause PendingStateManager to resubmit when we connect\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const firstPendingState = this.initialStates.shift()!;\n this.pendingStates.push(firstPendingState);\n if (firstPendingState.type === \"message\") {\n this._pendingMessagesCount++;\n }\n }\n }\n\n /**\n * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n * the batch information was preserved for batch messages.\n * @param message - The message that got ack'd and needs to be processed.\n */\n public processPendingLocalMessage(message: ISequencedDocumentMessage): unknown {\n // Pre-processing part - This may be the start of a batch.\n this.maybeProcessBatchBegin(message);\n\n // Get the next state from the pending queue and verify that it is of type \"message\".\n const pendingState = this.peekNextPendingState();\n assert(pendingState.type === \"message\", 0x169 /* \"No pending message found for this remote message\" */);\n this.pendingStates.shift();\n\n // Processing part - Verify that there has been no data corruption.\n // The clientSequenceNumber of the incoming message must match that of the pending message.\n if (pendingState.clientSequenceNumber !== message.clientSequenceNumber) {\n // Close the container because this could indicate data corruption.\n const error = DataProcessingError.create(\n \"pending local message clientSequenceNumber mismatch\",\n \"unexpectedAckReceived\",\n message,\n { expectedClientSequenceNumber: pendingState.clientSequenceNumber },\n );\n\n this.stateHandler.close(error);\n return;\n }\n\n this._pendingMessagesCount--;\n assert(this._pendingMessagesCount >= 0, 0x3d6 /* positive */);\n\n // Post-processing part - If we are processing a batch then this could be the last message in the batch.\n this.maybeProcessBatchEnd(message);\n\n return pendingState.localOpMetadata;\n }\n\n /**\n * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n * @param message - The message that is being processed.\n */\n private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\n /**\n * We are checking if the next message is the start of a batch. It can happen in the following scenarios:\n *\n * 1. The FlushMode was set to TurnBased before this message was sent.\n *\n * 2. The FlushMode was already TurnBased and a flush was called before this message was sent. This essentially\n * means that the flush marked the end of a previous batch and beginning of a new batch.\n *\n * Keep reading pending states from the queue until we encounter a message. It's possible that the FlushMode was\n * updated a bunch of times without sending any messages.\n */\n while (this.peekNextPendingState().type !== \"message\") {\n this.pendingStates.shift();\n }\n\n // This message is the first in a batch if the \"batch\" property on the metadata is set to true\n if (message.metadata?.batch) {\n // We should not already be processing a batch and there should be no pending batch begin message.\n assert(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n 0x16b /* \"The pending batch state indicates we are already processing a batch\" */);\n\n // Set the pending batch state indicating we have started processing a batch.\n this.pendingBatchBeginMessage = message;\n this.isProcessingBatch = true;\n }\n }\n\n /**\n * This message could be the last message in batch. If so, clear batch state since the batch is complete.\n * @param message - The message that is being processed.\n */\n private maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n if (!this.isProcessingBatch) {\n return;\n }\n\n const nextPendingState = this.peekNextPendingState();\n if (nextPendingState.type === \"message\") {\n return;\n }\n\n // There should be a pending batch begin message.\n assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* \"There is no pending batch begin message\" */);\n\n // Get the batch begin metadata from the first message in the batch.\n const batchBeginMetadata = this.pendingBatchBeginMessage.metadata?.batch;\n\n // There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n // are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n if (this.pendingBatchBeginMessage === message) {\n assert(batchBeginMetadata === undefined,\n 0x16e /* \"Batch with single message should not have batch metadata\" */);\n } else {\n // Get the batch metadata from the last message in the batch.\n const batchEndMetadata = message.metadata?.batch;\n if (batchBeginMetadata !== true || batchEndMetadata !== false) {\n this.stateHandler.close(DataProcessingError.create(\n \"Pending batch inconsistency\", // Formerly known as asserts 0x16f and 0x170\n \"processPendingLocalMessage\",\n message,\n {\n runtimeVersion: pkgVersion,\n batchClientId: this.pendingBatchBeginMessage.clientId,\n clientId: this.stateHandler.clientId(),\n hasBatchStart: batchBeginMetadata === true,\n hasBatchEnd: batchEndMetadata === false,\n messageType: message.type,\n batchStartSequenceNumber: this.pendingBatchBeginMessage.clientSequenceNumber,\n pendingMessagesCount: this.pendingMessagesCount,\n nextPendingState: nextPendingState.type,\n }));\n }\n }\n\n // Clear the pending batch state now that we have processed the entire batch.\n this.pendingBatchBeginMessage = undefined;\n this.isProcessingBatch = false;\n }\n\n /**\n * Returns the next pending state from the pending state queue.\n */\n private peekNextPendingState(): IPendingState {\n const nextPendingState = this.pendingStates.peekFront();\n assert(!!nextPendingState, 0x171 /* \"No pending state found for the remote message\" */);\n return nextPendingState;\n }\n\n /**\n * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n * states in its queue. This includes triggering resubmission of unacked ops.\n */\n public replayPendingStates() {\n assert(this.stateHandler.connected(), 0x172 /* \"The connection state is not consistent with the runtime\" */);\n\n // This assert suggests we are about to send same ops twice, which will result in data loss.\n assert(this.clientId !== this.stateHandler.clientId(),\n 0x173 /* \"replayPendingStates called twice for same clientId!\" */);\n this.clientId = this.stateHandler.clientId();\n\n assert(this.initialStates.isEmpty(), 0x174 /* \"initial states should be empty before replaying pending\" */);\n\n let pendingStatesCount = this.pendingStates.length;\n if (pendingStatesCount === 0) {\n return;\n }\n\n // Reset the pending message count because all these messages will be removed from the queue.\n this._pendingMessagesCount = 0;\n\n const messageBatchQueue = new Deque<IPendingMessage>();\n\n // Process exactly `pendingStatesCount` items in the queue as it represents the number of states that were\n // pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n // which must not be replayed.\n while (pendingStatesCount > 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const pendingState = this.pendingStates.shift()!;\n switch (pendingState.type) {\n case \"message\":\n assert(pendingState.opMetadata?.batch !== false || messageBatchQueue.length > 0,\n 0x41b /* We cannot process batches in chunks */);\n /**\n * We want to ensure grouped messages get processed in a batch.\n * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n * either receive the whole batch ack or nothing at all.\n */\n if (messageBatchQueue.length > 0 || pendingState.opMetadata?.batch) {\n messageBatchQueue.enqueue(pendingState);\n } else {\n this.stateHandler.reSubmit(\n pendingState.messageType,\n pendingState.content,\n pendingState.localOpMetadata,\n pendingState.opMetadata);\n }\n break;\n case \"flush\":\n /**\n * A \"flush\" call can indicate the end of a batch.\n * We can't rely on the \"batch\" property in the message metadata as it gets\n * updated elsewhere and it is not the same object instance that gets updated.\n */\n if (messageBatchQueue.length > 0) {\n this.stateHandler.orderSequentially(() => {\n while (messageBatchQueue.length > 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const message = messageBatchQueue.dequeue()!;\n this.stateHandler.reSubmit(\n message.messageType,\n message.content,\n message.localOpMetadata,\n message.opMetadata);\n }\n });\n }\n assert(messageBatchQueue.length === 0, 0x41c /* cannot flush in the middle of a batch */);\n this.stateHandler.flush();\n break;\n default:\n break;\n }\n pendingStatesCount--;\n }\n\n // There are some cases where ops are stashed but not flushed. We need to ensure they are resubmitted\n while (messageBatchQueue.length > 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const message = messageBatchQueue.dequeue()!;\n this.stateHandler.reSubmit(\n message.messageType,\n message.content,\n message.localOpMetadata,\n message.opMetadata);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAE5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAItE,OAAO,KAAK,MAAM,oBAAoB,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAmD9C;;;;;;;;GAQG;AACH,MAAM,OAAO,mBAAmB;IAgD5B,YACqB,YAAkC,EACnD,iBAAiD;;QADhC,iBAAY,GAAZ,YAAY,CAAsB;QAhDtC,oBAAe,GAAG,IAAI,KAAK,EAAmB,CAAC;QAC/C,oBAAe,GAAG,IAAI,KAAK,EAAmB,CAAC;QAC/C,gBAAW,GAAG,IAAI,IAAI,CAAO,GAAG,EAAE;YAC/C,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QACjC,CAAC,CAAC,CAAC;QAMH,+CAA+C;QACvC,sBAAiB,GAAY,KAAK,CAAC;QAyE3B,YAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;QAlCnD;;;;;;WAMG;QACH,IAAI,iBAAiB,aAAjB,iBAAiB,uBAAjB,iBAAiB,CAAE,aAAa,EAAE;YAClC,MAAM,aAAa,GAAG,iBAAiB,aAAjB,iBAAiB,uBAAjB,iBAAiB,CAAE,aAAa,CAAC;YACvD,IAAI,iBAAiB,GAAG,KAAK,CAAC;YAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;gBAC3C,MAAM,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;gBAEtC,6BAA6B;gBAC7B,IAAI,YAAY,CAAC,IAAI,KAAK,SAAS,EAAE;oBACjC,IAAI,MAAA,YAAY,CAAC,UAAU,0CAAE,KAAK,EAAE;wBAChC,iBAAiB,GAAG,IAAI,CAAC;qBAC5B;yBAAM,IAAI,CAAA,MAAA,YAAY,CAAC,UAAU,0CAAE,KAAK,MAAK,KAAK,EAAE;wBACjD,iBAAiB,GAAG,KAAK,CAAC;qBAC7B;yBAAM;oBACH,8FAA8F;oBAC9F,iBAAiB;2BACd,CAAC,CAAC,KAAK,aAAa,CAAC,MAAM,GAAG,CAAC,IAAI,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAC9E;wBACE,iBAAiB,GAAG,KAAK,CAAC;wBAC1B,YAAY,CAAC,UAAU,mCAAQ,YAAY,CAAC,UAAU,KAAE,KAAK,EAAE,KAAK,GAAE,CAAC;qBAC1E;oBACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;iBAC3C;aACJ;SACJ;IACL,CAAC;IA3ED,IAAW,oBAAoB;QAC3B,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;IACvC,CAAC;IAWD;;;OAGG;IACI,kBAAkB;QACrB,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;IAC9E,CAAC;IAEM,aAAa;QAChB,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,+DAA+D,CAAC,CAAC;QAC9G,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE;YACjC,OAAO;gBACH,aAAa,EAAE,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE;;oBAClE,0DAA0D;oBAC1D,8CAA8C;oBAC9C,GAAG,CAAC,IAAI,iCAAM,OAAO,KAAE,eAAe,EAAE,SAAS,IAAG,CAAC;oBAErD,iDAAiD;oBACjD,IAAI,CAAA,MAAA,OAAO,CAAC,UAAU,0CAAE,KAAK,MAAK,KAAK,EAAE;wBACrC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;qBAC/B;oBACD,OAAO,GAAG,CAAC;gBACf,CAAC,EAAE,IAAI,KAAK,EAAiB,CAAC;aACjC,CAAC;SACL;IACL,CAAC;IAuCD,IAAW,QAAQ,KAAK,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;IAG5D;;;;;;;OAOG;IACI,eAAe,CAClB,IAA0B,EAC1B,oBAA4B,EAC5B,uBAA+B,EAC/B,OAAY,EACZ,eAAwB,EACxB,UAA+C;QAE/C,MAAM,cAAc,GAAoB;YACpC,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,IAAI;YACjB,oBAAoB;YACpB,uBAAuB;YACvB,OAAO;YACP,eAAe;YACf,UAAU;SACb,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,iBAAiB,CAAC,MAAe;QAC1C,uCAAuC;QACvC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE;YACpC,oEAAoE;YACpE,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAG,CAAC;YACtD,IAAI,MAAM,KAAK,SAAS,EAAE;gBACtB,IAAI,WAAW,CAAC,uBAAuB,GAAG,MAAM,EAAE;oBAC9C,MAAM,CAAC,6CAA6C;iBACvD;gBACD,IAAI,WAAW,CAAC,uBAAuB,GAAG,MAAM,EAAE;oBAC9C,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;iBAC3E;aACJ;YAED,gGAAgG;YAChG,MAAM,eAAe,GACjB,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;YACzF,WAAW,CAAC,eAAe,GAAG,eAAe,CAAC;YAE9C,qGAAqG;YACrG,oEAAoE;YACpE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC,CAAC;SAC5D;IACL,CAAC;IAED;;;;OAIG;IACI,0BAA0B,CAAC,OAAkC;QAChE,0DAA0D;QAC1D,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAErC,wEAAwE;QACxE,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,CAAC;QACxD,MAAM,CAAC,cAAc,KAAK,SAAS,EAAE,KAAK,CAAC,wDAAwD,CAAC,CAAC;QACrG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAE7B,mEAAmE;QACnE,2FAA2F;QAC3F,IAAI,cAAc,CAAC,oBAAoB,KAAK,OAAO,CAAC,oBAAoB,EAAE;YACtE,mEAAmE;YACnE,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,CACpC,qDAAqD,EACrD,uBAAuB,EACvB,OAAO,EACP,EAAE,4BAA4B,EAAE,cAAc,CAAC,oBAAoB,EAAE,CACxE,CAAC;YAEF,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC/B,OAAO;SACV;QAED,wGAAwG;QACxG,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAEnC,OAAO,cAAc,CAAC,eAAe,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACK,sBAAsB,CAAC,OAAkC;;QAC7D,8FAA8F;QAC9F,IAAI,MAAA,OAAO,CAAC,QAAQ,0CAAE,KAAK,EAAE;YACzB,kGAAkG;YAClG,MAAM,CAAC,CAAC,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,wBAAwB,KAAK,SAAS,EACzE,KAAK,CAAC,2EAA2E,CAAC,CAAC;YAEvF,6EAA6E;YAC7E,IAAI,CAAC,wBAAwB,GAAG,OAAO,CAAC;YACxC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;SACjC;IACL,CAAC;IAED;;;OAGG;IACK,oBAAoB,CAAC,OAAkC;;QAC3D,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE;YACzB,OAAO;SACV;QAED,iDAAiD;QACjD,MAAM,CAAC,IAAI,CAAC,wBAAwB,KAAK,SAAS,EAAE,KAAK,CAAC,+CAA+C,CAAC,CAAC;QAE3G,MAAM,gBAAgB,GAAG,MAAA,OAAO,CAAC,QAAQ,0CAAE,KAAK,CAAC;QACjD,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,gBAAgB,KAAK,KAAK,EAAE;YAC9D,oEAAoE;YACpE,MAAM,kBAAkB,GAAG,MAAA,IAAI,CAAC,wBAAwB,CAAC,QAAQ,0CAAE,KAAK,CAAC;YAEzE,4GAA4G;YAC5G,mGAAmG;YACnG,IAAI,IAAI,CAAC,wBAAwB,KAAK,OAAO,EAAE;gBAC3C,MAAM,CAAC,kBAAkB,KAAK,SAAS,EACnC,KAAK,CAAC,gEAAgE,CAAC,CAAC;aAC/E;iBAAM;gBACH,IAAI,kBAAkB,KAAK,IAAI,IAAI,gBAAgB,KAAK,KAAK,EAAE;oBAC3D,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAC9C,6BAA6B,EAAE,4CAA4C;oBAC3E,4BAA4B,EAC5B,OAAO,EACP;wBACI,cAAc,EAAE,UAAU;wBAC1B,aAAa,EAAE,IAAI,CAAC,wBAAwB,CAAC,QAAQ;wBACrD,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE;wBACtC,aAAa,EAAE,kBAAkB,KAAK,IAAI;wBAC1C,WAAW,EAAE,gBAAgB,KAAK,KAAK;wBACvC,WAAW,EAAE,OAAO,CAAC,IAAI;wBACzB,wBAAwB,EAAE,IAAI,CAAC,wBAAwB,CAAC,oBAAoB;wBAC5E,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;qBAClD,CAAC,CAAC,CAAC;iBACX;aACJ;YAED,6EAA6E;YAC7E,IAAI,CAAC,wBAAwB,GAAG,SAAS,CAAC;YAC1C,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;SAClC;IACL,CAAC;IAED;;;OAGG;IACI,mBAAmB;;QACtB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,KAAK,CAAC,+DAA+D,CAAC,CAAC;QAE7G,4FAA4F;QAC5F,MAAM,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EACjD,KAAK,CAAC,2DAA2D,CAAC,CAAC;QACvE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;QAE7C,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,+DAA+D,CAAC,CAAC;QAE9G,IAAI,oBAAoB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;QACvD,IAAI,oBAAoB,KAAK,CAAC,EAAE;YAC5B,OAAO;SACV;QAED,8GAA8G;QAC9G,0GAA0G;QAC1G,8BAA8B;QAC9B,OAAO,oBAAoB,GAAG,CAAC,EAAE;YAC7B,oEAAoE;YACpE,IAAI,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC;YACnD,oBAAoB,EAAE,CAAC;YACvB,MAAM,CAAC,CAAA,MAAA,cAAc,CAAC,UAAU,0CAAE,KAAK,MAAK,KAAK,EAAE,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAEpG;;;;eAIG;YACH,IAAI,MAAA,cAAc,CAAC,UAAU,0CAAE,KAAK,EAAE;gBAClC,MAAM,CAAC,oBAAoB,GAAG,CAAC,EAAE,KAAK,CAAC,kDAAkD,CAAC,CAAC;gBAE3F,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,GAAG,EAAE;;oBACrC,OAAO,oBAAoB,IAAI,CAAC,EAAE,EAAE,4DAA4D;wBAC5F,IAAI,CAAC,YAAY,CAAC,QAAQ,CACtB,cAAc,CAAC,WAAW,EAC1B,cAAc,CAAC,OAAO,EACtB,cAAc,CAAC,eAAe,EAC9B,cAAc,CAAC,UAAU,CAAC,CAAC;wBAE/B,IAAI,CAAA,MAAA,cAAc,CAAC,UAAU,0CAAE,KAAK,MAAK,KAAK,EAAE;4BAC5C,MAAM;yBACT;wBACD,MAAM,CAAC,oBAAoB,GAAG,CAAC,EAAE,KAAK,CAAC,wBAAwB,CAAC,CAAC;wBAEjE,oEAAoE;wBACpE,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC;wBAC/C,oBAAoB,EAAE,CAAC;wBACvB,MAAM,CAAC,CAAA,MAAA,cAAc,CAAC,UAAU,0CAAE,KAAK,MAAK,IAAI,EAC5C,KAAK,CAAC,iDAAiD,CAAC,CAAC;qBAChE;gBACL,CAAC,CAAC,CAAC;aACN;iBAAM;gBACH,IAAI,CAAC,YAAY,CAAC,QAAQ,CACtB,cAAc,CAAC,WAAW,EAC1B,cAAc,CAAC,OAAO,EACtB,cAAc,CAAC,eAAe,EAC9B,cAAc,CAAC,UAAU,CAAC,CAAC;aAClC;SACJ;IACL,CAAC;CACJ","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable } from \"@fluidframework/common-definitions\";\nimport { assert, Lazy } from \"@fluidframework/common-utils\";\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { DataProcessingError } from \"@fluidframework/container-utils\";\nimport {\n ISequencedDocumentMessage,\n} from \"@fluidframework/protocol-definitions\";\nimport Deque from \"double-ended-queue\";\nimport { ContainerMessageType } from \"./containerRuntime\";\nimport { pkgVersion } from \"./packageVersion\";\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 type: \"message\";\n messageType: ContainerMessageType;\n clientSequenceNumber: number;\n referenceSequenceNumber: number;\n content: any;\n localOpMetadata: unknown;\n opMetadata: Record<string, unknown> | undefined;\n}\n\n/**\n * This represents an explicit flush call and is added to the pending queue when flush is called on the ContainerRuntime\n * to flush pending messages.\n * @deprecated Use batch metadata on IPendingMessage instead. To be removed in 2.0.0-internal.4.0.0 (AB#2496)\n */\nexport interface IPendingFlush {\n type: \"flush\";\n}\n\nexport type IPendingState = IPendingMessage | IPendingFlush;\n\nexport interface IPendingLocalState {\n /**\n * list of pending states, including ops and batch information\n */\n pendingStates: IPendingState[];\n}\n\nexport interface IRuntimeStateHandler {\n connected(): boolean;\n clientId(): string | undefined;\n close(error?: ICriticalContainerError): void;\n applyStashedOp: (type: ContainerMessageType, content: ISequencedDocumentMessage) => Promise<unknown>;\n reSubmit(\n type: ContainerMessageType,\n content: any,\n localOpMetadata: unknown,\n opMetadata: Record<string, unknown> | undefined): void;\n rollback(\n type: ContainerMessageType,\n content: any,\n localOpMetadata: unknown): void;\n orderSequentially(callback: () => void): void;\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 private readonly pendingMessages = new Deque<IPendingMessage>();\n private readonly initialMessages = new Deque<IPendingMessage>();\n private readonly disposeOnce = new Lazy<void>(() => {\n this.initialMessages.clear();\n this.pendingMessages.clear();\n });\n\n public get pendingMessagesCount(): number {\n return this.pendingMessages.length;\n }\n\n // Indicates whether we are processing a batch.\n private isProcessingBatch: boolean = false;\n\n // This stores the first message in the batch that we are processing. This is used to verify that we get\n // the correct batch metadata.\n private pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n private clientId: string | undefined;\n\n /**\n * Called to check if there are any pending messages in the pending message queue.\n * @returns A boolean indicating whether there are messages or not.\n */\n public hasPendingMessages(): boolean {\n return !this.pendingMessages.isEmpty() || !this.initialMessages.isEmpty();\n }\n\n public getLocalState(): IPendingLocalState | undefined {\n assert(this.initialMessages.isEmpty(), 0x2e9 /* \"Must call getLocalState() after applying initial states\" */);\n if (!this.pendingMessages.isEmpty()) {\n return {\n pendingStates: this.pendingMessages.toArray().reduce((arr, message) => {\n // delete localOpMetadata since it may not be serializable\n // and will be regenerated by applyStashedOp()\n arr.push({ ...message, localOpMetadata: undefined });\n\n // TODO: Remove in 2.0.0-internal.4.0.0 (AB#2496)\n if (message.opMetadata?.batch === false) {\n arr.push({ type: \"flush\" });\n }\n return arr;\n }, new Array<IPendingState>()),\n };\n }\n }\n\n constructor(\n private readonly stateHandler: IRuntimeStateHandler,\n initialLocalState: IPendingLocalState | undefined,\n ) {\n /**\n * Convert old local state format to the new format\n * The old format contained \"flush\" messages as the indicator of batch ends\n * The new format instead uses batch metadata on the last message to indicate batch ends\n * ! TODO: Remove this conversion in \"2.0.0-internal.4.0.0\" as rollback from future version will be new format\n * AB#2496 tracks removal\n */\n if (initialLocalState?.pendingStates) {\n const pendingStates = initialLocalState?.pendingStates;\n let currentlyBatching = false;\n for (let i = 0; i < pendingStates.length; i++) {\n const initialState = pendingStates[i];\n\n // Skip over \"flush\" messages\n if (initialState.type === \"message\") {\n if (initialState.opMetadata?.batch) {\n currentlyBatching = true;\n } else if (initialState.opMetadata?.batch === false) {\n currentlyBatching = false;\n } else if (\n // End of batch if we are currently batching and this is last message or next message is flush\n currentlyBatching\n && (i === pendingStates.length - 1 || pendingStates[i + 1].type === \"flush\")\n ) {\n currentlyBatching = false;\n initialState.opMetadata = { ...initialState.opMetadata, batch: false };\n }\n this.initialMessages.push(initialState);\n }\n }\n }\n }\n\n public get disposed() { return this.disposeOnce.evaluated; }\n public readonly dispose = () => this.disposeOnce.value;\n\n /**\n * Called when a message is submitted locally. Adds the message and the associated details to the pending state\n * queue.\n * @param type - The container message type.\n * @param clientSequenceNumber - The clientSequenceNumber associated with the message.\n * @param content - The message content.\n * @param localOpMetadata - The local metadata associated with the message.\n */\n public onSubmitMessage(\n type: ContainerMessageType,\n clientSequenceNumber: number,\n referenceSequenceNumber: number,\n content: any,\n localOpMetadata: unknown,\n opMetadata: Record<string, unknown> | undefined,\n ) {\n const pendingMessage: IPendingMessage = {\n type: \"message\",\n messageType: type,\n clientSequenceNumber,\n referenceSequenceNumber,\n content,\n localOpMetadata,\n opMetadata,\n };\n\n this.pendingMessages.push(pendingMessage);\n }\n\n /**\n * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n */\n public async applyStashedOpsAt(seqNum?: number) {\n // apply stashed ops at sequence number\n while (!this.initialMessages.isEmpty()) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const nextMessage = this.initialMessages.peekFront()!;\n if (seqNum !== undefined) {\n if (nextMessage.referenceSequenceNumber > seqNum) {\n break; // nothing left to do at this sequence number\n }\n if (nextMessage.referenceSequenceNumber < seqNum) {\n throw new Error(\"loaded from snapshot too recent to apply stashed ops\");\n }\n }\n\n // applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n const localOpMetadata =\n await this.stateHandler.applyStashedOp(nextMessage.messageType, nextMessage.content);\n nextMessage.localOpMetadata = localOpMetadata;\n\n // then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.pendingMessages.push(this.initialMessages.shift()!);\n }\n }\n\n /**\n * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n * the batch information was preserved for batch messages.\n * @param message - The message that got ack'd and needs to be processed.\n */\n public processPendingLocalMessage(message: ISequencedDocumentMessage): unknown {\n // Pre-processing part - This may be the start of a batch.\n this.maybeProcessBatchBegin(message);\n\n // Get the next message from the pending queue. Verify a message exists.\n const pendingMessage = this.pendingMessages.peekFront();\n assert(pendingMessage !== undefined, 0x169 /* \"No pending message found for this remote message\" */);\n this.pendingMessages.shift();\n\n // Processing part - Verify that there has been no data corruption.\n // The clientSequenceNumber of the incoming message must match that of the pending message.\n if (pendingMessage.clientSequenceNumber !== message.clientSequenceNumber) {\n // Close the container because this could indicate data corruption.\n const error = DataProcessingError.create(\n \"pending local message clientSequenceNumber mismatch\",\n \"unexpectedAckReceived\",\n message,\n { expectedClientSequenceNumber: pendingMessage.clientSequenceNumber },\n );\n\n this.stateHandler.close(error);\n return;\n }\n\n // Post-processing part - If we are processing a batch then this could be the last message in the batch.\n this.maybeProcessBatchEnd(message);\n\n return pendingMessage.localOpMetadata;\n }\n\n /**\n * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n * @param message - The message that is being processed.\n */\n private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\n // This message is the first in a batch if the \"batch\" property on the metadata is set to true\n if (message.metadata?.batch) {\n // We should not already be processing a batch and there should be no pending batch begin message.\n assert(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n 0x16b /* \"The pending batch state indicates we are already processing a batch\" */);\n\n // Set the pending batch state indicating we have started processing a batch.\n this.pendingBatchBeginMessage = message;\n this.isProcessingBatch = true;\n }\n }\n\n /**\n * This message could be the last message in batch. If so, clear batch state since the batch is complete.\n * @param message - The message that is being processed.\n */\n private maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n if (!this.isProcessingBatch) {\n return;\n }\n\n // There should be a pending batch begin message.\n assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* \"There is no pending batch begin message\" */);\n\n const batchEndMetadata = message.metadata?.batch;\n if (this.pendingMessages.isEmpty() || batchEndMetadata === false) {\n // Get the batch begin metadata from the first message in the batch.\n const batchBeginMetadata = this.pendingBatchBeginMessage.metadata?.batch;\n\n // There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n // are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n if (this.pendingBatchBeginMessage === message) {\n assert(batchBeginMetadata === undefined,\n 0x16e /* \"Batch with single message should not have batch metadata\" */);\n } else {\n if (batchBeginMetadata !== true || batchEndMetadata !== false) {\n this.stateHandler.close(DataProcessingError.create(\n \"Pending batch inconsistency\", // Formerly known as asserts 0x16f and 0x170\n \"processPendingLocalMessage\",\n message,\n {\n runtimeVersion: pkgVersion,\n batchClientId: this.pendingBatchBeginMessage.clientId,\n clientId: this.stateHandler.clientId(),\n hasBatchStart: batchBeginMetadata === true,\n hasBatchEnd: batchEndMetadata === false,\n messageType: message.type,\n batchStartSequenceNumber: this.pendingBatchBeginMessage.clientSequenceNumber,\n pendingMessagesCount: this.pendingMessagesCount,\n }));\n }\n }\n\n // Clear the pending batch state now that we have processed the entire batch.\n this.pendingBatchBeginMessage = undefined;\n this.isProcessingBatch = false;\n }\n }\n\n /**\n * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n * states in its queue. This includes triggering resubmission of unacked ops.\n */\n public replayPendingStates() {\n assert(this.stateHandler.connected(), 0x172 /* \"The connection state is not consistent with the runtime\" */);\n\n // This assert suggests we are about to send same ops twice, which will result in data loss.\n assert(this.clientId !== this.stateHandler.clientId(),\n 0x173 /* \"replayPendingStates called twice for same clientId!\" */);\n this.clientId = this.stateHandler.clientId();\n\n assert(this.initialMessages.isEmpty(), 0x174 /* \"initial states should be empty before replaying pending\" */);\n\n let pendingMessagesCount = this.pendingMessages.length;\n if (pendingMessagesCount === 0) {\n return;\n }\n\n // Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n // pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n // which must not be replayed.\n while (pendingMessagesCount > 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n let pendingMessage = this.pendingMessages.shift()!;\n pendingMessagesCount--;\n assert(pendingMessage.opMetadata?.batch !== false, 0x41b /* We cannot process batches in chunks */);\n\n /**\n * We want to ensure grouped messages get processed in a batch.\n * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n * either receive the whole batch ack or nothing at all.\n */\n if (pendingMessage.opMetadata?.batch) {\n assert(pendingMessagesCount > 0, 0x554 /* Last pending message cannot be a batch begin */);\n\n this.stateHandler.orderSequentially(() => {\n while (pendingMessagesCount >= 0) { // check is >= because batch end may be last pending message\n this.stateHandler.reSubmit(\n pendingMessage.messageType,\n pendingMessage.content,\n pendingMessage.localOpMetadata,\n pendingMessage.opMetadata);\n\n if (pendingMessage.opMetadata?.batch === false) {\n break;\n }\n assert(pendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n pendingMessage = this.pendingMessages.shift()!;\n pendingMessagesCount--;\n assert(pendingMessage.opMetadata?.batch !== true,\n 0x556 /* Batch start needs a corresponding batch end */);\n }\n });\n } else {\n this.stateHandler.reSubmit(\n pendingMessage.messageType,\n pendingMessage.content,\n pendingMessage.localOpMetadata,\n pendingMessage.opMetadata);\n }\n }\n }\n}\n"]}
|
|
@@ -25,7 +25,6 @@ export declare class SummarizerClientElection extends TypedEventEmitter<ISummari
|
|
|
25
25
|
private readonly summaryCollection;
|
|
26
26
|
readonly clientElection: IOrderedClientElection;
|
|
27
27
|
private readonly maxOpsSinceLastSummary;
|
|
28
|
-
private readonly electionEnabled;
|
|
29
28
|
/**
|
|
30
29
|
* Used to calculate number of ops since last summary ack for the current elected client.
|
|
31
30
|
* This will be undefined if there is no elected summarizer, or no summary ack has been
|
|
@@ -41,7 +40,7 @@ export declare class SummarizerClientElection extends TypedEventEmitter<ISummari
|
|
|
41
40
|
private lastReportedSeq;
|
|
42
41
|
get electedClientId(): string | undefined;
|
|
43
42
|
get electedParentId(): string | undefined;
|
|
44
|
-
constructor(logger: ITelemetryLogger, summaryCollection: IEventProvider<ISummaryCollectionOpEvents>, clientElection: IOrderedClientElection, maxOpsSinceLastSummary: number
|
|
43
|
+
constructor(logger: ITelemetryLogger, summaryCollection: IEventProvider<ISummaryCollectionOpEvents>, clientElection: IOrderedClientElection, maxOpsSinceLastSummary: number);
|
|
45
44
|
serialize(): ISerializedElection;
|
|
46
45
|
static isClientEligible(client: ITrackedClient): boolean;
|
|
47
46
|
static readonly clientDetailsPermitElection: (details: IClientDetails) => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarizerClientElection.d.ts","sourceRoot":"","sources":["../src/summarizerClientElection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC9F,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAe,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACtG,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD,MAAM,WAAW,+BAAgC,SAAQ,MAAM;IAC3D,CAAC,KAAK,EAAE,0BAA0B,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAClE;AAED,MAAM,WAAW,yBAA0B,SAAQ,cAAc,CAAC,+BAA+B,CAAC;IAC9F,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;CAChD;AAED;;;;GAIG;AACH,qBAAa,wBACT,SAAQ,iBAAiB,CAAC,+BAA+B,CACzD,YAAW,yBAAyB;IAuBhC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,iBAAiB;aAClB,cAAc,EAAE,sBAAsB;IACtD,OAAO,CAAC,QAAQ,CAAC,sBAAsB;
|
|
1
|
+
{"version":3,"file":"summarizerClientElection.d.ts","sourceRoot":"","sources":["../src/summarizerClientElection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC9F,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAe,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACtG,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD,MAAM,WAAW,+BAAgC,SAAQ,MAAM;IAC3D,CAAC,KAAK,EAAE,0BAA0B,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAClE;AAED,MAAM,WAAW,yBAA0B,SAAQ,cAAc,CAAC,+BAA+B,CAAC;IAC9F,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;CAChD;AAED;;;;GAIG;AACH,qBAAa,wBACT,SAAQ,iBAAiB,CAAC,+BAA+B,CACzD,YAAW,yBAAyB;IAuBhC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,iBAAiB;aAClB,cAAc,EAAE,sBAAsB;IACtD,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IAzB3C;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B,CAAqB;IACvD;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAAK;IAE5B,IAAW,eAAe,uBAEzB;IACD,IAAW,eAAe,uBAEzB;gBAGoB,MAAM,EAAE,gBAAgB,EACxB,iBAAiB,EAAE,cAAc,CAAC,0BAA0B,CAAC,EAC9D,cAAc,EAAE,sBAAsB,EACrC,sBAAsB,EAAE,MAAM;IAwD5C,SAAS,IAAI,mBAAmB;WASzB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO;IAS/D,gBAAuB,2BAA2B,YAAa,cAAc,KAAG,OAAO,CACT;CACjF"}
|
|
@@ -11,13 +11,12 @@ export const summarizerClientType = "summarizer";
|
|
|
11
11
|
* for some configured number of ops.
|
|
12
12
|
*/
|
|
13
13
|
export class SummarizerClientElection extends TypedEventEmitter {
|
|
14
|
-
constructor(logger, summaryCollection, clientElection, maxOpsSinceLastSummary
|
|
14
|
+
constructor(logger, summaryCollection, clientElection, maxOpsSinceLastSummary) {
|
|
15
15
|
super();
|
|
16
16
|
this.logger = logger;
|
|
17
17
|
this.summaryCollection = summaryCollection;
|
|
18
18
|
this.clientElection = clientElection;
|
|
19
19
|
this.maxOpsSinceLastSummary = maxOpsSinceLastSummary;
|
|
20
|
-
this.electionEnabled = electionEnabled;
|
|
21
20
|
/**
|
|
22
21
|
* Used to prevent excess logging by recording the sequence number that we last reported at,
|
|
23
22
|
* and making sure we don't report another event to telemetry. If things work as intended,
|
|
@@ -27,7 +26,7 @@ export class SummarizerClientElection extends TypedEventEmitter {
|
|
|
27
26
|
// On every inbound op, if enough ops pass without seeing a summary ack (per elected client),
|
|
28
27
|
// elect a new client and log to telemetry.
|
|
29
28
|
this.summaryCollection.on("default", ({ sequenceNumber }) => {
|
|
30
|
-
var _a, _b
|
|
29
|
+
var _a, _b;
|
|
31
30
|
const electedClientId = this.electedClientId;
|
|
32
31
|
if (electedClientId === undefined) {
|
|
33
32
|
// Reset election if no elected client, but eligible clients are connected.
|
|
@@ -39,7 +38,7 @@ export class SummarizerClientElection extends TypedEventEmitter {
|
|
|
39
38
|
}
|
|
40
39
|
return;
|
|
41
40
|
}
|
|
42
|
-
|
|
41
|
+
const electionSequenceNumber = this.clientElection.electionSequenceNumber;
|
|
43
42
|
const opsWithoutSummary = sequenceNumber - ((_a = this.lastSummaryAckSeqForClient) !== null && _a !== void 0 ? _a : electionSequenceNumber);
|
|
44
43
|
if (opsWithoutSummary > this.maxOpsSinceLastSummary) {
|
|
45
44
|
// Log and elect a new summarizer client.
|
|
@@ -51,35 +50,9 @@ export class SummarizerClientElection extends TypedEventEmitter {
|
|
|
51
50
|
lastSummaryAckSeqForClient: this.lastSummaryAckSeqForClient,
|
|
52
51
|
electionSequenceNumber,
|
|
53
52
|
nextElectedClientId: (_b = this.clientElection.peekNextElectedClient()) === null || _b === void 0 ? void 0 : _b.clientId,
|
|
54
|
-
electionEnabled: this.electionEnabled,
|
|
55
53
|
});
|
|
56
54
|
this.lastReportedSeq = sequenceNumber;
|
|
57
55
|
}
|
|
58
|
-
if (this.electionEnabled) {
|
|
59
|
-
const previousParentId = this.electedParentId;
|
|
60
|
-
this.clientElection.incrementElectedClient(sequenceNumber);
|
|
61
|
-
// Verify that state incremented as expected. This should be reliable,
|
|
62
|
-
// since all of OrderedClientElection is synchronous.
|
|
63
|
-
electionSequenceNumber = this.clientElection.electionSequenceNumber;
|
|
64
|
-
if (sequenceNumber > ((_c = this.lastSummaryAckSeqForClient) !== null && _c !== void 0 ? _c : electionSequenceNumber)) {
|
|
65
|
-
if (opsSinceLastReport > this.maxOpsSinceLastSummary) {
|
|
66
|
-
this.logger.sendErrorEvent({
|
|
67
|
-
eventName: "UnexpectedElectionSequenceNumber",
|
|
68
|
-
// Expected to be undefined
|
|
69
|
-
lastSummaryAckSeqForClient: this.lastSummaryAckSeqForClient,
|
|
70
|
-
// Expected to be same as op sequenceNumber
|
|
71
|
-
electionSequenceNumber,
|
|
72
|
-
sequenceNumber,
|
|
73
|
-
previousClientId: electedClientId,
|
|
74
|
-
previousParentId,
|
|
75
|
-
electedParentId: this.electedParentId,
|
|
76
|
-
electedClientId: this.electedClientId,
|
|
77
|
-
opsSinceLastReport,
|
|
78
|
-
maxOpsSinceLastSummary,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
56
|
}
|
|
84
57
|
});
|
|
85
58
|
// When a summary ack comes in, reset our op seq counter.
|