From 0de3c3b572c93a65290e8165954a186215217e9c Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Thu, 4 Apr 2024 09:49:36 +0200 Subject: [PATCH] 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: 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 --- tools/idf_py_actions/tools.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tools/idf_py_actions/tools.py b/tools/idf_py_actions/tools.py index 7bcc8a2f13..317784c447 100644 --- a/tools/idf_py_actions/tools.py +++ b/tools/idf_py_actions/tools.py @@ -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