diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py index 2ee826e2de1..44fef0722d3 100644 --- a/django/contrib/staticfiles/handlers.py +++ b/django/contrib/staticfiles/handlers.py @@ -56,9 +56,9 @@ class StaticFilesHandlerMixin: async def get_response_async(self, request): try: - return await sync_to_async(self.serve)(request) + return await sync_to_async(self.serve, thread_sensitive=False)(request) except Http404 as e: - return await sync_to_async(response_for_exception)(request, e) + return await sync_to_async(response_for_exception, thread_sensitive=False)(request, e) class StaticFilesHandler(StaticFilesHandlerMixin, WSGIHandler): diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 169df6b3cf8..8e8c3da06aa 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -148,7 +148,7 @@ class BaseHandler: response = await self._middleware_chain(request) response._resource_closers.append(request.close) if response.status_code >= 400: - await sync_to_async(log_response)( + await sync_to_async(log_response, thread_sensitive=False)( '%s: %s', response.reason_phrase, request.path, response=response, request=request, diff --git a/django/core/handlers/exception.py b/django/core/handlers/exception.py index 70b23edb628..3005a5eccb1 100644 --- a/django/core/handlers/exception.py +++ b/django/core/handlers/exception.py @@ -37,7 +37,7 @@ def convert_exception_to_response(get_response): try: response = await get_response(request) except Exception as exc: - response = await sync_to_async(response_for_exception)(request, exc) + response = await sync_to_async(response_for_exception, thread_sensitive=False)(request, exc) return response return inner else: diff --git a/django/test/client.py b/django/test/client.py index 1e6d9e2a398..ba1c37ed59b 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -181,7 +181,7 @@ class AsyncClientHandler(BaseHandler): body_file = FakePayload('') request_started.disconnect(close_old_connections) - await sync_to_async(request_started.send)(sender=self.__class__, scope=scope) + await sync_to_async(request_started.send, thread_sensitive=False)(sender=self.__class__, scope=scope) request_started.connect(close_old_connections) request = ASGIRequest(scope, body_file) # Sneaky little hack so that we can easily get round @@ -197,14 +197,14 @@ class AsyncClientHandler(BaseHandler): response.asgi_request = request # Emulate a server by calling the close method on completion. if response.streaming: - response.streaming_content = await sync_to_async(closing_iterator_wrapper)( + response.streaming_content = await sync_to_async(closing_iterator_wrapper, thread_sensitive=False)( response.streaming_content, response.close, ) else: request_finished.disconnect(close_old_connections) # Will fire request_finished. - await sync_to_async(response.close)() + await sync_to_async(response.close, thread_sensitive=False)() request_finished.connect(close_old_connections) return response diff --git a/docs/releases/3.1.3.txt b/docs/releases/3.1.3.txt index 6f526aa5c9c..300e4aff54d 100644 --- a/docs/releases/3.1.3.txt +++ b/docs/releases/3.1.3.txt @@ -51,3 +51,5 @@ Bugfixes * Fixed a regression in Django 3.1 that invalidated pre-Django 3.1 password reset tokens (:ticket:`32130`). + +* Added support for ``asgiref`` 3.3 (:ticket:`32128`). diff --git a/docs/topics/async.txt b/docs/topics/async.txt index a2f702b8e33..b25dee76051 100644 --- a/docs/topics/async.txt +++ b/docs/topics/async.txt @@ -214,14 +214,14 @@ as ensuring threadlocals work, it also enables the ``thread_sensitive`` mode of ``sync_to_async()`` ------------------- -.. function:: sync_to_async(sync_function, thread_sensitive=False) +.. function:: sync_to_async(sync_function, thread_sensitive=True) Takes a sync function and returns an async function that wraps it. Can be used as either a direct wrapper or a decorator:: from asgiref.sync import sync_to_async - async_function = sync_to_async(sync_function) + async_function = sync_to_async(sync_function, thread_sensitive=False) async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True) @sync_to_async @@ -234,13 +234,21 @@ directions. Sync functions tend to be written assuming they all run in the main thread, so :func:`sync_to_async` has two threading modes: -* ``thread_sensitive=False`` (the default): the sync function will run in a - brand new thread which is then closed once the invocation completes. +* ``thread_sensitive=True`` (the default): the sync function will run in the + same thread as all other ``thread_sensitive`` functions. This will be the + main thread, if the main thread is synchronous and you are using the + :func:`async_to_sync` wrapper. -* ``thread_sensitive=True``: the sync function will run in the same thread as - all other ``thread_sensitive`` functions. This will be the main thread, if - the main thread is synchronous and you are using the :func:`async_to_sync` - wrapper. +* ``thread_sensitive=False``: the sync function will run in a brand new thread + which is then closed once the invocation completes. + +.. warning:: + + ``asgiref`` version 3.3.0 changed the default value of the + ``thread_sensitive`` parameter to ``True``. This is a safer default, and in + many cases interacting with Django the correct value, but be sure to + evaluate uses of ``sync_to_async()`` if updating ``asgiref`` from a prior + version. Thread-sensitive mode is quite special, and does a lot of work to run all functions in the same thread. Note, though, that it *relies on usage of*