-
Notifications
You must be signed in to change notification settings - Fork 248
Evaluate CWL when conditionals before scheduling to avoid wasted batch-system submissions (#3990)
#5569
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: master
Are you sure you want to change the base?
Evaluate CWL when conditionals before scheduling to avoid wasted batch-system submissions (#3990)
#5569
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 |
|---|---|---|
|
|
@@ -2890,10 +2890,13 @@ def run(self, file_store: AbstractFileStore) -> CWLObjectType: | |
|
|
||
| class CWLJobWrapper(CWLNamedJob): | ||
| """ | ||
| Wrap a CWL job that uses dynamic resources requirement. | ||
| Wrap a CWL job that uses a dynamic resources requirement, or that may be | ||
| skipped by a `when` conditional that can't be safely evaluated until the | ||
|
Member
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. Saying it that way makes it sound like there are some conditionals that can be safely evaluated without the step's inputs, and others that can't, and our code actually knows the difference and acts on it to evaluate only the conditionals that need the inputs with this mechanism. That's not true, so we can probably just say "a conditional" instead of the long mis-explanatory phrase. |
||
| step's inputs are resolved. | ||
|
|
||
| When executed, this creates a new child job which has the correct resource | ||
| requirement set. | ||
| When executed, this runs on the leader with minimal resources, resolves | ||
| the job's inputs, and then either reports the step as skipped or creates | ||
| a new child job which has the correct resource requirement set. | ||
|
Comment on lines
-2895
to
+2899
Member
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 whole piece should probably be cut, because it's a summary rather than an explanation. If we do want to write down the responsibility, we should say something like "This job is responsible for creating a CWLJob child with the right resource requirements, when the job should not be skipped." |
||
| """ | ||
|
|
||
| def __init__( | ||
|
|
@@ -2921,7 +2924,7 @@ def run(self, file_store: AbstractFileStore) -> Any: | |
| """Create a child job with the correct resource requirements set.""" | ||
| cwljob = resolve_dict_w_promises(self.cwljob, file_store) | ||
|
|
||
| # Check confitional to license full evaluation of job inputs. | ||
| # Check conditional to license full evaluation of job inputs. | ||
| if self.conditional.is_false(cwljob): | ||
| return self.conditional.skipped_outputs() | ||
|
|
||
|
|
@@ -3507,29 +3510,37 @@ def makeJob( | |
| wfjob.addFollowOn(followOn) | ||
| return wfjob, followOn | ||
| else: | ||
| # Decied if we have any requirements we care about that are dynamic | ||
| # Decide if we have any requirements we care about that are dynamic | ||
| REQUIREMENT_TYPES = [ | ||
| "ResourceRequirement", | ||
| "http://commonwl.org/cwltool#CUDARequirement", | ||
| ] | ||
| has_dynamic_resource_requirement = False | ||
| for requirement_type in REQUIREMENT_TYPES: | ||
| req, _ = tool.get_requirement(requirement_type) | ||
| if req: | ||
| for r in req.values(): | ||
| if isinstance(r, str) and ("$(" in r or "${" in r): | ||
| # One of the keys in this requirement has a text substitution in it. | ||
| # TODO: This is not a real lex! | ||
| has_dynamic_resource_requirement = True | ||
|
|
||
| # Found a dynamic resource requirement so use a job wrapper | ||
| job_wrapper = CWLJobWrapper( | ||
| cast(ToilCommandLineTool, tool), | ||
| jobobj, | ||
| runtime_context, | ||
| parent_name=parent_name, | ||
| conditional=conditional, | ||
| ) | ||
| return job_wrapper, job_wrapper | ||
| # Otherwise, all requirements are known now. | ||
| if has_dynamic_resource_requirement or ( | ||
| conditional is not None and conditional.expression is not None | ||
| ): | ||
| # Resource requirements and the `when` conditional can depend on | ||
|
Member
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. It probably makes more sense to talk about conditionals in the code's terms, where we have a |
||
| # promises from upstream steps that only resolve once the job | ||
| # runs, so check them in a cheap local wrapper first. | ||
| job_wrapper = CWLJobWrapper( | ||
| cast(ToilCommandLineTool, tool), | ||
| jobobj, | ||
| runtime_context, | ||
| parent_name=parent_name, | ||
| conditional=conditional, | ||
| ) | ||
| return job_wrapper, job_wrapper | ||
| # Otherwise, all requirements are known now, and the step is | ||
| # unconditional, so it can be scheduled directly. | ||
| job = CWLJob( | ||
| tool, | ||
| jobobj, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # `consume`'s `when` references `produce`'s output, so the condition can only | ||
| # be evaluated once that output's promise has resolved. | ||
| # See <https://github.com/DataBiosphere/toil/issues/3990>. | ||
| cwlVersion: v1.2 | ||
| class: Workflow | ||
| requirements: | ||
| InlineJavascriptRequirement: {} | ||
| inputs: | ||
| sleep: int | ||
| outputs: [] | ||
| steps: | ||
| produce: | ||
| in: | ||
| sleep: sleep | ||
|
Member
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. We should rename this input to something other than |
||
| out: [result] | ||
| run: | ||
| cwlVersion: v1.2 | ||
| class: ExpressionTool | ||
| requirements: | ||
| InlineJavascriptRequirement: {} | ||
| inputs: | ||
| sleep: int | ||
| outputs: | ||
| result: int | ||
| expression: "$({'result': inputs.sleep})" | ||
| consume: | ||
| in: | ||
| result: produce/result | ||
| when: $(inputs.result > 1) | ||
| run: | ||
| cwlVersion: v1.2 | ||
| class: CommandLineTool | ||
| inputs: | ||
| result: int | ||
| baseCommand: "true" | ||
| outputs: [] | ||
| out: [] | ||
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.
Ideally we want a 1-line summary at the top of our docstring. Maybe say that this job determines how and whether to run the wrapped job?