From c202f3eddfcc60a602fa21e8d5eebb37ab239886 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Wed, 8 Jan 2025 15:56:06 +0100 Subject: [PATCH] Fix leaked semaphore object warning (#4132) This pull request fixes #4131: a "leaked semaphore object" warning appears when aborting with CTRL-C in certain configurations. Semaphores are used internally in `multiprocessing.Queue` which never get cleaned up properly. ### Scenario 1: pressing CTRL-C in an FastAPI `app` stared via uvicron from the command line with auto-reloading Because the `multiprocessing.Queue` objects in `native.py` have been created globally they where also instantiated when not in native mode. This PR fixes this by only creating the objects when native mode is activated. ### Scenario 2: pressing CTRL-C when using `ui.run(native=True, reload=True)` This is still unsolved. I verified that the shutdown/cleanup function for the Queue objects are correct. But the it seems that the pywebview window produces also leaked semaphores when pressing CTRL-C. --------- Co-authored-by: Falko Schindler --- nicegui/native/__init__.py | 4 +++- nicegui/native/native.py | 30 +++++++++++++++++++++++++++--- nicegui/native/native_mode.py | 2 ++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/nicegui/native/__init__.py b/nicegui/native/__init__.py index 2fb64809b..cabde5d04 100644 --- a/nicegui/native/__init__.py +++ b/nicegui/native/__init__.py @@ -1,4 +1,4 @@ -from .native import WindowProxy, method_queue, response_queue +from .native import WindowProxy, create_queues, method_queue, remove_queues, response_queue from .native_config import NativeConfig from .native_mode import activate, find_open_port @@ -6,7 +6,9 @@ 'NativeConfig', 'WindowProxy', 'activate', + 'create_queues', 'find_open_port', 'method_queue', + 'remove_queues', 'response_queue', ] diff --git a/nicegui/native/native.py b/nicegui/native/native.py index f432f8e81..db777506b 100644 --- a/nicegui/native/native.py +++ b/nicegui/native/native.py @@ -2,13 +2,34 @@ import inspect import warnings from multiprocessing import Queue -from typing import Any, Callable, Tuple +from typing import Any, Callable, Optional, Tuple from .. import run from ..logging import log -method_queue: Queue = Queue() -response_queue: Queue = Queue() +method_queue: Optional[Queue] = None +response_queue: Optional[Queue] = None + + +def create_queues() -> None: + """Create the message queues.""" + global method_queue, response_queue # pylint: disable=global-statement # noqa: PLW0603 + method_queue = Queue() + response_queue = Queue() + + +def remove_queues() -> None: + """Remove the message queues by closing them and waiting for threads to finish.""" + global method_queue, response_queue # pylint: disable=global-statement # noqa: PLW0603 + if method_queue is not None: + method_queue.close() + method_queue.join_thread() + method_queue = None + if response_queue is not None: + response_queue.close() + response_queue.join_thread() + response_queue = None + try: with warnings.catch_warnings(): @@ -120,11 +141,14 @@ def expose(self, function: Callable) -> None: # type: ignore # pylint: disable= def _send(self, *args: Any, **kwargs: Any) -> None: name = inspect.currentframe().f_back.f_code.co_name # type: ignore + assert method_queue is not None method_queue.put((name, args, kwargs)) async def _request(self, *args: Any, **kwargs: Any) -> Any: def wrapper(*args: Any, **kwargs: Any) -> Any: try: + assert method_queue is not None + assert response_queue is not None method_queue.put((name, args, kwargs)) return response_queue.get() # wait for the method to be called and writing its result to the queue except Exception: diff --git a/nicegui/native/native_mode.py b/nicegui/native/native_mode.py index fde7fc36a..d0de71b90 100644 --- a/nicegui/native/native_mode.py +++ b/nicegui/native/native_mode.py @@ -104,6 +104,7 @@ def check_shutdown() -> None: while not core.app.is_stopped: time.sleep(0.1) _thread.interrupt_main() + native.remove_queues() if not optional_features.has('webview'): log.error('Native mode is not supported in this configuration.\n' @@ -111,6 +112,7 @@ def check_shutdown() -> None: sys.exit(1) mp.freeze_support() + native.create_queues() args = host, port, title, width, height, fullscreen, frameless, native.method_queue, native.response_queue process = mp.Process(target=_open_window, args=args, daemon=True) process.start()