Skip to content

Commit 1804eb6

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 1200e1c commit 1804eb6

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
@@ -25,12 +25,6 @@
2525
from subprocess import Popen, TimeoutExpired
2626
from types import SimpleNamespace
2727

28-
import sys
29-
if sys.version_info >= (3, 11):
30-
from asyncio import timeout
31-
else:
32-
from async_timeout import timeout
33-
3428
from cylc.flow import CYLC_LOG
3529
from cylc.flow.exceptions import CylcError
3630
from cylc.flow.id import Tokens
@@ -145,6 +139,7 @@ async def test_start_services(
145139
return_value=Mock(
146140
spec=Popen,
147141
wait=Mock(return_value=0),
142+
communicate=lambda: ('out', 'err'),
148143
)
149144
)
150145
monkeypatch.setattr('cylc.uiserver.resolvers.Popen', mock_popen)
@@ -260,7 +255,11 @@ def wait(timeout):
260255

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

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

277276

278277
@pytest.fixture
@@ -331,7 +330,7 @@ async def test_cat_log(workflow_run_dir, app, fast_sleep):
331330

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

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

0 commit comments

Comments
 (0)