@fluidframework/container-runtime 2.0.0-internal.2.3.1 → 2.0.0-internal.3.0.0

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.
Files changed (79) hide show
  1. package/dist/blobManager.d.ts +3 -1
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +35 -2
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +45 -42
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +89 -40
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.d.ts +1 -0
  10. package/dist/dataStoreContext.d.ts.map +1 -1
  11. package/dist/dataStoreContext.js +7 -2
  12. package/dist/dataStoreContext.js.map +1 -1
  13. package/dist/garbageCollection.d.ts +15 -7
  14. package/dist/garbageCollection.d.ts.map +1 -1
  15. package/dist/garbageCollection.js +96 -36
  16. package/dist/garbageCollection.js.map +1 -1
  17. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  18. package/dist/opLifecycle/outbox.js +0 -1
  19. package/dist/opLifecycle/outbox.js.map +1 -1
  20. package/dist/packageVersion.d.ts +1 -1
  21. package/dist/packageVersion.js +1 -1
  22. package/dist/packageVersion.js.map +1 -1
  23. package/dist/pendingStateManager.d.ts +4 -13
  24. package/dist/pendingStateManager.d.ts.map +1 -1
  25. package/dist/pendingStateManager.js +130 -160
  26. package/dist/pendingStateManager.js.map +1 -1
  27. package/dist/summarizer.js.map +1 -1
  28. package/dist/summarizerClientElection.d.ts +1 -2
  29. package/dist/summarizerClientElection.d.ts.map +1 -1
  30. package/dist/summarizerClientElection.js +3 -30
  31. package/dist/summarizerClientElection.js.map +1 -1
  32. package/dist/summarizerTypes.d.ts +0 -4
  33. package/dist/summarizerTypes.d.ts.map +1 -1
  34. package/dist/summarizerTypes.js.map +1 -1
  35. package/lib/blobManager.d.ts +3 -1
  36. package/lib/blobManager.d.ts.map +1 -1
  37. package/lib/blobManager.js +35 -2
  38. package/lib/blobManager.js.map +1 -1
  39. package/lib/containerRuntime.d.ts +45 -42
  40. package/lib/containerRuntime.d.ts.map +1 -1
  41. package/lib/containerRuntime.js +89 -40
  42. package/lib/containerRuntime.js.map +1 -1
  43. package/lib/dataStoreContext.d.ts +1 -0
  44. package/lib/dataStoreContext.d.ts.map +1 -1
  45. package/lib/dataStoreContext.js +7 -2
  46. package/lib/dataStoreContext.js.map +1 -1
  47. package/lib/garbageCollection.d.ts +15 -7
  48. package/lib/garbageCollection.d.ts.map +1 -1
  49. package/lib/garbageCollection.js +97 -37
  50. package/lib/garbageCollection.js.map +1 -1
  51. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  52. package/lib/opLifecycle/outbox.js +0 -1
  53. package/lib/opLifecycle/outbox.js.map +1 -1
  54. package/lib/packageVersion.d.ts +1 -1
  55. package/lib/packageVersion.js +1 -1
  56. package/lib/packageVersion.js.map +1 -1
  57. package/lib/pendingStateManager.d.ts +4 -13
  58. package/lib/pendingStateManager.d.ts.map +1 -1
  59. package/lib/pendingStateManager.js +130 -160
  60. package/lib/pendingStateManager.js.map +1 -1
  61. package/lib/summarizer.js.map +1 -1
  62. package/lib/summarizerClientElection.d.ts +1 -2
  63. package/lib/summarizerClientElection.d.ts.map +1 -1
  64. package/lib/summarizerClientElection.js +3 -30
  65. package/lib/summarizerClientElection.js.map +1 -1
  66. package/lib/summarizerTypes.d.ts +0 -4
  67. package/lib/summarizerTypes.d.ts.map +1 -1
  68. package/lib/summarizerTypes.js.map +1 -1
  69. package/package.json +55 -20
  70. package/src/blobManager.ts +41 -2
  71. package/src/containerRuntime.ts +118 -85
  72. package/src/dataStoreContext.ts +12 -6
  73. package/src/garbageCollection.ts +103 -34
  74. package/src/opLifecycle/outbox.ts +0 -2
  75. package/src/packageVersion.ts +1 -1
  76. package/src/pendingStateManager.ts +146 -187
  77. package/src/summarizer.ts +1 -1
  78. package/src/summarizerClientElection.ts +1 -30
  79. package/src/summarizerTypes.ts +0 -4
