Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Taking Django Async

Taking Django Async

A talk I gave at PyCon US 2018

Andrew Godwin

May 11, 2018
Tweet

More Decks by Andrew Godwin

Other Decks in Programming

Transcript

  1. TAKING
    Andrew Godwin
    @andrewgodwin
    DJANGO
    ASYNC

    View Slide

  2. Hi, I’m
    Andrew Godwin
    • Django core team member
    • Senior Software Engineer at
    • Does network programming for "fun"

    View Slide

  3. 2015: Channels 0.1

    View Slide

  4. Adds async protocol support to Django
    Headline protocol is WebSockets
    Also allows background jobs

    View Slide

  5. 2017: Channels 1.0

    View Slide

  6. Python 2.7 compatible
    Twisted for webserver
    Synchronous Django code

    View Slide

  7. Webserver
    (Twisted)
    Django
    (Sync worker process)
    Client
    Channel Layer
    (Redis or other)

    View Slide

  8. Webserver
    (Twisted)
    Django
    (Sync worker process)
    Clients
    Channel Layer
    (Redis or other)
    Webserver
    (Twisted)
    Django
    (Sync worker process)

    View Slide

  9. Too many moving pieces
    No asyncio support
    Easy to shoot yourself in the foot

    View Slide

  10. I was wrong.

    View Slide

  11. 2018: Channels 2.0

    View Slide

  12. Asyncio-native
    Python 3.5 and up
    Supports async coroutines and sync threads

    View Slide

  13. Webserver
    (asyncio-native)
    Django
    (coroutines/threads)
    Client

    View Slide

  14. Webserver
    (Twisted)
    Django
    (Sync worker process)
    Clients
    Webserver
    (Twisted)
    Django
    (Sync worker process)
    Channel Layer
    (Redis or other)

    View Slide

  15. 75% rewrite

    View Slide

  16. Async interfaces are separate to sync
    You can't provide both through one API

    View Slide

  17. bit.ly/py-async-simp
    https://www.aeracode.org/2018/02/19/python-async-simplified/

    View Slide

  18. Django had to be partially async

    View Slide

  19. WSGI Handler
    (WSGI-to-request translator)
    URL Routing
    (path to view mapping)
    Django Middleware
    (auth, sessions, etc)
    Views
    (logic and presentation)
    ORM
    (database kerfuffling)

    View Slide

  20. ASGI Handler
    (ASGI-to-request translator)
    URL Routing
    (path to view mapping)
    Django Middleware
    (auth, sessions, etc.)
    Views
    (logic and presentation)
    ORM
    (database kerfuffling)

    View Slide

  21. ASGI Handler
    (ASGI-to-request translator)
    URL Routing
    (path to view mapping)
    Django Middleware
    (auth, sessions, etc.)
    Views
    (logic and presentation)
    ORM
    (database kerfuffling)
    ASGI Routing
    (path/protocol/etc. mapping)
    ASGI Middleware
    (auth, sessions, etc.)
    Consumers
    (logic and presentation)
    synchronous

    View Slide

  22. Async-native most of the way

    View Slide

  23. But how?

    View Slide

  24. sync_to_async
    async_to_sync

    View Slide

  25. sync_to_async
    async_to_sync

    View Slide

  26. Synchronous code has to run in threads
    ThreadPoolExecutor does most of the hard work

    View Slide

  27. loop = asyncio.get_event_loop()
    future = loop.run_in_executor(
    self.threadpool,
    func,
    )
    return await future

    View Slide

  28. Calling the ORM
    Rendering templates
    Handing off to Django views

    View Slide

  29. sync_to_async
    async_to_sync

    View Slide

  30. Async code runs on the event loop
    We need to go find it! Or make our own.

    View Slide

  31. # Make a future for the return information
    call_result = Future()
    # Use call_soon_threadsafe to schedule a synchronous callback on the
    # main event loop's thread
    if not (self.main_event_loop and self.main_event_loop.is_running()):
    # Make our own event loop and run inside that.
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
    loop.run_until_complete(self.main_wrap(args, kwargs, call_result))
    finally:
    try:
    if hasattr(loop, "shutdown_asyncgens"):
    loop.run_until_complete(loop.shutdown_asyncgens())
    finally:
    loop.close()
    asyncio.set_event_loop(self.main_event_loop)
    else:
    self.main_event_loop.call_soon_threadsafe(
    self.main_event_loop.create_task,
    self.main_wrap(
    args,
    kwargs,
    call_result,
    ),
    )
    # Wait for results from the future.
    return call_result.result()

    View Slide

  32. Make a Future
    Jump to the main thread and add the coroutine
    Tie the coroutine's end to triggering the Future
    Block the thread on the Future

    View Slide

  33. Lets us provide async-native APIs
    And still let synchronous code use them

    View Slide

  34. Both async and sync code are useful
    Channels lets you write your code as both

    View Slide

  35. ASGI Handler
    (ASGI-to-request translator)
    URL Routing
    (path to view mapping)
    Django Middleware
    (auth, sessions, etc.)
    Views
    (logic and presentation)
    ORM
    (database kerfuffling)
    ASGI Routing
    (path/protocol/etc. mapping)
    ASGI Middleware
    (auth, sessions, etc.)
    Consumers
    (logic and presentation)
    synchronous

    View Slide

  36. How to make WSGI async?

    View Slide

  37. ASGI: It's like WSGI but with an A in it
    Also asyncio and stuff like that

    View Slide

  38. def application(environ, start_response):
    ...
    start_response(...)
    yield data

    View Slide

  39. class Application:
    def __init__(self, scope):
    ...
    async def __call__(self, receive, send):
    event = await receive()
    ...
    await send(data)

    View Slide

  40. "Turtles all the way down"
    Routing is an ASGI app. Middleware is too.

    View Slide

  41. What does this mean for Django?

    View Slide

  42. How much can we make async?
    Can we keep sync compatability?

    View Slide

  43. What does an async ORM look like?
    Is it even a sensible endeavour?

    View Slide

  44. Can we make Django more pluggable?
    Finally get to that shareable-middleware goal

    View Slide

  45. Do we need to replace WSGI?
    Or does nobody want long-polling or websockets?

    View Slide

  46. Multiple servers (daphne, uvicorn)
    Multiple frameworks (Django, …?)

    View Slide

  47. Do we want to have everyone writing async?
    What's the balance? How do we be flexible enough?

    View Slide

  48. What is Django?

    View Slide

  49. Thanks.
    Andrew Godwin
    @andrewgodwin aeracode.org

    View Slide