@fluidframework/container-runtime 0.58.2001 → 0.59.2000-61729
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/blobManager.d.ts +15 -2
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +65 -9
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +63 -23
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +39 -7
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +161 -29
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.js +8 -1
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +9 -3
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +22 -6
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +13 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +39 -18
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaScheduler.d.ts +4 -5
- package/dist/deltaScheduler.d.ts.map +1 -1
- package/dist/deltaScheduler.js +54 -35
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/garbageCollection.d.ts +31 -27
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +76 -75
- package/dist/garbageCollection.js.map +1 -1
- package/dist/opTelemetry.d.ts +22 -0
- package/dist/opTelemetry.d.ts.map +1 -0
- package/dist/opTelemetry.js +59 -0
- package/dist/opTelemetry.js.map +1 -0
- 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/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 +9 -0
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +3 -4
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +14 -3
- package/dist/summaryManager.js.map +1 -1
- package/lib/blobManager.d.ts +15 -2
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +66 -10
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +63 -23
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +39 -7
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +163 -31
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.js +8 -1
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +9 -3
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +22 -6
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +13 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +39 -18
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaScheduler.d.ts +4 -5
- package/lib/deltaScheduler.d.ts.map +1 -1
- package/lib/deltaScheduler.js +54 -35
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/garbageCollection.d.ts +31 -27
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +75 -74
- package/lib/garbageCollection.js.map +1 -1
- package/lib/opTelemetry.d.ts +22 -0
- package/lib/opTelemetry.d.ts.map +1 -0
- package/lib/opTelemetry.js +55 -0
- package/lib/opTelemetry.js.map +1 -0
- 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/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 +9 -0
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +3 -4
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +14 -3
- package/lib/summaryManager.js.map +1 -1
- package/package.json +63 -19
- package/src/blobManager.ts +78 -11
- package/src/connectionTelemetry.ts +110 -19
- package/src/containerRuntime.ts +191 -36
- package/src/dataStore.ts +7 -1
- package/src/dataStoreContext.ts +22 -7
- package/src/dataStores.ts +40 -19
- package/src/deltaScheduler.ts +65 -39
- package/src/garbageCollection.ts +92 -78
- package/src/opTelemetry.ts +71 -0
- package/src/orderedClientElection.ts +155 -25
- package/src/packageVersion.ts +1 -1
- package/src/summarizerClientElection.ts +15 -2
- package/src/summarizerTypes.ts +9 -0
- package/src/summaryGenerator.ts +10 -8
- 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
|
|
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
|
@@ -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
|
@@ -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. */
|
package/src/summaryGenerator.ts
CHANGED
|
@@ -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
|
-
|
|
94
|
+
"nackRetryAfter";
|
|
88
95
|
type SummaryGeneratorTelemetry =
|
|
89
96
|
Pick<ITelemetryProperties, SummaryGeneratorRequiredTelemetryProperties> &
|
|
90
97
|
Partial<Pick<ITelemetryProperties, SummaryGeneratorOptionalTelemetryProperties>>;
|
|
@@ -279,11 +286,6 @@ export class SummaryGenerator {
|
|
|
279
286
|
// Use record type to prevent unexpected value types
|
|
280
287
|
let summaryData: SubmitSummaryResult | undefined;
|
|
281
288
|
try {
|
|
282
|
-
const generateSummaryEvent = PerformanceEvent.start(logger, {
|
|
283
|
-
eventName: "Summarize",
|
|
284
|
-
...summarizeTelemetryProps,
|
|
285
|
-
});
|
|
286
|
-
|
|
287
289
|
summaryData = await this.submitSummaryCallback({
|
|
288
290
|
fullTree,
|
|
289
291
|
refreshLatestAck,
|
|
@@ -298,6 +300,7 @@ export class SummaryGenerator {
|
|
|
298
300
|
summarizeTelemetryProps = {
|
|
299
301
|
...summarizeTelemetryProps,
|
|
300
302
|
referenceSequenceNumber,
|
|
303
|
+
minimumSequenceNumber: summaryData.minimumSequenceNumber,
|
|
301
304
|
opsSinceLastAttempt: referenceSequenceNumber - this.heuristicData.lastAttempt.refSequenceNumber,
|
|
302
305
|
opsSinceLastSummary,
|
|
303
306
|
};
|
|
@@ -351,7 +354,7 @@ export class SummaryGenerator {
|
|
|
351
354
|
}
|
|
352
355
|
|
|
353
356
|
// Log event here on summary success only, as Summarize_cancel duplicates failure logging.
|
|
354
|
-
|
|
357
|
+
summarizeEvent.reportEvent("generate", {...summarizeTelemetryProps});
|
|
355
358
|
resultsBuilder.summarySubmitted.resolve({ success: true, data: summaryData });
|
|
356
359
|
} catch (error) {
|
|
357
360
|
return fail("submitSummaryFailure", error);
|
|
@@ -416,7 +419,6 @@ export class SummaryGenerator {
|
|
|
416
419
|
summarizeEvent.end({
|
|
417
420
|
...summarizeTelemetryProps,
|
|
418
421
|
handle: ackNackOp.contents.handle,
|
|
419
|
-
message: "summaryAck",
|
|
420
422
|
});
|
|
421
423
|
resultsBuilder.receivedSummaryAckOrNack.resolve({ success: true, data: {
|
|
422
424
|
summaryAckOp: ackNackOp,
|
package/src/summaryManager.ts
CHANGED
|
@@ -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.
|
|
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
|