Files
deb-python-eventlet/doc/basic_usage.rst

10 KiB

Basic Usage

Eventlet is built around the concept of green threads (i.e. coroutines, we use the terms interchangeably) that are launched to do network-related work. Green threads differ from normal threads in two main ways:

  • Green threads are so cheap they are nearly free. You do not have to conserve green threads like you would normal threads. In general, there will be at least one green thread per network connection.
  • Green threads cooperatively yield to each other instead of preemptively being scheduled. The major advantage from this behavior is that shared data structures don't need locks, because only if a yield is explicitly called can another green thread have access to the data structure. It is also possible to inspect primitives such as queues to see if they have any pending data.

There are a bunch of basic patterns that Eventlet usage falls into. Here are a few examples that show their basic structure.

Client-side pattern

The canonical client-side example is a web crawler. This use case is given a list of urls and wants to retrieve their bodies for later processing. Here is a very simple example:

urls = ["http://www.google.com/intl/en_ALL/images/logo.gif",
       "https://wiki.secondlife.com/w/images/secondlife.jpg",
       "http://us.i1.yimg.com/us.yimg.com/i/ww/beta/y3.gif"]

import eventlet
from eventlet.green import urllib2  

def fetch(url):
    return urllib2.urlopen(url).read()

pool = eventlet.GreenPool()
for body in pool.imap(fetch, urls):
    print "got body", len(body)

There is a slightly more complex version of this in the web crawler example <web_crawler_example>. Here's a tour of the interesting lines in this crawler.

from eventlet.green import urllib2 is how you import a cooperatively-yielding version of urllib2. It is the same in all respects to the standard version, except that it uses green sockets for its communication.

pool = eventlet.GreenPool() constructs a GreenPool <eventlet.greenpool.GreenPool> of a thousand green threads. Using a pool is good practice because it provides an upper limit on the amount of work that this crawler will be doing simultaneously, which comes in handy when the input data changes dramatically.

for body in pool.imap(fetch, urls): iterates over the results of calling the fetch function in parallel. imap <eventlet.greenpool.GreenPool.imap> makes the function calls in parallel, and the results are returned in the order that they were executed.

Server-side pattern

Here's a simple server-side example, a simple echo server:

import eventlet
from eventlet.green import socket

def handle(client):
    while True:
        c = client.recv(1)
        if not c: break
        client.sendall(c)

server = socket.socket()
server.bind(('0.0.0.0', 6000))
server.listen(50)
pool = eventlet.GreenPool(10000)
while True:
    new_sock, address = server.accept()
    pool.spawn_n(handle, new_sock)

The file echo server example <echo_server_example> contains a somewhat more robust and complex version of this example.

from eventlet.green import socket imports eventlet's socket module, which is just like the regular socket module, but cooperatively yielding.

pool = eventlet.GreenPool(10000) creates a pool of green threads that could handle ten thousand clients.

pool.spawn_n(handle, new_sock) launches a green thread to handle the new client. The accept loop doesn't care about the return value of the handle function, so it uses spawn_n <eventlet.greenpool.GreenPool.spawn_n>, instead of spawn <eventlet.greenpool.GreenPool.spawn>.

Primary API

The design goal for Eventlet's API is simplicity and readability. You should be able to read its code and understand what it's doing. Fewer lines of code are preferred over excessively clever implementations. Like Python itself, there should be one, and only one obvious way to do it in Eventlet!

Though Eventlet has many modules, much of the most-used stuff is accessible simply by doing import eventlet. Here's a quick summary of the functionality available in the eventlet module, with links to more verbose documentation on each.

eventlet.spawn(func, args,*kw)

This launches a greenthread to call func. Spawning off multiple greenthreads gets work done in parallel. The return value from spawn is a greenthread.GreenThread object, which can be used to retrieve the return value of func. See spawn <eventlet.greenthread.spawn> for more details.

eventlet.spawn_n(func, args,*kw)

