$30 off During Our Annual Pro Sale. View Details »

Taking Channels Async

Taking Channels Async

A talk I gave at DjangoCon Europe 2018.

Andrew Godwin

May 24, 2018
Tweet

More Decks by Andrew Godwin

Other Decks in Programming

Transcript

  1. TAKING
    Andrew Godwin
    @andrewgodwin
    CHANNELS
    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: "django-onair"

    View Slide

  4. 2015: Channels 0.1

    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)
    Clients
    Channel Layer
    (Redis or other)
    Webserver
    (Twisted)
    Django
    (Sync worker process)

    View Slide

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

    View Slide

  9. I was wrong.

    View Slide

  10. 2018: Channels 2.0

    View Slide

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

    View Slide

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

    View Slide

  13. 75% rewrite

    View Slide

  14. Django had to be partially async

    View Slide

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

    View Slide

  16. WSGI/ASGI Handler
    (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

  17. Async-native most of the way

    View Slide

  18. But how?

    View Slide

  19. sync_to_async
    async_to_sync

    View Slide

  20. sync_to_async
    async_to_sync

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  24. sync_to_async
    async_to_sync

    View Slide

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

    View Slide

  26. # 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

  27. 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

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. 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

  33. How to make WSGI async?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. What does this mean for Django?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  43. What is Django?

    View Slide

  44. Thanks.
    Andrew Godwin
    @andrewgodwin aeracode.org

    View Slide