Skip to content

Commit 1901c8c

Browse files
committed
implement set-task --pre [skip ci]
1 parent 569797d commit 1901c8c

File tree

5 files changed

+117
-41
lines changed

5 files changed

+117
-41
lines changed

cylc/flow/network/resolvers.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -811,26 +811,28 @@ def force_spawn_children(
811811
self,
812812
tasks: Iterable[str],
813813
outputs: Optional[Iterable[str]] = None,
814-
flow: Iterable[str] = None,
814+
prerequisites: Optional[Iterable[str]] = None,
815+
flow: Optional[Iterable[str]] = None,
815816
flow_wait: bool = False,
816-
flow_descr: str = ""
817+
flow_descr: Optional[str] = None,
817818
) -> Tuple[bool, str]:
818819
"""Spawn children of given task outputs.
819820
820821
User-facing method name: set_task.
821822
822823
Args:
823-
tasks: List of identifiers or task globs.
824-
outputs: List of outputs to spawn on.
825-
flow (list):
826-
Flow ownership of triggered tasks.
824+
tasks: Identifiers or task globs.
825+
outputs: Outputs to set complete.
826+
prerequisites: Prerequisites to set satisfied.
827+
flow: Flows that spawned tasks should belong to.
827828
"""
828829
self.schd.command_queue.put(
829830
(
830831
"force_spawn_children",
831832
(tasks,),
832833
{
833834
"outputs": outputs,
835+
"prerequisites": prerequisites,
834836
"flow": flow,
835837
"flow_wait": flow_wait,
836838
"flow_descr": flow_descr,

cylc/flow/network/schema.py

-1
Original file line numberDiff line numberDiff line change
@@ -2111,7 +2111,6 @@ class Meta:
21112111
class Arguments(TaskMutation.Arguments, FlowMutationArguments):
21122112
outputs = graphene.List(
21132113
String,
2114-
default_value=[TASK_OUTPUT_SUCCEEDED],
21152114
description='List of task outputs to set complete.'
21162115
)
21172116
prerequisites = graphene.List(

cylc/flow/scheduler.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,7 @@ async def main_loop(self) -> None:
16761676
tinit = time()
16771677

16781678
# Useful for debugging core scheduler issues:
1679+
# import logging
16791680
# self.pool.log_task_pool(logging.CRITICAL)
16801681
if self.incomplete_ri_map:
16811682
self.manage_remote_init()
@@ -2125,14 +2126,14 @@ def command_force_trigger_tasks(self, items, flow, flow_wait, flow_descr):
21252126
items, flow, flow_wait, flow_descr)
21262127

21272128
def command_force_spawn_children(
2128-
self, items, outputs, flow, flow_wait, flow_descr
2129+
self, items, outputs, prerequisites, flow, flow_wait, flow_descr
21292130
):
21302131
"""Force spawn task successors.
21312132
21322133
User-facing method name: set_task.
21332134
"""
21342135
return self.pool.force_spawn_children(
2135-
items, outputs, flow, flow_wait, flow_descr
2136+
items, outputs, prerequisites, flow, flow_wait, flow_descr
21362137
)
21372138

21382139
def _update_profile_info(self, category, amount, amount_format="%s"):

cylc/flow/scripts/set_task.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@
3131
- started implies submitted
3232
- succeeded and failed imply started
3333
- custom outputs and expired do not imply any other outputs
34+
35+
Specify prerequisites in the form "point/task:message".
36+
3437
"""
3538

3639
from functools import partial
3740
from optparse import Values
3841

42+
from cylc.flow.exceptions import InputError
3943
from cylc.flow.network.client_factory import get_client
4044
from cylc.flow.network.multi import call_multi
4145
from cylc.flow.option_parsers import (
@@ -53,6 +57,8 @@
5357
ERR_OPT_FLOW_WAIT,
5458
validate_flow_opts
5559
)
60+
from cylc.flow.task_id import TaskID
61+
from cylc.flow.task_pool import REC_CLI_PREREQ
5662

5763

5864
MUTATION = '''
@@ -105,6 +111,7 @@ def get_option_parser() -> COP:
105111
"Set task prerequisites satisfied."
106112
' May be "all", which is equivalent to "cylc trigger".'
107113
" (Multiple use allowed, may be comma separated)."
114+
" Prerequisite format: 'point/task:message'."
108115
),
109116
action="append", default=None, dest="prerequisites"
110117
)
@@ -134,6 +141,25 @@ def get_option_parser() -> COP:
134141
return parser
135142

136143

144+
def get_prerequisite_opts(options):
145+
"""Convert prerequisite inputs to a single list, and validate.
146+
147+
This:
148+
--pre=a -pre=b,c
149+
is equivalent to this:
150+
--pre=a,b,c
151+
152+
Validation: format <point>/<name>:<qualifier>
153+
"""
154+
result = []
155+
for p in options.prerequisites:
156+
result += p.split(',')
157+
for p in result:
158+
if not REC_CLI_PREREQ.match(p):
159+
raise InputError(f"Bad prerequisite: {p}")
160+
return result
161+
162+
137163
async def run(options: 'Values', workflow_id: str, *tokens_list) -> None:
138164
pclient = get_client(workflow_id, timeout=options.comms_timeout)
139165

@@ -146,7 +172,7 @@ async def run(options: 'Values', workflow_id: str, *tokens_list) -> None:
146172
for tokens in tokens_list
147173
],
148174
'outputs': options.outputs,
149-
'prerequisites': options.prerequisites,
175+
'prerequisites': get_prerequisite_opts(options),
150176
'flow': options.flow,
151177
'flowWait': options.flow_wait,
152178
'flowDescr': options.flow_descr
@@ -158,8 +184,10 @@ async def run(options: 'Values', workflow_id: str, *tokens_list) -> None:
158184

159185
@cli_function(get_option_parser)
160186
def main(parser: COP, options: 'Values', *ids) -> None:
187+
161188
if options.flow is None:
162189
options.flow = [FLOW_ALL] # default to all active flows
190+
163191
validate_flow_opts(options)
164192

165193
call_multi(

cylc/flow/task_pool.py

+77-31
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
"""Wrangle task proxies to manage the workflow."""
1818

19+
import re
1920
from contextlib import suppress
2021
from collections import Counter
2122
import json
@@ -85,6 +86,15 @@
8586
Pool = Dict['PointBase', Dict[str, TaskProxy]]
8687

8788

89+
# CLI prerequisite pattern: point/name:label
90+
REC_CLI_PREREQ = re.compile(
91+
rf"({TaskID.POINT_RE})" +
92+
rf"{TaskID.DELIM2}" +
93+
rf"({TaskID.NAME_RE})" +
94+
r':' + r'(\w+)' # TODO: formally define qualifier RE?
95+
)
96+
97+
8898
class TaskPool:
8999
"""Task pool of a workflow."""
90100

@@ -702,7 +712,7 @@ def _get_spawned_or_merged_task(
702712
# ntask does not exist: spawn it in the flow.
703713
ntask = self.spawn_task(name, point, flow_nums)
704714
else:
705-
# ntask already exists (n=0 or incomplete): merge flows.
715+
# ntask already exists (n=0): merge flows.
706716
self.merge_flows(ntask, flow_nums)
707717
return ntask # may be None
708718

@@ -1259,7 +1269,7 @@ def spawn_on_output(self, itask, output, forced=False):
12591269
12601270
Args:
12611271
tasks: List of identifiers or task globs.
1262-
outputs: List of outputs to spawn on.
1272+
output: Output to spawn on.
12631273
forced: If True this is a manual spawn command.
12641274
12651275
"""
@@ -1576,45 +1586,79 @@ def spawn_task(
15761586
self.db_add_new_flow_rows(itask)
15771587
return itask
15781588

1589+
# TODO RENAME THIS METHOD
15791590
def force_spawn_children(
15801591
self,
15811592
items: Iterable[str],
15821593
outputs: List[str],
1594+
prerequisites: List[str],
15831595
flow: List[str],
15841596
flow_wait: bool = False,
1585-
flow_descr: str = "",
1597+
flow_descr: Optional[str] = None
15861598
):
1587-
"""Spawn downstream children of given outputs, on user command.
1599+
"""Force set prerequistes satisfied and outputs completed.
15881600
1589-
User-facing command name: set_task. Creates a transient parent just
1590-
for the purpose of spawning children.
1601+
For prerequisites:
1602+
- spawn target task if necessary, and set the prerequisites
1603+
1604+
For outputs:
1605+
- spawn child tasks if necessary, and spawn/update prereqs of
1606+
children
1607+
- TODO: set outputs completed in the target task (DB, and task
1608+
proxy if already spawned - but don't spawn a new one)
15911609
15921610
Args:
1593-
items: Identifiers for matching task definitions, each with the
1594-
form "point/name".
1595-
outputs: List of outputs to spawn on
1596-
flow: Flow number to attribute the outputs
1611+
items: Identifiers for matching task definitions
1612+
prerequisites: prerequisites to set and spawn children of
1613+
outputs: Outputs to set and spawn children of
1614+
flow: Flow numbers for spawned or updated tasks
1615+
flow_wait: wait for flows to catch up before continuing
1616+
flow_descr: description of new flow
15971617
15981618
"""
1599-
outputs = outputs or [TASK_OUTPUT_SUCCEEDED]
1600-
flow_nums = self._flow_cmd_helper(flow)
1619+
if not outputs and not prerequisites:
1620+
# Default: set all required outputs.
1621+
outputs = outputs or [TASK_OUTPUT_SUCCEEDED]
1622+
1623+
flow_nums = self._flow_cmd_helper(flow, flow_descr)
16011624
if flow_nums is None:
1602-
return
1625+
return
16031626

16041627
n_warnings, task_items = self.match_taskdefs(items)
16051628
for (_, point), taskdef in sorted(task_items.items()):
1606-
# This the parent task:
1607-
itask = TaskProxy(
1608-
self.tokens,
1609-
taskdef,
1610-
point,
1611-
flow_nums=flow_nums,
1629+
1630+
itask = self._get_spawned_or_merged_task(
1631+
point, taskdef.name, flow_nums
16121632
)
1613-
# Spawn children of selected outputs.
1614-
for trig, out, _ in itask.state.outputs.get_all():
1615-
if trig in outputs:
1616-
LOG.info(f"[{itask}] Forced spawning on {out}")
1617-
self.spawn_on_output(itask, out, forced=True)
1633+
if itask is None:
1634+
# Not in pool but was spawned already in this flow.
1635+
return
1636+
1637+
if outputs:
1638+
# Spawn children of outputs, add them to the pool.
1639+
# (Don't add the target task to pool if we just spawned it)
1640+
for trig, out, _ in itask.state.outputs.get_all():
1641+
if trig in outputs:
1642+
LOG.info(f"[{itask}] Forced spawning on {out}")
1643+
self.spawn_on_output(itask, out, forced=True)
1644+
self.workflow_db_mgr.put_update_task_outputs(itask)
1645+
1646+
if prerequisites:
1647+
for pre in prerequisites:
1648+
m = REC_CLI_PREREQ.match(pre)
1649+
if m is not None:
1650+
itask.state.satisfy_me({m.groups()})
1651+
else:
1652+
# TODO warn here? (checked on CLI)
1653+
continue
1654+
1655+
self.data_store_mgr.delta_task_prerequisite(itask)
1656+
self.add_to_pool(itask) # move from hidden if necessary
1657+
if (
1658+
self.runahead_limit_point is not None
1659+
and itask.point <= self.runahead_limit_point
1660+
):
1661+
self.rh_release_and_queue(itask)
16181662

16191663
def _get_active_flow_nums(self) -> Set[int]:
16201664
"""Return all active, or most recent previous, flow numbers.
@@ -1639,8 +1683,12 @@ def remove_tasks(self, items):
16391683
self.release_runahead_tasks()
16401684
return len(bad_items)
16411685

1642-
def _flow_cmd_helper(self, flow):
1643-
# TODO type hints
1686+
def _flow_cmd_helper(
1687+
self,
1688+
flow: List[str],
1689+
flow_descr: Optional[str]
1690+
) -> Optional[Set[int]]:
1691+
"""TODO"""
16441692
if set(flow).intersection({FLOW_ALL, FLOW_NEW, FLOW_NONE}):
16451693
if len(flow) != 1:
16461694
LOG.warning(
@@ -1669,7 +1717,7 @@ def force_trigger_tasks(
16691717
flow: List[str],
16701718
flow_wait: bool = False,
16711719
flow_descr: Optional[str] = None
1672-
) -> int:
1720+
):
16731721
"""Manual task triggering.
16741722
16751723
Don't get a new flow number for existing n=0 tasks (e.g. incomplete
@@ -1678,9 +1726,9 @@ def force_trigger_tasks(
16781726
Queue the task if not queued, otherwise release it to run.
16791727
16801728
"""
1681-
flow_nums = self._flow_cmd_helper(flow)
1729+
flow_nums = self._flow_cmd_helper(flow, flow_descr)
16821730
if flow_nums is None:
1683-
return
1731+
return
16841732

16851733
# n_warnings, task_items = self.match_taskdefs(items)
16861734
itasks, future_tasks, unmatched = self.filter_task_proxies(
@@ -1729,8 +1777,6 @@ def force_trigger_tasks(
17291777
# De-queue it to run now.
17301778
self.task_queue_mgr.force_release_task(itask)
17311779

1732-
return len(unmatched)
1733-
17341780
def sim_time_check(self, message_queue: 'Queue[TaskMsg]') -> bool:
17351781
"""Simulation mode: simulate task run times and set states."""
17361782
if not self.config.run_mode('simulation'):

0 commit comments

Comments
 (0)