From 4fe6313058942735f309f7df31034a3030070698 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 31 Jul 2023 10:02:04 +0200 Subject: [PATCH 1/3] avoid starting IOPub background thread after it's been stopped loop.add_callback(loop.stop) called before the thread starts can get consumed by the run_sync before loop.start, so it won't close the loop. --- ipykernel/iostream.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 5c2818dcc..9aafa66ad 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -57,6 +57,7 @@ def __init__(self, socket, pipe=False): piped from subprocesses. """ self.socket = socket + self._stopped = False self.background_socket = BackgroundSocket(self) self._master_pid = os.getpid() self._pipe_flag = pipe @@ -83,7 +84,12 @@ def _start_event_gc(): self._event_pipe_gc_task = asyncio.ensure_future(self._run_event_pipe_gc()) self.io_loop.run_sync(_start_event_gc) - self.io_loop.start() + + if not self._stopped: + # avoid race if stop called before start thread gets here + # probably only comes up in tests + self.io_loop.start() + if self._event_pipe_gc_task is not None: # cancel gc task to avoid pending task warnings async def _cancel(): @@ -219,10 +225,16 @@ def start(self): def stop(self): """Stop the IOPub thread""" + self._stopped = True if not self.thread.is_alive(): return self.io_loop.add_callback(self.io_loop.stop) - self.thread.join() + + self.thread.join(timeout=30) + if self.thread.is_alive(): + # avoid infinite hang if stop fails + msg = "IOPub thread did not terminate in 30 seconds" + raise TimeoutError(msg) # close *all* event pipes, created in any thread # event pipes can only be used from other threads while self.thread.is_alive() # so after thread.join, this should be safe From 7d49b0f8a8f9dc3c04a0906fde2fbb6e15f7bde8 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 2 Sep 2023 14:57:31 -0500 Subject: [PATCH 2/3] handle timeout on cancel operation --- ipykernel/iostream.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 9aafa66ad..cb7685440 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -94,8 +94,11 @@ def _start_event_gc(): # cancel gc task to avoid pending task warnings async def _cancel(): self._event_pipe_gc_task.cancel() # type:ignore - - self.io_loop.run_sync(_cancel) + + try: + self.io_loop.run_sync(_cancel) + except TimeoutError: + pass self.io_loop.close(all_fds=True) def _setup_event_pipe(self): From b0d05c81519b02444667b39aa3ef9d3b59abb6b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 2 Sep 2023 19:57:41 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ipykernel/iostream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index cb7685440..74e65e4d9 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -94,7 +94,7 @@ def _start_event_gc(): # cancel gc task to avoid pending task warnings async def _cancel(): self._event_pipe_gc_task.cancel() # type:ignore - + try: self.io_loop.run_sync(_cancel) except TimeoutError: