Skip to content

Commit b9d5f22

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 b00306f commit b9d5f22

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
@@ -23,12 +23,6 @@
2323
from subprocess import Popen, TimeoutExpired
2424
from types import SimpleNamespace
2525

26-
import sys
27-
if sys.version_info >= (3, 11):
28-
from asyncio import timeout
29-
else:
30-
from async_timeout import timeout
31-
3226
from cylc.flow import CYLC_LOG
3327
from cylc.flow.exceptions import CylcError
3428
from cylc.flow.id import Tokens
@@ -139,6 +133,7 @@ async def test_play(
139133
return_value=Mock(
140134
spec=Popen,
141135
wait=Mock(return_value=0),
136+
communicate=lambda: ('out', 'err'),
142137
)
143138
)
144139
monkeypatch.setattr('cylc.uiserver.resolvers.Popen', mock_popen)
@@ -247,7 +242,11 @@ def wait(timeout):
247242

248243
mock_popen = Mock(
249244
spec=Popen,
250-
return_value=Mock(spec=Popen, wait=wait)
245+
return_value=Mock(
246+
spec=Popen,
247+
wait=wait,
248+
communicate=lambda: ('out', 'err'),
249+
),
251250
)
252251
monkeypatch.setattr('cylc.uiserver.resolvers.Popen', mock_popen)
253252

@@ -257,9 +256,9 @@ def wait(timeout):
257256
{},
258257
log=Mock(),
259258
)
260-
assert ret == [
261-
False, "Command 'cylc play wflow1' timed out after 120 seconds"
262-
]
259+
assert ret == (
260+
False, "Command 'cylc play wflow1' timed out after 120 seconds\nerr"
261+
)
263262

264263

265264
@pytest.fixture
@@ -318,7 +317,7 @@ async def test_cat_log(workflow_run_dir, app, fast_sleep):
318317

319318
# note - timeout tests that the cat-log process is being stopped correctly
320319
first_response = None
321-
async with timeout(20):
320+
async with asyncio.timeout(20):
322321
ret = services.cat_log(workflow, app, info)
323322
actual = ''
324323
is_first = True
@@ -367,7 +366,7 @@ async def test_cat_log_timeout(workflow_run_dir, app, fast_sleep):
367366

368367
ret = services.cat_log(workflow, app, info)
369368
responses = []
370-
async with timeout(5):
369+
async with asyncio.timeout(5):
371370
async for response in ret:
372371
responses.append(response)
373372
await asyncio.sleep(0)

0 commit comments

Comments
 (0)