171 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			7.0 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
 | 
						|
 | 
						|
Eventlet features:
 | 
						|
 | 
						|
 * utilities for spawning and controlling greenlet execution:
 | 
						|
   api.spawn, api.kill, coros.Job
 | 
						|
 * utilities for communicating between greenlets:
 | 
						|
   coros.event, coros.queue
 | 
						|
 * 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 on-twisted hub select 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, coros.Job*.spawn_new,
 | 
						|
   twistedutil.deferToGreenThread and others based on api.spawn.
 | 
						|
 | 
						|
2. send(), send_exception(), poll(), ready() methods of coros.event,
 | 
						|
   coros.Job and _unbounded_ coros.queue.
 | 
						|
 | 
						|
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 although 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 you can use
 | 
						|
twistedutil.protocol.GreenTransportCreator (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
 | 
						|
 | 
						|
 | 
						|
greenlet == coroutine == green thread == microthread in this document
 | 
						|
 | 
						|
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.
 | 
						|
 | 
						|
 | 
						|
How does eventlet work
 | 
						|
----------------------
 | 
						|
 | 
						|
Twisted's reactor and eventlet's hub are very similar in what they do.
 | 
						|
Both continuously preform 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. Deferreds help there.
 | 
						|
 | 
						|
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 dedicated greenlet
 | 
						|
started implicitly. The execution is organized in a such way that the switching
 | 
						|
almost always involves MAIN_LOOP. All of the blocking use this algorithm:
 | 
						|
 | 
						|
1. register a callback that switches back to the current greenlet when
 | 
						|
   an event of interest happen
 | 
						|
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
 | 
						|
 |