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 #4469

Open
wants to merge 2 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 @@ -341,8 +341,8 @@ export class HeftActionRunner {
executeAsync: (state: IWatchLoopState): Promise<OperationStatus> => {
return this._executeOnceAsync(executionManager, state.abortSignal, state.requestRun);
},
onRequestRun: (requestor?: string) => {
terminal.writeLine(Colors.bold(`New run requested by ${requestor || 'unknown task'}`));
onRequestRun: (requester: string) => {
terminal.writeLine(Colors.bold(`New run requested by ${requester}`));
},
onAbort: () => {
terminal.writeLine(Colors.bold(`Cancelling incremental build...`));
Expand All @@ -354,7 +354,7 @@ export class HeftActionRunner {
private async _executeOnceAsync(
executionManager: OperationExecutionManager,
abortSignal: AbortSignal,
requestRun?: (requestor?: string) => void
requestRun?: (requester: string) => void
): Promise<OperationStatus> {
// Execute the action operations
return await runWithLoggingAsync(
Expand Down Expand Up @@ -453,6 +453,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 @@ -472,6 +473,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
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?: (requester: string) => void;
terminal: ITerminal;
}

Expand All @@ -50,23 +50,23 @@ export interface IOperationExecutionOptions {
// (undocumented)
parallelism: number;
// (undocumented)
requestRun?: (requestor?: string) => void;
requestRun?: (requester: 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;
requester: 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: (requester: string) => void;
}

// @beta
export interface IWatchLoopState {
// (undocumented)
get abortSignal(): AbortSignal;
// (undocumented)
requestRun: (requestor?: string) => void;
requestRun: (requester: 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;
// (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: (requester: 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 @@ -21,7 +21,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 @@ -67,7 +67,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?: (requester: string) => void;

/**
* Terminal to write output to.
Expand Down Expand Up @@ -100,7 +100,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 @@ -173,11 +173,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 @@ -279,7 +279,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`);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(Improved this error message)

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 @@ -20,7 +20,7 @@ export interface IOperationExecutionOptions {
parallelism: number;
terminal: ITerminal;

requestRun?: (requestor?: string) => void;
requestRun?: (requester: string) => void;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(The requester is now also non-optional)

Copy link
Contributor

Choose a reason for hiding this comment

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

The requester could ultimately be multiple layers of identifiers; I guess we can just concatenate strings, but it might be better to make this an array of hierarchy nodes (e.g. Operation, file). When Rush receives one of these messages over IPC it'll get wrapped in a further node for the Rush-level Operation.

}

/**
Expand Down Expand Up @@ -76,8 +76,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
20 changes: 11 additions & 9 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: (requester: 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: (requester: string) => void;
}

/**
Expand All @@ -61,7 +61,7 @@ export class WatchLoop implements IWatchLoopState {
private _isRunning: boolean;
private _runRequested: boolean;
private _requestRunPromise: Promise<string | undefined>;
private _resolveRequestRun!: (requestor?: string) => void;
private _resolveRequestRun!: (requester: string) => void;

public constructor(options: IWatchLoopOptions) {
this._options = options;
Expand Down Expand Up @@ -148,7 +148,7 @@ export class WatchLoop implements IWatchLoopState {
let runRequestedFromHost: boolean = true;
let status: OperationStatus = OperationStatus.Ready;

function requestRunFromHost(requestor?: string): void {
function requestRunFromHost(requester: string): void {
if (runRequestedFromHost) {
return;
}
Expand All @@ -157,7 +157,7 @@ export class WatchLoop implements IWatchLoopState {

const requestRunMessage: IRequestRunEventMessage = {
event: 'requestRun',
requestor
requester
};

host.send!(requestRunMessage);
Expand Down Expand Up @@ -193,7 +193,9 @@ export class WatchLoop implements IWatchLoopState {

try {
status = await this.runUntilStableAsync(abortController.signal);
this._requestRunPromise.finally(requestRunFromHost);
this._requestRunPromise.finally(() => {
requestRunFromHost('runIPCAsync');
Copy link
Collaborator Author

@octogonz octogonz Jan 9, 2024

Choose a reason for hiding this comment

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

@dmichon-msft what is the appropriate name to supply here? In the current implementation it was implicitly getting mapped to undefined.

(If we're going to be referring to requesters/operations by names, then we should provide a meaningful name in every code path, rather than printing awkward messages like "This job was started by an unknown source.")

Copy link
Contributor

Choose a reason for hiding this comment

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

If .finally is not forwarding the promise result (which is the underlying requester), use .then instead. The error code path is unreachable code, since the reject callback is discarded.

});
} catch (err) {
status = OperationStatus.Failure;
return reject(err);
Expand Down Expand Up @@ -224,16 +226,16 @@ export class WatchLoop implements IWatchLoopState {
/**
* Requests that a new run occur.
*/
public requestRun: (requestor?: string) => void = (requestor?: string) => {
public requestRun: (requester: string) => void = (requester: string) => {
if (!this._runRequested) {
this._options.onRequestRun(requestor);
this._options.onRequestRun(requester);
this._runRequested = true;
if (this._isRunning) {
this._options.onAbort();
this._abortCurrent();
}
}
this._resolveRequestRun(requestor);
this._resolveRequestRun(requester);
};

/**
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;
requester: string;
}

/**
Expand Down
Loading
Loading