@fluidframework/container-runtime 0.59.1000 → 0.59.2000-63294

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 (88) hide show
  1. package/dist/connectionTelemetry.js +1 -1
  2. package/dist/connectionTelemetry.js.map +1 -1
  3. package/dist/containerRuntime.d.ts.map +1 -1
  4. package/dist/containerRuntime.js +6 -6
  5. package/dist/containerRuntime.js.map +1 -1
  6. package/dist/garbageCollection.d.ts +26 -8
  7. package/dist/garbageCollection.d.ts.map +1 -1
  8. package/dist/garbageCollection.js +81 -57
  9. package/dist/garbageCollection.js.map +1 -1
  10. package/dist/orderedClientElection.d.ts +57 -6
  11. package/dist/orderedClientElection.d.ts.map +1 -1
  12. package/dist/orderedClientElection.js +141 -26
  13. package/dist/orderedClientElection.js.map +1 -1
  14. package/dist/packageVersion.d.ts +1 -1
  15. package/dist/packageVersion.d.ts.map +1 -1
  16. package/dist/packageVersion.js +1 -1
  17. package/dist/packageVersion.js.map +1 -1
  18. package/dist/runningSummarizer.d.ts.map +1 -1
  19. package/dist/runningSummarizer.js +11 -10
  20. package/dist/runningSummarizer.js.map +1 -1
  21. package/dist/summarizer.d.ts +1 -0
  22. package/dist/summarizer.d.ts.map +1 -1
  23. package/dist/summarizer.js +8 -4
  24. package/dist/summarizer.js.map +1 -1
  25. package/dist/summarizerClientElection.d.ts +2 -0
  26. package/dist/summarizerClientElection.d.ts.map +1 -1
  27. package/dist/summarizerClientElection.js +15 -2
  28. package/dist/summarizerClientElection.js.map +1 -1
  29. package/dist/summarizerTypes.d.ts +47 -1
  30. package/dist/summarizerTypes.d.ts.map +1 -1
  31. package/dist/summarizerTypes.js.map +1 -1
  32. package/dist/summaryGenerator.d.ts +0 -2
  33. package/dist/summaryGenerator.d.ts.map +1 -1
  34. package/dist/summaryGenerator.js +2 -3
  35. package/dist/summaryGenerator.js.map +1 -1
  36. package/dist/summaryManager.d.ts.map +1 -1
  37. package/dist/summaryManager.js +29 -18
  38. package/dist/summaryManager.js.map +1 -1
  39. package/lib/connectionTelemetry.js +1 -1
  40. package/lib/connectionTelemetry.js.map +1 -1
  41. package/lib/containerRuntime.d.ts.map +1 -1
  42. package/lib/containerRuntime.js +6 -6
  43. package/lib/containerRuntime.js.map +1 -1
  44. package/lib/garbageCollection.d.ts +26 -8
  45. package/lib/garbageCollection.d.ts.map +1 -1
  46. package/lib/garbageCollection.js +81 -57
  47. package/lib/garbageCollection.js.map +1 -1
  48. package/lib/orderedClientElection.d.ts +57 -6
  49. package/lib/orderedClientElection.d.ts.map +1 -1
  50. package/lib/orderedClientElection.js +141 -26
  51. package/lib/orderedClientElection.js.map +1 -1
  52. package/lib/packageVersion.d.ts +1 -1
  53. package/lib/packageVersion.d.ts.map +1 -1
  54. package/lib/packageVersion.js +1 -1
  55. package/lib/packageVersion.js.map +1 -1
  56. package/lib/runningSummarizer.d.ts.map +1 -1
  57. package/lib/runningSummarizer.js +11 -10
  58. package/lib/runningSummarizer.js.map +1 -1
  59. package/lib/summarizer.d.ts +1 -0
  60. package/lib/summarizer.d.ts.map +1 -1
  61. package/lib/summarizer.js +8 -4
  62. package/lib/summarizer.js.map +1 -1
  63. package/lib/summarizerClientElection.d.ts +2 -0
  64. package/lib/summarizerClientElection.d.ts.map +1 -1
  65. package/lib/summarizerClientElection.js +15 -2
  66. package/lib/summarizerClientElection.js.map +1 -1
  67. package/lib/summarizerTypes.d.ts +47 -1
  68. package/lib/summarizerTypes.d.ts.map +1 -1
  69. package/lib/summarizerTypes.js.map +1 -1
  70. package/lib/summaryGenerator.d.ts +0 -2
  71. package/lib/summaryGenerator.d.ts.map +1 -1
  72. package/lib/summaryGenerator.js +2 -3
  73. package/lib/summaryGenerator.js.map +1 -1
  74. package/lib/summaryManager.d.ts.map +1 -1
  75. package/lib/summaryManager.js +29 -18
  76. package/lib/summaryManager.js.map +1 -1
  77. package/package.json +29 -53
  78. package/src/connectionTelemetry.ts +2 -2
  79. package/src/containerRuntime.ts +4 -6
  80. package/src/garbageCollection.ts +96 -61
  81. package/src/orderedClientElection.ts +155 -25
  82. package/src/packageVersion.ts +1 -1
  83. package/src/runningSummarizer.ts +13 -10
  84. package/src/summarizer.ts +9 -4
  85. package/src/summarizerClientElection.ts +15 -2
  86. package/src/summarizerTypes.ts +60 -1
  87. package/src/summaryGenerator.ts +3 -51
  88. package/src/summaryManager.ts +32 -23
