@fluidframework/container-runtime 0.58.2001 → 0.59.1000-61898

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 (124) hide show
  1. package/dist/blobManager.d.ts +15 -2
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +65 -9
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/connectionTelemetry.d.ts.map +1 -1
  6. package/dist/connectionTelemetry.js +63 -23
  7. package/dist/connectionTelemetry.js.map +1 -1
  8. package/dist/containerRuntime.d.ts +39 -7
  9. package/dist/containerRuntime.d.ts.map +1 -1
  10. package/dist/containerRuntime.js +161 -29
  11. package/dist/containerRuntime.js.map +1 -1
  12. package/dist/dataStore.js +8 -1
  13. package/dist/dataStore.js.map +1 -1
  14. package/dist/dataStoreContext.d.ts +9 -3
  15. package/dist/dataStoreContext.d.ts.map +1 -1
  16. package/dist/dataStoreContext.js +22 -6
  17. package/dist/dataStoreContext.js.map +1 -1
  18. package/dist/dataStores.d.ts +13 -5
  19. package/dist/dataStores.d.ts.map +1 -1
  20. package/dist/dataStores.js +39 -18
  21. package/dist/dataStores.js.map +1 -1
  22. package/dist/deltaScheduler.d.ts +4 -5
  23. package/dist/deltaScheduler.d.ts.map +1 -1
  24. package/dist/deltaScheduler.js +54 -35
  25. package/dist/deltaScheduler.js.map +1 -1
  26. package/dist/garbageCollection.d.ts +31 -27
  27. package/dist/garbageCollection.d.ts.map +1 -1
  28. package/dist/garbageCollection.js +76 -75
  29. package/dist/garbageCollection.js.map +1 -1
  30. package/dist/opTelemetry.d.ts +22 -0
  31. package/dist/opTelemetry.d.ts.map +1 -0
  32. package/dist/opTelemetry.js +59 -0
  33. package/dist/opTelemetry.js.map +1 -0
  34. package/dist/orderedClientElection.d.ts +57 -6
  35. package/dist/orderedClientElection.d.ts.map +1 -1
  36. package/dist/orderedClientElection.js +140 -25
  37. package/dist/orderedClientElection.js.map +1 -1
  38. package/dist/packageVersion.d.ts +1 -1
  39. package/dist/packageVersion.d.ts.map +1 -1
  40. package/dist/packageVersion.js +1 -1
  41. package/dist/packageVersion.js.map +1 -1
  42. package/dist/summarizerClientElection.d.ts +2 -0
  43. package/dist/summarizerClientElection.d.ts.map +1 -1
  44. package/dist/summarizerClientElection.js +7 -2
  45. package/dist/summarizerClientElection.js.map +1 -1
  46. package/dist/summarizerTypes.d.ts +9 -0
  47. package/dist/summarizerTypes.d.ts.map +1 -1
  48. package/dist/summarizerTypes.js.map +1 -1
  49. package/dist/summaryGenerator.d.ts.map +1 -1
  50. package/dist/summaryGenerator.js +1 -1
  51. package/dist/summaryGenerator.js.map +1 -1
  52. package/dist/summaryManager.d.ts.map +1 -1
  53. package/dist/summaryManager.js +14 -3
  54. package/dist/summaryManager.js.map +1 -1
  55. package/lib/blobManager.d.ts +15 -2
  56. package/lib/blobManager.d.ts.map +1 -1
  57. package/lib/blobManager.js +66 -10
  58. package/lib/blobManager.js.map +1 -1
  59. package/lib/connectionTelemetry.d.ts.map +1 -1
  60. package/lib/connectionTelemetry.js +63 -23
  61. package/lib/connectionTelemetry.js.map +1 -1
  62. package/lib/containerRuntime.d.ts +39 -7
  63. package/lib/containerRuntime.d.ts.map +1 -1
  64. package/lib/containerRuntime.js +163 -31
  65. package/lib/containerRuntime.js.map +1 -1
  66. package/lib/dataStore.js +8 -1
  67. package/lib/dataStore.js.map +1 -1
  68. package/lib/dataStoreContext.d.ts +9 -3
  69. package/lib/dataStoreContext.d.ts.map +1 -1
  70. package/lib/dataStoreContext.js +22 -6
  71. package/lib/dataStoreContext.js.map +1 -1
  72. package/lib/dataStores.d.ts +13 -5
  73. package/lib/dataStores.d.ts.map +1 -1
  74. package/lib/dataStores.js +39 -18
  75. package/lib/dataStores.js.map +1 -1
  76. package/lib/deltaScheduler.d.ts +4 -5
  77. package/lib/deltaScheduler.d.ts.map +1 -1
  78. package/lib/deltaScheduler.js +54 -35
  79. package/lib/deltaScheduler.js.map +1 -1
  80. package/lib/garbageCollection.d.ts +31 -27
  81. package/lib/garbageCollection.d.ts.map +1 -1
  82. package/lib/garbageCollection.js +75 -74
  83. package/lib/garbageCollection.js.map +1 -1
  84. package/lib/opTelemetry.d.ts +22 -0
  85. package/lib/opTelemetry.d.ts.map +1 -0
  86. package/lib/opTelemetry.js +55 -0
  87. package/lib/opTelemetry.js.map +1 -0
  88. package/lib/orderedClientElection.d.ts +57 -6
  89. package/lib/orderedClientElection.d.ts.map +1 -1
  90. package/lib/orderedClientElection.js +140 -25
  91. package/lib/orderedClientElection.js.map +1 -1
  92. package/lib/packageVersion.d.ts +1 -1
  93. package/lib/packageVersion.d.ts.map +1 -1
  94. package/lib/packageVersion.js +1 -1
  95. package/lib/packageVersion.js.map +1 -1
  96. package/lib/summarizerClientElection.d.ts +2 -0
  97. package/lib/summarizerClientElection.d.ts.map +1 -1
  98. package/lib/summarizerClientElection.js +7 -2
  99. package/lib/summarizerClientElection.js.map +1 -1
  100. package/lib/summarizerTypes.d.ts +9 -0
  101. package/lib/summarizerTypes.d.ts.map +1 -1
  102. package/lib/summarizerTypes.js.map +1 -1
  103. package/lib/summaryGenerator.d.ts.map +1 -1
  104. package/lib/summaryGenerator.js +1 -1
  105. package/lib/summaryGenerator.js.map +1 -1
  106. package/lib/summaryManager.d.ts.map +1 -1
  107. package/lib/summaryManager.js +14 -3
  108. package/lib/summaryManager.js.map +1 -1
  109. package/package.json +63 -19
  110. package/src/blobManager.ts +78 -11
  111. package/src/connectionTelemetry.ts +110 -19
  112. package/src/containerRuntime.ts +191 -36
  113. package/src/dataStore.ts +7 -1
  114. package/src/dataStoreContext.ts +22 -7
  115. package/src/dataStores.ts +40 -19
  116. package/src/deltaScheduler.ts +65 -39
  117. package/src/garbageCollection.ts +92 -78
  118. package/src/opTelemetry.ts +71 -0
  119. package/src/orderedClientElection.ts +154 -25
  120. package/src/packageVersion.ts +1 -1
  121. package/src/summarizerClientElection.ts +7 -2
  122. package/src/summarizerTypes.ts +9 -0
  123. package/src/summaryGenerator.ts +9 -1
  124. package/src/summaryManager.ts +15 -4
