A deep dive into PEP-3156
and the new asyncio module
Saúl Ibarra Corretgé
@saghul

FOSDEM 2014
repr(self)
>>> from Amsterdam import saghul
>>>
>>> saghul.work()
VoIP, SIP, XMPP, chat, Real Time Communications
>>>
>>> saghul.other()
networking, event loops, sockets, MOAR PYTHON
>>>
>>> saghul.languages()
Python, C
>>>
import open_source

github.com/saghul
import socket
import socket
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 1234))
server.listen(128)
print("Server listening on: {}".format(server.getsockname()))
client, addr = server.accept()
print("Client connected: {}".format(addr))
while True:
data = client.recv(4096)
if not data:
print("Client has disconnected")
break
client.send(data)
server.close()
I/O is hard
• Sync i/o is bad, async i/o is Good (TM)
• Different paradigms in Unix vs
Windows
• “are you ready?” vs “call me later”

• Event loops are The Way To Go
• See the c10k problem
Frameworks
• Platform abstraction
• Protocol implementations
• Integration with other event loops: Qt,
GLib, ...
• Different API styles
import twisted
• Uses select, poll, kqueue, epoll from
the select module
• IOCP on Windows
• Integration with other event loops: Qt
• Factory/Protocol/Transport
abstractions
• Deferred
import tornado
• Uses select, poll, kqueue, epoll from
the select module
• select() on Windows :-(
• Mainly oriented to web development
• Synchronous looking API with
coroutines
import gevent

• Uses libevent in version 0.x and libev in
1.x

• select() on Windows :-(
• Syncronous API using greenlet
import asyncore

• raise RuntimeError(“NOT GOOD ENOUGH”)
• “asyncore: included batteries don’t fit”
bit.ly/182HcHT
Solution!
I’m not trying to reinvent the
wheel. I’m trying to build a
good one.
Guido van Rossum
asyncio

import tulip
import asyncio

• Reference implementation for
PEP-3156
• Basic components for doing async i/o
• Works (officially) on Python >= 3.3 [*]
Goals
• Modern implementation of async i/o for
Python
• Use yield from (PEP-380)
• But don’t force it

• Don’t use anything that requires
Python > 3.3
• Interoperability with other frameworks
Goals

• Unix and Windows support
• IPv4 and IPv6
• TCP, UDP and pipes
• Basic SSL (secure by default)
• Subprocesses
Non goals
• Perfection
• Replacing current frameworks
• Protocol implementations
• Replace httplib, smtplib, ...
• Make it work on Python < 3.3
Interoperability?

twisted

tornado

gevent

...

asyncio
selectors

iocp
Tornado interop.

tornado

tornado

epoll/kqueue

asyncio
Rose

asyncio
pyuv

github.com/saghul/rose
Architecture
Components
Event loop, policy

Coroutines, Futures, Tasks

Transports, Protocols
Calculate
poll time

Poll

Run
callbacks

Event loop
Event loop & policy

• Chooses the best i/o mechanism for a
given platform
• APIs for creating server and client
connections (TCP, UDP, ...)
Callbacks

• loop.call_soon(func, *args)
• loop.call_later(delay, func, *args)
• loop.call_at(when, func, *args)
• loop.time()
Callbacks for I/O

• loop.add_reader(fd, func, *args)
• loop.add_writer(fd, func, *args)
• loop.remove_reader(fd)
• loop.remove_writer(fd)
Unix signals

• loop.add_signal_handler(sig, func, *args)
• loop.remove_signal_handler(sig)
Working with threads
• loop.call_soon_threadsafe(func, *args)
• loop.run_in_executor(exc, func, *args)
• loop.set_default_executor(exc)
• PEP-3148 executor
Starting / stopping

• loop.run_forever()
• loop.stop()
• loop.run_until_complete(f)
The loop instance

• get_event_loop()
• set_event_loop(loop)
• new_event_loop()
Policy (default)

• Defines the event loop context
• One event loop per thread
• An event lop is automagically created
just for the main thread
Policy

• Configures what get/set/new
_event_loop do
• The single global object
• It can be changed (example: rose)
Coroutines, Futures &
Tasks
Coroutines, Futures & Tasks
• Coroutines
• a generator function, can receive values
• decorated with @coroutine

• Future
• promise of a result or an error

• Task
• Future which runs a coroutine
Coroutines & yield from
import asyncio
import socket
loop = asyncio.get_event_loop()
@asyncio.coroutine
def handle_client(client, addr):
print("Client connected: {}".format(addr))
while True:
data = yield from loop.sock_recv(client, 4096)
if not data:
print("Client has disconnected")
break
client.send(data)
@asyncio.coroutine
def accept_connections(server_socket):
while True:
client, addr = yield from loop.sock_accept(server_socket)
asyncio.async(handle_client(client, addr))
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 1234))
server.listen(128)
server.setblocking(False)
print("Server listening on: {}".format(server.getsockname()))
loop.run_until_complete(accept_connections(server))
Coroutines & yield from

