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

The Long Road To Asynchrony

The Long Road To Asynchrony

My keynote from PyCon Belarus 2020.

Andrew Godwin

February 22, 2020
Tweet

More Decks by Andrew Godwin

Other Decks in Programming

Transcript

  1. LONG ROAD
    ANDREW GODWIN // @andrewgodwin
    ASYNCHRONY
    TO
    THE

    View full-size slide

  2. Andrew Godwin / @andrewgodwin
    Hi, I’m
    Andrew Godwin
    • Django core developer
    • Worked on Migrations, Channels & Async
    • Once a Londoner, now from Denver, USA

    View full-size slide

  3. Andrew Godwin / @andrewgodwin

    View full-size slide

  4. Andrew Godwin / @andrewgodwin
    "Asynchronous Programming"
    What is it, really?

    View full-size slide

  5. Andrew Godwin / @andrewgodwin
    Concurrent Programming
    The more general term

    View full-size slide

  6. Andrew Godwin / @andrewgodwin
    Input
    Process
    Output
    Sequential execution

    View full-size slide

  7. Andrew Godwin / @andrewgodwin
    Input
    Process
    Output
    Process
    Concurrent execution
    Archive

    View full-size slide

  8. Andrew Godwin / @andrewgodwin
    Shared use of a single resource
    In this case, CPUs

    View full-size slide

  9. Andrew Godwin / @andrewgodwin
    "Communicating Sequential Processes", C. A. R Hoare

    View full-size slide

  10. Andrew Godwin / @andrewgodwin
    func main() {
    messages := make(chan string)
    go func() { messages <- "ping" }()
    msg := <-messages
    fmt.Println(msg)
    }

    View full-size slide

  11. Andrew Godwin / @andrewgodwin
    Multiple processes
    multiprocessing
    Threads
    threading
    Event loops
    asyncio / twisted

    View full-size slide

  12. Andrew Godwin / @andrewgodwin
    Multiple processes scale best
    It's also difficult and costs the most!

    View full-size slide

  13. Andrew Godwin / @andrewgodwin
    Threads are unpredictable
    Also, the GIL is our ever-present friend

    View full-size slide

  14. Andrew Godwin / @andrewgodwin
    Event loops are a good compromise
    They do require shared memory, though.

    View full-size slide

  15. Andrew Godwin / @andrewgodwin
    Asynchronous ≈ Event loops
    Most of the time!

    View full-size slide

  16. Andrew Godwin / @andrewgodwin
    # Ready when a timer finishes
    await asyncio.sleep(1)
    # Ready when network packets return
    await client.get("http://example.com")
    # Ready when the coroutine exits
    await my_function("hello", 64.2)

    View full-size slide

  17. Andrew Godwin / @andrewgodwin
    Network/timer updates
    An event loop's flow
    Select a ready task
    Run task
    Add new tasks to queue
    await

    View full-size slide

  18. Andrew Godwin / @andrewgodwin
    Coroutines
    Time →

    View full-size slide

  19. Andrew Godwin / @andrewgodwin
    How did we get here?

    View full-size slide

  20. Andrew Godwin / @andrewgodwin
    1998 threading module, Stackless Python
    2002 Twisted
    2006 Greenlets (later gevent, eventlet)
    2008 multiprocessing module
    2012 Tulip, PEP 3156
    2014 asyncio module
    2005 Coroutine-friendly generators (PEP 342)

    View full-size slide

  21. Andrew Godwin / @andrewgodwin
    2017 Django Channels 1.0
    2018 Django Channels 2.0
    2019 DEP 9 (Async support)
    2020 Async views land in Django

    View full-size slide

  22. Andrew Godwin / @andrewgodwin
    No solution is perfect
    Everyone chooses different tradeoffs

    View full-size slide

  23. Andrew Godwin / @andrewgodwin
    Asyncio is based on yield from
    Because it was prototyped in Python 2

    View full-size slide

  24. Andrew Godwin / @andrewgodwin
    Can't tell if a function returns a coroutine!
    There are standard hints, but no actual guaranteed way

    View full-size slide

  25. Andrew Godwin / @andrewgodwin
    async def calculate(x):
    result = await coroutine(x)
    return result
    # These both return a coroutine
    def calculate(x):
    result = coroutine(x)
    return result

    View full-size slide

  26. Andrew Godwin / @andrewgodwin
    Can't have one function service both
    How we got here makes sense, but it's still annoying sometimes.

    View full-size slide

  27. Andrew Godwin / @andrewgodwin
    # Calls get.__call__
    instance = MyModel.objects.get(id=3)
    # Calls get.__call__
    # and then awaits its result
    instance = await MyModel.objects.get(id=3)

    View full-size slide

  28. Andrew Godwin / @andrewgodwin
    You have to namespace async functions
    I really, really wish we didn't have to

    View full-size slide

  29. Andrew Godwin / @andrewgodwin
    instance = MyModel.objects.get(id=3)
    instance = await MyModel.objects.async.get(id=3)

    View full-size slide

  30. Andrew Godwin / @andrewgodwin
    Completely different libraries!
    Even sleep() is different.

    View full-size slide

  31. Andrew Godwin / @andrewgodwin
    time.sleep ➞ asyncio.sleep
    requests ➞ httpx
    psycopg2 ➞ aiopg
    WSGI ➞ ASGI
    Django ➞ Django?

    View full-size slide

  32. Andrew Godwin / @andrewgodwin
    Django & Async

    View full-size slide

  33. Andrew Godwin / @andrewgodwin
    Asyncio only benefits IO-bound code
    Code that thrashes the CPU doesn't benefit at all

    View full-size slide

  34. Andrew Godwin / @andrewgodwin
    We're adding async to some parts
    The bits where it makes sense!

    View full-size slide

  35. Andrew Godwin / @andrewgodwin
    But, you can't mix sync and async
    So we have to have two parallel request paths

    View full-size slide

  36. Andrew Godwin / @andrewgodwin
    WSGIHandler
    __call__
    WSGI Server
    WSGIRequest
    BaseHandler
    get_response
    URLs Middleware
    View
    __call__
    HTTP protocol
    Socket handling
    Transfer encodings
    Headers-to-META
    Upload file wrapping
    GET/POST parsing
    Exception catching
    Atomic view wrapper
    Django 3.0 Request Flow

    View full-size slide

  37. Andrew Godwin / @andrewgodwin
    WSGIHandler
    __call__
    WSGI Server
    WSGIRequest
    BaseHandler
    get_response
    URLs Middleware
    Async View
    __call__
    ASGIHandler
    __call__
    ASGI Server
    ASGIRequest
    Sync View
    __call__
    Asynchronous
    request path
    Proposed async request flow

    View full-size slide

  38. Andrew Godwin / @andrewgodwin
    WSGIHandler
    __call__
    WSGI Server
    WSGIRequest URLs Middleware
    View
    __call__
    ASGIHandler
    __call__
    ASGI Server
    ASGIRequest
    Asynchronous
    request path
    BaseHandler
    get_response_async
    BaseHandler
    get_response
    URLs Middleware
    Async View
    __call__
    Implemented async request flow

    View full-size slide

  39. Andrew Godwin / @andrewgodwin
    We have to work with what we have
    I'd rather let people ship code than argue about perfection.

    View full-size slide

  40. Andrew Godwin / @andrewgodwin
    Django's main job is safety
    It matters more than anything else

    View full-size slide

  41. Andrew Godwin / @andrewgodwin
    Deadlocks
    Livelocks
    Starvation
    Race conditions

    View full-size slide

  42. Andrew Godwin / @andrewgodwin
    Coroutines & the GIL actually help!
    You're saved from all the awful memory corruption bugs

    View full-size slide

  43. Andrew Godwin / @andrewgodwin
    async def transfer_money(p1, p2):
    await get_lock()
    # Code between awaits is atomic!
    subtract_money(p1)
    add_money(p2)
    await release_lock()

    View full-size slide

  44. Andrew Godwin / @andrewgodwin
    You can still screw up a lot
    Trust me, I have lived it while developing async

    View full-size slide

  45. Andrew Godwin / @andrewgodwin
    async def log_message(m):
    await client.post("log-server", m)
    result = calculate_result()
    log_message(m)

    View full-size slide

  46. Andrew Godwin / @andrewgodwin
    async def log_message(m):
    await client.post("log-server", m)
    result = calculate_result()
    await log_message(m)

    View full-size slide

  47. Andrew Godwin / @andrewgodwin
    Async usability has a long way to go
    But it is undoubtedly the future!

    View full-size slide

  48. Andrew Godwin / @andrewgodwin
    Python is the language of pragmatism
    If anyone can get it right, we can

    View full-size slide

  49. Andrew Godwin / @andrewgodwin
    What does the future hold?
    Hopefully, no GIL!

    View full-size slide

  50. Andrew Godwin / @andrewgodwin
    Let's make async understandable
    Almost every project could benefit, if we made it worth their time.

    View full-size slide

  51. Thanks.
    Andrew Godwin
    @andrewgodwin // aeracode.org

    View full-size slide