@fluidframework/container-runtime 0.59.1000-61898 → 0.59.1000

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 (35) hide show
  1. package/dist/orderedClientElection.d.ts +6 -57
  2. package/dist/orderedClientElection.d.ts.map +1 -1
  3. package/dist/orderedClientElection.js +25 -140
  4. package/dist/orderedClientElection.js.map +1 -1
  5. package/dist/packageVersion.d.ts +1 -1
  6. package/dist/packageVersion.d.ts.map +1 -1
  7. package/dist/packageVersion.js +1 -1
  8. package/dist/packageVersion.js.map +1 -1
  9. package/dist/summarizerClientElection.d.ts +0 -2
  10. package/dist/summarizerClientElection.d.ts.map +1 -1
  11. package/dist/summarizerClientElection.js +2 -7
  12. package/dist/summarizerClientElection.js.map +1 -1
  13. package/dist/summaryManager.d.ts.map +1 -1
  14. package/dist/summaryManager.js +3 -14
  15. package/dist/summaryManager.js.map +1 -1
  16. package/lib/orderedClientElection.d.ts +6 -57
  17. package/lib/orderedClientElection.d.ts.map +1 -1
  18. package/lib/orderedClientElection.js +25 -140
  19. package/lib/orderedClientElection.js.map +1 -1
  20. package/lib/packageVersion.d.ts +1 -1
  21. package/lib/packageVersion.d.ts.map +1 -1
  22. package/lib/packageVersion.js +1 -1
  23. package/lib/packageVersion.js.map +1 -1
  24. package/lib/summarizerClientElection.d.ts +0 -2
  25. package/lib/summarizerClientElection.d.ts.map +1 -1
  26. package/lib/summarizerClientElection.js +2 -7
  27. package/lib/summarizerClientElection.js.map +1 -1
  28. package/lib/summaryManager.d.ts.map +1 -1
  29. package/lib/summaryManager.js +3 -14
  30. package/lib/summaryManager.js.map +1 -1
  31. package/package.json +17 -17
  32. package/src/orderedClientElection.ts +25 -154
  33. package/src/packageVersion.ts +1 -1
  34. package/src/summarizerClientElection.ts +2 -7
  35. package/src/summaryManager.ts +4 -15
@@ -6,10 +6,8 @@
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";
10
9
  import { IClient, IQuorumClients, ISequencedClient } from "@fluidframework/protocol-definitions";
11
10
  import { ChildLogger } from "@fluidframework/telemetry-utils";
12
- import { summarizerClientType } from "./summarizerClientElection";
13
11
 
14
12
  // helper types for recursive readonly.
15
13
  // eslint-disable-next-line @typescript-eslint/ban-types
