how do event loops work in python?

31
How do event loops work in Python? Saúl Ibarra Corretgé FOSDEM 2013

Upload: saul-ibarra-corretge

Post on 08-May-2015

9.287 views

Category:

Technology


2 download

DESCRIPTION

Slides from the talk given at FOSDEM 2013 Python Devroom about how async I/O and event loops work in Python.

TRANSCRIPT

Page 1: How do event loops work in Python?

How do event loops work in Python?

Saúl Ibarra Corretgé

FOSDEM 2013

Page 2: How do event loops work in Python?

print(self)

• Hi there!

• @saghul

• I work on VoIP and Real Time Communications

• Python and C are my tools

Page 3: How do event loops work in Python?

try: socketsfrom __future__ import print_function

import socket

server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)server.bind(('127.0.0.1', 1234))server.listen(10)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.upper())

server.close()

Page 4: How do event loops work in Python?

except Exception:

• We can only handle one client at a time!

• Solutions for handling multiple clients

• Threads

• I/O multiplexing

• Check the C10K problem if you haven’t already!

• http://www.kegel.com/c10k.html

Page 5: How do event loops work in Python?

try: sockets + threadsfrom __future__ import print_function

import socketimport thread

def handle_client(client, addr): print("Client connected: {}".format(addr)) while True: data = client.recv(4096) if not data: print("Client has disconnected") break client.send(data.upper())

server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)server.bind(('127.0.0.1', 1234))server.listen(10)print("Server listening on: {}".format(server.getsockname()))

while True: client, addr = server.accept() thread.start_new_thread(handle_client, (client, addr))

Page 6: How do event loops work in Python?

except Exception:

• Threads have overhead

• Stack size

• Context switching

• Synchronization

• GIL?

• Not for I/O!

Page 7: How do event loops work in Python?

I/O multiplexing

• Examine and block for I/O in multiple file descriptors at the same time

• Single thread

• A file descriptor is ready if the corresponding I/O operation can be performed without blocking

• File descriptor has to be set to be non-blocking

Page 8: How do event loops work in Python?

I/O multiplexing (II)

1. Put file descriptors in non-blocking mode

2. Add file descriptors to a I/O multiplexor

3. Block for some time

4. Perform blocking operations on file descriptors which are ready

Page 9: How do event loops work in Python?

def set_nonblockingdef set_nonblocking(fdobj): try: setblocking = fdobj.setblocking except AttributeError: try: import fcntl except ImportError: raise NotImplementedError try: fd = fdobj.fileno() except AttributeError: fd = fdobj flags = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) else: setblocking(False)

Page 10: How do event loops work in Python?

select

• Lowest common denominator I/O multiplexor

• Takes a list of “readers”, “writers”, “exceptional conditions” and a timeout

• Limit of FD_SETSIZE file descriptors, usually 1024

• Processing takes O(number of fds)

Page 11: How do event loops work in Python?
Page 12: How do event loops work in Python?

epoll

• “A better select”

• No FD_SETSIZE limit!

• Processing takes O(1) time!

• Linux only

Page 13: How do event loops work in Python?

Other I/O multiplexors

• kqueue

• Like epoll but for Max OSX and BSD

• poll

• Like select but without a limit of file descriptors

• O(number of file descriptors) processing time

• Very broken on some systems (OSX 10.3-5)

Page 14: How do event loops work in Python?

sys.platform == “win32”• Windows supports select

• 64 file descriptors per thread - WAT

• Starting with Vista WSAPoll (poll) is supported

• It’s broken - http://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/

• IOCP is the good stuff

• Based on completion, not readiness

Page 15: How do event loops work in Python?

edge/level triggering

• Level triggering (the most common)

• You get notified when the condition is present

• Edge triggering

• You get notified when the condition happens

• epoll and kqueue support both mechanisms

• IOCP is kind-of edge-triggered epoll

Page 16: How do event loops work in Python?
Page 17: How do event loops work in Python?

