182 lines
7.6 KiB
Plaintext
182 lines
7.6 KiB
Plaintext
--work in progress--
|
|
|
|
Introduction
|
|
------------
|
|
Twisted provides solid foundation for asynchronous programming in Python.
|
|
Eventlet makes asynchronous programming look like synchronous, thus
|
|
achieving higher signal-to-noise ratio than traditional twisted programs have.
|
|
|
|
Eventlet on top of twisted provides:
|
|
* stable twisted
|
|
* usable and readable synchronous style
|
|
* existing twisted code can be used without any changes
|
|
* existing blocking code can be used after trivial changes applied
|
|
|
|
NOTE: the maintainer of Eventlet's Twisted support no longer supports it; it still exists but may have had some breakage along the way. Please treat it as experimental, and if you'd like to maintain it, please do!
|
|
|
|
Eventlet features:
|
|
|
|
* utilities for spawning and controlling greenlet execution:
|
|
api.spawn, api.kill, proc module
|
|
* utilities for communicating between greenlets:
|
|
event.Event, queue.Queue, semaphore.Semaphore
|
|
* standard Python modules that won't block the reactor:
|
|
eventlet.green package
|
|
* utilities specific to twisted hub:
|
|
eventlet.twistedutil package
|
|
|
|
|
|
Getting started with eventlet on twisted
|
|
----------------------------------------
|
|
|
|
This section will only mention stuff that may be useful but it
|
|
won't explain in details how to use it. For that, refer to the
|
|
docstrings of the modules and the examples.
|
|
|
|
There are 2 ways of using twisted with eventlet, one that is
|
|
familiar to twisted developers and another that is familiar
|
|
to eventlet developers:
|
|
|
|
1. explicitly start the main loop in the main greenlet;
|
|
2. implicitly start the main loop in a dedicated greenlet.
|
|
|
|
To enable (1), add this line at the top of your program:
|
|
from eventlet.twistedutil import join_reactor
|
|
then start the reactor as you would do in a regular twisted application.
|
|
|
|
For (2) just make sure that you have reactor installed before using
|
|
any of eventlet functions. Otherwise a non-twisted hub will be selected
|
|
and twisted code won't work.
|
|
|
|
Most of examples/twisted_* use twisted style with the exception of
|
|
twisted_client.py and twisted_srvconnector.py. All of the non-twisted
|
|
examples in examples directory use eventlet-style (they work with any
|
|
of eventlet's hubs, not just twisted-based).
|
|
|
|
Eventlet implements "blocking" operations by switching to the main loop
|
|
greenlet, thus it's impossible to call a blocking function when you are
|
|
already in the main loop. Therefore one must be cautious in a twisted
|
|
callback, calling only a non-blocking subset of eventlet API here. The
|
|
following functions won't unschedule the current greenlet and are safe
|
|
to call from anywhere:
|
|
|
|
1. Greenlet creation functions: api.spawn, proc.spawn,
|
|
twistedutil.deferToGreenThread and others based on api.spawn.
|
|
|
|
2. send(), send_exception(), poll(), ready() methods of event.Event
|
|
and queue.Queue.
|
|
|
|
3. wait(timeout=0) is identical to poll(). Currently only Proc.wait
|
|
supports timeout parameter.
|
|
|
|
4. Proc.link/link_value/link_exception
|
|
|
|
Other classes that use these names should follow the convention.
|
|
|
|
For an example on how to take advantage of eventlet in a twisted
|
|
application using deferToGreenThread see examples/twisted_http_proxy.py
|
|
|
|
Although eventlet provides eventlet.green.socket module that implements
|
|
interface of the standard Python socket, there's also a way to use twisted's
|
|
network code in a synchronous fashion via GreenTransport class.
|
|
A GreenTransport interface is reminiscent of socket but it's not a drop-in
|
|
replacement. It combines features of TCPTransport and Protocol in a single
|
|
object:
|
|
|
|
* all of transport methods (like getPeer()) are available directly on
|
|
a GreenTransport instance; in addition, underlying transport object
|
|
is available via 'transport' attribute;
|
|
* write method is overriden: it may block if transport write buffer is full;
|
|
* read() and recv() methods are provided to retrieve the data from protocol
|
|
synchronously.
|
|
|
|
To make a GreenTransport instance use twistedutil.protocol.GreenClientCreator
|
|
(usage is similar to that of twisted.internet.protocol.ClientCreator)
|
|
|
|
For an example on how to get a connected GreenTransport instance,
|
|
see twisted_client.py, twisted_srvconnect.py or twisted_portforward.py.
|
|
For an example on how to use GreenTransport for incoming connections,
|
|
see twisted_server.py, twisted_portforward.py.
|
|
|
|
|
|
also
|
|
* twistedutil.block_on - wait for a deferred to fire
|
|
block_on(reactor.callInThread(func, args))
|
|
* twistedutil.protocol.basic.LineOnlyReceiverTransport - a green transport
|
|
variant built on top of LineOnlyReceiver protocol. Demonstrates how
|
|
to convert a protocol to a synchronous mode.
|
|
|
|
|
|
Coroutines
|
|
----------
|
|
|
|
To understand how eventlet works, one has to understand how to use greenlet:
|
|
http://codespeak.net/py/dist/greenlet.html
|
|
|
|
Essential points
|
|
|
|
* There always exists MAIN greenlet
|
|
* Every greenlet except MAIN has a parent. MAIN therefore could be detected as g.parent is None
|
|
* When greenlet is finished it's return value is propagated to the parent (i.e. switch() call
|
|
in the parent greenlet returns it)
|
|
* When an exception leaves a greelen, it's propagated to the parent (i.e. switch() in the parent
|
|
re-raises it) unless it's a subclass of GreenletExit, which is returned as a value.
|
|
* parent can be reassigned (by simply setting 'parent' attribute). A cycle would be detected and
|
|
rejected with ValueError
|
|
|
|
|
|
Note, that there's no scheduler of any sort; if a coroutine wants to be
|
|
scheduled again it must take care of it itself. As an application developer,
|
|
however, you don't need to worry about it as that's what eventlet does behind
|
|
the scenes. The cost of that is that you should not use greenlet's switch() and
|
|
throw() methods, they will likely leave the current greenlet unscheduled
|
|
forever. Eventlet also takes advantage of greenlet's `parent' attribute,
|
|
so you should not meddle with it either.
|
|
|
|
|
|
How does eventlet work
|
|
----------------------
|
|
|
|
Twisted's reactor and eventlet's hub are very similar in what they do.
|
|
Both continuously perform polling on the list of registered descriptors
|
|
and each time a specific event is fired, the associated callback function
|
|
is called. In addition, both maintain a list of scheduled calls.
|
|
|
|
Polling is performed by the main loop - a function that both reactor and hub have.
|
|
When twisted calls user's callback it's expected to return almost immediately,
|
|
without any blocking I/O calls.
|
|
|
|
Eventlet runs the main loop in a dedicated greenlet (MAIN_LOOP). It is the same
|
|
greenlet as MAIN if you use join_reactor. Otherwise it's a separate greenlet
|
|
started implicitly. The execution is organized in a such way that the switching
|
|
always involves MAIN_LOOP. All of functions in eventlet that appear "blocking"
|
|
use the following algorithm:
|
|
|
|
1. register a callback that switches back to the current greenlet when
|
|
an event of interest happens
|
|
2. switch to the MAIN_LOOP
|
|
|
|
For example, here's what eventlet's socket recv() does:
|
|
|
|
= blocking operation RECV on socket d =
|
|
|
|
user's greenlet (USER) main loop's greenlet (MAIN_LOOP)
|
|
|
|
|
(inside d.recv() call)
|
|
|
|
|
add_descriptor(d, RECV)
|
|
|
|
|
data=MAIN_LOOP.switch() ---------> poll for events
|
|
^---------------------\ |
|
|
| ... ---------------------------> may execute other greenlets here
|
|
| |
|
|
| event RECV on descriptor d?
|
|
| |
|
|
| d.remove_descriptor(d, RECV)
|
|
| |
|
|
| data = d.recv() # calling blocking op that will return immediately
|
|
| |
|
|
\--------- USER.switch(data) # argument data here becomes return value in user's switch
|
|
return data
|
|
|