--- /dev/null
+.. change::
+ :tags: bug, events
+ :tickets: 4794
+
+ Fixed issue in event system where using the ``once=True`` flag with
+ dynamically generated listener functions would cause event registration of
+ future events to fail if those listener functions were garbage collected
+ after they were used, due to an assumption that a listened function is
+ strongly referenced. The "once" wrapped is now modified to strongly
+ reference the inner function persistently, and documentation is updated
+ that using "once" does not imply automatic de-registration of listener
+ functions.
.. versionadded:: 0.9.4 Added ``once=True`` to :func:`.event.listen`
and :func:`.event.listens_for`.
+ .. warning:: The ``once`` argument does not imply automatic de-registration
+ of the listener function after it has been invoked a first time; a
+ listener entry will remain associated with the target object.
+ Associating an arbitrarily high number of listeners without explictitly
+ removing them will cause memory to grow unbounded even if ``once=True``
+ is specified.
+
.. note::
The :func:`.listen` function cannot be called at the same time
.. versionadded:: 0.9.4 Added ``once=True`` to :func:`.event.listen`
and :func:`.event.listens_for`.
+ .. warning:: The ``once`` argument does not imply automatic de-registration
+ of the listener function after it has been invoked a first time; a
+ listener entry will remain associated with the target object.
+ Associating an arbitrarily high number of listeners without explictitly
+ removing them will cause memory to grow unbounded even if ``once=True``
+ is specified.
+
.. seealso::
:func:`.listen` - general description of event listening
once = [fn]
def go(*arg, **kw):
+ # strong reference fn so that it isn't garbage collected,
+ # which interferes with the event system's expectations
+ strong_fn = fn # noqa
if once:
once_fn = once.pop()
return once_fn(*arg, **kw)
eq_(m3.mock_calls, [call("x")])
eq_(m4.mock_calls, [call("z")])
+ def test_once_doesnt_dereference_listener(self):
+ # test for [ticket:4794]
+
+ Target = self._fixture()
+
+ canary = Mock()
+
+ def go(target, given_id):
+ def anonymous(run_id):
+ canary(run_id, given_id)
+
+ event.listen(target, "event_one", anonymous, once=True)
+
+ t1 = Target()
+
+ assert_calls = []
+ given_ids = []
+ for given_id in range(100):
+ given_ids.append(given_id)
+ go(t1, given_id)
+ if given_id % 10 == 0:
+ t1.dispatch.event_one(given_id)
+ assert_calls.extend(call(given_id, i) for i in given_ids)
+ given_ids[:] = []
+
+ eq_(canary.mock_calls, assert_calls)
+
def test_propagate(self):
Target = self._fixture()