Skip to content

Commit

Permalink
[3.1.x] Fixed #32128 -- Added asgiref 3.3 compatibility.
Browse files Browse the repository at this point in the history
Thread sensitive parameter is True by default from asgiref v3.3.0.
Added an explicit thread_sensitive=False to previously implicit uses.

Backport of e17ee44 from master
  • Loading branch information
carltongibson committed Oct 27, 2020
1 parent 4e71900 commit d00127c
Show file tree
Hide file tree
Showing 7 changed files with 26 additions and 16 deletions.
4 changes: 2 additions & 2 deletions django/contrib/staticfiles/handlers.py
Expand Up @@ -56,9 +56,9 @@ def get_response(self, request):

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):
Expand Down
2 changes: 1 addition & 1 deletion django/core/handlers/base.py
Expand Up @@ -148,7 +148,7 @@ async def get_response_async(self, request):
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,
Expand Down
2 changes: 1 addition & 1 deletion django/core/handlers/exception.py
Expand Up @@ -37,7 +37,7 @@ async def inner(request):
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:
Expand Down
6 changes: 3 additions & 3 deletions django/test/client.py
Expand Up @@ -178,7 +178,7 @@ async def __call__(self, scope):
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
Expand All @@ -194,14 +194,14 @@ async def __call__(self, scope):
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

Expand Down
2 changes: 2 additions & 0 deletions docs/releases/3.1.3.txt
Expand Up @@ -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`).
24 changes: 16 additions & 8 deletions docs/topics/async.txt
Expand Up @@ -216,14 +216,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
Expand All @@ -236,13 +236,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*
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Expand Up @@ -41,7 +41,7 @@ scripts = django/bin/django-admin.py
include_package_data = true
zip_safe = false
install_requires =
asgiref ~= 3.2.10
asgiref >=3.2.10,<4
pytz
sqlparse >= 0.2.2

Expand Down

0 comments on commit d00127c

Please sign in to comment.