UNPKG

@rushstack/operation-graph

Version:

Library for managing and executing operations in a directed acyclic graph.

579 lines (550 loc) 18.1 kB
/// <reference types="node" /> import type { ITerminal } from '@rushstack/terminal'; /** * The set of known messages from the host to the watch loop. * @beta */ export declare type CommandMessageFromHost = ICancelCommandMessage | IExitCommandMessage | IRunCommandMessage | ISyncCommandMessage; /** * The set of known messages from the watch loop to the host. * @beta */ export declare type EventMessageFromClient = IRequestRunEventMessage | IAfterExecuteEventMessage | ISyncEventMessage; /** * A message sent to the host upon completion of a run of this task. * * @beta */ export declare interface IAfterExecuteEventMessage { event: 'after-execute'; status: OperationStatus; } /** * A message sent by the host to tell the watch loop to cancel the current run. * * @beta */ export declare interface ICancelCommandMessage { command: 'cancel'; } /** * Information provided to `executeAsync` by the `OperationExecutionManager`. * * @beta */ export declare interface IExecuteOperationContext extends Omit<IOperationRunnerContext, 'isFirstRun' | 'requestRun'> { /** * Function to invoke before execution of an operation, for logging. */ beforeExecute(operation: Operation, state: IOperationState): void; /** * Function to invoke after execution of an operation, for logging. */ afterExecute(operation: Operation, state: IOperationState): void; /** * Function used to schedule the concurrency-limited execution of an operation. * * Will return OperationStatus.Aborted if execution is aborted before the task executes. */ queueWork(workFn: () => Promise<OperationStatus>, priority: number): Promise<OperationStatus>; /** * A callback to the overarching orchestrator to request that the operation be invoked again. * Used in watch mode to signal that inputs have changed. */ requestRun?: (requestor?: string) => void; /** * Terminal to write output to. */ terminal: ITerminal; } /** * A message sent by the host to tell the watch loop to shutdown gracefully. * * @beta */ export declare interface IExitCommandMessage { command: 'exit'; } /** * Options for the current run. * * @beta */ export declare interface IOperationExecutionOptions { abortSignal: AbortSignal; parallelism: number; terminal: ITerminal; requestRun?: (requestor?: string) => void; } /** * Options for constructing a new Operation. * @beta */ export declare interface IOperationOptions { /** * The name of this operation, for logging. */ name?: string | undefined; /** * The group that this operation belongs to. Will be used for logging and duration tracking. */ groupName?: string | undefined; /** * When the scheduler is ready to process this `Operation`, the `runner` implements the actual work of * running the operation. */ runner?: IOperationRunner | undefined; /** * The weight used by the scheduler to determine order of execution. */ weight?: number | undefined; } /** * The `Operation` class is a node in the dependency graph of work that needs to be scheduled by the * `OperationExecutionManager`. Each `Operation` has a `runner` member of type `IOperationRunner`, whose * implementation manages the actual process for running a single operation. * * @beta */ export declare interface IOperationRunner { /** * Name of the operation, for logging. */ readonly name: string; /** * Indicates that this runner is architectural and should not be reported on. */ silent: boolean; /** * Method to be executed for the operation. */ executeAsync(context: IOperationRunnerContext): Promise<OperationStatus>; } /** * Information passed to the executing `IOperationRunner` * * @beta */ export declare interface IOperationRunnerContext { /** * An abort signal for the overarching execution. Runners should do their best to gracefully abort * as soon as possible if the signal is aborted. */ abortSignal: AbortSignal; /** * If this is the first time this operation has been executed. */ isFirstRun: boolean; /** * A callback to the overarching orchestrator to request that the operation be invoked again. * Used in watch mode to signal that inputs have changed. */ requestRun?: () => void; } /** * Interface contract for a single state of an operation. * * @beta */ export declare interface IOperationState { /** * The status code for the operation. */ status: OperationStatus; /** * Whether the operation has been run at least once. */ hasBeenRun: boolean; /** * The error, if the status is `OperationStatus.Failure`. */ error: OperationError | undefined; /** * Timing information for the operation. */ stopwatch: Stopwatch; } /** * Interface contract for the current and past state of an operation. * * @beta */ export declare interface IOperationStates { /** * The current state of the operation. */ readonly state: Readonly<IOperationState> | undefined; /** * The previous state of the operation. */ readonly lastState: Readonly<IOperationState> | undefined; } /** * The interface contract for IPC send/receive, to support alternate channels and unit tests. * * @beta */ export declare type IPCHost = Pick<typeof process, 'on' | 'send'>; /** * A message sent to the host to ask it to run this task. * * @beta */ export declare interface IRequestRunEventMessage { event: 'requestRun'; requestor?: string; } /** * A message sent by the host to tell the watch loop to perform a single run. * * @beta */ export declare interface IRunCommandMessage { command: 'run'; } /** * A message sent by the host to ask for to resync status information. * * @beta */ export declare interface ISyncCommandMessage { command: 'sync'; } /** * A message sent to the host upon connection of the channel, to indicate * to the host that this task supports the protocol and to provide baseline status information. * * @beta */ export declare interface ISyncEventMessage { event: 'sync'; status: OperationStatus; } /** * Callbacks for the watch loop. * * @beta */ export declare interface IWatchLoopOptions { /** * Callback that performs the core work of a single iteration. */ executeAsync: (state: IWatchLoopState) => Promise<OperationStatus>; /** * Logging callback immediately before execution occurs. */ onBeforeExecute: () => void; /** * Logging callback when a run is requested (and hasn't already been). */ onRequestRun: (requestor?: string) => void; /** * Logging callback when a run is aborted. */ onAbort: () => void; } /** * The public API surface of the watch loop, for use in the `executeAsync` callback. * * @beta */ export declare interface IWatchLoopState { get abortSignal(): AbortSignal; requestRun: (requestor?: string) => void; } /** * The `Operation` class is a node in the dependency graph of work that needs to be scheduled by the * `OperationExecutionManager`. Each `Operation` has a `runner` member of type `IOperationRunner`, whose * implementation manages the actual process of running a single operation. * * The graph of `Operation` instances will be cloned into a separate execution graph after processing. * * @beta */ export declare class Operation implements IOperationStates { /** * A set of all dependencies which must be executed before this operation is complete. */ readonly dependencies: Set<Operation>; /** * A set of all operations that wait for this operation. */ readonly consumers: Set<Operation>; /** * If specified, the name of a grouping to which this Operation belongs, for logging start and end times. */ readonly groupName: string | undefined; /** * The name of this operation, for logging. */ readonly name: string | undefined; /** * When the scheduler is ready to process this `Operation`, the `runner` implements the actual work of * running the operation. */ runner: IOperationRunner | undefined; /** * This number represents how far away this Operation is from the furthest "root" operation (i.e. * an operation with no consumers). This helps us to calculate the critical path (i.e. the * longest chain of projects which must be executed in order, thereby limiting execution speed * of the entire operation tree. * * This number is calculated via a memoized depth-first search, and when choosing the next * operation to execute, the operation with the highest criticalPathLength is chosen. * * Example: * (0) A * \\ * (1) B C (0) (applications) * \\ /|\\ * \\ / | \\ * (2) D | X (1) (utilities) * | / \\ * |/ \\ * (2) Y Z (2) (other utilities) * * All roots (A & C) have a criticalPathLength of 0. * B has a score of 1, since A depends on it. * D has a score of 2, since we look at the longest chain (e.g D-\>B-\>A is longer than D-\>C) * X has a score of 1, since the only package which depends on it is A * Z has a score of 2, since only X depends on it, and X has a score of 1 * Y has a score of 2, since the chain Y-\>X-\>C is longer than Y-\>C * * The algorithm is implemented in AsyncOperationQueue.ts as calculateCriticalPathLength() */ criticalPathLength: number | undefined; /** * The weight for this operation. This scalar is the contribution of this operation to the * `criticalPathLength` calculation above. Modify to indicate the following: * - `weight` === 1: indicates that this operation has an average duration * - `weight` &gt; 1: indicates that this operation takes longer than average and so the scheduler * should try to favor starting it over other, shorter operations. An example might be an operation that * bundles an entire application and runs whole-program optimization. * - `weight` &lt; 1: indicates that this operation takes less time than average and so the scheduler * should favor other, longer operations over it. An example might be an operation to unpack a cached * output, or an operation using NullOperationRunner, which might use a value of 0. */ weight: number; /** * The state of this operation the previous time a manager was invoked. */ lastState: IOperationState | undefined; /** * The current state of this operation */ state: IOperationState | undefined; /** * A cached execution promise for the current OperationExecutionManager invocation of this operation. */ private _promise; /** * If true, then a run of this operation is currently wanted. * This is used to track state from the `requestRun` callback passed to the runner. */ private _runPending; constructor(options?: IOperationOptions); addDependency(dependency: Operation): void; deleteDependency(dependency: Operation): void; reset(): void; /** * @internal */ _executeAsync(context: IExecuteOperationContext): Promise<OperationStatus>; private _executeInnerAsync; } /** * Encapsulates information about an error * * @beta */ export declare class OperationError extends Error { protected _type: string; constructor(type: string, message: string); get message(): string; toString(): string; } /** * A class which manages the execution of a set of tasks with interdependencies. * Initially, and at the end of each task execution, all unblocked tasks * are added to a ready queue which is then executed. This is done continually until all * tasks are complete, or prematurely fails if any of the tasks fail. * * @beta */ export declare class OperationExecutionManager { /** * The set of operations that will be executed */ private readonly _operations; /** * Group records are metadata-only entities used for tracking the start and end of a set of related tasks. * This is the only extent to which the operation graph is aware of Heft phases. */ private readonly _groupRecordByName; /** * The total number of non-silent operations in the graph. * Silent operations are generally used to simplify the construction of the graph. */ private readonly _trackedOperationCount; constructor(operations: ReadonlySet<Operation>); /** * Executes all operations which have been registered, returning a promise which is resolved when all the * operations are completed successfully, or rejects when any operation fails. */ executeAsync(executionOptions: IOperationExecutionOptions): Promise<OperationStatus>; } /** * Meta-entity that tracks information about a group of related operations. * * @beta */ export declare class OperationGroupRecord { private readonly _operations; private _remainingOperations; private _groupStopwatch; private _hasCancellations; private _hasFailures; readonly name: string; get duration(): number; get finished(): boolean; get hasCancellations(): boolean; get hasFailures(): boolean; constructor(name: string); addOperation(operation: Operation): void; startTimer(): void; setOperationAsComplete(operation: Operation, state: IOperationState): void; reset(): void; } /** * Enumeration defining potential states of an operation * @beta */ export declare enum OperationStatus { /** * The Operation is on the queue, ready to execute */ Ready = "READY", /** * The Operation is on the queue, waiting for one or more depencies */ Waiting = "WAITING", /** * The Operation is currently executing */ Executing = "EXECUTING", /** * The Operation completed successfully and did not write to standard output */ Success = "SUCCESS", /** * The Operation failed */ Failure = "FAILURE", /** * The operation was aborted */ Aborted = "ABORTED", /** * The Operation could not be executed because one or more of its dependencies failed */ Blocked = "BLOCKED", /** * The operation performed no meaningful work. */ NoOp = "NO OP" } /** * Represents a typical timer/stopwatch which keeps track * of elapsed time in between two events. * * @public */ export declare class Stopwatch { private _startTime; private _endTime; private _running; constructor(); /** * Static helper function which creates a stopwatch which is immediately started */ static start(): Stopwatch; get isRunning(): boolean; /** * Starts the stopwatch. Note that if end() has been called, * reset() should be called before calling start() again. */ start(): Stopwatch; /** * Stops executing the stopwatch and saves the current timestamp */ stop(): Stopwatch; /** * Resets all values of the stopwatch back to the original */ reset(): Stopwatch; /** * Displays how long the stopwatch has been executing in a human readable format. */ toString(): string; /** * Get the duration in seconds. */ get duration(): number; /** * Return the start time of the most recent stopwatch run. */ get startTime(): number | undefined; /** * Return the end time of the most recent stopwatch run. */ get endTime(): number | undefined; } /** * This class implements a watch loop. * * @beta */ export declare class WatchLoop implements IWatchLoopState { private readonly _options; private _abortController; private _isRunning; private _runRequested; private _requestRunPromise; private _resolveRequestRun; constructor(options: IWatchLoopOptions); /** * Runs the inner loop until the abort signal is cancelled or a run completes without a new run being requested. */ runUntilStableAsync(abortSignal: AbortSignal): Promise<OperationStatus>; /** * Runs the inner loop until the abort signal is aborted. Will otherwise wait indefinitely for a new run to be requested. */ runUntilAbortedAsync(abortSignal: AbortSignal, onWaiting: () => void): Promise<void>; /** * Sets up an IPC handler that will run the inner loop when it receives a "run" message from the host. * Runs until receiving an "exit" message from the host, or aborts early if an unhandled error is thrown. */ runIPCAsync(host?: IPCHost): Promise<void>; /** * Requests that a new run occur. */ requestRun: (requestor?: string) => void; /** * The abort signal for the current iteration. */ get abortSignal(): AbortSignal; /** * Cancels the current iteration (if possible). */ private _abortCurrent; /** * Resets the abort signal and run request state. */ private _reset; /** * Runs a single iteration of the loop. * @returns The status of the iteration. */ private _runIterationAsync; } export { }