• Imagine the yield from is not there
• Imagine the code is executed
sequentially
• Not exactly the formal definition of yield
from (from PEP-380)
Futures
• Similar to Futures from PEP-3148
•

concurrent.futures.Future

• API (almost) identical:
•

f.set_result(); r = f.result()

•

f.set_exception(e); e = f.exception()

•

f.done()

•

f.add_done_callback(x); f.remove_done_callback(x)

•

f.cancel(); f.cancelled()
Futures + Coroutines
• yield from works with Futures!
• f = Future()
•

Someone will set the result or exception

• r = yield from f
•

Waits until done and returns f.result()

• Usually returned by functions
Undoing callbacks
@asyncio.coroutine
def sync_looking_function(*args):
fut = asyncio.Future()
def cb(result, error):
if error is not None:
fut.set_result(result)
else:
fut.set_exception(Exception(error))
async_function(cb, *args)
return (yield from fut)
Tasks
• Unicorns covered in fairy dust
• It’s a coroutine wrapped in a Future
• WAT
• Inherits from Future
• Works with yield from
• r = yield from Task(coro(...))
Tasks vs coroutines
• A coroutine doesn’t “advance” without
a scheduling mechanism
• Tasks can advance in their own
• The event loop is the scheduler!
• Magic!
Example
import asyncio
loop = asyncio.get_event_loop()
clients = {} # task -> (reader, writer)
def accept_client(client_reader, client_writer):
task = asyncio.Task(handle_client(client_reader, client_writer))
clients[task] = (client_reader, client_writer)
def client_done(task):
del clients[task]
task.add_done_callback(client_done)
@asyncio.coroutine
def handle_client(client_reader, client_writer):
while True:
data = (yield from client_reader.readline())
client_writer.write(data)

f = asyncio.start_server(accept_client, '127.0.0.1', 12345)
server = loop.run_until_complete(f)
loop.run_forever()
Transports & Protocols
Transports & Protocols

• Transport: represents a connection
(socket, pipe, ...)

• Protocol: represents an application
(HTTP server, IRC client, ...)

• They always go together
• API is based on function calls and
callbacks
Clients & servers
• loop.create_connection(...)
• creates a Transport and a Protocol

• loop.create_server(...)
• creates a Transport and a Protocol for each
accepted connection
• returns a Server object
Clients & servers
• loop.open_connection(...)
• wrapper around create_connection, returns
(stream_reader, stream_writer)

• loop.start_server(...)
• wrapper around create_server, calls a
callback with (stream_reader, stream_writer)
for each accepted conection
Transport -> Protocol

• connection_made(transport)
• data_received(data)
• eof_received()
• connection_lost(exc)
• UDP, pipes and subprocesses are
slightly different
Protocol -> Transport

• write(data)
• writelines(seq)
• write_eof()
• close()
More Transport methods
• can_write_eof()
• abort()
• get_extra_info(key)
• ‘socket’
• ‘sockname’, ‘peername’
• ‘sslcontext’
• ...
Enter Trollius!

• Backport of asyncio for Python >= 2.6
• By Victor Stinner
• Slightly different syntax
•
•

yield instead of yield from
raise Return(x) instead of return x on
generators

• pip install trollius
Status

• PEP (provisionally) accepted
• Available since Python 3.4b1 and on
PyPI

• Still evolving!
That was all?

• Yes and No
• Go read PEP-3156
• Implement a simple protocol (IRC
client)

• Checkout the third party libraries
• Use asyncio in your next project
Questions?

@saghul
bettercallsaghul.com
References
• code.google.com/p/tulip/
• groups.google.com/forum/#!forum/
python-tulip
• PEP-3156
• http://www.youtube.com/watch?
v=1coLC-MUCJc
• https://www.dropbox.com/s/
essjj4qmmtrhys4/SFMeetup2013.pdf

