@@ -211,8 +211,9 @@ def __init__(
211211 self .config .runtime ['descendants' ]
212212 )
213213
214- self .tasks_to_hold : Set [Tuple [str , 'PointBase' ]] = set ()
215- self .tasks_to_trigger_now : Set ['TaskProxy' ] = set ()
214+ self .tasks_to_hold : set [tuple [str , 'PointBase' ]] = set ()
215+ self .tasks_to_trigger_now : set ['TaskProxy' ] = set ()
216+ self .pre_start_tasks_to_trigger : set [tuple [str , 'PointBase' ]] = set ()
216217
217218 def set_stop_task (self , task_id ):
218219 """Set stop after a task."""
@@ -837,9 +838,11 @@ def spawn_to_rh_limit(
837838 longer parentless, and/or hit the runahead limit.
838839
839840 """
840- if not flow_nums or point is None :
841- # Force-triggered no-flow task.
842- # Or called with an invalid next_point.
841+ if (
842+ not flow_nums # Force-triggered no-flow task
843+ or point is None # Reached end of sequence?
844+ or point < self .config .start_point # Warm start
845+ ):
843846 return
844847 if self .runahead_limit_point is None :
845848 self .compute_runahead ()
@@ -848,7 +851,7 @@ def spawn_to_rh_limit(
848851
849852 is_xtrig_sequential = False
850853 while point is not None and (point <= self .runahead_limit_point ):
851- if tdef .is_parentless (point ):
854+ if tdef .is_parentless (point , cutoff = self . config . start_point ):
852855 ntask , is_in_pool , is_xtrig_sequential = (
853856 self .get_or_spawn_task (point , tdef , flow_nums )
854857 )
@@ -866,7 +869,11 @@ def spawn_to_rh_limit(
866869
867870 def spawn_if_parentless (self , tdef , point , flow_nums ):
868871 """Spawn a task if parentless, regardless of runahead limit."""
869- if flow_nums and point is not None and tdef .is_parentless (point ):
872+ if (
873+ flow_nums
874+ and point is not None
875+ and tdef .is_parentless (point , cutoff = self .config .start_point )
876+ ):
870877 ntask , is_in_pool , _ = self .get_or_spawn_task (
871878 point , tdef , flow_nums
872879 )
@@ -906,6 +913,9 @@ def remove(self, itask: 'TaskProxy', reason: Optional[str] = None) -> None:
906913 pass
907914 else :
908915 self .tasks_to_trigger_now .discard (itask )
916+ self .pre_start_tasks_to_trigger .discard (
917+ (itask .tdef .name , itask .point )
918+ )
909919 self .tasks_removed = True
910920 self .active_tasks_changed = True
911921 if not self .active_tasks [itask .point ]:
@@ -1727,8 +1737,8 @@ def can_be_spawned(self, name: str, point: 'PointBase') -> bool:
17271737 return True
17281738
17291739 def _get_task_history (
1730- self , name : str , point : 'PointBase' , flow_nums : Set [ int ]
1731- ) -> Tuple [int , Optional [ str ] , bool ]:
1740+ self , name : str , point : 'PointBase' , flow_nums : 'FlowNums'
1741+ ) -> tuple [int , str | None , bool ]:
17321742 """Get submit_num, status, flow_wait for point/name in flow_nums.
17331743
17341744 Args:
@@ -1766,7 +1776,10 @@ def _get_task_history(
17661776 return submit_num , status , flow_wait
17671777
17681778 def _load_historical_outputs (self , itask : 'TaskProxy' ) -> None :
1769- """Load a task's historical outputs from the DB."""
1779+ """Load a task's historical outputs from the DB.
1780+
1781+ NOTE this creates a task_states/task_outputs DB entry if not present.
1782+ """
17701783 info = self .workflow_db_mgr .pri_dao .select_task_outputs (
17711784 itask .tdef .name , str (itask .point ))
17721785 if not info :
@@ -1803,9 +1816,9 @@ def spawn_task(
18031816 self ,
18041817 name : str ,
18051818 point : 'PointBase' ,
1806- flow_nums : Set [ int ] ,
1819+ flow_nums : 'FlowNums' ,
18071820 flow_wait : bool = False ,
1808- ) -> Optional [ TaskProxy ] :
1821+ ) -> TaskProxy | None :
18091822 """Return a new task proxy for the given flow if possible.
18101823
18111824 We need to hit the DB for:
@@ -1826,8 +1839,18 @@ def spawn_task(
18261839 self ._get_task_history (name , point , flow_nums )
18271840 )
18281841
1842+ if (
1843+ not prev_status
1844+ and point < self .config .start_point
1845+ and flow_nums .issuperset ({1 })
1846+ # Warm start - treat pre-startcp tasks as already run in flow=1,
1847+ # unless manually triggered:
1848+ and (name , point ) not in self .pre_start_tasks_to_trigger
1849+ ):
1850+ return None
1851+
18291852 # Create the task proxy with any completed outputs loaded.
1830- itask = self ._get_task_proxy_db_outputs (
1853+ itask = self ._load_db_task_proxy (
18311854 point ,
18321855 self .config .get_taskdef (name ),
18331856 flow_nums ,
@@ -1925,7 +1948,7 @@ def _spawn_after_flow_wait(self, itask: TaskProxy) -> None:
19251948 self .workflow_db_mgr .put_update_task_flow_wait (itask )
19261949 return None
19271950
1928- def _get_task_proxy_db_outputs (
1951+ def _load_db_task_proxy (
19291952 self ,
19301953 point : 'PointBase' ,
19311954 taskdef : 'TaskDef' ,
@@ -1935,8 +1958,11 @@ def _get_task_proxy_db_outputs(
19351958 transient : bool = False ,
19361959 is_manual_submit : bool = False ,
19371960 submit_num : int = 0 ,
1938- ) -> Optional ['TaskProxy' ]:
1939- """Spawn a task, update outputs from DB."""
1961+ ) -> 'TaskProxy | None' :
1962+ """Spawn a task, update outputs from DB.
1963+
1964+ NOTE this creates a task_states/task_outputs DB entry if not present.
1965+ """
19401966
19411967 if not self .can_be_spawned (taskdef .name , point ):
19421968 return None
@@ -2140,7 +2166,7 @@ def set_prereqs_and_outputs(
21402166 no_op = False
21412167 else :
21422168 # Outputs (may be empty list)
2143- trans = self ._get_task_proxy_db_outputs (
2169+ trans = self ._load_db_task_proxy (
21442170 icycle , tdef , flow_nums ,
21452171 flow_wait = flow_wait , transient = True
21462172 )
0 commit comments