@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.
- package/dist/connectionTelemetry.js +1 -1
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +6 -6
- package/dist/containerRuntime.js.map +1 -1
- package/dist/garbageCollection.d.ts +26 -8
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +81 -57
- package/dist/garbageCollection.js.map +1 -1
- package/dist/orderedClientElection.d.ts +57 -6
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +141 -26
- package/dist/orderedClientElection.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +11 -10
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizer.d.ts +1 -0
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +8 -4
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerClientElection.d.ts +2 -0
- package/dist/summarizerClientElection.d.ts.map +1 -1
- package/dist/summarizerClientElection.js +15 -2
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summarizerTypes.d.ts +47 -1
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryGenerator.d.ts +0 -2
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +2 -3
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +29 -18
- package/dist/summaryManager.js.map +1 -1
- package/lib/connectionTelemetry.js +1 -1
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +6 -6
- package/lib/containerRuntime.js.map +1 -1
- package/lib/garbageCollection.d.ts +26 -8
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +81 -57
- package/lib/garbageCollection.js.map +1 -1
- package/lib/orderedClientElection.d.ts +57 -6
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js +141 -26
- package/lib/orderedClientElection.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +11 -10
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizer.d.ts +1 -0
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +8 -4
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerClientElection.d.ts +2 -0
- package/lib/summarizerClientElection.d.ts.map +1 -1
- package/lib/summarizerClientElection.js +15 -2
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summarizerTypes.d.ts +47 -1
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryGenerator.d.ts +0 -2
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +2 -3
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +29 -18
- package/lib/summaryManager.js.map +1 -1
- package/package.json +29 -53
- package/src/connectionTelemetry.ts +2 -2
- package/src/containerRuntime.ts +4 -6
- package/src/garbageCollection.ts +96 -61
- package/src/orderedClientElection.ts +155 -25
- package/src/packageVersion.ts +1 -1
- package/src/runningSummarizer.ts +13 -10
- package/src/summarizer.ts +9 -4
- package/src/summarizerClientElection.ts +15 -2
- package/src/summarizerTypes.ts +60 -1
- package/src/summaryGenerator.ts +3 -51
- 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
|
|
253
|
-
return this.
|
|
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.
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
365
|
+
let change = false;
|
|
366
|
+
const isSummarizerClient = client?.client.details.type === summarizerClientType;
|
|
310
367
|
const prevClient = this._electedClient;
|
|
311
|
-
this._electedClient
|
|
312
|
-
|
|
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
|
|
397
|
+
private findFirstEligibleParent(client: ILinkedClient | undefined): ILinkedClient | undefined {
|
|
322
398
|
let candidateClient = client;
|
|
323
|
-
while (candidateClient !== undefined &&
|
|
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
|
-
|
|
339
|
-
|
|
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
|
-
//
|
|
356
|
-
|
|
357
|
-
|
|
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.
|
|
368
|
-
|
|
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.
|
|
373
|
-
this.
|
|
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.
|
|
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
|
}
|
package/src/packageVersion.ts
CHANGED
package/src/runningSummarizer.ts
CHANGED
|
@@ -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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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.
|
|
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.
|
|
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
|
|
160
|
+
details.capabilities.interactive || details.type === summarizerClientType;
|
|
148
161
|
}
|
package/src/summarizerTypes.ts
CHANGED
|
@@ -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
|
-
"
|
|
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
|
+
}
|
package/src/summaryGenerator.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ITelemetryLogger
|
|
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
|
-
|
|
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,
|