A deep dive into PEP-3156 and the new asyncio module

  • 1.
    A deep diveinto PEP-3156 and the new asyncio module Saúl Ibarra Corretgé @saghul FOSDEM 2014
  • 2.
    repr(self) >>> from Amsterdamimport saghul >>> >>> saghul.work() VoIP, SIP, XMPP, chat, Real Time Communications >>> >>> saghul.other() networking, event loops, sockets, MOAR PYTHON >>> >>> saghul.languages() Python, C >>>
  • 3.
  • 4.
    import socket import socket server= socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) server.bind(('127.0.0.1', 1234)) server.listen(128) print("Server listening on: {}".format(server.getsockname())) client, addr = server.accept() print("Client connected: {}".format(addr)) while True: data = client.recv(4096) if not data: print("Client has disconnected") break client.send(data) server.close()
  • 5.
    I/O is hard •Sync i/o is bad, async i/o is Good (TM) • Different paradigms in Unix vs Windows • “are you ready?” vs “call me later” • Event loops are The Way To Go • See the c10k problem
  • 7.
    Frameworks • Platform abstraction •Protocol implementations • Integration with other event loops: Qt, GLib, ... • Different API styles
  • 8.
    import twisted • Usesselect, poll, kqueue, epoll from the select module • IOCP on Windows • Integration with other event loops: Qt • Factory/Protocol/Transport abstractions • Deferred
  • 9.
    import tornado • Usesselect, poll, kqueue, epoll from the select module • select() on Windows :-( • Mainly oriented to web development • Synchronous looking API with coroutines
  • 10.
    import gevent • Useslibevent in version 0.x and libev in 1.x • select() on Windows :-( • Syncronous API using greenlet
  • 11.
    import asyncore • raiseRuntimeError(“NOT GOOD ENOUGH”) • “asyncore: included batteries don’t fit” bit.ly/182HcHT
  • 12.
  • 13.
    I’m not tryingto reinvent the wheel. I’m trying to build a good one. Guido van Rossum
  • 14.
  • 15.
    import asyncio • Referenceimplementation for PEP-3156 • Basic components for doing async i/o • Works (officially) on Python >= 3.3 [*]
  • 16.
    Goals • Modern implementationof async i/o for Python • Use yield from (PEP-380) • But don’t force it • Don’t use anything that requires Python > 3.3 • Interoperability with other frameworks
  • 17.
    Goals • Unix andWindows support • IPv4 and IPv6 • TCP, UDP and pipes • Basic SSL (secure by default) • Subprocesses
  • 18.
    Non goals • Perfection •Replacing current frameworks • Protocol implementations • Replace httplib, smtplib, ... • Make it work on Python < 3.3
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
    Components Event loop, policy Coroutines,Futures, Tasks Transports, Protocols
  • 24.
  • 25.
    Event loop &policy • Chooses the best i/o mechanism for a given platform • APIs for creating server and client connections (TCP, UDP, ...)
  • 26.
    Callbacks • loop.call_soon(func, *args) •loop.call_later(delay, func, *args) • loop.call_at(when, func, *args) • loop.time()
  • 27.
    Callbacks for I/O •loop.add_reader(fd, func, *args) • loop.add_writer(fd, func, *args) • loop.remove_reader(fd) • loop.remove_writer(fd)
  • 28.
    Unix signals • loop.add_signal_handler(sig,func, *args) • loop.remove_signal_handler(sig)
  • 29.
    Working with threads •loop.call_soon_threadsafe(func, *args) • loop.run_in_executor(exc, func, *args) • loop.set_default_executor(exc) • PEP-3148 executor
  • 30.
    Starting / stopping •loop.run_forever() • loop.stop() • loop.run_until_complete(f)
  • 31.
    The loop instance •get_event_loop() • set_event_loop(loop) • new_event_loop()
  • 32.
    Policy (default) • Definesthe event loop context • One event loop per thread • An event lop is automagically created just for the main thread
  • 33.
    Policy • Configures whatget/set/new _event_loop do • The single global object • It can be changed (example: rose)
  • 34.
  • 35.
    Coroutines, Futures &Tasks • Coroutines • a generator function, can receive values • decorated with @coroutine • Future • promise of a result or an error • Task • Future which runs a coroutine
  • 36.
    Coroutines & yieldfrom import asyncio import socket loop = asyncio.get_event_loop() @asyncio.coroutine def handle_client(client, addr): print("Client connected: {}".format(addr)) while True: data = yield from loop.sock_recv(client, 4096) if not data: print("Client has disconnected") break client.send(data) @asyncio.coroutine def accept_connections(server_socket): while True: client, addr = yield from loop.sock_accept(server_socket) asyncio.async(handle_client(client, addr)) server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) server.bind(('127.0.0.1', 1234)) server.listen(128) server.setblocking(False) print("Server listening on: {}".format(server.getsockname())) loop.run_until_complete(accept_connections(server))
  • 37.
    Coroutines & yieldfrom • Imagine the yield from is not there • Imagine the code is executed sequentially • Not exactly the formal definition of yield from (from PEP-380)
  • 38.
    Futures • Similar toFutures from PEP-3148 • concurrent.futures.Future • API (almost) identical: • f.set_result(); r = f.result() • f.set_exception(e); e = f.exception() • f.done() • f.add_done_callback(x); f.remove_done_callback(x) • f.cancel(); f.cancelled()
  • 39.
    Futures + Coroutines •yield from works with Futures! • f = Future() • Someone will set the result or exception • r = yield from f • Waits until done and returns f.result() • Usually returned by functions
  • 40.
    Undoing callbacks @asyncio.coroutine def sync_looking_function(*args): fut= asyncio.Future() def cb(result, error): if error is not None: fut.set_result(result) else: fut.set_exception(Exception(error)) async_function(cb, *args) return (yield from fut)
  • 41.
    Tasks • Unicorns coveredin fairy dust • It’s a coroutine wrapped in a Future • WAT • Inherits from Future • Works with yield from • r = yield from Task(coro(...))
  • 42.
    Tasks vs coroutines •A coroutine doesn’t “advance” without a scheduling mechanism • Tasks can advance in their own • The event loop is the scheduler! • Magic!
  • 43.
    Example import asyncio loop =asyncio.get_event_loop() clients = {} # task -> (reader, writer) def accept_client(client_reader, client_writer): task = asyncio.Task(handle_client(client_reader, client_writer)) clients[task] = (client_reader, client_writer) def client_done(task): del clients[task] task.add_done_callback(client_done) @asyncio.coroutine def handle_client(client_reader, client_writer): while True: data = (yield from client_reader.readline()) client_writer.write(data) f = asyncio.start_server(accept_client, '127.0.0.1', 12345) server = loop.run_until_complete(f) loop.run_forever()
  • 44.
  • 45.
    Transports & Protocols •Transport: represents a connection (socket, pipe, ...) • Protocol: represents an application (HTTP server, IRC client, ...) • They always go together • API is based on function calls and callbacks
  • 46.
    Clients & servers •loop.create_connection(...) • creates a Transport and a Protocol • loop.create_server(...) • creates a Transport and a Protocol for each accepted connection • returns a Server object
  • 47.
    Clients & servers •loop.open_connection(...) • wrapper around create_connection, returns (stream_reader, stream_writer) • loop.start_server(...) • wrapper around create_server, calls a callback with (stream_reader, stream_writer) for each accepted conection
  • 48.
    Transport -> Protocol •connection_made(transport) • data_received(data) • eof_received() • connection_lost(exc) • UDP, pipes and subprocesses are slightly different
  • 49.
    Protocol -> Transport •write(data) • writelines(seq) • write_eof() • close()
  • 50.
    More Transport methods •can_write_eof() • abort() • get_extra_info(key) • ‘socket’ • ‘sockname’, ‘peername’ • ‘sslcontext’ • ...
  • 51.
    Enter Trollius! • Backportof asyncio for Python >= 2.6 • By Victor Stinner • Slightly different syntax • • yield instead of yield from raise Return(x) instead of return x on generators • pip install trollius
  • 52.
    Status • PEP (provisionally)accepted • Available since Python 3.4b1 and on PyPI • Still evolving!
  • 53.
    That was all? •Yes and No • Go read PEP-3156 • Implement a simple protocol (IRC client) • Checkout the third party libraries • Use asyncio in your next project
  • 54.
  • 55.
    References • code.google.com/p/tulip/ • groups.google.com/forum/#!forum/ python-tulip •PEP-3156 • http://www.youtube.com/watch? v=1coLC-MUCJc • https://www.dropbox.com/s/ essjj4qmmtrhys4/SFMeetup2013.pdf