mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-08-10 04:00:36 +02:00
fix(connector): break C++-anchored reference cycle retaining task owners
This commit is contained in:
@@ -403,8 +403,18 @@ class BECConnector:
|
||||
# Keep a reference to the worker so it is not garbage collected.
|
||||
self._workers.append(worker)
|
||||
|
||||
# When the worker is done (success or failure), remove it from our list.
|
||||
# When the worker is done (success or failure), remove it from our
|
||||
# list. The closure must disconnect itself from both signals before
|
||||
# returning: the Qt connection holds the closure strongly from C++,
|
||||
# and the closure captures self and worker — without the disconnect
|
||||
# this forms a reference cycle anchored in C++ that Python's GC
|
||||
# cannot break, keeping the owning widget alive forever.
|
||||
def _discard_worker(*_):
|
||||
for signal in (worker.signals.completed, worker.signals.failed):
|
||||
try:
|
||||
signal.disconnect(_discard_worker)
|
||||
except (RuntimeError, TypeError):
|
||||
pass
|
||||
try:
|
||||
self._workers.remove(worker)
|
||||
except ValueError:
|
||||
|
||||
@@ -277,3 +277,26 @@ def test_bec_connector_parent_id_returns_none_on_error(bec_connector):
|
||||
bec_connector, "_get_rpc_parent_ancestor", side_effect=ValueError("broken hierarchy")
|
||||
):
|
||||
assert bec_connector.parent_id is None
|
||||
|
||||
|
||||
def test_bec_connector_worker_completion_does_not_retain_owner(qtbot, mocked_client):
|
||||
"""The worker-discard closure is held strongly
|
||||
by the Qt signal connection and captures the owner; without disconnecting
|
||||
itself it forms a C++-anchored reference cycle that keeps the owner (and
|
||||
widget) alive forever."""
|
||||
import gc
|
||||
import weakref
|
||||
|
||||
connector = _CleanupBroadcastWidget(client=mocked_client)
|
||||
connector.submit_task(lambda: None)
|
||||
qtbot.waitUntil(lambda: not connector._workers, timeout=5000)
|
||||
|
||||
ref = weakref.ref(connector)
|
||||
connector.close()
|
||||
connector.deleteLater()
|
||||
qtbot.wait(20)
|
||||
del connector
|
||||
for _ in range(3):
|
||||
gc.collect()
|
||||
|
||||
assert ref() is None, "connector kept alive by worker completion closure"
|
||||
|
||||
Reference in New Issue
Block a user