Skip to content

Conversation

@Arfey
Copy link
Contributor

@Arfey Arfey commented Apr 26, 2025

I have a problem with async database connections for Django TestCase.

The psycopg3 connection is not thread-safe. As a result, I need to create a new connection per thread. This is a problem for the TestCase coz it uses a transaction for each test case.

class TestCase(TransactionTestCase):
    async def _apost_teardown(self):
        for conn in async_connections.all(initialized_only=True):
            await conn.close()

    def _setup_and_call(self, result, debug=False):
        if iscoroutinefunction(testMethod):
            ############### convert test 
            setattr(self, self._testMethodName, async_to_sync(testMethod))

        if debug:
            super().debug()
        else:
            super().__call__(result)

       self._post_teardown()
       ############### convert teardown 
       async_to_sync(self._apost_teardown)()       

I can do something like that coz each async_to_sync function uses a new thread executor (and different connection as a result). To solve that, I try to implement something similar for ThreadSensitiveContext.

class TestCase(TransactionTestCase):
    def _setup_and_call(self, result, debug=False):
        with AsyncSingleThreadContext()
            if iscoroutinefunction(testMethod):
                ############### convert test 
                setattr(self, self._testMethodName, async_to_sync(testMethod))

            if debug:
                super().debug()
            else:
                super().__call__(result)

           self._post_teardown()
           ############### convert teardown 
           async_to_sync(self._apost_teardown)()       

@fcurella
Copy link

Hi! how can we help getting this reviewed?

@carltongibson
Copy link
Member

Hey @fcurella. You can review it. Tell me it works 🙂

@fcurella
Copy link

fcurella commented Sep 26, 2025

@Arfey the code LGTM, but I think she should add some documentation in docs/ the README and the CHANGELOG

@carltongibson
Copy link
Member

I'd forgotten about this 🤹‍♀️ I've been meaning to get to your PR @fcurella. Again 🤹‍♀️.

When I looked at this initially I thought "seems sensible".

Looking again (Friday afternoon) the description is a bit quick/loose. A comment along the lines of "yes, this looks correct to me because ..." would help me pick it up again.

Happy to get a release out with it if we're confident.


await sync_to_async(inner)()

# They should not have run in the main thread, and on the same threads

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# They should not have run in the main thread, and on the same threads
# They should have run in the main thread, and on the same threads

@fcurella
Copy link

I've verified that:

  1. The new context manager picks the event loop's main thread if available, or creates a new thread.
  2. Nested uses of the context manager keep using the thread selected by the outermost one.

@carltongibson
Copy link
Member

Ok, great thanks. Let me look at it properly next week.

@carltongibson carltongibson force-pushed the feat/added-async-single-thread-context branch from 495ec35 to bd23f84 Compare October 5, 2025 09:06
@carltongibson carltongibson merged commit b08087c into django:main Oct 5, 2025
7 checks passed
@carltongibson
Copy link
Member

https://pypi.org/project/asgiref/3.10.0/

Thanks both!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants