/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ import { TypedEventEmitter } from "@fluid-internal/client-utils"; import type { IDeltaManager } from "@fluidframework/container-definitions/internal"; import type { IEvent, IEventProvider, ITelemetryBaseLogger } from "@fluidframework/core-interfaces"; import type { IClient, IQuorumClients } from "@fluidframework/driver-definitions"; import { type ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal"; export type ImmutablePrimitives = undefined | null | boolean | string | number | Function; export type Immutable = T extends ImmutablePrimitives ? T : T extends (infer A)[] ? readonly Immutable[] : T extends Map ? ReadonlyMap, Immutable> : T extends Set ? ReadonlySet> : { readonly [K in keyof T]: Immutable; }; /** * Minimum information for a client tracked for election consideration. */ export interface ITrackedClient { readonly clientId: string; readonly sequenceNumber: number; readonly client: Immutable; } /** * Common contract for link nodes within an OrderedClientCollection. */ export interface ILinkNode { readonly sequenceNumber: number; youngerClient: ILinkedClient | undefined; } /** * Placeholder root node within an OrderedClientCollection; does not represent a client. */ export interface IRootLinkNode extends ILinkNode { readonly sequenceNumber: -1; readonly olderClient: undefined; } /** * Additional information required to keep track of the client within the doubly-linked list. */ export interface ILinkedClient extends ILinkNode, ITrackedClient { olderClient: LinkNode; } /** * Any link node within OrderedClientCollection including the placeholder root node. */ export type LinkNode = IRootLinkNode | ILinkedClient; /** * Events raised by an OrderedClientCollection. */ export interface IOrderedClientCollectionEvents extends IEvent { /** * Event fires when client is being added. */ (event: "addClient" | "removeClient", listener: (client: ILinkedClient, sequenceNumber: number) => void): any; } /** * Contract for a sorted collection of all clients in the quorum. */ export interface IOrderedClientCollection extends IEventProvider { /** * Count of clients in the collection. */ readonly count: number; /** * Pointer to the oldest client in the collection. */ readonly oldestClient: ILinkedClient | undefined; /** * Returns a sorted array of all the clients in the collection. */ getAllClients(): ILinkedClient[]; } /** * Tracks clients in the Quorum. It maintains their order using their join op * sequence numbers. * Internally, the collection of clients is maintained in a doubly-linked list, * with pointers to both the first and last nodes. * The first (root) node is a placeholder to simplify logic and reduce null checking. */ export declare class OrderedClientCollection extends TypedEventEmitter implements IOrderedClientCollection { /** * Collection of ALL clients currently in the quorum, with client ids as keys. */ private readonly clientMap; /** * Placeholder head node of linked list, for simplified null checking. */ private readonly rootNode; /** * Pointer to end of linked list, for optimized client adds. */ private _youngestClient; private readonly logger; get count(): number; get oldestClient(): ILinkedClient | undefined; constructor(logger: ITelemetryBaseLogger, deltaManager: Pick, "lastSequenceNumber">, quorum: Pick); private addClient; private removeClient; /** * Returns an array of all clients being tracked in order from oldest to newest. */ getAllClients(): ILinkedClient[]; } /** * Events raised by an OrderedClientElection. */ export interface IOrderedClientElectionEvents extends IEvent { /** * Event fires when the currently elected client changes. */ (event: "election", listener: ( /** * Newly elected client. */ client: ITrackedClient | undefined, /** * Sequence number where election took place. */ sequenceNumber: number, /** * Previously elected client. */ prevClient: ITrackedClient | undefined) => void): any; } /** * Serialized state of IOrderedClientElection. * @internal */ export interface ISerializedElection { /** * Sequence number at the time of the latest election. */ readonly electionSequenceNumber: number; /** * Most recently elected client id. This is either: * * 1. the interactive elected parent client, in which case electedClientId === electedParentId, * and the SummaryManager on the elected client will spawn a summarizer client, or * * 2. the non-interactive summarizer client itself. */ readonly electedClientId: string | undefined; /** * Most recently elected parent client id. This is always an interactive client. */ readonly electedParentId: string | undefined; } /** * Contract for maintaining a deterministic client election based on eligibility. */ export interface IOrderedClientElection extends IEventProvider { /** * Count of eligible clients in the collection. */ readonly eligibleCount: number; /** * Currently elected client. This is either: * * 1. the interactive elected parent client, in which case electedClientId === electedParentId, * and the SummaryManager on the elected client will spawn a summarizer client, or * * 2. the non-interactive summarizer client itself. */ readonly electedClient: ITrackedClient | undefined; /** * Currently elected parent client. This is always an interactive client. */ readonly electedParent: ITrackedClient | undefined; /** * Sequence number of most recent election. */ readonly electionSequenceNumber: number; /** * Resets the currently elected client back to the oldest eligible client. */ resetElectedClient(sequenceNumber: number): void; /** * Peeks at what the next elected client would be if incrementElectedClient were called. */ peekNextElectedClient(): ITrackedClient | undefined; /** * Returns a sorted array of all the eligible clients in the collection. */ getAllEligibleClients(): ITrackedClient[]; /** * Serialize election data */ serialize(): ISerializedElection; } /** * Adapter for OrderedClientCollection, with the purpose of deterministically maintaining * a currently elected client, excluding ineligible clients, in a distributed fashion. * This can be true as long as incrementElectedClient and resetElectedClient calls * are called under the same conditions for all clients. */ export declare class OrderedClientElection extends TypedEventEmitter implements IOrderedClientElection { private readonly logger; private readonly orderedClientCollection; private readonly isEligibleFn; private readonly recordPerformanceEvents; private _eligibleCount; private _electedClient; private _electedParent; private _electionSequenceNumber; get eligibleCount(): number; get electionSequenceNumber(): number; /** * OrderedClientCollection tracks electedClient and electedParent separately. This allows us to handle the case * where a new interactive parent client has been elected, but the summarizer is still doing work, so * a new summarizer should not yet be spawned. In this case, changing electedParent will cause SummaryManager * to stop the current summarizer, but a new summarizer will not be spawned until the old summarizer client has * left the quorum. * * Details: * * electedParent is the interactive client that has been elected to spawn a summarizer. It is typically the oldest * eligible interactive client in the quorum. Only the electedParent is permitted to spawn a summarizer. * Once elected, this client will remain the electedParent until it leaves the quorum or the summarizer that * it spawned stops producing summaries, at which point a new electedParent will be chosen. * * electedClient is the non-interactive summarizer client if one exists. If not, then electedClient is equal to * electedParent. If electedParent === electedClient, this is the signal for electedParent to spawn a new * electedClient. Once a summarizer client becomes electedClient, a new summarizer will not be spawned until * electedClient leaves the quorum. * * A typical sequence looks like this: * * i. Begin by electing A. electedParent === A, electedClient === A. * * ii. SummaryManager running on A spawns a summarizer client, A'. electedParent === A, electedClient === A' * * iii. A' stops producing summaries. A new parent client, B, is elected. electedParent === B, electedClient === A' * * iv. SummaryManager running on A detects the change to electedParent and tells the summarizer to stop, but A' * is in mid-summarization. No new summarizer is spawned, as electedParent !== electedClient. * * v. A' completes its summary, and the summarizer and backing client are torn down. * * vi. A' leaves the quorum, and B takes its place as electedClient. electedParent === B, electedClient === B * * vii. SummaryManager running on B spawns a summarizer client, B'. electedParent === B, electedClient === B' */ get electedClient(): ILinkedClient | undefined; get electedParent(): ILinkedClient | undefined; constructor(logger: ITelemetryLoggerExt, orderedClientCollection: IOrderedClientCollection, /** * Serialized state from summary or current sequence number at time of load if new. */ initialState: ISerializedElection | number, isEligibleFn: (c: ITrackedClient) => boolean, recordPerformanceEvents?: boolean); /** * Tries changing the elected client, raising an event if it is different. * Note that this function does no eligibility or suitability checks. If we get here, then * we will set _electedClient, and we will set _electedParent if this is an interactive client. */ private tryElectingClient; private tryElectingParent; /** * Helper function to find the first eligible parent client starting with the passed in client, * or undefined if none are eligible. * @param client - client to start checking * @returns oldest eligible client starting with passed in client or undefined if none. */ private findFirstEligibleParent; /** * Updates tracking for when a new client is added to the collection. * Will automatically elect that new client if none is elected currently. * @param client - client added to the collection * @param sequenceNumber - sequence number when client was added */ private addClient; /** * Updates tracking for when an existing client is removed from the collection. * Will automatically elect next oldest client if currently elected is removed. * @param client - client removed from the collection * @param sequenceNumber - sequence number when client was removed */ private removeClient; getAllEligibleClients(): ITrackedClient[]; /** * (Re-)start election with the oldest client in the quorum. This is called if we need to summarize * and no client has been elected. */ resetElectedClient(sequenceNumber: number): void; peekNextElectedClient(): ITrackedClient | undefined; serialize(): ISerializedElection; private sendPerformanceEvent; } //# sourceMappingURL=orderedClientElection.d.ts.map