Skip to content

Commit

Permalink
Fix leaked semaphore object warning (#4132)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
rodja and falkoschindler authored Jan 8, 2025
1 parent 98fd634 commit c202f3e
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 4 deletions.
4 changes: 3 additions & 1 deletion nicegui/native/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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

__all__ = [
'NativeConfig',
'WindowProxy',
'activate',
'create_queues',
'find_open_port',
'method_queue',
'remove_queues',
'response_queue',
]
30 changes: 27 additions & 3 deletions nicegui/native/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions nicegui/native/native_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@ 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'
'Please run "pip install pywebview" to use it.')
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()
Expand Down

0 comments on commit c202f3e

Please sign in to comment.