16
16
"""Utilities supporting simulation and skip modes
17
17
"""
18
18
19
+ from dataclasses import dataclass
19
20
from typing import TYPE_CHECKING , Any , Dict , List , Optional , Union
20
21
from time import time
21
22
23
+ from cylc .flow import LOG
22
24
from cylc .flow .cycling .loader import get_point
23
- from cylc .flow .network . resolvers import TaskMsg
25
+ from cylc .flow .exceptions import PointParsingError
24
26
from cylc .flow .platforms import FORBIDDEN_WITH_PLATFORM
25
27
from cylc .flow .task_state import (
26
28
TASK_STATUS_RUNNING ,
27
29
TASK_STATUS_FAILED ,
28
30
TASK_STATUS_SUCCEEDED ,
29
31
)
30
- from cylc .flow .wallclock import get_current_time_string
32
+ from cylc .flow .wallclock import get_unix_time_from_time_string
31
33
32
34
from metomi .isodatetime .parsers import DurationParser
33
35
34
36
if TYPE_CHECKING :
35
- from queue import Queue
36
- from cylc .flow .cycling import PointBase
37
+ from cylc .flow .task_events_mgr import TaskEventsManager
37
38
from cylc .flow .task_proxy import TaskProxy
39
+ from cylc .flow .workflow_db_mgr import WorkflowDatabaseManager
40
+ from cylc .flow .cycling import PointBase
41
+
42
+
43
+ @dataclass
44
+ class ModeSettings :
45
+ """A store of state for simulation modes.
46
+
47
+ Used instead of modifying the runtime config.
48
+
49
+ Args:
50
+ itask:
51
+ The task proxy this submission relates to.
52
+ broadcast_mgr:
53
+ The broadcast manager is used to apply any runtime alterations
54
+ pre simulated submission.
55
+ db_mgr:
56
+ The database manager must be provided for simulated jobs
57
+ that are being resumed after workflow restart. It is used to
58
+ extract the original scheduled finish time for the job.
59
+
60
+ Attrs:
61
+ simulated_run_length:
62
+ The length of time this simulated job will take to run in seconds.
63
+ timeout:
64
+ The wall-clock time at which this simulated job will finish as
65
+ a Unix epoch time.
66
+ sim_task_fails:
67
+ True, if this job is intended to fail when it finishes, else False.
68
+
69
+ """
70
+ simulated_run_length : float = 0.0
71
+ sim_task_fails : bool = False
72
+ timeout : float = 0.0
73
+
74
+ def __init__ (
75
+ self ,
76
+ itask : 'TaskProxy' ,
77
+ db_mgr : 'WorkflowDatabaseManager' ,
78
+ rtconfig : Dict [str , Any ]
79
+ ):
80
+
81
+ # itask.summary['started_time'] and mode_settings.timeout need
82
+ # repopulating from the DB on workflow restart:
83
+ started_time = itask .summary ['started_time' ]
84
+ try_num = None
85
+ if started_time is None :
86
+ # Get DB info
87
+ db_info = db_mgr .pri_dao .select_task_job (
88
+ itask .tokens ['cycle' ],
89
+ itask .tokens ['task' ],
90
+ itask .tokens ['job' ],
91
+ )
92
+
93
+ # Get the started time:
94
+ if db_info ['time_submit' ]:
95
+ started_time = get_unix_time_from_time_string (
96
+ db_info ["time_submit" ])
97
+ itask .summary ['started_time' ] = started_time
98
+ else :
99
+ started_time = time ()
100
+
101
+ # Get the try number:
102
+ try_num = db_info ["try_num" ]
103
+
104
+ # Parse fail cycle points:
105
+ if rtconfig != itask .tdef .rtconfig :
106
+ try :
107
+ rtconfig ["simulation" ][
108
+ "fail cycle points"
109
+ ] = parse_fail_cycle_points (
110
+ rtconfig ["simulation" ]["fail cycle points" ]
111
+ )
112
+ except PointParsingError as exc :
113
+ # Broadcast Fail CP didn't parse
114
+ LOG .warning (
115
+ 'Broadcast fail cycle point was invalid:\n '
116
+ f' { exc .args [0 ]} '
117
+ )
118
+ rtconfig ['simulation' ][
119
+ 'fail cycle points'
120
+ ] = itask .tdef .rtconfig ['simulation' ]['fail cycle points' ]
121
+
122
+ # Calculate simulation info:
123
+ self .simulated_run_length = (
124
+ get_simulated_run_len (rtconfig ))
125
+ self .sim_task_fails = sim_task_failed (
126
+ rtconfig ['simulation' ],
127
+ itask .point ,
128
+ try_num or itask .get_try_num ()
129
+ )
130
+ self .timeout = started_time + self .simulated_run_length
38
131
39
132
40
133
def configure_sim_modes (taskdefs , sim_mode ):
@@ -46,23 +139,17 @@ def configure_sim_modes(taskdefs, sim_mode):
46
139
for tdef in taskdefs :
47
140
# Compute simulated run time by scaling the execution limit.
48
141
rtc = tdef .rtconfig
49
- sleep_sec = get_simulated_run_len (rtc )
50
142
51
- rtc ['execution time limit' ] = (
52
- sleep_sec + DurationParser ().parse (str (
53
- rtc ['simulation' ]['time limit buffer' ])).get_seconds ()
54
- )
55
-
56
- rtc ['simulation' ]['simulated run length' ] = sleep_sec
57
143
rtc ['submission retry delays' ] = [1 ]
58
144
59
- # Generate dummy scripting.
60
- rtc ['init-script' ] = ""
61
- rtc ['env-script' ] = ""
62
- rtc ['pre-script' ] = ""
63
- rtc ['post-script' ] = ""
64
- rtc ['script' ] = build_dummy_script (
65
- rtc , sleep_sec ) if dummy_mode else ""
145
+ if dummy_mode :
146
+ # Generate dummy scripting.
147
+ rtc ['init-script' ] = ""
148
+ rtc ['env-script' ] = ""
149
+ rtc ['pre-script' ] = ""
150
+ rtc ['post-script' ] = ""
151
+ rtc ['script' ] = build_dummy_script (
152
+ rtc , get_simulated_run_len (rtc ))
66
153
67
154
disable_platforms (rtc )
68
155
@@ -77,12 +164,13 @@ def configure_sim_modes(taskdefs, sim_mode):
77
164
78
165
79
166
def get_simulated_run_len (rtc : Dict [str , Any ]) -> int :
80
- """Get simulated run time.
167
+ """Calculate simulation run time from a task's config .
81
168
82
169
rtc = run time config
83
170
"""
84
171
limit = rtc ['execution time limit' ]
85
172
speedup = rtc ['simulation' ]['speedup factor' ]
173
+
86
174
if limit and speedup :
87
175
sleep_sec = (DurationParser ().parse (
88
176
str (limit )).get_seconds () / speedup )
@@ -145,19 +233,26 @@ def parse_fail_cycle_points(
145
233
True
146
234
>>> this([])
147
235
[]
236
+ >>> this(None) is None
237
+ True
148
238
"""
149
- f_pts : 'Optional[List[PointBase]]'
150
- if 'all' in f_pts_orig :
239
+ f_pts : 'Optional[List[PointBase]]' = []
240
+ if (
241
+ f_pts_orig is None
242
+ or f_pts_orig and 'all' in f_pts_orig
243
+ ):
151
244
f_pts = None
152
- else :
245
+ elif f_pts_orig :
153
246
f_pts = []
154
247
for point_str in f_pts_orig :
155
248
f_pts .append (get_point (point_str ).standardise ())
156
249
return f_pts
157
250
158
251
159
252
def sim_time_check (
160
- message_queue : 'Queue[TaskMsg]' , itasks : 'List[TaskProxy]'
253
+ task_events_manager : 'TaskEventsManager' ,
254
+ itasks : 'List[TaskProxy]' ,
255
+ db_mgr : 'WorkflowDatabaseManager' ,
161
256
) -> bool :
162
257
"""Check if sim tasks have been "running" for as long as required.
163
258
@@ -166,38 +261,42 @@ def sim_time_check(
166
261
Returns:
167
262
True if _any_ simulated task state has changed.
168
263
"""
169
- sim_task_state_changed = False
170
264
now = time ()
265
+ sim_task_state_changed : bool = False
171
266
for itask in itasks :
172
267
if itask .state .status != TASK_STATUS_RUNNING :
173
268
continue
174
- # Started time is not set on restart
175
- if itask .summary ['started_time' ] is None :
176
- itask .summary ['started_time' ] = now
177
- timeout = (
178
- itask .summary ['started_time' ] +
179
- itask .tdef .rtconfig ['simulation' ]['simulated run length' ]
180
- )
181
- if now > timeout :
182
- job_d = itask .tokens .duplicate (job = str (itask .submit_num ))
183
- now_str = get_current_time_string ()
184
- if sim_task_failed (
185
- itask .tdef .rtconfig ['simulation' ],
186
- itask .point ,
187
- itask .get_try_num ()
188
- ):
189
- message_queue .put (
190
- TaskMsg (job_d , now_str , 'CRITICAL' , TASK_STATUS_FAILED )
269
+
270
+ # This occurs if the workflow has been restarted.
271
+ if itask .mode_settings is None :
272
+ rtconfig = task_events_manager .broadcast_mgr .get_updated_rtconfig (
273
+ itask )
274
+ itask .mode_settings = ModeSettings (
275
+ itask ,
276
+ db_mgr ,
277
+ rtconfig
278
+ )
279
+
280
+ if now > itask .mode_settings .timeout :
281
+ if itask .mode_settings .sim_task_fails :
282
+ task_events_manager .process_message (
283
+ itask , 'CRITICAL' , TASK_STATUS_FAILED ,
284
+ flag = task_events_manager .FLAG_RECEIVED
191
285
)
192
286
else :
193
- # Simulate message outputs.
194
- for msg in itask .tdef .rtconfig ['outputs' ].values ():
195
- message_queue .put (
196
- TaskMsg (job_d , now_str , 'DEBUG' , msg )
197
- )
198
- message_queue .put (
199
- TaskMsg (job_d , now_str , 'DEBUG' , TASK_STATUS_SUCCEEDED )
287
+ task_events_manager .process_message (
288
+ itask , 'DEBUG' , TASK_STATUS_SUCCEEDED ,
289
+ flag = task_events_manager .FLAG_RECEIVED
200
290
)
291
+ # Simulate message outputs.
292
+ for msg in itask .tdef .rtconfig ['outputs' ].values ():
293
+ task_events_manager .process_message (
294
+ itask , 'DEBUG' , msg ,
295
+ flag = task_events_manager .FLAG_RECEIVED
296
+ )
297
+
298
+ # We've finished this pseudo job, so delete all the mode settings.
299
+ itask .mode_settings = None
201
300
sim_task_state_changed = True
202
301
return sim_task_state_changed
203
302
0 commit comments