The same as spawn, but it's not possible to retrieve the return value. This makes execution faster. See spawn_n <eventlet.greenthread.spawn_n> for more details.

eventlet.spawn_after(seconds, func, args,*kw)

Spawns func after seconds have elapsed; a delayed version of spawn. To abort the spawn and prevent func from being called, call GreenThread.cancel on the return value of spawn_after. See spawn_after <eventlet.greenthread.spawn_after> for more details.

eventlet.sleep(seconds=0)

Suspends the current greenthread and allows others a chance to process. See sleep <eventlet.greenthread.sleep> for more details.

Pools control concurrency. It's very common in applications to want to consume only a finite amount of memory, or to restrict the amount of connections that one part of the code holds open so as to leave more for the rest, or to behave consistently in the face of unpredictable input data. GreenPools provide this control. See GreenPool <eventlet.greenpool.GreenPool> for more on how to use these.

GreenPile objects represent chunks of work. In essence a GreenPile is an iterator that can be stuffed with work, and the results read out later. See GreenPile <eventlet.greenpool.GreenPile> for more details.

Queues are a fundamental construct for communicating data between execution units. Eventlet's Queue class is used to communicate between greenthreads, and provides a bunch of useful features for doing that. See Queue <eventlet.queue.Queue> for more details.

Raises exception in the current greenthread after timeout seconds:

timeout = Timeout(seconds, exception)
try:
    ... # execution here is limited by timeout
finally:
    timeout.cancel()

When exception is omitted or None, the Timeout instance itself is raised:

>>> Timeout(0.1) >>> eventlet.sleep(0.2) Traceback (most recent call last): ... Timeout: 0.1 seconds

In Python 2.5 and newer, you can use the with statement for additional convenience:

with Timeout(seconds, exception) as timeout:
    pass # ... code block ...

This is equivalent to the try/finally block in the first example.

There is an additional feature when using the with statement: if exception is False, the timeout is still raised, but the with statement suppresses it, so the code outside the with-block won't see it:

data = None
with Timeout(5, False):
    data = mysock.makefile().readline()
if data is None:
    ... # 5 seconds passed without reading a line
else:
    ... # a line was read within 5 seconds

As a very special case, if seconds is None, the timer is not scheduled, and is only useful if you're planning to raise it directly.

There are two Timeout caveats to be aware of:

  • If the code block in the try/finally or with-block never cooperatively yields, the timeout cannot be raised. In Eventlet, this should rarely be a problem, but be aware that you cannot time out CPU-only operations with this class.
  • If the code block catches and doesn't re-raise BaseException (for example, with except:), then it will catch the Timeout exception, and might not abort as intended.

When catching timeouts, keep in mind that the one you catch may not be the one you have set; if you going to silence a timeout, always check that it's the same instance that you set:

timeout = Timeout(1)
try:
    ...
except Timeout, t:
    if t is not timeout:
        raise # not my timeout                

eventlet.with_timeout(seconds, function, args,*kwds)

Wrap a call to some (yielding) function with a timeout; if the called function fails to return before the timeout, cancel it and return a flag value.

param seconds

seconds before timeout occurs

type seconds

int or float

param func

the callable to execute with a timeout; it must cooperatively yield, or else the timeout will not be able to trigger

param *args

positional arguments to pass to func

param **kwds

keyword arguments to pass to func

param timeout_value

value to return if timeout occurs (by default raises Timeout)

rtype

Value returned by func if func returns before seconds, else timeout_value if provided, else raises Timeout.

exception Timeout

if func times out and no timeout_value has been provided.

exception

Any exception raised by func

Example:

data = with_timeout(30, urllib2.open, 'http://www.google.com/', timeout_value="")

Here data is either the result of the get() call, or the empty string if it took too long to return. Any exception raised by the get() call is passed through to the caller.

These are the basic primitives of Eventlet; there are a lot more out there in the other Eventlet modules; check out the modules.