hi
so i was looking at the test case for signals and saw that there are no tests for the async asend
method
so i made a copy of django/tests/dispatch/tests.py at main · django/django · GitHub this test which looks like this:
async def test_multiple_registration_async(self):
a = Callable()
a_signal.connect(a)
a_signal.connect(a)
a_signal.connect(a)
a_signal.connect(a)
a_signal.connect(a)
a_signal.connect(a)
result = await a_signal.asend(sender=self, val="test")
self.assertEqual(len(result), 1)
self.assertEqual(len(a_signal.receivers), 1)
del a
del result
garbage_collect()
self.assertTestIsClean(a_signal)
this uses a sync receiver with asend
, so the receiver is ran by sync_to_async
i should mentioned that i have a fork of this which uses pytest with anyio to run async tests
when running this with django’s test runner, i get the following error
Testing against Django installed in '/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django' with up to 16 processes
Found 22 test(s).
System check identified no issues (0 silenced).
..
test_multiple_registration_async (dispatch.tests.DispatcherTests.test_multiple_registration_async) failed:
AssertionError('True is not false')
Unfortunately, tracebacks cannot be pickled, making it impossible for the
parallel test runner to handle this exception cleanly.
In order to see the traceback, you should install tblib:
python -m pip install tblib
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
File "/usr/lib/python3.13/unittest/case.py", line 58, in testPartExecutor
yield
File "/usr/lib/python3.13/unittest/case.py", line 651, in run
self._callTestMethod(testMethod)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
File "/usr/lib/python3.13/unittest/case.py", line 606, in _callTestMethod
if method() is not None:
~~~~~~^^
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/asgiref/sync.py", line 254, in __call__
return call_result.result()
~~~~~~~~~~~~~~~~~~^^
File "/usr/lib/python3.13/concurrent/futures/_base.py", line 449, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/usr/lib/python3.13/concurrent/futures/_base.py", line 401, in __get_result
raise self._exception
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/asgiref/sync.py", line 331, in main_wrap
result = await self.awaitable(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/amirreza/projects/django/tests/dispatch/tests.py", line 182, in test_multiple_registration_async
self.assertTestIsClean(a_signal)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/home/amirreza/projects/django/tests/dispatch/tests.py", line 35, in assertTestIsClean
self.assertFalse(signal.has_listeners())
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/unittest/case.py", line 738, in assertFalse
raise self.failureException(msg)
AssertionError: True is not false
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib/python3.13/multiprocessing/pool.py", line 125, in worker
result = (True, func(*args, **kwds))
~~~~^^^^^^^^^^^^^^^
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/runner.py", line 462, in _run_subsuite
result = runner.run(subsuite)
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/runner.py", line 377, in run
test(result)
~~~~^^^^^^^^
File "/usr/lib/python3.13/unittest/suite.py", line 84, in __call__
return self.run(*args, **kwds)
~~~~~~~~^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/unittest/suite.py", line 122, in run
test(result)
~~~~^^^^^^^^
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 302, in __call__
self._setup_and_call(result)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 357, in _setup_and_call
super().__call__(result)
~~~~~~~~~~~~~~~~^^^^^^^^
File "/usr/lib/python3.13/unittest/case.py", line 707, in __call__
return self.run(*args, **kwds)
~~~~~~~~^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/unittest/case.py", line 650, in run
with outcome.testPartExecutor(self):
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/usr/lib/python3.13/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/usr/lib/python3.13/unittest/case.py", line 75, in testPartExecutor
_addError(self.result, test_case, exc_info)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/unittest/case.py", line 98, in _addError
result.addFailure(test, exc_info)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/runner.py", line 307, in addFailure
self.check_picklable(test, err)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/runner.py", line 215, in check_picklable
self._confirm_picklable(err)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/runner.py", line 185, in _confirm_picklable
pickle.loads(pickle.dumps(obj))
~~~~~~~~~~~~^^^^^
TypeError: cannot pickle 'traceback' object
"""
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/amirreza/projects/django/tests/runtests.py", line 788, in <module>
failures = django_tests(
options.verbosity,
...<16 lines>...
getattr(options, "durations", None),
)
File "/home/amirreza/projects/django/tests/runtests.py", line 427, in django_tests
failures = test_runner.run_tests(test_labels)
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/runner.py", line 1093, in run_tests
result = self.run_suite(suite)
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/runner.py", line 1020, in run_suite
return runner.run(suite)
~~~~~~~~~~^^^^^^^
File "/usr/lib/python3.13/unittest/runner.py", line 240, in run
test(result)
~~~~^^^^^^^^
File "/usr/lib/python3.13/unittest/suite.py", line 84, in __call__
return self.run(*args, **kwds)
~~~~~~~~^^^^^^^^^^^^^^^
File "/home/amirreza/projects/django/.venv/lib/python3.13/site-packages/django/test/runner.py", line 549, in run
subsuite_index, events = test_results.next(timeout=0.1)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
File "/usr/lib/python3.13/multiprocessing/pool.py", line 873, in next
raise value
TypeError: cannot pickle 'traceback' object
Exception ignored in: <function Pool.__del__ at 0x7738feae67a0>
Traceback (most recent call last):
File "/usr/lib/python3.13/multiprocessing/pool.py", line 268, in __del__
ResourceWarning: unclosed running multiprocessing pool <multiprocessing.pool.Pool state=RUN pool_size=2>
with the pytest version, i only get the assertion error, not the pickle error
essentially the error happens because the receiver still exists, even after garbage collecting
i managed to fix this issue by adding a asyncio.sleep(0)
after the garbage collector call
after the sleep, there are no receivers left
my guess is that sync_to_async
is somehow keeping the sync receiver from being collected
you can find my pytest version of this at signals-py/tests/test_dispatch/test_dispatch.py at main · khiyavan/signals-py · GitHub