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

  • 5,089 views
Uploaded on

Slides from the talk I gave at FOSDEM 2014 about what PEP-3156 specifies and how the asyncio module works.

Slides from the talk I gave at FOSDEM 2014 about what PEP-3156 specifies and how the asyncio module works.

More in: Technology , Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
5,089
On Slideshare
0
From Embeds
0
Number of Embeds
2

Actions

Shares
Downloads
65
Comments
0
Likes
16

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. A deep dive into PEP-3156 and the new asyncio module Saúl Ibarra Corretgé @saghul FOSDEM 2014
  • 2. 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 >>>
  • 3. import open_source github.com/saghul
  • 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
  • 6. Frameworks • Platform abstraction • Protocol implementations • Integration with other event loops: Qt, GLib, ... • Different API styles
  • 7. 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
  • 8. import tornado • Uses select, poll, kqueue, epoll from the select module • select() on Windows :-( • Mainly oriented to web development • Synchronous looking API with coroutines
  • 9. import gevent • Uses libevent in version 0.x and libev in 1.x • select() on Windows :-( • Syncronous API using greenlet
  • 10. import asyncore • raise RuntimeError(“NOT GOOD ENOUGH”) • “asyncore: included batteries don’t fit” bit.ly/182HcHT
  • 11. Solution!
  • 12. I’m not trying to reinvent the wheel. I’m trying to build a good one. Guido van Rossum
  • 13. asyncio import tulip
  • 14. import asyncio • Reference implementation for PEP-3156 • Basic components for doing async i/o • Works (officially) on Python >= 3.3 [*]
  • 15. 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
  • 16. Goals • Unix and Windows support • IPv4 and IPv6 • TCP, UDP and pipes • Basic SSL (secure by default) • Subprocesses
  • 17. Non goals • Perfection • Replacing current frameworks • Protocol implementations • Replace httplib, smtplib, ... • Make it work on Python < 3.3
  • 18. Interoperability? twisted tornado gevent ... asyncio selectors iocp
  • 19. Tornado interop. tornado tornado epoll/kqueue asyncio
  • 20. Rose asyncio pyuv github.com/saghul/rose
  • 21. Architecture
  • 22. Components Event loop, policy Coroutines, Futures, Tasks Transports, Protocols
  • 23. Calculate poll time Poll Run callbacks Event loop
  • 24. Event loop & policy • Chooses the best i/o mechanism for a given platform • APIs for creating server and client connections (TCP, UDP, ...)
  • 25. Callbacks • loop.call_soon(func, *args) • loop.call_later(delay, func, *args) • loop.call_at(when, func, *args) • loop.time()
  • 26. Callbacks for I/O • loop.add_reader(fd, func, *args) • loop.add_writer(fd, func, *args) • loop.remove_reader(fd) • loop.remove_writer(fd)
  • 27. Unix signals • loop.add_signal_handler(sig, func, *args) • loop.remove_signal_handler(sig)
  • 28. Working with threads • loop.call_soon_threadsafe(func, *args) • loop.run_in_executor(exc, func, *args) • loop.set_default_executor(exc) • PEP-3148 executor
  • 29. Starting / stopping • loop.run_forever() • loop.stop() • loop.run_until_complete(f)
  • 30. The loop instance • get_event_loop() • set_event_loop(loop) • new_event_loop()
  • 31. Policy (default) • Defines the event loop context • One event loop per thread • An event lop is automagically created just for the main thread
  • 32. Policy • Configures what get/set/new _event_loop do • The single global object • It can be changed (example: rose)
  • 33. Coroutines, Futures & Tasks
  • 34. 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
  • 35. 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))
  • 36. 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)
  • 37. 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()
  • 38. 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
  • 39. 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)
  • 40. 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(...))
  • 41. 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!
  • 42. 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()
  • 43. Transports & Protocols
  • 44. 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
  • 45. 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
  • 46. 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
  • 47. Transport -> Protocol • connection_made(transport) • data_received(data) • eof_received() • connection_lost(exc) • UDP, pipes and subprocesses are slightly different
  • 48. Protocol -> Transport • write(data) • writelines(seq) • write_eof() • close()
  • 49. More Transport methods • can_write_eof() • abort() • get_extra_info(key) • ‘socket’ • ‘sockname’, ‘peername’ • ‘sslcontext’ • ...
  • 50. 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
  • 51. Status • PEP (provisionally) accepted • Available since Python 3.4b1 and on PyPI • Still evolving!
  • 52. 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
  • 53. Questions? @saghul bettercallsaghul.com
  • 54. 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