Django's signal framework may register listeners more than once
|Reported by:||Owned by:||Jacob|
|Cc:||paul@…, ferringb@…||Triage Stage:||Accepted|
|Has patch:||no||Needs documentation:||no|
|Needs tests:||no||Patch needs improvement:||no|
In the current Django setup, it is possible for a signal listener to register itself with the dispatcher (PyDispatcher) more than once.
The result is that the same function can end-up being called twice when a single signal is sent.
class A(models.Model): a = models.IntegerField(default=1) def test_function_A(): print "Called listener" dispatcher.connect(test_function_A, signal=signals.pre_init, sender=A)
Interactive session (
>>> from django.dispatch import dispatcher >>> from django.db.models import signals >>> from testproj.testapp.models import A >>> dispatcher.getReceivers(sender=A, signal=signals.pre_init) [<function test_function_A at 0x1203fb0>] >>> from testapp.models import A >>> dispatcher.getReceivers(sender=A, signal=signals.pre_init) [<function test_function_A at 0x1203fb0>, <function test_function_A at 0x1206a30>] >>> A() Called listener Called listener <A: A object>
The problem seems to stem from the fact that Django puts itself in Python's path more than once. For example, for the project '
testproj', Python will look in
/path/to/project/testproj/. This, both
testproj.testapp are valid.
Though these locations are both valid and reference the same content, they are actually treated as being different. Models seem to be treated as singletons, but other objects do not. I assume that this is because of the way in which models are loaded when Django fires-up.
>>> from testproj.testapp.models import A as full_path_A_class >>> from testapp.models import A as short_path_A_class >>> id(full_path_A_class), id(short_path_A_class) (6797536, 6797536) >>> from testproj.testapp.models import test_function_A as full_path_A_function >>> from testapp.models import test_function_A as short_path_A_function >>> id(full_path_A_function), id(short_path_A_function) (18898800, 18960624)
Note in the last line of output that two different values are returned (18898800 != 18960624). Thus, the
dispatcher.connect function has no way to determine that the listeners are the same.
The code responsible for the registration of listeners in PyDispatch can be found at
django/dispatch/dispatcher.py:153-170; however, I'm not sure that we should go hacking on the PyDispatcher code. I'm honestly not sure what the solution to this one should be.
The following things should be noted:
- I am using
trunkfor testing; however, I've also tried manually updating to PyDispatcher 2.0.0 and the problem still exists.
- All of my testing has been on Python 2.5. I assume that older versions will behave similarly.
- All of my testing has been done in the shell and with the built-in server.
- It is possible that this issue does not exist in a FCGI/mod_python deployment... I haven't tested those environments.