Skip to content

Commit 40cf316

Browse files
authored
Fixes for circular dependencies preventing Entity destruction, and Listener re/un-assignment (#129)
* Don't rely on the _entities dictionary in the Entity finalizer. In the presence of circular references the object being finalized may well have already been removed from the WeakValueDictionary that makes up the _entities store. See: https://gist.github.com/willstott101/8544a7c966b0788ff4f93da410e9fea7 * Support un-assigning listeners in Entity, and fix bug with reassignment. * Add test for listener reassignment corner-cases
1 parent 30ddbb0 commit 40cf316

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

cyclonedds/core.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,10 @@ def __init__(self, ref: int, listener: "Listener" = None) -> None:
187187
self._listener = listener
188188

189189
def __del__(self) -> None:
190-
if not hasattr(self, "_ref") or self._ref not in self._entities:
190+
if not hasattr(self, "_ref"):
191191
return
192192

193-
del self._entities[self._ref]
193+
self._entities.pop(self._ref, None)
194194
self._delete(self._ref)
195195

196196
def get_subscriber(self) -> Optional["cyclonedds.sub.Subscriber"]:
@@ -474,23 +474,33 @@ def get_listener(self) -> "Listener":
474474
"""
475475
return self._listener.copy() if self._listener else Listener()
476476

477-
def set_listener(self, listener: "Listener") -> None:
477+
def set_listener(self, listener: Optional["Listener"]) -> None:
478478
"""Update the listener for this object. If a listener already exist for this object only the fields you explicitly
479-
have set on your new listener are overwritten.
479+
have set on your new listener are overwritten. Passing None will remove this entity's Listener.
480+
481+
Future changes to the passed Listener object will not affect the Listener associated with this Entity.
480482
481483
Parameters
482484
----------
483-
listener : Listener
484-
The listener object to use.
485+
listener :
486+
The listener object to use, or None to remove the current listener from this Entity.
485487
486488
Raises
487489
------
488490
DDSException
489491
"""
490-
if self._listener != listener:
491-
listener.copy_to(self._listener)
492+
if listener is not None:
493+
if self._listener is not None:
494+
if self._listener != listener:
495+
listener.copy_to(self._listener)
496+
else:
497+
self._listener = listener.copy()
498+
ref = self._listener._ref
499+
else:
500+
ref = None
501+
self._listener = None
492502

493-
ret = self._set_listener(self._ref, listener._ref)
503+
ret = self._set_listener(self._ref, ref)
494504
if ret == 0:
495505
return
496506
raise DDSException(ret, f"Occurred when setting the Listener for {repr(self)}")

tests/test_entity.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,46 @@ def test_set_listener():
178178
dp.set_listener(Listener())
179179

180180

181+
def test_listener_reassignment(manual_setup, hitpoint_factory):
182+
class L(Listener):
183+
def __init__(self):
184+
super().__init__()
185+
self.hitpoint = hitpoint_factory()
186+
187+
def on_data_available(self, _):
188+
self.hitpoint.hit()
189+
190+
l_1 = L()
191+
192+
# Check we can recieve events
193+
dr = manual_setup.dr(listener=l_1)
194+
manual_setup.dw().write(manual_setup.msg)
195+
196+
assert l_1.hitpoint.was_hit()
197+
l_1.hitpoint.hp.clear()
198+
199+
# Check we stop recieving events after setting None
200+
dr.set_listener(None)
201+
manual_setup.dw().write(manual_setup.msg)
202+
203+
assert l_1.hitpoint.was_not_hit()
204+
205+
# Check we can recieve events after re-assigning the same handler from None
206+
dr.set_listener(l_1)
207+
manual_setup.dw().write(manual_setup.msg)
208+
209+
assert l_1.hitpoint.was_hit()
210+
l_1.hitpoint.hp.clear()
211+
212+
# Check we can recieve events from a new handler that replaces the old one
213+
l_2 = L()
214+
dr.set_listener(l_2)
215+
manual_setup.dw().write(manual_setup.msg)
216+
217+
assert l_1.hitpoint.was_not_hit()
218+
assert l_2.hitpoint.was_hit()
219+
220+
181221
def test_retain_listener():
182222
m = lambda x, y: 0
183223
l = Listener(on_data_available=m)

0 commit comments

Comments
 (0)