fix: collect exceptions on task errors

When async tasks fail, all exceptions need to be collected to not get
subsequent exceptions about invalid future states. This is achieved by
gathering the task results, instead of just waiting for them. By
gathering the results, also user-requested cancellation (e.g. via
ctrl-c) works without throwing tons of additional exceptions.
Since ac437308 we more likely run into that case, which unvealed the bug.

By properly handling the exception, a TaskResultError is returned
instead of the underlying CommandExecError. This change is reflected in
the corresponding unit test.

Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
This commit is contained in:
Felix Moessbauer 2023-05-18 09:15:06 +02:00 committed by Jan Kiszka
parent d2ecff4243
commit ecd670e9ae
3 changed files with 18 additions and 13 deletions

View File

@ -98,7 +98,14 @@ def _atexit_handler():
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
pending = asyncio.Task.all_tasks(loop) pending = asyncio.Task.all_tasks(loop)
if not loop.is_closed(): if not loop.is_closed():
loop.run_until_complete(asyncio.gather(*pending)) # this code path is observed on older python versions (e.g. 3.6).
# In case the loop is not yet closed, tasks still might throw
# exceptions, but we are not interested in these as they are
# likely due to the cancellation. By that, we simply drop them.
try:
loop.run_until_complete(asyncio.gather(*pending))
except KasUserError:
pass
loop.close() loop.close()

View File

@ -197,11 +197,10 @@ def repos_fetch(repos):
tasks.append(asyncio.ensure_future(repo.fetch_async())) tasks.append(asyncio.ensure_future(repo.fetch_async()))
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) try:
loop.run_until_complete(asyncio.gather(*tasks))
for task in tasks: except CommandExecError as e:
if task.result(): raise TaskExecError('fetch repos', e.ret_code)
raise TaskExecError('fetch repos', task.result())
def repos_apply_patches(repos): def repos_apply_patches(repos):
@ -216,11 +215,10 @@ def repos_apply_patches(repos):
tasks.append(asyncio.ensure_future(repo.apply_patches_async())) tasks.append(asyncio.ensure_future(repo.apply_patches_async()))
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) try:
loop.run_until_complete(asyncio.gather(*tasks))
for task in tasks: except CommandExecError as e:
if task.result(): raise TaskExecError('apply patches', e.ret_code)
raise TaskExecError('apply patches', task.result())
def get_build_environ(build_system): def get_build_environ(build_system):

View File

@ -28,7 +28,7 @@ import json
import yaml import yaml
import pytest import pytest
from kas import kas from kas import kas
from kas.kasusererror import CommandExecError from kas.libkas import TaskExecError
def test_for_all_repos(changedir, tmpdir): def test_for_all_repos(changedir, tmpdir):
@ -67,7 +67,7 @@ def test_invalid_checkout(changedir, tmpdir, capsys):
tdir = str(tmpdir / 'test_commands') tdir = str(tmpdir / 'test_commands')
shutil.copytree('tests/test_commands', tdir) shutil.copytree('tests/test_commands', tdir)
os.chdir(tdir) os.chdir(tdir)
with pytest.raises(CommandExecError): with pytest.raises(TaskExecError):
kas.kas(['checkout', 'test-invalid.yml']) kas.kas(['checkout', 'test-invalid.yml'])