How do event loops work in Python?

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

description

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

Transcript of How do event loops work in Python?

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