mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-07-28 22:22:59 +02:00
fix(rpc): isolate broadcast callbacks and retry delivery after failures
This commit is contained in:
@@ -182,6 +182,7 @@ class RPCRegister:
|
||||
self._dirty = False
|
||||
connections = self.list_all_connections()
|
||||
dead_refs = []
|
||||
delivery_failed = False
|
||||
for callback_ref in list(self.callbacks):
|
||||
callback = callback_ref()
|
||||
if callback is None:
|
||||
@@ -193,12 +194,21 @@ class RPCRegister:
|
||||
# e.g. during shutdown): calling it would raise on any Qt access.
|
||||
dead_refs.append(callback_ref)
|
||||
continue
|
||||
callback(connections)
|
||||
try:
|
||||
callback(connections)
|
||||
except Exception:
|
||||
delivery_failed = True
|
||||
logger.exception(f"RPC registry broadcast callback {callback!r} failed")
|
||||
for ref in dead_refs:
|
||||
try:
|
||||
self.callbacks.remove(ref)
|
||||
except ValueError:
|
||||
pass
|
||||
if delivery_failed:
|
||||
# The state change must not be lost: leave the registry dirty so the
|
||||
# next broadcast retries delivery (duplicates are safe, the payload
|
||||
# is the full state).
|
||||
self._dirty = True
|
||||
|
||||
def object_is_registered(self, obj: BECConnector) -> bool:
|
||||
"""
|
||||
|
||||
@@ -189,3 +189,34 @@ def test_broadcast_without_callbacks_skips_registry_walk(rpc_register, monkeypat
|
||||
rpc_register.broadcast()
|
||||
walk.assert_called_once()
|
||||
assert len(received) == 1
|
||||
|
||||
|
||||
def test_broadcast_prunes_callbacks_of_destroyed_qobjects(rpc_register, qapp):
|
||||
"""A bound-method callback whose QObject was destroyed (Python wrapper still
|
||||
referenced, C++ side gone — the shutdown scenario) must be pruned, not called."""
|
||||
import shiboken6
|
||||
from qtpy.QtCore import QObject
|
||||
|
||||
class Listener(QObject):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.received = []
|
||||
|
||||
def on_update(self, connections):
|
||||
self.received.append(connections)
|
||||
|
||||
listener = Listener()
|
||||
rpc_register.add_callback(listener.on_update)
|
||||
rpc_register.mark_dirty()
|
||||
rpc_register.broadcast()
|
||||
assert len(listener.received) == 1
|
||||
callbacks_before = len(rpc_register.callbacks)
|
||||
|
||||
shiboken6.delete(listener)
|
||||
assert not shiboken6.isValid(listener)
|
||||
|
||||
rpc_register.mark_dirty()
|
||||
rpc_register.broadcast() # must neither call the dead callback nor raise
|
||||
|
||||
assert len(listener.received) == 1
|
||||
assert len(rpc_register.callbacks) == callbacks_before - 1
|
||||
|
||||
Reference in New Issue
Block a user