Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #30451 -- Added ASGI handler and coroutine-safety. #11209

Merged
merged 2 commits into from Jun 20, 2019

Conversation

@andrewgodwin
Copy link
Member

@andrewgodwin andrewgodwin commented Apr 13, 2019

This adds in an AsgiHandler handler class, get_asgi_application, and an asgi.py file into the default project template. It serves Django over an ASGI interface, but does not yet allow asynchronous views; get_response is where the context changes from asynchronous to synchronous.

It also swaps out Django's threading-based safety for asgiref.local, which provides hybrid thread- and async-task-based safety.

The goal would be to land this in master, and then start work on getting async through the middleware layers and down to views if they are declared as async def. That way we can stabilise this, make sure it's OK, and hopefully ship it in 3.0, and then see how far we can get async view handling as another PR so it's not all tied up in one big patch.

This will be squashed down to a single commit for merge.

docs/spelling_wordlist Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
Copy link
Contributor

@tomdyson tomdyson left a comment

Tiny docstring and documentation fixes

django/core/handlers/asgi.py Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
docs/howto/deployment/asgi/daphne.txt Outdated Show resolved Hide resolved
docs/howto/deployment/asgi/index.txt Outdated Show resolved Hide resolved
docs/howto/deployment/asgi/uvicorn.txt Outdated Show resolved Hide resolved
@andrewgodwin andrewgodwin marked this pull request as ready for review Apr 24, 2019
@andrewgodwin
Copy link
Member Author

@andrewgodwin andrewgodwin commented Apr 24, 2019

The MariaDB test appears to be consistently failing for all PRs today, so I am going to say this is otherwise ready for review.

django/core/handlers/asgi.py Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
docs/howto/deployment/asgi/daphne.txt Outdated Show resolved Hide resolved
django/core/asgi.py Show resolved Hide resolved
@ngnpope
Copy link
Member

@ngnpope ngnpope commented Apr 26, 2019

So I'm fairly ignorant of async stuff at moment, but I see two things crop up a lot here:

  • django.utils.asyncio.async_unsafe()
  • asgiref.local.Local() in place of threading.local()

I presume that third-party apps I use may need to make use of these to support running under ASGI?

In the case of the latter, is asgiref.local.Local() expected to replace all uses of threading.local()?

Given these questions, will there be some documentation on implementing minimal support for ASGI for apps?

@andrewgodwin
Copy link
Member Author

@andrewgodwin andrewgodwin commented Apr 26, 2019

@pope1ni If you want to be fully async-safe, then yes, it's required that you account for coroutines in a third-party app. async_unsafe is a general protection against this occurring if you have code you know is unsafe for sure, while Local is a helpful implementation if you want threadlocal-like behaviour with async coroutines.

Neither is required for writing ASGI apps at all, but we will need a guide that is "adapting Django apps to work under async" which mentions both of these, as well as a slew of other factors like "don't use normal, sync requests in async code" and "don't use normal sockets". I did not intend to include that in this patch, since it's far more extensive than this basic safety. I'll add a warning to the ASGI deployment docs that are in here, though.

(Moreover, in ASGI mode Django still runs all client code as sync, so this would only be a problem after this patch if a user was overriding the AsgiHandler)

@ngnpope
Copy link
Member

@ngnpope ngnpope commented Apr 27, 2019

@andrewgodwin Thanks for the explanation. Looking forward to seeing how this develops.

django/core/handlers/asgi.py Outdated Show resolved Hide resolved
body += message["body"]
# Limit the maximum request data size that will be handled in-memory.
# TODO: Stream the body to temp disk instead?
# (we can't provide a file-like with direct reading as we would not be async)
Copy link
Member

@apollo13 apollo13 May 1, 2019

This probably cannot use the upload handlers for similar reasons?

Copy link
Member Author

@andrewgodwin andrewgodwin May 2, 2019

I haven't looked into that yet, but probably not. File input in general has been a pain in Channels and I expect the same here.

@andrewgodwin
Copy link
Member Author

@andrewgodwin andrewgodwin commented May 6, 2019

I have added a Trac ticket to match this here: https://code.djangoproject.com/ticket/30451

@felixxm felixxm changed the title Implement ASGI handler and coroutine-safety Fixed #30451 -- Implemented ASGI handler and coroutine-safety. May 6, 2019
Copy link
Member

@carltongibson carltongibson left a comment

Hey @andrewgodwin. This looks like a good starting point. I agree we should merge in and allow pushing forwards.

I made a small adjustment pulling up get_response(). Please review and squash in if you're happy.

I have a curious test collection issue on macOS, which isn't occurring elsewhere, with the async_to_sync wrapper on the test cases...

  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 235, in getTestCaseNames
    testFnNames = list(filter(shouldIncludeMethod, dir(testCaseClass)))
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 232, in shouldIncludeMethod
    fullName = '%s.%s' % (testCaseClass.__module__, testFunc.__qualname__)
AttributeError: 'functools.partial' object has no attribute '__qualname__'

Where:

ipdb> testCaseClass                                                                                                            
<class 'asgi.tests.ASGITest'>
ipdb> testFunc                                                                                                                 
functools.partial(<bound method AsyncToSync.__call__ of <asgiref.sync.AsyncToSync object at 0x10b8ed4e0>>, None)

I'm guessing some use of wraps somewhere but I haven't dug fully yet.

Copy link
Member

@felixxm felixxm left a comment

@andrewgodwin Thanks for this patch 🚀 I left some comments and questions. Sorry for some of them but I'm not an async expert.

Can you generally use single quotes and wrap comments at 79 chars? 🙏 Thanks.

