fix: exit gracefully when process started via asyncio is terminated

Currently when process is started through asyncio Runner and it is termited
e.g. with SIGINT(ctrl+c) a traceback is printed instead of gracefully
exit.

Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x7fe980970900>
Traceback (most recent call last):
  File "/usr/lib64/python3.12/asyncio/base_subprocess.py", line 129, in __del__
    self.close()
  File "/usr/lib64/python3.12/asyncio/base_subprocess.py", line 107, in close
    proto.pipe.close()
  File "/usr/lib64/python3.12/asyncio/unix_events.py", line 568, in close
    self._close(None)
  File "/usr/lib64/python3.12/asyncio/unix_events.py", line 592, in _close
    self._loop.call_soon(self._call_connection_lost, exc)
  File "/usr/lib64/python3.12/asyncio/base_events.py", line 793, in call_soon
    self._check_closed()
  File "/usr/lib64/python3.12/asyncio/base_events.py", line 540, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

This is caused because asyncio Runner context in asyncio.run is closing the event
loop and if exception is unhandled in coroutine(run_command) the transport is not
closed before the even loop is closed and we get RuntimeError: Event loop is closed
in the transport __del__ function because it's trying to use the closed
even loop.

Let's catch asyncio.CancelledError in case the process we are trying to
read from is terminated, print message, let the asyncio finish and exit
gracefully.

Closes https://github.com/espressif/esp-idf/issues/13418

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
release/v5.1
Frantisek Hrbata 2024-04-04 09:49:36 +02:00
rodzic 2fd087b246
commit 0de3c3b572
1 zmienionych plików z 22 dodań i 4 usunięć

Wyświetl plik

@ -10,7 +10,15 @@ import sys
from asyncio.subprocess import Process
from pkgutil import iter_modules
from types import FunctionType
from typing import Any, Dict, Generator, List, Match, Optional, TextIO, Tuple, Union
from typing import Any
from typing import Dict
from typing import Generator
from typing import List
from typing import Match
from typing import Optional
from typing import TextIO
from typing import Tuple
from typing import Union
import click
import yaml
@ -344,9 +352,19 @@ class RunTool:
stderr_output_file = os.path.join(self.build_dir, log_dir_name, f'idf_py_stderr_output_{p.pid}')
stdout_output_file = os.path.join(self.build_dir, log_dir_name, f'idf_py_stdout_output_{p.pid}')
if p.stderr and p.stdout: # it only to avoid None type in p.std
await asyncio.gather(
self.read_and_write_stream(p.stderr, stderr_output_file, sys.stderr),
self.read_and_write_stream(p.stdout, stdout_output_file, sys.stdout))
try:
await asyncio.gather(
self.read_and_write_stream(p.stderr, stderr_output_file, sys.stderr),
self.read_and_write_stream(p.stdout, stdout_output_file, sys.stdout))
except asyncio.CancelledError:
# The process we are trying to read from was terminated. Print the
# message here and let the asyncio to finish, because
# Runner context in asyncio.run is closing the event loop and
# if exception is raised(unhandled here) the transport is not closed before
# the even loop is closed and we get RuntimeError: Event loop is closed
# in the transport __del__ function because it's trying to use the closed
# even loop.
red_print(f'\n{self.tool_name} process terminated\n')
await p.wait() # added for avoiding None returncode
return p, stderr_output_file, stdout_output_file