@@ -31,6 +31,7 @@ export interface IPendingMessage {
31
31
  /**
32
32
  * This represents an explicit flush call and is added to the pending queue when flush is called on the ContainerRuntime
33
33
  * to flush pending messages.
34
+ * @deprecated Use batch metadata on IPendingMessage instead. To be removed in 2.0.0-internal.4.0.0 (AB#2496)
34
35
  */
35
36
  export interface IPendingFlush {
36
37
  type: "flush";
@@ -45,12 +46,11 @@ export interface IPendingLocalState {
45
46
  pendingStates: IPendingState[];
46
47
  }
47
48
 
48
- export interface IRuntimeStateHandler{
49
+ export interface IRuntimeStateHandler {
49
50
  connected(): boolean;
50
51
  clientId(): string | undefined;
51
52
  close(error?: ICriticalContainerError): void;
52
53
  applyStashedOp: (type: ContainerMessageType, content: ISequencedDocumentMessage) => Promise<unknown>;
53
- flush(): void;
54
54
  reSubmit(
55
55
  type: ContainerMessageType,
56
56
  content: any,
@@ -73,17 +73,15 @@ export interface IRuntimeStateHandler{
73
73
  * It verifies that all the ops are acked, are received in the right order and batch information is correct.
74
74
  */
75
75
  export class PendingStateManager implements IDisposable {
76
- private readonly pendingStates = new Deque<IPendingState>();
77
- private readonly initialStates: Deque<IPendingState>;
76
+ private readonly pendingMessages = new Deque<IPendingMessage>();
77
+ private readonly initialMessages = new Deque<IPendingMessage>();
78
78
  private readonly disposeOnce = new Lazy<void>(() => {
79
- this.initialStates.clear();
80
- this.pendingStates.clear();
79
+ this.initialMessages.clear();
80
+ this.pendingMessages.clear();
81
81
  });
82
82
 
83
- // Maintains the count of messages that are currently unacked.
84
- private _pendingMessagesCount: number = 0;
85
83
  public get pendingMessagesCount(): number {
86
- return this._pendingMessagesCount;
84
+ return this.pendingMessages.length;
87
85
  }
88
86
 
89
87
  // Indicates whether we are processing a batch.
@@ -96,21 +94,28 @@ export class PendingStateManager implements IDisposable {
96
94
  private clientId: string | undefined;
97
95
 
98
96
  /**
99
- * Called to check if there are any pending messages in the pending state queue.
97
+ * Called to check if there are any pending messages in the pending message queue.
100
98
  * @returns A boolean indicating whether there are messages or not.
101
99
  */
102
100
  public hasPendingMessages(): boolean {
103
- return this._pendingMessagesCount !== 0 || !this.initialStates.isEmpty();
101
+ return !this.pendingMessages.isEmpty() || !this.initialMessages.isEmpty();
104
102
  }
105
103
 
106
104
  public getLocalState(): IPendingLocalState | undefined {
107
- assert(this.initialStates.isEmpty(), 0x2e9 /* "Must call getLocalState() after applying initial states" */);
108
- if (this.hasPendingMessages()) {
105
+ assert(this.initialMessages.isEmpty(), 0x2e9 /* "Must call getLocalState() after applying initial states" */);
106
+ if (!this.pendingMessages.isEmpty()) {
109
107
  return {
110
- pendingStates: this.pendingStates.toArray().map(
108
+ pendingStates: this.pendingMessages.toArray().reduce((arr, message) => {
111
109
  // delete localOpMetadata since it may not be serializable
112
110
  // and will be regenerated by applyStashedOp()
113
- (state) => state.type === "message" ? { ...state, localOpMetadata: undefined } : state),
111
+ arr.push({ ...message, localOpMetadata: undefined });
112
+
113
+ // TODO: Remove in 2.0.0-internal.4.0.0 (AB#2496)
114
+ if (message.opMetadata?.batch === false) {
115
+ arr.push({ type: "flush" });
116
+ }
117
+ return arr;
118
+ }, new Array<IPendingState>()),
114
119
  };
115
120
  }
116
121
  }
@@ -119,7 +124,37 @@ export class PendingStateManager implements IDisposable {
119
124
  private readonly stateHandler: IRuntimeStateHandler,
120
125
  initialLocalState: IPendingLocalState | undefined,
121
126
  ) {
122
- this.initialStates = new Deque<IPendingState>(initialLocalState?.pendingStates ?? []);
127
+ /**
128
+ * Convert old local state format to the new format
129
+ * The old format contained "flush" messages as the indicator of batch ends
130
+ * The new format instead uses batch metadata on the last message to indicate batch ends
131
+ * ! TODO: Remove this conversion in "2.0.0-internal.4.0.0" as rollback from future version will be new format
132
+ * AB#2496 tracks removal
133
+ */
134
+ if (initialLocalState?.pendingStates) {
135
+ const pendingStates = initialLocalState?.pendingStates;
136
+ let currentlyBatching = false;
137
+ for (let i = 0; i < pendingStates.length; i++) {
138
+ const initialState = pendingStates[i];
139
+
140
+ // Skip over "flush" messages
141
+ if (initialState.type === "message") {
142
+ if (initialState.opMetadata?.batch) {
143
+ currentlyBatching = true;
144
+ } else if (initialState.opMetadata?.batch === false) {
145
+ currentlyBatching = false;
146
+ } else if (
147
+ // End of batch if we are currently batching and this is last message or next message is flush
148
+ currentlyBatching
149
+ && (i === pendingStates.length - 1 || pendingStates[i + 1].type === "flush")
150
+ ) {
151
+ currentlyBatching = false;
152
+ initialState.opMetadata = { ...initialState.opMetadata, batch: false };
153
+ }
154
+ this.initialMessages.push(initialState);
155
+ }
156
+ }
157
+ }
123
158
  }
124
159
 
125
160
  public get disposed() { return this.disposeOnce.evaluated; }
@@ -151,23 +186,7 @@ export class PendingStateManager implements IDisposable {
151
186
  opMetadata,
152
187
  };
153
188
 
154
- this.pendingStates.push(pendingMessage);
155
-
156
- this._pendingMessagesCount++;
157
- }
158
-
159
- /**
160
- * Called when flush() is called on the ContainerRuntime to manually flush messages.
161
- */
162
- public onFlush() {
163
- // If the previous state is not a message, flush is a no-op.
164
- const previousState = this.pendingStates.peekBack();
165
- if (previousState?.type !== "message") {
166
- return;
167
- }
168
-
169
- // An explicit flush is interesting and is tracked only if there are messages sent in TurnBased mode.
170
- this.pendingStates.push({ type: "flush" });
189
+ this.pendingMessages.push(pendingMessage);
171
190
  }
172
191
 
173
192
  /**
@@ -176,31 +195,26 @@ export class PendingStateManager implements IDisposable {
176
195
  */
177
196
  public async applyStashedOpsAt(seqNum?: number) {
178
197
  // apply stashed ops at sequence number
179
- while (!this.initialStates.isEmpty()) {
198
+ while (!this.initialMessages.isEmpty()) {
180
199
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
181
- const nextState = this.initialStates.peekFront()!;
182
- if (nextState.type === "message") {
183
- if (seqNum !== undefined) {
184
- if (nextState.referenceSequenceNumber > seqNum) {
185
- break; // nothing left to do at this sequence number
186
- } else if (nextState.referenceSequenceNumber < seqNum) {
187
- throw new Error("loaded from snapshot too recent to apply stashed ops");
188
- }
200
+ const nextMessage = this.initialMessages.peekFront()!;
201
+ if (seqNum !== undefined) {
202
+ if (nextMessage.referenceSequenceNumber > seqNum) {
203
+ break; // nothing left to do at this sequence number
204
+ }
205
+ if (nextMessage.referenceSequenceNumber < seqNum) {
206
+ throw new Error("loaded from snapshot too recent to apply stashed ops");
189
207
  }
190
-
191
- // applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
192
- const localOpMetadata =
193
- await this.stateHandler.applyStashedOp(nextState.messageType, nextState.content);
194
- nextState.localOpMetadata = localOpMetadata;
195
208
  }
196
209
 
197
- // then we push onto pendingStates which will cause PendingStateManager to resubmit when we connect
210
+ // applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
211
+ const localOpMetadata =
212
+ await this.stateHandler.applyStashedOp(nextMessage.messageType, nextMessage.content);
213
+ nextMessage.localOpMetadata = localOpMetadata;
214
+
215
+ // then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
198
216
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
199
- const firstPendingState = this.initialStates.shift()!;
200
- this.pendingStates.push(firstPendingState);
201
- if (firstPendingState.type === "message") {
202
- this._pendingMessagesCount++;
203
- }
217
+ this.pendingMessages.push(this.initialMessages.shift()!);
204
218
  }
205
219
  }
206
220
 
@@ -213,33 +227,30 @@ export class PendingStateManager implements IDisposable {
213
227
  // Pre-processing part - This may be the start of a batch.
214
228
  this.maybeProcessBatchBegin(message);
215
229
 
216
- // Get the next state from the pending queue and verify that it is of type "message".
217
- const pendingState = this.peekNextPendingState();
218
- assert(pendingState.type === "message", 0x169 /* "No pending message found for this remote message" */);
219
- this.pendingStates.shift();
230
+ // Get the next message from the pending queue. Verify a message exists.
231
+ const pendingMessage = this.pendingMessages.peekFront();
232
+ assert(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
233
+ this.pendingMessages.shift();
220
234
 
221
235
  // Processing part - Verify that there has been no data corruption.
222
236
  // The clientSequenceNumber of the incoming message must match that of the pending message.
223
- if (pendingState.clientSequenceNumber !== message.clientSequenceNumber) {
237
+ if (pendingMessage.clientSequenceNumber !== message.clientSequenceNumber) {
224
238
  // Close the container because this could indicate data corruption.
225
239
  const error = DataProcessingError.create(
226
240
  "pending local message clientSequenceNumber mismatch",
227
241
  "unexpectedAckReceived",
228
242
  message,
229
- { expectedClientSequenceNumber: pendingState.clientSequenceNumber },
243
+ { expectedClientSequenceNumber: pendingMessage.clientSequenceNumber },
230
244
  );
231
245
 
232
246
  this.stateHandler.close(error);
233
247
  return;
234
248
  }
235
249
 
236
- this._pendingMessagesCount--;
237
- assert(this._pendingMessagesCount >= 0, 0x3d6 /* positive */);
238
-
239
250
  // Post-processing part - If we are processing a batch then this could be the last message in the batch.
240
251
  this.maybeProcessBatchEnd(message);
241
252
 
242
- return pendingState.localOpMetadata;
253
+ return pendingMessage.localOpMetadata;
243
254
  }
244
255
 
245
256
  /**
@@ -247,21 +258,6 @@ export class PendingStateManager implements IDisposable {
247
258
  * @param message - The message that is being processed.
248
259
  */
249
260
  private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {
250
- /**
251
- * We are checking if the next message is the start of a batch. It can happen in the following scenarios:
252
- *
253
- * 1. The FlushMode was set to TurnBased before this message was sent.
254
- *
255
- * 2. The FlushMode was already TurnBased and a flush was called before this message was sent. This essentially
256
- * means that the flush marked the end of a previous batch and beginning of a new batch.
257
- *
258
- * Keep reading pending states from the queue until we encounter a message. It's possible that the FlushMode was
259
- * updated a bunch of times without sending any messages.
260
- */
261
- while (this.peekNextPendingState().type !== "message") {
262
- this.pendingStates.shift();
263
- }
264
-
265
261
  // This message is the first in a batch if the "batch" property on the metadata is set to true
266
262
  if (message.metadata?.batch) {
267
263
  // We should not already be processing a batch and there should be no pending batch begin message.
@@ -283,56 +279,42 @@ export class PendingStateManager implements IDisposable {
283
279
  return;
284
280
  }
285
281
 
286
- const nextPendingState = this.peekNextPendingState();
287
- if (nextPendingState.type === "message") {
288
- return;
289
- }
290
-
291
282
  // There should be a pending batch begin message.
292
283
  assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* "There is no pending batch begin message" */);
293
284
 
294
- // Get the batch begin metadata from the first message in the batch.
295
- const batchBeginMetadata = this.pendingBatchBeginMessage.metadata?.batch;
296
-
297
- // There could be just a single message in the batch. If so, it should not have any batch metadata. If there
298
- // are multiple messages in the batch, verify that we got the correct batch begin and end metadata.
299
- if (this.pendingBatchBeginMessage === message) {
300
- assert(batchBeginMetadata === undefined,
301
- 0x16e /* "Batch with single message should not have batch metadata" */);
302
- } else {
303
- // Get the batch metadata from the last message in the batch.
304
- const batchEndMetadata = message.metadata?.batch;
305
- if (batchBeginMetadata !== true || batchEndMetadata !== false) {
306
- this.stateHandler.close(DataProcessingError.create(
307
- "Pending batch inconsistency", // Formerly known as asserts 0x16f and 0x170
308
- "processPendingLocalMessage",
309
- message,
310
- {
311
- runtimeVersion: pkgVersion,
312
- batchClientId: this.pendingBatchBeginMessage.clientId,
313
- clientId: this.stateHandler.clientId(),
314
- hasBatchStart: batchBeginMetadata === true,
315
- hasBatchEnd: batchEndMetadata === false,
316
- messageType: message.type,
317
- batchStartSequenceNumber: this.pendingBatchBeginMessage.clientSequenceNumber,
318
- pendingMessagesCount: this.pendingMessagesCount,
319
- nextPendingState: nextPendingState.type,
320
- }));
285
+ const batchEndMetadata = message.metadata?.batch;
286
+ if (this.pendingMessages.isEmpty() || batchEndMetadata === false) {
287
+ // Get the batch begin metadata from the first message in the batch.
288
+ const batchBeginMetadata = this.pendingBatchBeginMessage.metadata?.batch;
289
+
290
+ // There could be just a single message in the batch. If so, it should not have any batch metadata. If there
291
+ // are multiple messages in the batch, verify that we got the correct batch begin and end metadata.
292
+ if (this.pendingBatchBeginMessage === message) {
293
+ assert(batchBeginMetadata === undefined,
294
+ 0x16e /* "Batch with single message should not have batch metadata" */);
295
+ } else {
296
+ if (batchBeginMetadata !== true || batchEndMetadata !== false) {
297
+ this.stateHandler.close(DataProcessingError.create(
298
+ "Pending batch inconsistency", // Formerly known as asserts 0x16f and 0x170
299
+ "processPendingLocalMessage",
300
+ message,
301
+ {
302
+ runtimeVersion: pkgVersion,
303
+ batchClientId: this.pendingBatchBeginMessage.clientId,
304
+ clientId: this.stateHandler.clientId(),
305
+ hasBatchStart: batchBeginMetadata === true,
306
+ hasBatchEnd: batchEndMetadata === false,
307
+ messageType: message.type,
308
+ batchStartSequenceNumber: this.pendingBatchBeginMessage.clientSequenceNumber,
309
+ pendingMessagesCount: this.pendingMessagesCount,
310
+ }));
311
+ }
321
312
  }
322
- }
323
313
 
324
- // Clear the pending batch state now that we have processed the entire batch.
325
- this.pendingBatchBeginMessage = undefined;
326
- this.isProcessingBatch = false;
327
- }
328
-
329
- /**
330
- * Returns the next pending state from the pending state queue.
331
- */
332
- private peekNextPendingState(): IPendingState {
333
- const nextPendingState = this.pendingStates.peekFront();
334
- assert(!!nextPendingState, 0x171 /* "No pending state found for the remote message" */);
335
- return nextPendingState;
314
+ // Clear the pending batch state now that we have processed the entire batch.
315
+ this.pendingBatchBeginMessage = undefined;
316
+ this.isProcessingBatch = false;
317
+ }
336
318
  }
337
319
 
338
320
  /**
@@ -347,80 +329,57 @@ export class PendingStateManager implements IDisposable {
347
329
  0x173 /* "replayPendingStates called twice for same clientId!" */);
348
330
  this.clientId = this.stateHandler.clientId();
349
331
 
350
- assert(this.initialStates.isEmpty(), 0x174 /* "initial states should be empty before replaying pending" */);
332
+ assert(this.initialMessages.isEmpty(), 0x174 /* "initial states should be empty before replaying pending" */);
351
333
 
352
- let pendingStatesCount = this.pendingStates.length;
353
- if (pendingStatesCount === 0) {
334
+ let pendingMessagesCount = this.pendingMessages.length;
335
+ if (pendingMessagesCount === 0) {
354
336
  return;
355
337
  }
356
338
 
357
- // Reset the pending message count because all these messages will be removed from the queue.
358
- this._pendingMessagesCount = 0;
359
-
360
- const messageBatchQueue = new Deque<IPendingMessage>();
361
-
362
- // Process exactly `pendingStatesCount` items in the queue as it represents the number of states that were
339
+ // Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were
363
340
  // pending when we connected. This is important because the `reSubmitFn` might add more items in the queue
364
341
  // which must not be replayed.
365
- while (pendingStatesCount > 0) {
342
+ while (pendingMessagesCount > 0) {
366
343
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
367
- const pendingState = this.pendingStates.shift()!;
368
- switch (pendingState.type) {
369
- case "message":
370
- assert(pendingState.opMetadata?.batch !== false || messageBatchQueue.length > 0,
371
- 0x41b /* We cannot process batches in chunks */);
372
- /**
373
- * We want to ensure grouped messages get processed in a batch.
374
- * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
375
- * either receive the whole batch ack or nothing at all.
376
- */
377
- if (messageBatchQueue.length > 0 || pendingState.opMetadata?.batch) {
378
- messageBatchQueue.enqueue(pendingState);
379
- } else {
344
+ let pendingMessage = this.pendingMessages.shift()!;
345
+ pendingMessagesCount--;
346
+ assert(pendingMessage.opMetadata?.batch !== false, 0x41b /* We cannot process batches in chunks */);
347
+
348
+ /**
349
+ * We want to ensure grouped messages get processed in a batch.
350
+ * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
351
+ * either receive the whole batch ack or nothing at all.
352
+ */
353
+ if (pendingMessage.opMetadata?.batch) {
354
+ assert(pendingMessagesCount > 0, 0x554 /* Last pending message cannot be a batch begin */);
355
+
356
+ this.stateHandler.orderSequentially(() => {
357
+ while (pendingMessagesCount >= 0) { // check is >= because batch end may be last pending message
380
358
  this.stateHandler.reSubmit(
381
- pendingState.messageType,
382
- pendingState.content,
383
- pendingState.localOpMetadata,
384
- pendingState.opMetadata);
385
- }
386
- break;
387
- case "flush":
388
- /**
389
- * A "flush" call can indicate the end of a batch.
390
- * We can't rely on the "batch" property in the message metadata as it gets
391
- * updated elsewhere and it is not the same object instance that gets updated.
392
- */
393
- if (messageBatchQueue.length > 0) {
394
- this.stateHandler.orderSequentially(() => {
395
- while (messageBatchQueue.length > 0) {
396
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
397
- const message = messageBatchQueue.dequeue()!;
398
- this.stateHandler.reSubmit(
399
- message.messageType,
400
- message.content,
401
- message.localOpMetadata,
402
- message.opMetadata);
403
- }
404
- });
359
+ pendingMessage.messageType,
360
+ pendingMessage.content,
361
+ pendingMessage.localOpMetadata,
362
+ pendingMessage.opMetadata);
363
+
364
+ if (pendingMessage.opMetadata?.batch === false) {
365
+ break;
366
+ }
367
+ assert(pendingMessagesCount > 0, 0x555 /* No batch end found */);
368
+
369
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
370
+ pendingMessage = this.pendingMessages.shift()!;
371
+ pendingMessagesCount--;
372
+ assert(pendingMessage.opMetadata?.batch !== true,
373
+ 0x556 /* Batch start needs a corresponding batch end */);
405
374
  }
406
- assert(messageBatchQueue.length === 0, 0x41c /* cannot flush in the middle of a batch */);
407
- this.stateHandler.flush();
408
- break;
409
- default:
410
- break;
375
+ });
376
+ } else {
377
+ this.stateHandler.reSubmit(
378
+ pendingMessage.messageType,
379
+ pendingMessage.content,
380
+ pendingMessage.localOpMetadata,
381
+ pendingMessage.opMetadata);
411
382
  }
412
- pendingStatesCount--;
413
- }
414
-
415
- // There are some cases where ops are stashed but not flushed. We need to ensure they are resubmitted
416
- while (messageBatchQueue.length > 0) {
417
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
418
- const message = messageBatchQueue.dequeue()!;
419
- this.stateHandler.reSubmit(
420
- message.messageType,
421
- message.content,
422
- message.localOpMetadata,
423
- message.opMetadata);
424
383
  }
425
384
  }
426
385
  }
package/src/summarizer.ts CHANGED
@@ -160,7 +160,7 @@ export class Summarizer extends EventEmitter implements ISummarizer {
160
160
  // This will result in "summarizerClientDisconnected" stop reason recorded in telemetry,
161
161
  // unless stop() was called earlier
162
162
  this.dispose();
163
- (this.runtime.disposeFn ?? this.runtime.closeFn)()
163
+ (this.runtime.disposeFn ?? this.runtime.closeFn)();
164
164
  }
165
165
 
166
166
  private async runCore(onBehalfOf: string): Promise<SummarizerStopReason> {
@@ -54,7 +54,6 @@ export class SummarizerClientElection
54
54
  private readonly summaryCollection: IEventProvider<ISummaryCollectionOpEvents>,
55
55
  public readonly clientElection: IOrderedClientElection,
56
56
  private readonly maxOpsSinceLastSummary: number,
57
- private readonly electionEnabled: boolean,
58
57
  ) {
59
58
  super();
60
59
  // On every inbound op, if enough ops pass without seeing a summary ack (per elected client),
@@ -71,7 +70,7 @@ export class SummarizerClientElection
71
70
  }
72
71
  return;
73
72
  }
74
- let electionSequenceNumber = this.clientElection.electionSequenceNumber;
73
+ const electionSequenceNumber = this.clientElection.electionSequenceNumber;
75
74
  const opsWithoutSummary = sequenceNumber - (this.lastSummaryAckSeqForClient ?? electionSequenceNumber);
76
75
  if (opsWithoutSummary > this.maxOpsSinceLastSummary) {
77
76
  // Log and elect a new summarizer client.
@@ -83,37 +82,9 @@ export class SummarizerClientElection
83
82
  lastSummaryAckSeqForClient: this.lastSummaryAckSeqForClient,
84
83
  electionSequenceNumber,
85
84
  nextElectedClientId: this.clientElection.peekNextElectedClient()?.clientId,
86
- electionEnabled: this.electionEnabled,
87
85
  });
88
86
  this.lastReportedSeq = sequenceNumber;
89
87
  }
90
-
91
- if (this.electionEnabled) {
92
- const previousParentId = this.electedParentId;
93
- this.clientElection.incrementElectedClient(sequenceNumber);
94
-
95
- // Verify that state incremented as expected. This should be reliable,
96
- // since all of OrderedClientElection is synchronous.
97
- electionSequenceNumber = this.clientElection.electionSequenceNumber;
98
- if (sequenceNumber > (this.lastSummaryAckSeqForClient ?? electionSequenceNumber)) {
99
- if (opsSinceLastReport > this.maxOpsSinceLastSummary) {
100
- this.logger.sendErrorEvent({
101
- eventName: "UnexpectedElectionSequenceNumber",
102
- // Expected to be undefined
103
- lastSummaryAckSeqForClient: this.lastSummaryAckSeqForClient,
104
- // Expected to be same as op sequenceNumber
105
- electionSequenceNumber,
106
- sequenceNumber,
107
- previousClientId: electedClientId,
108
- previousParentId,
109
- electedParentId: this.electedParentId,
110
- electedClientId: this.electedClientId,
111
- opsSinceLastReport,
112
- maxOpsSinceLastSummary,
113
- });
114
- }
115
- }
116
- }
117
88
  }
118
89
  });
119
90
 
@@ -96,10 +96,6 @@ export interface ISummarizerRuntime extends IConnectableRuntime {
96
96
  readonly summarizerClientId: string | undefined;
97
97
  disposeFn?(): void;
98
98
  closeFn(): void;
99
- /** @deprecated 1.0, please remove all implementations and usage */
100
- on(event: "batchEnd", listener: (error: any, op: ISequencedDocumentMessage) => void): this;
101
- /** @deprecated 1.0, please remove all implementations and usage */
102
- removeListener(event: "batchEnd", listener: (error: any, op: ISequencedDocumentMessage) => void): this;
103
99
  }
104
100
 
105
101
  /** Options affecting summarize behavior. */