@@ -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,47 @@ 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) ??
506
+ this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
378
507
  }
379
508
 
380
509
  public serialize(): ISerializedElection {
381
510
  return {
382
511
  electionSequenceNumber: this.electionSequenceNumber,
383
512
  electedClientId: this.electedClient?.clientId,
513
+ electedParentId: this.electedParent?.clientId,
384
514
  };
385
515
  }
386
516
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "0.59.1000";
9
+ export const pkgVersion = "0.59.2000-63294";
@@ -27,6 +27,7 @@ import {
27
27
  ISummaryCancellationToken,
28
28
  ISummarizeResults,
29
29
  ISummarizeTelemetryProperties,
30
+ ISummarizeRunnerTelemetry,
30
31
  } from "./summarizerTypes";
31
32
  import { IClientSummaryWatcher, SummaryCollection } from "./summaryCollection";
32
33
  import {
@@ -108,13 +109,15 @@ export class RunningSummarizer implements IDisposable {
108
109
  private readonly stopSummarizerCallback: (reason: SummarizerStopReason) => void,
109
110
  { disableHeuristics = false }: Readonly<Partial<ISummarizerOptions>> = {},
110
111
  ) {
112
+ const telemetryProps: ISummarizeRunnerTelemetry = {
113
+ summarizeCount: () => this.summarizeCount,
114
+ summarizerSuccessfulAttempts: () => this.totalSuccessfulAttempts,
115
+ };
116
+
111
117
  this.logger = ChildLogger.create(
112
118
  baseLogger, "Running",
113
119
  {
114
- all: {
115
- summarizeCount: () => this.summarizeCount,
116
- summarizerSuccessfulAttempts: () => this.totalSuccessfulAttempts,
117
- },
120
+ all: telemetryProps,
118
121
  },
119
122
  );
120
123
 
@@ -231,7 +234,7 @@ export class RunningSummarizer implements IDisposable {
231
234
  if (this.summarizingLock === undefined) {
232
235
  this.trySummarizeOnce(
233
236
  // summarizeProps
234
- { summarizeReason: "lastSummary" },
237
+ { reason: "lastSummary" },
235
238
  // ISummarizeOptions, using defaults: { refreshLatestAck: false, fullTree: false }
236
239
  {});
237
240
  }
@@ -330,7 +333,7 @@ export class RunningSummarizer implements IDisposable {
330
333
 
331
334
  /** Heuristics summarize attempt. */
332
335
  private trySummarize(
333
- summarizeReason: SummarizeReason,
336
+ reason: SummarizeReason,
334
337
  cancellationToken = this.cancellationToken): void
335
338
  {
336
339
  if (this.summarizingLock !== undefined) {
@@ -365,7 +368,7 @@ export class RunningSummarizer implements IDisposable {
365
368
  const delaySeconds = overrideDelaySeconds ?? regularDelaySeconds;
366
369
 
367
370
  const summarizeProps: ISummarizeTelemetryProperties = {
368
- summarizeReason,
371
+ reason,
369
372
  summaryAttempts,
370
373
  summaryAttemptsPerPhase,
371
374
  summaryAttemptPhase: summaryAttemptPhase + 1, // make everything 1-based
@@ -402,7 +405,7 @@ export class RunningSummarizer implements IDisposable {
402
405
  // If all attempts failed, log error (with last attempt info) and close the summarizer container
403
406
  this.logger.sendErrorEvent({
404
407
  eventName: "FailToSummarize",
405
- summarizeReason,
408
+ reason,
406
409
  message: lastResult?.message,
407
410
  }, lastResult?.error);
408
411
 
@@ -430,7 +433,7 @@ export class RunningSummarizer implements IDisposable {
430
433
  throw new UsageError("Attempted to run an already-running summarizer on demand");
431
434
  }
432
435
  const result = this.trySummarizeOnce(
433
- { summarizeReason: `onDemand/${reason}` },
436
+ { reason: `onDemand/${reason}` },
434
437
  options,
435
438
  this.cancellationToken,
436
439
  resultsBuilder);
@@ -490,7 +493,7 @@ export class RunningSummarizer implements IDisposable {
490
493
  // Set to undefined first, so that subsequent enqueue attempt while summarize will occur later.
491
494
  this.enqueuedSummary = undefined;
492
495
  this.trySummarizeOnce(
493
- { summarizeReason: `enqueuedSummary/${reason}` },
496
+ { reason: `enqueuedSummary/${reason}` },
494
497
  options,
495
498
  this.cancellationToken,
496
499
  resultsBuilder);
package/src/summarizer.ts CHANGED
@@ -145,8 +145,7 @@ export class Summarizer extends EventEmitter implements ISummarizer {
145
145
  this.stop("summarizerException");
146
146
  throw SummarizingWarning.wrap(error, false /* logged */, this.logger);
147
147
  } finally {
148
- this.dispose();
149
- this.runtime.closeFn();
148
+ this.close();
150
149
  }
151
150
  }
152
151
 
@@ -159,6 +158,13 @@ export class Summarizer extends EventEmitter implements ISummarizer {
159
158
  this.stopDeferred.resolve(reason);
160
159
  }
161
160
 
161
+ public close() {
162
+ // This will result in "summarizerClientDisconnected" stop reason recorded in telemetry,
163
+ // unless stop() was called earlier
164
+ this.dispose();
165
+ this.runtime.closeFn();
166
+ }
167
+
162
168
  private async runCore(
163
169
  onBehalfOf: string,
164
170
  options?: Readonly<Partial<ISummarizerOptions>>): Promise<SummarizerStopReason> {
@@ -344,8 +350,7 @@ export class Summarizer extends EventEmitter implements ISummarizer {
344
350
  const stopReason = await Promise.race([this.stopDeferred.promise, runCoordinator.waitCancelled]);
345
351
  await runningSummarizer.waitStop(false);
346
352
  runCoordinator.stop(stopReason);
347
- this.dispose();
348
- this.runtime.closeFn();
353
+ this.close();
349
354
  }).catch((reason) => {
350
355
  builder.fail("Failed to start summarizer", reason);
351
356
  });
@@ -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,
@@ -85,6 +89,7 @@ export class SummarizerClientElection
85
89
  }
86
90
 
87
91
  if (this.electionEnabled) {
92
+ const previousParentId = this.electedParentId;
88
93
  this.clientElection.incrementElectedClient(sequenceNumber);
89
94
 
90
95
  // Verify that state incremented as expected. This should be reliable,
@@ -98,6 +103,13 @@ export class SummarizerClientElection
98
103
  lastSummaryAckSeqForClient: this.lastSummaryAckSeqForClient,
99
104
  // Expected to be same as op sequenceNumber
100
105
  electionSequenceNumber,
106
+ sequenceNumber,
107
+ previousClientId: electedClientId,
108
+ previousParentId,
109
+ electedParentId: this.electedParentId,
110
+ electedClientId: this.electedClientId,
111
+ opsSinceLastReport,
112
+ maxOpsSinceLastSummary,
101
113
  });
102
114
  }
103
115
  }
@@ -127,9 +139,10 @@ export class SummarizerClientElection
127
139
  }
128
140
 
129
141
  public serialize(): ISerializedElection {
130
- const { electedClientId, electionSequenceNumber } = this.clientElection.serialize();
142
+ const { electedClientId, electedParentId, electionSequenceNumber } = this.clientElection.serialize();
131
143
  return {
132
144
  electedClientId,
145
+ electedParentId,
133
146
  electionSequenceNumber: this.lastSummaryAckSeqForClient ?? electionSequenceNumber,
134
147
  };
135
148
  }
@@ -144,5 +157,5 @@ export class SummarizerClientElection
144
157
  }
145
158
 
146
159
  public static readonly clientDetailsPermitElection = (details: IClientDetails): boolean =>
147
- details.capabilities.interactive && details.type !== summarizerClientType;
160
+ details.capabilities.interactive || details.type === summarizerClientType;
148
161
  }
@@ -9,6 +9,7 @@ import {
9
9
  ITelemetryLogger,
10
10
  ITelemetryProperties,
11
11
  } from "@fluidframework/common-definitions";
12
+ import { ITelemetryLoggerPropertyBag } from "@fluidframework/telemetry-utils";
12
13
  import {
13
14
  IFluidLoadable,
14
15
  } from "@fluidframework/core-interfaces";
@@ -296,8 +297,16 @@ export interface ISummarizerEvents extends IEvent {
296
297
 
297
298
  export interface ISummarizer extends
298
299
  IEventProvider<ISummarizerEvents>, IFluidLoadable, Partial<IProvideSummarizer>{
300
+ /*
301
+ * Asks summarizer to move to exit.
302
+ * Summarizer will finish current processes, which may take a while.
303
+ * For example, summarizer may complete last summary before exiting.
304
+ */
299
305
  stop(reason: SummarizerStopReason): void;
300
306
 
307
+ /* Closes summarizer. Any pending processes (summary in flight) are abandoned. */
308
+ close(): void;
309
+
301
310
  run(onBehalfOf: string, options?: Readonly<Partial<ISummarizerOptions>>): Promise<SummarizerStopReason>;
302
311
 
303
312
  /**
@@ -380,7 +389,7 @@ export interface ISummarizeHeuristicRunner {
380
389
 
381
390
  type ISummarizeTelemetryRequiredProperties =
382
391
  /** Reason code for attempting to summarize */
383
- "summarizeReason";
392
+ "reason";
384
393
 
385
394
  type ISummarizeTelemetryOptionalProperties =
386
395
  /** Number of attempts within the last time window, used for calculating the throttle delay. */
@@ -394,3 +403,53 @@ type ISummarizeTelemetryOptionalProperties =
394
403
  export type ISummarizeTelemetryProperties =
395
404
  Pick<ITelemetryProperties, ISummarizeTelemetryRequiredProperties> &
396
405
  Partial<Pick<ITelemetryProperties, ISummarizeTelemetryOptionalProperties>>;
406
+
407
+ type SummaryGeneratorRequiredTelemetryProperties =
408
+ /** True to generate the full tree with no handle reuse optimizations */
409
+ "fullTree" |
410
+ /** Time since we last attempted to generate a summary */
411
+ "timeSinceLastAttempt" |
412
+ /** Time since we last successfully generated a summary */
413
+ "timeSinceLastSummary";
414
+
415
+ type SummaryGeneratorOptionalTelemetryProperties =
416
+ /** Reference sequence number as of the generate summary attempt. */
417
+ "referenceSequenceNumber" |
418
+ /** minimum sequence number (at the reference sequence number) */
419
+ "minimumSequenceNumber" |
420
+ /** Delta between the current reference sequence number and the reference sequence number of the last attempt */
421
+ "opsSinceLastAttempt" |
422
+ /** Delta between the current reference sequence number and the reference sequence number of the last summary */
423
+ "opsSinceLastSummary" |
424
+ /** Delta in sum of op sizes between the current reference sequence number and the reference
425
+ * sequence number of the last summary */
426
+ "opsSizesSinceLastSummary" |
427
+ /** Delta between the number of non-system ops since the last summary @see isSystemMessage */
428
+ "nonSystemOpsSinceLastSummary" |
429
+ /** Time it took to generate the summary tree and stats. */
430
+ "generateDuration" |
431
+ /** The handle returned by storage pointing to the uploaded summary tree. */
432
+ "handle" |
433
+ /** Time it took to upload the summary tree to storage. */
434
+ "uploadDuration" |
435
+ /** The client sequence number of the summarize op submitted for the summary. */
436
+ "clientSequenceNumber" |
437
+ /** Time it took for this summary to be acked after it was generated */
438
+ "ackWaitDuration" |
439
+ /** Reference sequence number of the ack/nack message */
440
+ "ackNackSequenceNumber" |
441
+ /** Actual sequence number of the summary op proposal. */
442
+ "summarySequenceNumber" |
443
+ /** Optional Retry-After time in seconds. If specified, the client should wait this many seconds before retrying. */
444
+ "nackRetryAfter";
445
+
446
+ export type SummaryGeneratorTelemetry =
447
+ Pick<ITelemetryProperties, SummaryGeneratorRequiredTelemetryProperties> &
448
+ Partial<Pick<ITelemetryProperties, SummaryGeneratorOptionalTelemetryProperties>>;
449
+
450
+ export interface ISummarizeRunnerTelemetry extends ITelemetryLoggerPropertyBag {
451
+ /** Number of times the summarizer run. */
452
+ summarizeCount: () => number;
453
+ /** Number of successful attempts to summarize. */
454
+ summarizerSuccessfulAttempts: () => number;
455
+ }
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
6
+ import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import {
8
8
  assert,
9
9
  Deferred,
@@ -27,6 +27,7 @@ import {
27
27
  SummarizeResultPart,
28
28
  ISummaryCancellationToken,
29
29
  ISummarizeTelemetryProperties,
30
+ SummaryGeneratorTelemetry,
30
31
  } from "./summarizerTypes";
31
32
  import { IClientSummaryWatcher } from "./summaryCollection";
32
33
 
@@ -55,47 +56,6 @@ export async function raceTimer<T>(
55
56
  const maxSummarizeTimeoutTime = 20000; // 20 sec
56
57
  const maxSummarizeTimeoutCount = 5; // Double and resend 5 times
57
58
 
58
- type SummaryGeneratorRequiredTelemetryProperties =
59
- /** True to generate the full tree with no handle reuse optimizations */
60
- "fullTree" |
61
- /** Time since we last attempted to generate a summary */
62
- "timeSinceLastAttempt" |
63
- /** Time since we last successfully generated a summary */
64
- "timeSinceLastSummary";
65
- type SummaryGeneratorOptionalTelemetryProperties =
66
- /** Reference sequence number as of the generate summary attempt. */
67
- "referenceSequenceNumber" |
68
- /** minimum sequence number (at the reference sequence number) */
69
- "minimumSequenceNumber" |
70
- /** Delta between the current reference sequence number and the reference sequence number of the last attempt */
71
- "opsSinceLastAttempt" |
72
- /** Delta between the current reference sequence number and the reference sequence number of the last summary */
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" |
79
- /** Time it took to generate the summary tree and stats. */
80
- "generateDuration" |
81
- /** The handle returned by storage pointing to the uploaded summary tree. */
82
- "handle" |
83
- /** Time it took to upload the summary tree to storage. */
84
- "uploadDuration" |
85
- /** The client sequence number of the summarize op submitted for the summary. */
86
- "clientSequenceNumber" |
87
- /** Time it took for this summary to be acked after it was generated */
88
- "ackWaitDuration" |
89
- /** Reference sequence number of the ack/nack message */
90
- "ackNackSequenceNumber" |
91
- /** Actual sequence number of the summary op proposal. */
92
- "summarySequenceNumber" |
93
- /** Optional Retry-After time in seconds. If specified, the client should wait this many seconds before retrying. */
94
- "nackRetryAfter";
95
- type SummaryGeneratorTelemetry =
96
- Pick<ITelemetryProperties, SummaryGeneratorRequiredTelemetryProperties> &
97
- Partial<Pick<ITelemetryProperties, SummaryGeneratorOptionalTelemetryProperties>>;
98
-
99
59
  export type SummarizeReason =
100
60
  /**
101
61
  * Attempt to summarize after idle timeout has elapsed.
@@ -126,8 +86,6 @@ export type SummarizeReason =
126
86
  * stay connected long enough for summarizer client to catch up.
127
87
  */
128
88
  | "lastSummary"
129
- /** Previous summary attempt failed, and we are retrying. */
130
- | `retry${number}`
131
89
  /** On-demand summary requested with specified reason. */
132
90
  | `onDemand;${string}`
133
91
  /** Enqueue summarize attempt with specified reason. */
@@ -286,11 +244,6 @@ export class SummaryGenerator {
286
244
  // Use record type to prevent unexpected value types
287
245
  let summaryData: SubmitSummaryResult | undefined;
288
246
  try {
289
- const generateSummaryEvent = PerformanceEvent.start(logger, {
290
- eventName: "Summarize",
291
- ...summarizeTelemetryProps,
292
- });
293
-
294
247
  summaryData = await this.submitSummaryCallback({
295
248
  fullTree,
296
249
  refreshLatestAck,
@@ -359,7 +312,7 @@ export class SummaryGenerator {
359
312
  }
360
313
 
361
314
  // Log event here on summary success only, as Summarize_cancel duplicates failure logging.
362
- generateSummaryEvent.reportEvent("generate", {...summarizeTelemetryProps});
315
+ summarizeEvent.reportEvent("generate", {...summarizeTelemetryProps});
363
316
  resultsBuilder.summarySubmitted.resolve({ success: true, data: summaryData });
364
317
  } catch (error) {
365
318
  return fail("submitSummaryFailure", error);
@@ -424,7 +377,6 @@ export class SummaryGenerator {
424
377
  summarizeEvent.end({
425
378
  ...summarizeTelemetryProps,
426
379
  handle: ackNackOp.contents.handle,
427
- message: "summaryAck",
428
380
  });
429
381
  resultsBuilder.receivedSummaryAckOrNack.resolve({ success: true, data: {
430
382
  summaryAckOp: ackNackOp,