@@ -208,26 +206,16 @@ export interface IOrderedClientElectionEvents extends IEvent {
208
206
  export interface ISerializedElection {
209
207
  /** Sequence number at the time of the latest election. */
210
208
  readonly electionSequenceNumber: number;
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. */
209
+ /** Most recently elected client id. */
215
210
  readonly electedClientId: string | undefined;
216
- /** Most recently elected parent client id. This is always an interactive client. */
217
- readonly electedParentId: string | undefined;
218
211
  }
219
212
 
220
213
  /** Contract for maintaining a deterministic client election based on eligibility. */
221
214
  export interface IOrderedClientElection extends IEventProvider<IOrderedClientElectionEvents> {
222
215
  /** Count of eligible clients in the collection. */
223
216
  readonly eligibleCount: number;
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. */
217
+ /** Currently elected client. */
228
218
  readonly electedClient: ITrackedClient | undefined;
229
- /** Currently elected parent client. This is always an interactive client. */
230
- readonly electedParent: ITrackedClient | undefined;
231
219
  /** Sequence number of most recent election. */
232
220
  readonly electionSequenceNumber: number;
233
221
  /** Marks the currently elected client as invalid, and elects the next eligible client. */
@@ -253,50 +241,16 @@ export class OrderedClientElection
253
241
  implements IOrderedClientElection {
254
242
  private _eligibleCount: number = 0;
255
243
  private _electedClient: ILinkedClient | undefined;
256
- private _electedParent: ILinkedClient | undefined;
257
244
  private _electionSequenceNumber: number;
258
245
 
259
246
  public get eligibleCount() {
260
247
  return this._eligibleCount;
261
248
  }
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
- */
295
249
  public get electedClient() {
296
250
  return this._electedClient;
297
251
  }
298
- public get electedParent() {
299
- return this._electedParent;
252
+ public get electionSequenceNumber() {
253
+ return this._electionSequenceNumber;
300
254
  }
301
255
 
302
256
  constructor(
@@ -308,20 +262,11 @@ export class OrderedClientElection
308
262
  ) {
309
263
  super();
310
264
  let initialClient: ILinkedClient | undefined;
311
- let initialParent: ILinkedClient | undefined;
312
265
  for (const client of orderedClientCollection.getAllClients()) {
313
266
  this.addClient(client, 0);
314
267
  if (typeof initialState !== "number") {
315
268
  if (client.clientId === initialState.electedClientId) {
316
269
  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;
325
270
  }
326
271
  }
327
272
  }
@@ -343,7 +288,7 @@ export class OrderedClientElection
343
288
  });
344
289
  } else if (initialClient !== undefined && !isEligibleFn(initialClient)) {
345
290
  // Initially elected client is ineligible, so elect next eligible client.
346
- initialClient = initialParent = this.findFirstEligibleParent(initialParent);
291
+ initialClient = this.findFirstEligibleClient(initialClient);
347
292
  logger.sendErrorEvent({
348
293
  eventName: "InitialElectedClientIneligible",
349
294
  electionSequenceNumber: initialState.electionSequenceNumber,
@@ -351,53 +296,31 @@ export class OrderedClientElection
351
296
  electedClientId: initialClient?.clientId,
352
297
  });
353
298
  }
354
- this._electedParent = initialParent;
355
299
  this._electedClient = initialClient;
356
300
  this._electionSequenceNumber = initialState.electionSequenceNumber;
357
301
  }
358
302
  }
359
303
 
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
- */
304
+ /** Tries changing the elected client, raising an event if it is different. */
364
305
  private tryElectingClient(client: ILinkedClient | undefined, sequenceNumber: number): void {
365
- let change = false;
366
- const isSummarizerClient = client?.client.details.type === summarizerClientType;
367
- const prevClient = this._electedClient;
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);
306
+ this._electionSequenceNumber = sequenceNumber;
307
+ if (this._electedClient === client) {
308
+ return;
388
309
  }
310
+ const prevClient = this._electedClient;
311
+ this._electedClient = client;
312
+ this.emit("election", client, sequenceNumber, prevClient);
389
313
  }
390
314
 
391
315
  /**
392
- * Helper function to find the first eligible parent client starting with the passed in client,
316
+ * Helper function to find the first eligible client starting with the passed in client,
393
317
  * or undefined if none are eligible.
394
318
  * @param client - client to start checking
395
319
  * @returns oldest eligible client starting with passed in client or undefined if none.
396
320
  */
397
- private findFirstEligibleParent(client: ILinkedClient | undefined): ILinkedClient | undefined {
321
+ private findFirstEligibleClient(client: ILinkedClient | undefined): ILinkedClient | undefined {
398
322
  let candidateClient = client;
399
- while (candidateClient !== undefined &&
400
- (!this.isEligibleFn(candidateClient) || candidateClient.client.details.type === summarizerClientType)) {
323
+ while (candidateClient !== undefined && !this.isEligibleFn(candidateClient)) {
401
324
  candidateClient = candidateClient.youngerClient;
402
325
  }
403
326
  return candidateClient;
@@ -412,16 +335,10 @@ export class OrderedClientElection
412
335
  private addClient(client: ILinkedClient, sequenceNumber: number): void {
413
336
  if (this.isEligibleFn(client)) {
414
337
  this._eligibleCount++;
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)) {
338
+ if (this._electedClient === undefined) {
339
+ // Automatically elect latest client
419
340
  this.tryElectingClient(client, sequenceNumber);
420
341
  }
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
- }
425
342
  }
426
343
  }
427
344
 
@@ -435,33 +352,9 @@ export class OrderedClientElection
435
352
  if (this.isEligibleFn(client)) {
436
353
  this._eligibleCount--;
437
354
  if (this._electedClient === client) {
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);
355
+ // Automatically shift to next oldest client
356
+ const nextClient = this.findFirstEligibleClient(this._electedClient.youngerClient);
357
+ this.tryElectingClient(nextClient, sequenceNumber);
465
358
  }
466
359
  }
467
360
  }