django/core/exceptions.py Show resolved Hide resolved
django/core/exceptions.py Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
django/utils/asyncio.py Outdated Show resolved Hide resolved
docs/howto/deployment/asgi/daphne.txt Outdated Show resolved Hide resolved
docs/howto/deployment/asgi/daphne.txt Show resolved Hide resolved
docs/howto/deployment/asgi/uvicorn.txt Outdated Show resolved Hide resolved
docs/releases/3.0.txt Outdated Show resolved Hide resolved
@andrewgodwin
Copy link
Member Author

@andrewgodwin andrewgodwin commented May 8, 2019

@carltongibson The get_response thing looks fine, happily pulled in. I'll re-squash this all down once the review changes are done.

The qualname thing is strange, I've not seen that here or in the Jenkins tests. I'll go looking for what it is, but without a Mac to replicate on, I may be a bit stuck.

@felixxm I have changed the quotes to single-quotes where it matches (e.g. the signals file was double-quotes already). Also reasonably sure I got all the comments under 79 but let me know if you spot any more.

Copy link
Member

@felixxm felixxm left a comment

@andrewgodwin Thanks again for your hard work 🚀 I left few more small comments, sorry for partial reviews, but I'm not really comfortable in async-area.

django/core/handlers/asgi.py Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Show resolved Hide resolved
docs/releases/3.0.txt Outdated Show resolved Hide resolved
@andrewgodwin
Copy link
Member Author

@andrewgodwin andrewgodwin commented Jun 7, 2019

I have re-squashed after the latest round of tweaks requested by @felixxm - I feel like we're getting ready to land, but I'm going to wait until I get an explicit "yes" from both of the Fellows :)

Copy link
Member

@carltongibson carltongibson left a comment

Hey @andrewgodwin.

I have a couple of comments on asgi.py.

(The tempfile.SpooledTemporaryFile approach to the body handling is super. 🙂)

django/core/handlers/asgi.py Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
django/core/handlers/asgi.py Outdated Show resolved Hide resolved
@carltongibson
Copy link
Member

@carltongibson carltongibson commented Jun 15, 2019

OK, this looks good to me. It's the start of a long road. 🙂

I kind of think that maybe we can tweak doc-here or doc-there but that it's pretty clear for the stage we're at. I think we should merge as-is and come back as the rest falls into place, and people start using it, particularly towards the end of the year.

Outstanding question: do we want a merge #11471, and abstract that bit. I don't really mind either way. DRY, yes, but sometimes these things are just better inlined. @felixxm you can decide. 🙂 And I assume you'll want to give it one last look over for remaining cosmetic changes?

@andrewgodwin
Copy link
Member Author

@andrewgodwin andrewgodwin commented Jun 15, 2019

I merged #11471 as @felixmm and I both agreed it should be spun out like that (and it's not like Request doesn't do that a lot already). Will rework this so it matches.

@felixxm
Copy link
Member

@felixxm felixxm commented Jun 17, 2019

@andrewgodwin I pushed some minor edits to comments and tests, added asgiref to the list of required packages for running all tests, and added two extra tests.

I'm going to check docs tomorrow and maybe add few more tests (if you prefer we can push them in a separate commit).

I have also one question about ASGIStaticFilesHandler, because currently it is unused. Can you give me some advice about it? How can I use it? 🤔

@felixxm
Copy link
Member

@felixxm felixxm commented Jun 17, 2019

@andrewgodwin Can you check asgi.tests.ASGITest.test_file_response failure on Windows?

@andrewgodwin
Copy link
Member Author

@andrewgodwin andrewgodwin commented Jun 17, 2019

@felixxm Do we need to add asgiref to the guide for running tests since the patch also introduces it as a Django dependency in setup.py?

Also, I'm looking into the test failure on Windows, it's an apparent lack of a system magic-numbers database. Will fix so it accepts both as valid mimetypes.

@andrewgodwin
Copy link
Member Author

@andrewgodwin andrewgodwin commented Jun 17, 2019

Also, ASGIStaticFilesHandler would in theory be used either in an asgi.py that was used to serve it in production, or an ASGI-enabled runserver (where the current static files handler is used).

@carltongibson
Copy link
Member

@carltongibson carltongibson commented Jun 17, 2019

...ASGIStaticFilesHandler...

Good yes. 🙂

So you'd do something like:

application = ASGIStaticFilesHandler(get_asgi_application())

@felixxm
Copy link
Member

@felixxm felixxm commented Jun 17, 2019

Do we need to add asgiref to the guide for running tests since the patch also introduces it as a Django dependency in setup.py?

Yes, we have all required packages there, also packages included in the setup.py like sqlparse or pytz.

@felixxm felixxm changed the title Fixed #30451 -- Implemented ASGI handler and coroutine-safety. Fixed #30451 -- Added ASGI handler and coroutine-safety. Jun 18, 2019
@felixxm
Copy link
Member

@felixxm felixxm commented Jun 18, 2019

I squashed commits, pushed minor edits to docs and added more tests.

andrewgodwin and others added 2 commits Jun 20, 2019
This adds an ASGI handler, asgi.py file for the default project layout,
a few async utilities and adds async-safety to many parts of Django.
@felixxm felixxm merged commit 7f19e37 into django:master Jun 20, 2019
19 checks passed
@felixxm
Copy link
Member

@felixxm felixxm commented Jun 20, 2019

Many thanks Andrew 🚀

@PetterS
Copy link
Contributor

@PetterS PetterS commented Jun 20, 2019

Whohoo! Great job!

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