Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[heft] Require a logging name for every operation (followup) #4953

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions apps/heft/src/cli/HeftActionRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ export class HeftActionRunner {
executeAsync: (state: IWatchLoopState): Promise<OperationStatus> => {
return this._executeOnceAsync(executionManager, state.abortSignal, state.requestRun);
},
onRequestRun: (requestor?: string) => {
terminal.writeLine(Colorize.bold(`New run requested by ${requestor || 'unknown task'}`));
onRequestRun: (requestor: string) => {
terminal.writeLine(Colorize.bold(`New run requested by ${requestor}`));
},
onAbort: () => {
terminal.writeLine(Colorize.bold(`Cancelling incremental build...`));
Expand All @@ -346,7 +346,7 @@ export class HeftActionRunner {
private async _executeOnceAsync(
executionManager: OperationExecutionManager,
abortSignal: AbortSignal,
requestRun?: (requestor?: string) => void
requestRun?: (requestor: string) => void
): Promise<OperationStatus> {
// Record this as the start of task execution.
this._metricsCollector.setStartTime();
Expand Down Expand Up @@ -447,6 +447,7 @@ function _getOrCreatePhaseOperation(
// Only create the operation. Dependencies are hooked up separately
operation = new Operation({
groupName: phase.phaseName,
operationName: `${phase.phaseName} phase`,
runner: new PhaseOperationRunner({ phase, internalHeftSession })
});
operations.set(key, operation);
Expand All @@ -466,6 +467,7 @@ function _getOrCreateTaskOperation(
if (!operation) {
operation = new Operation({
groupName: task.parentPhase.phaseName,
operationName: `${task.taskName} task`,
runner: new TaskOperationRunner({
internalHeftSession,
task
Expand Down
2 changes: 1 addition & 1 deletion apps/heft/src/operations/runners/PhaseOperationRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class PhaseOperationRunner implements IOperationRunner {
private readonly _options: IPhaseOperationRunnerOptions;
private _isClean: boolean = false;

public get name(): string {
public get operationName(): string {
return `Phase ${JSON.stringify(this._options.phase.phaseName)}`;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/heft/src/operations/runners/TaskOperationRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class TaskOperationRunner implements IOperationRunner {

public readonly silent: boolean = false;

public get name(): string {
public get operationName(): string {
const { taskName, parentPhase } = this._options.task;
return `Task ${JSON.stringify(taskName)} of phase ${JSON.stringify(parentPhase.phaseName)}`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft",
"comment": "Require a logging name for every operation.",
"type": "patch"
}
],
"packageName": "@rushstack/heft"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/operation-graph",
"comment": "(BREAKING API CHANGE) Require an operationName for every operation to improve logging.",
"type": "minor"
}
],
"packageName": "@rushstack/operation-graph"
}
20 changes: 10 additions & 10 deletions common/reviews/api/operation-graph.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface IExecuteOperationContext extends Omit<IOperationRunnerContext,
afterExecute(operation: Operation, state: IOperationState): void;
beforeExecute(operation: Operation, state: IOperationState): void;
queueWork(workFn: () => Promise<OperationStatus>, priority: number): Promise<OperationStatus>;
requestRun?: (requestor?: string) => void;
requestRun?: (requestor: string) => void;
terminal: ITerminal;
}

Expand All @@ -50,23 +50,23 @@ export interface IOperationExecutionOptions {
// (undocumented)
parallelism: number;
// (undocumented)
requestRun?: (requestor?: string) => void;
requestRun?: (requestor: string) => void;
// (undocumented)
terminal: ITerminal;
}

// @beta
export interface IOperationOptions {
groupName?: string | undefined;
name?: string | undefined;
operationName: string;
runner?: IOperationRunner | undefined;
weight?: number | undefined;
}

// @beta
export interface IOperationRunner {
executeAsync(context: IOperationRunnerContext): Promise<OperationStatus>;
readonly name: string;
readonly operationName: string;
silent: boolean;
}

Expand Down Expand Up @@ -99,7 +99,7 @@ export interface IRequestRunEventMessage {
// (undocumented)
event: 'requestRun';
// (undocumented)
requestor?: string;
requestor: string;
}

// @beta
Expand Down Expand Up @@ -127,20 +127,20 @@ export interface IWatchLoopOptions {
executeAsync: (state: IWatchLoopState) => Promise<OperationStatus>;
onAbort: () => void;
onBeforeExecute: () => void;
onRequestRun: (requestor?: string) => void;
onRequestRun: (requestor: string) => void;
}

// @beta
export interface IWatchLoopState {
// (undocumented)
get abortSignal(): AbortSignal;
// (undocumented)
requestRun: (requestor?: string) => void;
requestRun: (requestor: string) => void;
}

// @beta
export class Operation implements IOperationStates {
constructor(options?: IOperationOptions);
constructor(options: IOperationOptions);
// (undocumented)
addDependency(dependency: Operation): void;
readonly consumers: Set<Operation>;
Expand All @@ -152,7 +152,7 @@ export class Operation implements IOperationStates {
_executeAsync(context: IExecuteOperationContext): Promise<OperationStatus>;
readonly groupName: string | undefined;
lastState: IOperationState | undefined;
readonly name: string | undefined;
readonly operationName: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a breaking API change. Is this rename necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this change is beneficial to ensure we are more explicit in the naming. The purpose of name is easily lost outside of the operation-graph lib context. Specifically as downstream libs use this interface to implement custom operations like the heft TaskOperationRunner.

// (undocumented)
reset(): void;
runner: IOperationRunner | undefined;
Expand Down Expand Up @@ -231,7 +231,7 @@ export class Stopwatch {
export class WatchLoop implements IWatchLoopState {
constructor(options: IWatchLoopOptions);
get abortSignal(): AbortSignal;
requestRun: (requestor?: string) => void;
requestRun: (requestor: string) => void;
runIPCAsync(host?: IPCHost): Promise<void>;
runUntilAbortedAsync(abortSignal: AbortSignal, onWaiting: () => void): Promise<void>;
runUntilStableAsync(abortSignal: AbortSignal): Promise<OperationStatus>;
Expand Down
2 changes: 1 addition & 1 deletion libraries/operation-graph/src/IOperationRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export interface IOperationRunner {
/**
* Name of the operation, for logging.
*/
readonly name: string;
readonly operationName: string;

/**
* Indicates that this runner is architectural and should not be reported on.
Expand Down
14 changes: 8 additions & 6 deletions libraries/operation-graph/src/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface IOperationOptions {
/**
* The name of this operation, for logging.
*/
name?: string | undefined;
operationName: string;

/**
* The group that this operation belongs to. Will be used for logging and duration tracking.
Expand Down Expand Up @@ -68,7 +68,7 @@ export interface IExecuteOperationContext extends Omit<IOperationRunnerContext,
* 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;
requestRun?: (requestor: string) => void;

/**
* Terminal to write output to.
Expand Down Expand Up @@ -101,7 +101,7 @@ export class Operation implements IOperationStates {
/**
* The name of this operation, for logging.
*/
public readonly name: string | undefined;
public readonly operationName: string;

/**
* When the scheduler is ready to process this `Operation`, the `runner` implements the actual work of
Expand Down Expand Up @@ -174,11 +174,11 @@ export class Operation implements IOperationStates {
*/
private _runPending: boolean = true;

public constructor(options?: IOperationOptions) {
public constructor(options: IOperationOptions) {
this.groupName = options?.groupName;
this.runner = options?.runner;
this.weight = options?.weight || 1;
this.name = options?.name;
this.operationName = options.operationName;
}

public addDependency(dependency: Operation): void {
Expand Down Expand Up @@ -280,7 +280,9 @@ export class Operation implements IOperationStates {
// The requestRun callback is assumed to remain constant
// throughout the lifetime of the process, so it is safe
// to capture here.
return requestRun(this.name);
return requestRun(this.operationName);
case undefined:
throw new InternalError(`The operation state is undefined`);
default:
// This line is here to enforce exhaustiveness
const currentStatus: undefined = this.state?.status;
Expand Down
6 changes: 3 additions & 3 deletions libraries/operation-graph/src/OperationExecutionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface IOperationExecutionOptions {
parallelism: number;
terminal: ITerminal;

requestRun?: (requestor?: string) => void;
requestRun?: (requestor: string) => void;
}

/**
Expand Down Expand Up @@ -77,8 +77,8 @@ export class OperationExecutionManager {
for (const dependency of consumer.dependencies) {
if (!operations.has(dependency)) {
throw new Error(
`Operation ${JSON.stringify(consumer.name)} declares a dependency on operation ` +
`${JSON.stringify(dependency.name)} that is not in the set of operations to execute.`
`Operation ${JSON.stringify(consumer.operationName)} declares a dependency on operation ` +
`${JSON.stringify(dependency.operationName)} that is not in the set of operations to execute.`
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion libraries/operation-graph/src/OperationGroupRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class OperationGroupRecord {

public setOperationAsComplete(operation: Operation, state: IOperationState): void {
if (!this._remainingOperations.has(operation)) {
throw new InternalError(`Operation ${operation.name} is not in the group ${this.name}`);
throw new InternalError(`Operation ${operation.operationName} is not in the group ${this.name}`);
}

if (state.status === OperationStatus.Aborted) {
Expand Down
19 changes: 11 additions & 8 deletions libraries/operation-graph/src/WatchLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface IWatchLoopOptions {
/**
* Logging callback when a run is requested (and hasn't already been).
*/
onRequestRun: (requestor?: string) => void;
onRequestRun: (requestor: string) => void;
/**
* Logging callback when a run is aborted.
*/
Expand All @@ -45,7 +45,7 @@ export interface IWatchLoopOptions {
*/
export interface IWatchLoopState {
get abortSignal(): AbortSignal;
requestRun: (requestor?: string) => void;
requestRun: (requestor: string) => void;
}

/**
Expand All @@ -60,7 +60,7 @@ export class WatchLoop implements IWatchLoopState {
private _isRunning: boolean;
private _runRequested: boolean;
private _requestRunPromise: Promise<string | undefined>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private _requestRunPromise: Promise<string | undefined>;
private _requestRunPromise: Promise<string>;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And on line 267.

private _resolveRequestRun!: (requestor?: string) => void;
private _resolveRequestRun!: (requestor: string) => void;

public constructor(options: IWatchLoopOptions) {
this._options = options;
Expand Down Expand Up @@ -147,7 +147,7 @@ export class WatchLoop implements IWatchLoopState {
}
}

function requestRunFromHost(requestor?: string): void {
function requestRunFromHost(requestor: string): void {
if (runRequestedFromHost) {
return;
}
Expand Down Expand Up @@ -192,9 +192,12 @@ export class WatchLoop implements IWatchLoopState {

try {
status = await this.runUntilStableAsync(abortController.signal);
// ESLINT: "Promises must be awaited, end with a call to .catch, end with a call to .then ..."
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this._requestRunPromise.finally(requestRunFromHost);
this._requestRunPromise
// the reject callback in the promise is discarded so we ignore errors
.catch(() => {})
Comment on lines +196 to +197
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this behavior correct/expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the watchloop is using a deferred promise pattern, but only binds the resolve function per:

this._requestRunPromise = new Promise<string | undefined>((resolve) => {
this._resolveRequestRun = resolve;
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here is wrong. requestRunPromise resolves with the requestor, which is why it had directly passed requestRunFromHost as the callback.

.finally(() => {
requestRunFromHost('runIPCAsync');
});
} catch (err) {
status = OperationStatus.Failure;
return reject(err);
Expand Down Expand Up @@ -225,7 +228,7 @@ export class WatchLoop implements IWatchLoopState {
/**
* Requests that a new run occur.
*/
public requestRun: (requestor?: string) => void = (requestor?: string) => {
public requestRun: (requestor: string) => void = (requestor: string) => {
if (!this._runRequested) {
this._options.onRequestRun(requestor);
this._runRequested = true;
Expand Down
8 changes: 5 additions & 3 deletions libraries/operation-graph/src/calculateCriticalPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// See LICENSE in the project root for license information.

export interface ISortableOperation<T extends ISortableOperation<T>> {
name: string | undefined;
operationName: string | undefined;
criticalPathLength?: number | undefined;
weight: number;
consumers: Set<T>;
Expand Down Expand Up @@ -54,7 +54,9 @@ export function calculateShortestPath<T extends ISortableOperation<T>>(
}

if (!finalParent) {
throw new Error(`Could not find a path from "${startOperation.name}" to "${endOperation.name}"`);
throw new Error(
`Could not find a path from "${startOperation.operationName}" to "${endOperation.operationName}"`
);
}

// Walk back up the path from the end operation to the start operation
Expand All @@ -81,7 +83,7 @@ export function calculateCriticalPathLength<T extends ISortableOperation<T>>(

throw new Error(
'A cyclic dependency was encountered:\n ' +
shortestPath.map((visitedTask) => visitedTask.name).join('\n -> ')
shortestPath.map((visitedTask) => visitedTask.operationName).join('\n -> ')
);
}

Expand Down
2 changes: 1 addition & 1 deletion libraries/operation-graph/src/protocol.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { OperationStatus } from './OperationStatus';
*/
export interface IRequestRunEventMessage {
event: 'requestRun';
requestor?: string;
requestor: string;
}

/**
Expand Down
Loading
Loading