Skip to content

Commit f8f0f51

Browse files
services - play: fix timeout implementation
* We were applying a timeout to the `Popen.wait` method, however, this doesn't actually kill the process (as one might expect) when the timeout elapses, it just stops waiting for it?!
1 parent 87ae6d3 commit f8f0f51

File tree

2 files changed

+26
-15
lines changed

2 files changed

+26
-15
lines changed

cylc/uiserver/resolvers.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
DEVNULL,
2828
PIPE,
2929
Popen,
30+
TimeoutExpired,
3031
)
3132
from textwrap import indent
3233
from time import time
@@ -306,6 +307,7 @@ async def run_command(
306307
The application log, used to record this command invocation.
307308
timeout:
308309
Length of time to wait for the command to complete.
310+
The command will be killed if the timeout elapses.
309311
success_msg:
310312
Message to be used in the response if the command succeeds.
311313
fail_msg:
@@ -341,7 +343,7 @@ async def run_command(
341343
env.pop('CYLC_ENV_NAME', None)
342344
env['CYLC_VERSION'] = cylc_version
343345

344-
# run cylc play
346+
# run command
345347
proc = Popen(
346348
cmd,
347349
env=env,
@@ -350,11 +352,21 @@ async def run_command(
350352
stderr=PIPE,
351353
text=True
352354
)
353-
ret_code = proc.wait(timeout=timeout)
355+
356+
try:
357+
ret_code = proc.wait(timeout=timeout)
358+
except TimeoutExpired as exc:
359+
proc.kill()
360+
ret_code = 124 # mimic `timeout` command error code
361+
# NOTE: preserve any stderr that the command produced this
362+
# far as this may help with debugging
363+
out, err = proc.communicate()
364+
err = str(exc) + (('\n' + err) if err else '')
365+
else:
366+
out, err = proc.communicate()
354367

355368
if ret_code:
356369
msg = f"{fail_msg} ({ret_code}): {cmd_repr}"
357-
out, err = proc.communicate()
358370
results[wflow] = err.strip() or out.strip() or msg
359371
log.error(
360372
f"{msg}\n"

cylc/uiserver/tests/test_resolvers.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@
2424
from subprocess import Popen, TimeoutExpired
2525
from types import SimpleNamespace
2626

27-
import sys
28-
if sys.version_info >= (3, 11):
29-
from asyncio import timeout
30-
else:
31-
from async_timeout import timeout
32-
3327
from cylc.flow import CYLC_LOG
3428
from cylc.flow.exceptions import CylcError
3529
from cylc.flow.id import Tokens
@@ -144,6 +138,7 @@ async def test_start_services(
144138
return_value=Mock(
145139
spec=Popen,
146140
wait=Mock(return_value=0),
141+
communicate=lambda: ('out', 'err'),
147142
)
148143
)
149144
monkeypatch.setattr('cylc.uiserver.resolvers.Popen', mock_popen)
@@ -259,7 +254,11 @@ def wait(timeout):
259254

260255
mock_popen = Mock(
261256
spec=Popen,
262-
return_value=Mock(spec=Popen, wait=wait)
257+
return_value=Mock(
258+
spec=Popen,
259+
wait=wait,
260+
communicate=lambda: ('out', 'err'),
261+
),
263262
)
264263
monkeypatch.setattr('cylc.uiserver.resolvers.Popen', mock_popen)
265264

@@ -269,9 +268,9 @@ def wait(timeout):
269268
{},
270269
log=Mock(),
271270
)
272-
assert ret == [
273-
False, "Command 'cylc play wflow1' timed out after 120 seconds"
274-
]
271+
assert ret == (
272+
False, "Command 'cylc play wflow1' timed out after 120 seconds\nerr"
273+
)
275274

276275

277276
@pytest.fixture
@@ -330,7 +329,7 @@ async def test_cat_log(workflow_run_dir, app, fast_sleep):
330329

331330
# note - timeout tests that the cat-log process is being stopped correctly
332331
first_response = None
333-
async with timeout(20):
332+
async with asyncio.timeout(20):
334333
ret = services.cat_log(workflow, app, info)
335334
actual = ''
336335
is_first = True
@@ -379,7 +378,7 @@ async def test_cat_log_timeout(workflow_run_dir, app, fast_sleep):
379378

380379
ret = services.cat_log(workflow, app, info)
381380
responses = []
382-
async with timeout(5):
381+
async with asyncio.timeout(5):
383382
async for response in ret:
384383
responses.append(response)
385384
await asyncio.sleep(0)

0 commit comments

Comments
 (0)