Event loop libraries

epoll, kqueue

IOCP on windows

File I/O

libev YES NO libeio

libevent YES YES NO

libuv YES YES YES

Page 18: How do event loops work in Python?

Event loop libraries (II)

1. Update loop time

2. Process timers

3. Process idle handles

4. Process prepare handles

5. Block for I/O

6. Process check handles

Page 19: How do event loops work in Python?

libuv: the power underneath nodeJS

Page 20: How do event loops work in Python?

nodeJS <= 0.4.x

node standard library

node bindings

V8 libev libeio

JavaScript

C / C++

Page 21: How do event loops work in Python?

nodeJS >= 0.6.x

node standard library

node bindings

V8 libuv

JavaScript

C / C++

Page 22: How do event loops work in Python?

libuv

• Originally based on libev + libeio + IOCP

• Now: epoll + kqueue + event ports + IOCP + file I/O

• TCP, UDP, named pipes, TTY

• Nice additions: interface addresses, process title, ...

• Most complete cross platform networking library

• Python bindings - pyuv

Page 23: How do event loops work in Python?

import pyuv

• Written in C, wraps everything libuv has to offer

• Python >= 2.6, supports Python 3!

• Works on Windows

• https://github.com/saghul/pyuv

Page 24: How do event loops work in Python?

from __future__ import print_function

import signalimport pyuv

def on_read(client, data, error): if data is None: print("Client read error: {}".format(pyuv.errno.strerror(error))) client.close() clients.remove(client) return client.write(data.upper())

def on_connection(server, error): client = pyuv.TCP(server.loop) server.accept(client) clients.append(client) client.start_read(on_read) print("Client connected: {}".format(client.getpeername()))

def signal_cb(handle, signum): [c.close() for c in clients] signal_h.close() server.close()

clients = []loop = pyuv.Loop.default_loop()server = pyuv.TCP(loop)server.bind(("127.0.0.1", 1234))server.listen(on_connection)signal_h = pyuv.Signal(loop)signal_h.start(signal_cb, signal.SIGINT)loop.run()

Page 25: How do event loops work in Python?

import pyuv

• Experiments in replacing event loops of common Python networking frameworks

• Twisted - https://github.com/saghul/twisted-pyuv

• Tornado - https://github.com/saghul/tornado-pyuv

• Gevent - https://github.com/saghul/uvent

Page 26: How do event loops work in Python?

import pyuv

• gaffer: application deployment and supervisor

• https://github.com/benoitc/gaffer

• ts_analyzer: realtime analysis of multicast video streams

• https://github.com/tijmenNL/ts_analyzer

Page 27: How do event loops work in Python?

The Python async I/O problem

• Each framework uses it’s own event loop implementation

• Protocols aren’t reusable

• PEP-3156 to the rescue!

• For Python >= 3.3

• Reference implementation (Tulip)https://code.google.com/p/tulip/

Page 28: How do event loops work in Python?

import rose

• PEP-3156 EventLoop implementation using pyuv

• pyuv.Poll: high performance level triggered I/O(works on Windows as well!)

• Uses fairy dust covered unicorns

• https://github.com/saghul/rose

Page 29: How do event loops work in Python?

import roseimport signalfrom rose import events, protocols

class EchoProtocol(protocols.Protocol): def connection_made(self, transport): # TODO: Transport should probably expose getsockname/getpeername print("Client connected: {}".format(transport._sock.getpeername())) self.transport = transport def data_received(self, data): self.transport.write(data.upper()) def eof_received(self): self.transport.close() def connection_lost(self, exc): print("Client closed connection")

reactor = events.new_event_loop()events.set_event_loop(reactor)

reactor.start_serving(EchoProtocol, '127.0.0.1', 1234)reactor.add_signal_handler(signal.SIGINT, reactor.stop)

reactor.run()

Page 30: How do event loops work in Python?
Page 31: How do event loops work in Python?

saghul