Slide 1

Slide 1 text

LONG ROAD ANDREW GODWIN // @andrewgodwin ASYNCHRONY TO THE

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Andrew Godwin / @andrewgodwin

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Andrew Godwin / @andrewgodwin Concurrent Programming The more general term

Slide 6

Slide 6 text

Andrew Godwin / @andrewgodwin Input Process Output Sequential execution

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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)

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Andrew Godwin / @andrewgodwin Coroutines Time →

Slide 19

Slide 19 text

Andrew Godwin / @andrewgodwin How did we get here?

Slide 20

Slide 20 text

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)

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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)

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Andrew Godwin / @andrewgodwin Django & Async

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Andrew Godwin / @andrewgodwin Deadlocks Livelocks Starvation Race conditions

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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()

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Thanks. Andrew Godwin @andrewgodwin // aeracode.org