-
Notifications
You must be signed in to change notification settings - Fork 202
Refactor Runner parameter overloads and resolve review comments #2210
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ export interface IExecutedAction { | |
| } | ||
|
|
||
| export interface IExecutionOptions { | ||
| projectDir?: string; | ||
| bigquery?: { | ||
| jobPrefix?: string; | ||
| actionRetryLimit?: number; | ||
|
|
@@ -32,23 +33,77 @@ export interface IExecutionOptions { | |
| }; | ||
| } | ||
|
|
||
| export type RunOptionsOrResult = IExecutionOptions | dataform.IRunResult; | ||
| export type CancelReason = "timeout" | "user" | "cancellation"; | ||
|
|
||
| export function run( | ||
| dbadapter: dbadapters.IDbAdapter, | ||
| graph: dataform.IExecutionGraph, | ||
| executionOptions?: IExecutionOptions, | ||
| optionsOrResult: RunOptionsOrResult = {}, | ||
| partiallyExecutedRunResult: dataform.IRunResult = {}, | ||
| runnerNotificationPeriodMillis: number = flags.runnerNotificationPeriodMillis.get() | ||
| ): Runner { | ||
| const { options, runResult } = handleParamsOverloads(optionsOrResult, partiallyExecutedRunResult); | ||
| return new Runner( | ||
| dbadapter, | ||
| graph, | ||
| executionOptions, | ||
| partiallyExecutedRunResult, | ||
| options, | ||
| runResult, | ||
| runnerNotificationPeriodMillis | ||
| ).execute(); | ||
| } | ||
|
|
||
| // Legacy `run()` and the positional-arg `Runner` constructor accept either | ||
| // `IExecutionOptions` or a partial `dataform.IRunResult` in the same slot. | ||
| // Discriminate on `.actions` presence (runResult has it, options doesn't). | ||
| // New callers should prefer `Runner.create` / `Runner.resume`, which pass | ||
| // unambiguous arguments through this helper. | ||
| function handleParamsOverloads( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not obvious to me why we are handling this that way. Can we put some information on what this function is supposed to do?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding a doc block |
||
| optionsOrResult: RunOptionsOrResult, | ||
| partiallyExecutedRunResult: dataform.IRunResult | ||
| ): { options: IExecutionOptions; runResult: dataform.IRunResult } { | ||
| if (optionsOrResult && (optionsOrResult as dataform.IRunResult).actions !== undefined) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If an IRunResult object is passed with actions omitted or undefined, it will inadvertently be misclassified as IExecutionOptions, right? Is that expected? |
||
| return { | ||
| runResult: { | ||
| ...(optionsOrResult as dataform.IRunResult) | ||
| }, | ||
| options: { projectDir: "." } | ||
| }; | ||
| } | ||
| const options: IExecutionOptions = { | ||
| projectDir: ".", | ||
| ...(optionsOrResult as IExecutionOptions) | ||
| }; | ||
| return { | ||
| options, | ||
| runResult: { | ||
| actions: [], | ||
| ...partiallyExecutedRunResult | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| export class Runner { | ||
| public static create( | ||
| dbadapter: dbadapters.IDbAdapter, | ||
| graph: dataform.IExecutionGraph, | ||
| options: IExecutionOptions = {}, | ||
| runnerNotificationPeriodMillis: number = flags.runnerNotificationPeriodMillis.get() | ||
| ): Runner { | ||
| const { options: parsedOptions, runResult } = handleParamsOverloads(options, {}); | ||
| return new Runner(dbadapter, graph, parsedOptions, runResult, runnerNotificationPeriodMillis); | ||
| } | ||
|
|
||
| public static resume( | ||
| dbadapter: dbadapters.IDbAdapter, | ||
| graph: dataform.IExecutionGraph, | ||
| runResult: dataform.IRunResult, | ||
| runnerNotificationPeriodMillis: number = flags.runnerNotificationPeriodMillis.get() | ||
| ): Runner { | ||
| const { options, runResult: parsedRunResult } = handleParamsOverloads(runResult, {}); | ||
| return new Runner(dbadapter, graph, options, parsedRunResult, runnerNotificationPeriodMillis); | ||
| } | ||
|
|
||
| private readonly warehouseStateByTarget: Map<string, dataform.ITableMetadata>; | ||
|
|
||
| private readonly allActionTargets: Set<string>; | ||
|
|
@@ -63,22 +118,40 @@ export class Runner { | |
| private cancelled = false; | ||
| private timeout: NodeJS.Timer; | ||
| private timedOut = false; | ||
| private skipReason: string = ""; | ||
| private executionTask: Promise<dataform.IRunResult>; | ||
| private readonly executionOptions: IExecutionOptions; | ||
|
|
||
| constructor( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we want to use |
||
| dbadapter: dbadapters.IDbAdapter, | ||
| graph: dataform.IExecutionGraph, | ||
| options: IExecutionOptions, | ||
| runResult: dataform.IRunResult, | ||
| runnerNotificationPeriodMillis?: number | ||
| ); | ||
| constructor( | ||
| dbadapter: dbadapters.IDbAdapter, | ||
| graph: dataform.IExecutionGraph, | ||
| optionsOrResult?: RunOptionsOrResult, | ||
| partiallyExecutedRunResult?: dataform.IRunResult, | ||
| runnerNotificationPeriodMillis?: number | ||
| ); | ||
| constructor( | ||
| private readonly dbadapter: dbadapters.IDbAdapter, | ||
| private readonly graph: dataform.IExecutionGraph, | ||
| private readonly executionOptions: IExecutionOptions = {}, | ||
| optionsOrResult: RunOptionsOrResult = {}, | ||
| partiallyExecutedRunResult: dataform.IRunResult = {}, | ||
| private readonly runnerNotificationPeriodMillis: number = flags.runnerNotificationPeriodMillis.get() | ||
| ) { | ||
| const { options, runResult } = handleParamsOverloads( | ||
| optionsOrResult, | ||
| partiallyExecutedRunResult | ||
| ); | ||
| this.executionOptions = options; | ||
| this.runResult = runResult; | ||
| this.allActionTargets = new Set<string>( | ||
| graph.actions.map(action => targetStringifier.stringify(action.target)) | ||
| ); | ||
| this.runResult = { | ||
| actions: [], | ||
| ...partiallyExecutedRunResult | ||
| }; | ||
| this.warehouseStateByTarget = new Map<string, dataform.ITableMetadata>(); | ||
| graph.warehouseState.tables?.forEach(tableMetadata => | ||
| this.warehouseStateByTarget.set( | ||
|
|
@@ -121,7 +194,7 @@ export class Runner { | |
| const timeoutMillis = this.graph.runConfig.timeoutMillis - elapsedTimeMillis; | ||
| this.timeout = setTimeout(() => { | ||
| this.timedOut = true; | ||
| this.cancel(); | ||
| this.cancel("timeout"); | ||
| }, timeoutMillis); | ||
| } | ||
| return this; | ||
|
|
@@ -131,7 +204,16 @@ export class Runner { | |
| this.stopped = true; | ||
| } | ||
|
|
||
| public cancel() { | ||
| public cancel(reason: CancelReason = "user") { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest preparing explicit map of possible reasons to avoid mistakes, and to make it more readable. |
||
| if (!this.skipReason) { | ||
| if (reason === "timeout") { | ||
| this.skipReason = `Run timed out after ${this.graph.runConfig.timeoutMillis / 1000} seconds`; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible that |
||
| } else if (reason === "user") { | ||
| this.skipReason = "Run cancelled"; | ||
| } else { | ||
| this.skipReason = "Run cancelled before action started"; | ||
| } | ||
| } | ||
| this.cancelled = true; | ||
| this.eEmitter.emit(CANCEL_EVENT, undefined, undefined); | ||
| } | ||
|
|
@@ -228,15 +310,28 @@ export class Runner { | |
| if (this.cancelled) { | ||
| const allPendingActions = this.pendingActions; | ||
| this.pendingActions = []; | ||
| allPendingActions.forEach(pendingAction => | ||
| const skipErrorMessage = this.skipReason || "Run cancelled before action started"; | ||
| allPendingActions.forEach(pendingAction => { | ||
| let tasks: dataform.ITaskResult[]; | ||
| if (pendingAction.tasks && pendingAction.tasks.length > 0) { | ||
| tasks = pendingAction.tasks.map((_, i) => ({ | ||
| status: dataform.TaskResult.ExecutionStatus.SKIPPED, | ||
| ...(i === 0 ? { errorMessage: skipErrorMessage } : {}) | ||
| })); | ||
| } else { | ||
| tasks = [ | ||
| { | ||
| status: dataform.TaskResult.ExecutionStatus.SKIPPED, | ||
| errorMessage: skipErrorMessage | ||
| } | ||
| ]; | ||
| } | ||
| this.runResult.actions.push({ | ||
| target: pendingAction.target, | ||
| status: dataform.ActionResult.ExecutionStatus.SKIPPED, | ||
| tasks: pendingAction.tasks.map(() => ({ | ||
| status: dataform.TaskResult.ExecutionStatus.SKIPPED | ||
| })) | ||
| }) | ||
| ); | ||
| tasks | ||
| }); | ||
| }); | ||
| this.notifyListeners(); | ||
| return; | ||
| } | ||
|
|
@@ -361,7 +456,8 @@ export class Runner { | |
| } | ||
| } else { | ||
| actionResult.tasks.push({ | ||
| status: dataform.TaskResult.ExecutionStatus.SKIPPED | ||
| status: dataform.TaskResult.ExecutionStatus.SKIPPED, | ||
| errorMessage: this.skipReason || "Task skipped after cancellation" | ||
| }); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this
projectDirused downstream in latter PRs?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this PR I can see that we are adding this field here, and then we are setting it (under some conditions to "."), but I am not sure how it is used. Possibly it is needed latter.
I can guess what this field does in general by the name, but is it needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is needed in the next PR - #2211. It is passed as the second arg to jitCompiler (via JitCompileChildProcess.compile) — it's the resolution root the worker needs to locate the caller's @dataform/core install. Plumbing it in here is the way to reduce the size of the next PR and still keep the reasonable amount of PRs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is needed in the next PR — #2211. It is passed as the second arg to
jitCompiler(viaJitCompileChildProcess.compile) — it is the resolution root the worker needs to locate the caller's@dataform/coreinstall. Plumbing it in here is the way to reduce the size of the next PR and still keep the reasonable amount of PRs.