@@ -470,46 +363,24 @@ export class OrderedClientElection
470
363
  return this.orderedClientCollection.getAllClients().filter(this.isEligibleFn);
471
364
  }
472
365
 
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
- */
476
366
  public incrementElectedClient(sequenceNumber: number): void {
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
- }
367
+ const nextClient = this.findFirstEligibleClient(this._electedClient?.youngerClient);
368
+ this.tryElectingClient(nextClient, sequenceNumber);
487
369
  }
488
370
 
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
- */
492
371
  public resetElectedClient(sequenceNumber: number): void {
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
- }
372
+ const firstClient = this.findFirstEligibleClient(this.orderedClientCollection.oldestClient);
373
+ this.tryElectingClient(firstClient, sequenceNumber);
502
374
  }
503
375
 
504
376
  public peekNextElectedClient(): ITrackedClient | undefined {
505
- return this.findFirstEligibleParent(this._electedParent?.youngerClient);
377
+ return this.findFirstEligibleClient(this._electedClient?.youngerClient);
506
378
  }
507
379
 
508
380
  public serialize(): ISerializedElection {
509
381
  return {
510
382
  electionSequenceNumber: this.electionSequenceNumber,
511
383
  electedClientId: this.electedClient?.clientId,
512
- electedParentId: this.electedParent?.clientId,
513
384
  };
514
385
  }
515
386
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "0.59.1000-61898";
9
+ export const pkgVersion = "0.59.1000";
@@ -17,7 +17,6 @@ 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;
21
20
  }
22
21
 
23
22
  /**
@@ -45,9 +44,6 @@ export class SummarizerClientElection
45
44
  public get electedClientId() {
46
45
  return this.clientElection.electedClient?.clientId;
47
46
  }
48
- public get electedParentId() {
49
- return this.clientElection.electedParent?.clientId;
50
- }
51
47
 
52
48
  constructor(
53
49
  private readonly logger: ITelemetryLogger,
@@ -131,10 +127,9 @@ export class SummarizerClientElection
131
127
  }
132
128
 
133
129
  public serialize(): ISerializedElection {
134
- const { electedClientId, electedParentId, electionSequenceNumber } = this.clientElection.serialize();
130
+ const { electedClientId, electionSequenceNumber } = this.clientElection.serialize();
135
131
  return {
136
132
  electedClientId,
137
- electedParentId,
138
133
  electionSequenceNumber: this.lastSummaryAckSeqForClient ?? electionSequenceNumber,
139
134
  };
140
135
  }
@@ -149,5 +144,5 @@ export class SummarizerClientElection
149
144
  }
150
145
 
151
146
  public static readonly clientDetailsPermitElection = (details: IClientDetails): boolean =>
152
- details.capabilities.interactive || details.type === summarizerClientType;
147
+ details.capabilities.interactive && details.type !== summarizerClientType;
153
148
  }
@@ -137,15 +137,9 @@ 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.
144
140
  if (!this.connectedState.connected) {
145
141
  return { shouldSummarize: false, stopReason: "parentNotConnected" };
146
- } else if (this.connectedState.clientId !== this.clientElection.electedParentId ||
147
- (this.state !== SummaryManagerState.Running &&
148
- this.connectedState.clientId !== this.clientElection.electedClientId)) {
142
+ } else if (this.connectedState.clientId !== this.clientElection.electedClientId) {
149
143
  return { shouldSummarize: false, stopReason: "parentShouldNotSummarize" };
150
144
  } else if (this.disposed) {
151
145
  assert(false, 0x260 /* "Disposed should mean disconnected!" */);
@@ -205,23 +199,18 @@ export class SummaryManager implements IDisposable {
205
199
  return;
206
200
  }
207
201
 
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
-
215
202
  const summarizer = await this.requestSummarizerFn();
216
203
 
217
204
  // Re-validate that it need to be running. Due to asynchrony, it may be not the case anymore
218
205
  const shouldSummarizeState = this.getShouldSummarizeState();
219
206
  if (shouldSummarizeState.shouldSummarize === false) {
220
- this.state = SummaryManagerState.Starting;
221
207
  summarizer.stop(shouldSummarizeState.stopReason);
222
208
  return;
223
209
  }
224
210
 
211
+ assert(this.state === SummaryManagerState.Starting, 0x263 /* "Expected: starting" */);
212
+ this.state = SummaryManagerState.Running;
213
+
225
214
  this.summarizer = summarizer;
226
215
 
227
216
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion