@rushstack/operation-graph
Version:
Library for managing and executing operations in a directed acyclic graph.
579 lines (550 loc) • 18.1 kB
TypeScript
/// <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` > 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` < 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 { }