@@ -6,8 +6,10 @@
6
6
  import { IEvent, IEventProvider, ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
8
8
  import { IDeltaManager } from "@fluidframework/container-definitions";
9
+ import { UsageError } from "@fluidframework/container-utils";
9
10
  import { IClient, IQuorumClients, ISequencedClient } from "@fluidframework/protocol-definitions";
10
11
  import { ChildLogger } from "@fluidframework/telemetry-utils";
12
+ import { summarizerClientType } from "./summarizerClientElection";
11
13
 
12
14
  // helper types for recursive readonly.
13
15
  // eslint-disable-next-line @typescript-eslint/ban-types
@@ -206,16 +208,26 @@ export interface IOrderedClientElectionEvents extends IEvent {
206
208
  export interface ISerializedElection {
207
209
  /** Sequence number at the time of the latest election. */
208
210
  readonly electionSequenceNumber: number;
209
- /** Most recently elected client id. */
211
+ /** Most recently elected client id. This is either:
212
+ * 1. the interactive elected parent client, in which case electedClientId === electedParentId,
213
+ * and the SummaryManager on the elected client will spawn a summarizer client, or
214
+ * 2. the non-interactive summarizer client itself. */
210
215
  readonly electedClientId: string | undefined;
216
+ /** Most recently elected parent client id. This is always an interactive client. */
217
+ readonly electedParentId: string | undefined;
211
218
  }
212
219
 
213
220
  /** Contract for maintaining a deterministic client election based on eligibility. */
214
221
  export interface IOrderedClientElection extends IEventProvider<IOrderedClientElectionEvents> {
215
222
  /** Count of eligible clients in the collection. */
216
223
  readonly eligibleCount: number;
217
- /** Currently elected client. */
224
+ /** Currently elected client. This is either:
225
+ * 1. the interactive elected parent client, in which case electedClientId === electedParentId,
226
+ * and the SummaryManager on the elected client will spawn a summarizer client, or
227
+ * 2. the non-interactive summarizer client itself. */
218
228
  readonly electedClient: ITrackedClient | undefined;
229
+ /** Currently elected parent client. This is always an interactive client. */
230
+ readonly electedParent: ITrackedClient | undefined;
219
231
  /** Sequence number of most recent election. */
220
232
  readonly electionSequenceNumber: number;
221
233
  /** Marks the currently elected client as invalid, and elects the next eligible client. */
@@ -241,16 +253,50 @@ export class OrderedClientElection
241
253
  implements IOrderedClientElection {
242
254
  private _eligibleCount: number = 0;
243
255
  private _electedClient: ILinkedClient | undefined;
256
+ private _electedParent: ILinkedClient | undefined;
244
257
  private _electionSequenceNumber: number;
245
258
 
246
259
  public get eligibleCount() {
247
260
  return this._eligibleCount;
248
261
  }
262
+ public get electionSequenceNumber() {
263
+ return this._electionSequenceNumber;
264
+ }
265
+
266
+ /**
267
+ * OrderedClientCollection tracks electedClient and electedParent separately. This allows us to handle the case
268
+ * where a new interactive parent client has been elected, but the summarizer is still doing work, so
269
+ * a new summarizer should not yet be spawned. In this case, changing electedParent will cause SummaryManager
270
+ * to stop the current summarizer, but a new summarizer will not be spawned until the old summarizer client has
271
+ * left the quorum.
272
+ *
273
+ * Details:
274
+ *
275
+ * electedParent is the interactive client that has been elected to spawn a summarizer. It is typically the oldest
276
+ * eligible interactive client in the quorum. Only the electedParent is permitted to spawn a summarizer.
277
+ * Once elected, this client will remain the electedParent until it leaves the quorum or the summarizer that
278
+ * it spawned stops producing summaries, at which point a new electedParent will be chosen.
279
+ *
280
+ * electedClient is the non-interactive summarizer client if one exists. If not, then electedClient is equal to
281
+ * electedParent. If electedParent === electedClient, this is the signal for electedParent to spawn a new
282
+ * electedClient. Once a summarizer client becomes electedClient, a new summarizer will not be spawned until
283
+ * electedClient leaves the quorum.
284
+ *
285
+ * A typical sequence looks like this:
286
+ * i. Begin by electing A. electedParent === A, electedClient === A.
287
+ * ii. SummaryManager running on A spawns a summarizer client, A'. electedParent === A, electedClient === A'
288
+ * iii. A' stops producing summaries. A new parent client, B, is elected. electedParent === B, electedClient === A'
289
+ * iv. SummaryManager running on A detects the change to electedParent and tells the summarizer to stop, but A'
290
+ * is in mid-summarization. No new summarizer is spawned, as electedParent !== electedClient.
291
+ * v. A' completes its summary, and the summarizer and backing client are torn down.
292
+ * vi. A' leaves the quorum, and B takes its place as electedClient. electedParent === B, electedClient === B
293
+ * vii. SummaryManager running on B spawns a summarizer client, B'. electedParent === B, electedClient === B'
294
+ */
249
295
  public get electedClient() {
250
296
  return this._electedClient;
251
297
  }
252
- public get electionSequenceNumber() {
253
- return this._electionSequenceNumber;
298
+ public get electedParent() {
299
+ return this._electedParent;
254
300
  }
255
301
 
256
302
  constructor(
@@ -262,11 +308,20 @@ export class OrderedClientElection
262
308
  ) {
263
309
  super();
264
310
  let initialClient: ILinkedClient | undefined;
311
+ let initialParent: ILinkedClient | undefined;
265
312
  for (const client of orderedClientCollection.getAllClients()) {
266
313
  this.addClient(client, 0);
267
314
  if (typeof initialState !== "number") {
268
315
  if (client.clientId === initialState.electedClientId) {
269
316
  initialClient = client;
317
+ if (initialState.electedParentId === undefined &&
318
+ client.client.details.type !== summarizerClientType) {
319
+ // If there was no elected parent in the serialized data, use this one.
320
+ initialParent = client;
321
+ }
322
+ }
323
+ if (client.clientId === initialState.electedParentId) {
324
+ initialParent = client;
270
325
  }
271
326
  }
272
327
  }
@@ -288,7 +343,7 @@ export class OrderedClientElection
288
343
  });
289
344
  } else if (initialClient !== undefined && !isEligibleFn(initialClient)) {
290
345
  // Initially elected client is ineligible, so elect next eligible client.
291
- initialClient = this.findFirstEligibleClient(initialClient);
346
+ initialClient = initialParent = this.findFirstEligibleParent(initialParent);
292
347
  logger.sendErrorEvent({
293
348
  eventName: "InitialElectedClientIneligible",
294
349
  electionSequenceNumber: initialState.electionSequenceNumber,
@@ -296,31 +351,53 @@ export class OrderedClientElection
296
351
  electedClientId: initialClient?.clientId,
297
352
  });
298
353
  }
354
+ this._electedParent = initialParent;
299
355
  this._electedClient = initialClient;
300
356
  this._electionSequenceNumber = initialState.electionSequenceNumber;
301
357
  }
302
358
  }
303
359
 
304
- /** Tries changing the elected client, raising an event if it is different. */
360
+ /** Tries changing the elected client, raising an event if it is different.
361
+ * Note that this function does no eligibility or suitability checks. If we get here, then
362
+ * we will set _electedClient, and we will set _electedParent if this is an interactive client.
363
+ */
305
364
  private tryElectingClient(client: ILinkedClient | undefined, sequenceNumber: number): void {
306
- this._electionSequenceNumber = sequenceNumber;
307
- if (this._electedClient === client) {
308
- return;
309
- }
365
+ let change = false;
366
+ const isSummarizerClient = client?.client.details.type === summarizerClientType;
310
367
  const prevClient = this._electedClient;
311
- this._electedClient = client;
312
- this.emit("election", client, sequenceNumber, prevClient);
368
+ if (this._electedClient !== client) {
369
+ // Changing the elected client. Record the sequence number and note that we have to fire an event.
370
+ this._electionSequenceNumber = sequenceNumber;
371
+ this._electedClient = client;
372
+ change = true;
373
+ }
374
+ if (this._electedParent !== client && !isSummarizerClient) {
375
+ // Changing the elected parent as well.
376
+ this._electedParent = client;
377
+ change = true;
378
+ }
379
+ if (change) {
380
+ this.emit("election", client, sequenceNumber, prevClient);
381
+ }
382
+ }
383
+
384
+ private tryElectingParent(client: ILinkedClient | undefined, sequenceNumber: number): void {
385
+ if (this._electedParent !== client) {
386
+ this._electedParent = client;
387
+ this.emit("election", this._electedClient, sequenceNumber, this._electedClient);
388
+ }
313
389
  }
314
390
 
315
391
  /**
316
- * Helper function to find the first eligible client starting with the passed in client,
392
+ * Helper function to find the first eligible parent client starting with the passed in client,
317
393
  * or undefined if none are eligible.
318
394
  * @param client - client to start checking
319
395
  * @returns oldest eligible client starting with passed in client or undefined if none.
320
396
  */
321
- private findFirstEligibleClient(client: ILinkedClient | undefined): ILinkedClient | undefined {
397
+ private findFirstEligibleParent(client: ILinkedClient | undefined): ILinkedClient | undefined {
322
398
  let candidateClient = client;
323
- while (candidateClient !== undefined && !this.isEligibleFn(candidateClient)) {
399
+ while (candidateClient !== undefined &&
400
+ (!this.isEligibleFn(candidateClient) || candidateClient.client.details.type === summarizerClientType)) {
324
401
  candidateClient = candidateClient.youngerClient;
325
402
  }
326
403
  return candidateClient;
@@ -335,10 +412,16 @@ export class OrderedClientElection
335
412
  private addClient(client: ILinkedClient, sequenceNumber: number): void {
336
413
  if (this.isEligibleFn(client)) {
337
414
  this._eligibleCount++;
338
- if (this._electedClient === undefined) {
339
- // Automatically elect latest client
415
+ const newClientIsSummarizer = client.client.details.type === summarizerClientType;
416
+ const electedClientIsSummarizer = this._electedClient?.client.details.type === summarizerClientType;
417
+ // Note that we allow a summarizer client to supercede an interactive client as elected client.
418
+ if (this._electedClient === undefined || (!electedClientIsSummarizer && newClientIsSummarizer)) {
340
419
  this.tryElectingClient(client, sequenceNumber);
341
420
  }
421
+ else if (this._electedParent === undefined && !newClientIsSummarizer) {
422
+ // This is an odd case. If the _electedClient is set, the _electedParent should be as well.
423
+ this.tryElectingParent(client, sequenceNumber);
424
+ }
342
425
  }
343
426
  }
344
427
 
@@ -352,9 +435,33 @@ export class OrderedClientElection
352
435
  if (this.isEligibleFn(client)) {
353
436
  this._eligibleCount--;
354
437
  if (this._electedClient === client) {
355
- // Automatically shift to next oldest client
356
- const nextClient = this.findFirstEligibleClient(this._electedClient.youngerClient);
357
- this.tryElectingClient(nextClient, sequenceNumber);
438
+ // Removing the _electedClient. There are 2 possible cases:
439
+ if (this._electedParent !== client) {
440
+ // 1. The _electedClient is a summarizer that we've been allowing to finish its work.
441
+ // Let the _electedParent become the _electedClient so that it can start its own summarizer.
442
+ if (this._electedClient.client.details.type !== summarizerClientType) {
443
+ throw new UsageError("Elected client should be a summarizer client 1");
444
+ }
445
+ this.tryElectingClient(this._electedParent, sequenceNumber);
446
+ }
447
+ else {
448
+ // 2. The _electedClient is an interactive client that has left the quorum.
449
+ // Automatically shift to next oldest client.
450
+ const nextClient = this.findFirstEligibleParent(this._electedParent?.youngerClient) ??
451
+ this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
452
+ this.tryElectingClient(nextClient, sequenceNumber);
453
+ }
454
+ }
455
+ else if (this._electedParent === client) {
456
+ // Removing the _electedParent (but not _electedClient).
457
+ // Shift to the next oldest parent, but do not replace the _electedClient,
458
+ // which is a summarizer that is still doing work.
459
+ if (this._electedClient?.client.details.type !== summarizerClientType) {
460
+ throw new UsageError("Elected client should be a summarizer client 2");
461
+ }
462
+ const nextParent = this.findFirstEligibleParent(this._electedParent?.youngerClient) ??
463
+ this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
464
+ this.tryElectingParent(nextParent, sequenceNumber);
358
465
  }
359
466
  }
360
467
  }
@@ -363,24 +470,46 @@ export class OrderedClientElection
363
470
  return this.orderedClientCollection.getAllClients().filter(this.isEligibleFn);
364
471
  }
365
472
 
473
+ /** Advance election to the next-oldest client. This is called if the current parent is leaving the quorum,
474
+ * or if the current summarizer is not responsive and we want to stop it and spawn a new one.
475
+ */
366
476
  public incrementElectedClient(sequenceNumber: number): void {
367
- const nextClient = this.findFirstEligibleClient(this._electedClient?.youngerClient);
368
- this.tryElectingClient(nextClient, sequenceNumber);
477
+ const nextClient = this.findFirstEligibleParent(this._electedParent?.youngerClient) ??
478
+ this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
479
+ if (this._electedClient === undefined || this._electedClient === this._electedParent) {
480
+ this.tryElectingClient(nextClient, sequenceNumber);
481
+ }
482
+ else {
483
+ // The _electedClient is a summarizer and should not be replaced until it leaves the quorum.
484
+ // Changing the _electedParent will stop the summarizer.
485
+ this.tryElectingParent(nextClient, sequenceNumber);
486
+ }
369
487
  }
370
488
 
489
+ /** (Re-)start election with the oldest client in the quorum. This is called if we need to summarize
490
+ * and no client has been elected.
491
+ */
371
492
  public resetElectedClient(sequenceNumber: number): void {
372
- const firstClient = this.findFirstEligibleClient(this.orderedClientCollection.oldestClient);
373
- this.tryElectingClient(firstClient, sequenceNumber);
493
+ const firstClient = this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
494
+ if (this._electedClient === undefined || this._electedClient === this._electedParent) {
495
+ this.tryElectingClient(firstClient, sequenceNumber);
496
+ }
497
+ else {
498
+ // The _electedClient is a summarizer and should not be replaced until it leaves the quorum.
499
+ // Changing the _electedParent will stop the summarizer.
500
+ this.tryElectingParent(firstClient, sequenceNumber);
501
+ }
374
502
  }
375
503
 
376
504
  public peekNextElectedClient(): ITrackedClient | undefined {
377
- return this.findFirstEligibleClient(this._electedClient?.youngerClient);
505
+ return this.findFirstEligibleParent(this._electedParent?.youngerClient);
378
506
  }
379
507
 
380
508
  public serialize(): ISerializedElection {
381
509
  return {
382
510
  electionSequenceNumber: this.electionSequenceNumber,
383
511
  electedClientId: this.electedClient?.clientId,
512
+ electedParentId: this.electedParent?.clientId,
384
513
  };
385
514
  }
386
515
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "0.58.2001";
9
+ export const pkgVersion = "0.59.1000-61898";
@@ -17,6 +17,7 @@ export interface ISummarizerClientElectionEvents extends IEvent {
17
17
 
18
18
  export interface ISummarizerClientElection extends IEventProvider<ISummarizerClientElectionEvents> {
19
19
  readonly electedClientId: string | undefined;
20
+ readonly electedParentId: string | undefined;
20
21
  }
21
22
 
22
23
  /**
@@ -44,6 +45,9 @@ export class SummarizerClientElection
44
45
  public get electedClientId() {
45
46
  return this.clientElection.electedClient?.clientId;
46
47
  }
48
+ public get electedParentId() {
49
+ return this.clientElection.electedParent?.clientId;
50
+ }
47
51
 
48
52
  constructor(
49
53
  private readonly logger: ITelemetryLogger,
@@ -127,9 +131,10 @@ export class SummarizerClientElection
127
131
  }
128
132
 
129
133
  public serialize(): ISerializedElection {
130
- const { electedClientId, electionSequenceNumber } = this.clientElection.serialize();
134
+ const { electedClientId, electedParentId, electionSequenceNumber } = this.clientElection.serialize();
131
135
  return {
132
136
  electedClientId,
137
+ electedParentId,
133
138
  electionSequenceNumber: this.lastSummaryAckSeqForClient ?? electionSequenceNumber,
134
139
  };
135
140
  }
@@ -144,5 +149,5 @@ export class SummarizerClientElection
144
149
  }
145
150
 
146
151
  public static readonly clientDetailsPermitElection = (details: IClientDetails): boolean =>
147
- details.capabilities.interactive && details.type !== summarizerClientType;
152
+ details.capabilities.interactive || details.type === summarizerClientType;
148
153
  }
@@ -143,6 +143,14 @@ export interface IGeneratedSummaryStats extends ISummaryStats {
143
143
  readonly summarizedDataStoreCount: number;
144
144
  /** The number of data stores whose GC reference state was updated in this summary. */
145
145
  readonly gcStateUpdatedDataStoreCount?: number;
146
+ /** The size of the gc blobs in this summary. */
147
+ readonly gcTotalBlobsSize?: number;
148
+ /** The number of gc blobs in this summary. */
149
+ readonly gcBlobNodeCount?: number;
150
+ /** Sum of the sizes of all op contents since the last summary */
151
+ readonly opsSizesSinceLastSummary: number;
152
+ /** Number of non-system ops since the last summary @see isSystemMessage */
153
+ readonly nonSystemOpsSinceLastSummary: number;
146
154
  }
147
155
 
148
156
  /** Base results for all submitSummary attempts. */
@@ -152,6 +160,7 @@ export interface IBaseSummarizeResult {
152
160
  readonly error: any;
153
161
  /** Reference sequence number as of the generate summary attempt. */
154
162
  readonly referenceSequenceNumber: number;
163
+ readonly minimumSequenceNumber: number;
155
164
  }
156
165
 
157
166
  /** Results of submitSummary after generating the summary tree. */
@@ -65,10 +65,17 @@ type SummaryGeneratorRequiredTelemetryProperties =
65
65
  type SummaryGeneratorOptionalTelemetryProperties =
66
66
  /** Reference sequence number as of the generate summary attempt. */
67
67
  "referenceSequenceNumber" |
68
+ /** minimum sequence number (at the reference sequence number) */
69
+ "minimumSequenceNumber" |
68
70
  /** Delta between the current reference sequence number and the reference sequence number of the last attempt */
69
71
  "opsSinceLastAttempt" |
70
72
  /** Delta between the current reference sequence number and the reference sequence number of the last summary */
71
73
  "opsSinceLastSummary" |
74
+ /** Delta in sum of op sizes between the current reference sequence number and the reference
75
+ * sequence number of the last summary */
76
+ "opsSizesSinceLastSummary" |
77
+ /** Delta between the number of non-system ops since the last summary @see isSystemMessage */
78
+ "nonSystemOpsSinceLastSummary" |
72
79
  /** Time it took to generate the summary tree and stats. */
73
80
  "generateDuration" |
74
81
  /** The handle returned by storage pointing to the uploaded summary tree. */
@@ -84,7 +91,7 @@ type SummaryGeneratorOptionalTelemetryProperties =
84
91
  /** Actual sequence number of the summary op proposal. */
85
92
  "summarySequenceNumber" |
86
93
  /** Optional Retry-After time in seconds. If specified, the client should wait this many seconds before retrying. */
87
- "nackRetryAfter";
94
+ "nackRetryAfter";
88
95
  type SummaryGeneratorTelemetry =
89
96
  Pick<ITelemetryProperties, SummaryGeneratorRequiredTelemetryProperties> &
90
97
  Partial<Pick<ITelemetryProperties, SummaryGeneratorOptionalTelemetryProperties>>;
@@ -298,6 +305,7 @@ export class SummaryGenerator {
298
305
  summarizeTelemetryProps = {
299
306
  ...summarizeTelemetryProps,
300
307
  referenceSequenceNumber,
308
+ minimumSequenceNumber: summaryData.minimumSequenceNumber,
301
309
  opsSinceLastAttempt: referenceSequenceNumber - this.heuristicData.lastAttempt.refSequenceNumber,
302
310
  opsSinceLastSummary,
303
311
  };
@@ -137,9 +137,15 @@ export class SummaryManager implements IDisposable {
137
137
  state === SummaryManagerState.Starting || state === SummaryManagerState.Running;
138
138
 
139
139
  private getShouldSummarizeState(): ShouldSummarizeState {
140
+ // Note that if we're in the Running state, the electedClient may be a summarizer client, so we can't
141
+ // enforce connectedState.clientId === clientElection.electedClientId. But once we're Running, we should
142
+ // only transition to Stopping when the electedParentId changes. Stopping the summarizer without
143
+ // changing the electedParent will just cause us to transition to Starting again.
140
144
  if (!this.connectedState.connected) {
141
145
  return { shouldSummarize: false, stopReason: "parentNotConnected" };
142
- } else if (this.connectedState.clientId !== this.clientElection.electedClientId) {
146
+ } else if (this.connectedState.clientId !== this.clientElection.electedParentId ||
147
+ (this.state !== SummaryManagerState.Running &&
148
+ this.connectedState.clientId !== this.clientElection.electedClientId)) {
143
149
  return { shouldSummarize: false, stopReason: "parentShouldNotSummarize" };
144
150
  } else if (this.disposed) {
145
151
  assert(false, 0x260 /* "Disposed should mean disconnected!" */);
@@ -199,18 +205,23 @@ export class SummaryManager implements IDisposable {
199
205
  return;
200
206
  }
201
207
 
208
+ // We transition to Running before requesting the summarizer, because after requesting we can't predict
209
+ // when the electedClient will be replaced with the new summarizer client.
210
+ // The alternative would be to let connectedState.clientId !== clientElection.electedClientId when
211
+ // state === Starting || state === Running.
212
+ assert(this.state === SummaryManagerState.Starting, 0x263 /* "Expected: starting" */);
213
+ this.state = SummaryManagerState.Running;
214
+
202
215
  const summarizer = await this.requestSummarizerFn();
203
216
 
204
217
  // Re-validate that it need to be running. Due to asynchrony, it may be not the case anymore
205
218
  const shouldSummarizeState = this.getShouldSummarizeState();
206
219
  if (shouldSummarizeState.shouldSummarize === false) {
220
+ this.state = SummaryManagerState.Starting;
207
221
  summarizer.stop(shouldSummarizeState.stopReason);
208
222
  return;
209
223
  }
210
224
 
211
- assert(this.state === SummaryManagerState.Starting, 0x263 /* "Expected: starting" */);
212
- this.state = SummaryManagerState.Running;
213
-
214
225
  this.summarizer = summarizer;
215
226
 
216
227
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion