A talk I gave at DjangoCon Europe 2020.
HOW TOBREAK DJANGO:ANDREW GODWIN // @andrewgodwinWITHASYNC
View Slide
Andrew Godwin / @andrewgodwinHi, I’mAndrew Godwin• Django core developer• Worked on Migrations, Channels & Async• Not currently in Europe
Andrew Godwin / @andrewgodwin
Andrew Godwin / @andrewgodwinAsync views are released!Find them in a Django 3.1 near you.
Andrew Godwin / @andrewgodwinWSGIHandler__call__WSGI ServerWSGIRequest URLs MiddlewareView__call__ASGIHandler__call__ASGI ServerASGIRequestAsynchronousrequest pathBaseHandlerget_response_asyncBaseHandlerget_responseURLs MiddlewareAsync View__call__Implemented async request flow
Andrew Godwin / @andrewgodwinSynchronous programming is quite safeSometimes that's worth the performance cost
Andrew Godwin / @andrewgodwinAsync programming is hardBut it can definitely be worth it!
Andrew Godwin / @andrewgodwinI love learning by example… so please don't do any of these things.
Andrew Godwin / @andrewgodwinasync def my_view(request, book_id):book = Book.objects.get(pk=book_id)return render(request,"book.html",{"book": book})
Andrew Godwin / @andrewgodwinasync def my_view(request, book_id):book = await Book.objects.get_async(pk=book_id)return render(request,"book.html",{"book": book})
Andrew Godwin / @andrewgodwinSynchronousOnlyOperation: You cannot callthis from an async context - use a thread orsync_to_async
Andrew Godwin / @andrewgodwinasync def my_view(request, book_id):book = Book.objects.get_async(pk=book_id)return render(request,"book.html",{"book": book})
Andrew Godwin / @andrewgodwinDjango's got your back...this time, anyway.
Andrew Godwin / @andrewgodwinasync def my_view(request):...asyncio.wait({create_user_account(),send_created_email(),})...
Andrew Godwin / @andrewgodwinThere's no ordering guarantee!Or even a guarantee it will execute.
Andrew Godwin / @andrewgodwinasync def my_view(request):...await create_user_account()await send_created_email()...
Andrew Godwin / @andrewgodwinRace conditions!Names are power - now you can research it!
Andrew Godwin / @andrewgodwin# This is how you need to do DB access in 3.1@sync_to_asyncdef create_user_account():User.objects.create(...)
Andrew Godwin / @andrewgodwinasync def my_view(request):with transaction.atomic():await verify_valid_user(...)await create_user_account(...)
Andrew Godwin / @andrewgodwinasync def my_view(request):with transaction.atomic():await verify_valid_user(...)await create_user_account(...)Runs in async threadIn different, sync thread
Andrew Godwin / @andrewgodwinYou don't actually have a transaction.We need to address this when we make the ORM async-aware!
Andrew Godwin / @andrewgodwinstats = {"alive": 0, "dead": 0}async def fetch_site(site):alive = stats["alive"]response = await client.get(site)if response.status_code < 400:stats["alive"] = alive + 1...asyncio.wait([fetch_site(site) for site in sites])
Andrew Godwin / @andrewgodwinstats = {"alive": 0, "dead": 0}async def fetch_site(site):response = await client.get(site)if response.status_code < 400:alive = stats["alive"]stats["alive"] = alive + 1...asyncio.wait([fetch_site(site) for site in sites])
Andrew Godwin / @andrewgodwinBad with threads. Good with async!The await model brings us atomic sections for free
Andrew Godwin / @andrewgodwinready = Falseasync def run():await client.get("some-url")ready = Trueasync def notify():while not ready:time.sleep(0.01)send_email()asyncio.wait({run, notify})
Andrew Godwin / @andrewgodwinready = Falseasync def run():await client.get("some-url")ready = Trueasync def notify():while not ready:await time.sleep(0.01)send_email()asyncio.wait({run, notify})
Andrew Godwin / @andrewgodwinready = Falseasync def run():await client.get("some-url")ready = Trueasync def notify():while not ready:await asyncio.sleep(0.01)send_email()asyncio.wait({run, notify})
Andrew Godwin / @andrewgodwinThis maybe is a Python thingWe can't have functions be both sync and async.
Andrew Godwin / @andrewgodwin@sync_to_asyncdef setup_conn():connection.execute("SET SESSION CHARACTERISTICS…")@sync_to_asyncdef deactivate_users():with transaction.atomic():for user in User.objects.filter(old=True):...async def my_view(request):await setup_conn()await deactivate_users(users)
Andrew Godwin / @andrewgodwinThis one's on me.Fix is already in the works!
Andrew Godwin / @andrewgodwinHow do we defend against these?Seems awfully easy to slip up, doesn't it.
Andrew Godwin / @andrewgodwinIt's an env variable. Set it to 1!PYTHONASYNCIODEBUG
Andrew Godwin / @andrewgodwinDetects slow coroutinesLike ones that are calling synchronous code!Detects unawaited coroutinesBecause we all forget await sometimesSlow I/O & thread-safety tooThough these are not usually your fault
Andrew Godwin / @andrewgodwinSynchronousOnlyOperationDjango's here to save you from the worst errors, where we can
Andrew Godwin / @andrewgodwinAgain, async programming is hardThis is not just Python!
Andrew Godwin / @andrewgodwinWrite things sync first, with testsAs they say, "make it work, then make it fast"
Andrew Godwin / @andrewgodwinDjango will do our part - where we canWe want to provide a place where you can quickly & safely add async.
Andrew Godwin / @andrewgodwinWe'll get better patterns!Until then, be safe out there
Thanks.Andrew Godwin@andrewgodwin // aeracode.org