Преглед изворни кода

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 <falko@zauberzeug.com>
Rodja Trappe пре 4 месеци
родитељ
комит
c202f3eddf
3 измењених фајлова са 32 додато и 4 уклоњено
  1. 3 1
      nicegui/native/__init__.py
  2. 27 3
      nicegui/native/native.py
  3. 2 0
      nicegui/native/native_mode.py

+ 3 - 1
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 @@ __all__ = [
     'NativeConfig',
     'WindowProxy',
     'activate',
+    'create_queues',
     'find_open_port',
     'method_queue',
+    'remove_queues',
     'response_queue',
 ]

+ 27 - 3
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 @@ try:
 
         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:

+ 2 - 0
nicegui/native/native_mode.py

@@ -104,6 +104,7 @@ def activate(host: str, port: int, title: str, width: int, height: int, fullscre
         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 activate(host: str, port: int, title: str, width: int, height: int, fullscre
         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()