Tweaked the way monkey_patch works so that it'll work better if called after the modules it patches are imported. Added major patcher docs overall. Added blog link to the main page.

This commit is contained in:
Ryan Williams
2010-02-07 20:18:02 -08:00
parent 5242cfc490
commit 6a68fa5318
16 changed files with 120 additions and 61 deletions

View File

@@ -71,7 +71,7 @@ The file :ref:`echo server example <echo_server_example>` contains a somewhat mo
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 only one right way to do something with Eventlet!
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 <http://www.python.org/dev/peps/pep-0020/>`_, 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``
@@ -100,39 +100,3 @@ Though Eventlet has many modules, much of the most-used stuff is accessible simp
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 :class:`queue.Queue` for more details.
These are the basic primitives of Eventlet; there are a lot more out there in the other Eventlet modules; check out the :doc:`modules`.
Green Libraries
----------------
The package ``eventlet.green`` contains libraries that have the same interfaces as common standard ones, but they are modified to behave well with green threads. This can be preferable than monkeypatching in many circumstances, because it may be necessary to interoperate with some module that needs the standard libraries unmolested, or simply because it's good engineering practice to be able to understand how a file behaves based simply on its contents.
To use green libraries, simply import the desired module from ``eventlet.green``::
from eventlet.green import socket
from eventlet.green import threading
from eventlet.green import asyncore
That's all there is to it!
Monkeypatching the Standard Library
----------------------------------------
.. automethod:: eventlet.util::wrap_socket_with_coroutine_socket
Eventlet's socket object, whose implementation can be found in the
:mod:`eventlet.greenio` module, is designed to match the interface of the
standard library :mod:`socket` object. However, it is often useful to be able to
use existing code which uses :mod:`socket` directly without modifying it to use the eventlet apis. To do this, one must call :func:`~eventlet.util.wrap_socket_with_coroutine_socket`. It is only necessary
to do this once, at the beginning of the program, and it should be done before
any socket objects which will be used are created.
.. automethod:: eventlet.util::wrap_select_with_coroutine_select
Some code which is written in a multithreaded style may perform some tricks,
such as calling :mod:`select` with only one file descriptor and a timeout to
prevent the operation from being unbounded. For this specific situation there
is :func:`~eventlet.util.wrap_select_with_coroutine_select`; however it's
always a good idea when trying any new library with eventlet to perform some
tests to ensure eventlet is properly able to multiplex the operations.

View File

@@ -4,30 +4,35 @@ Examples
Here are a bunch of small example programs that use Eventlet. All of these examples can be found in the ``examples`` directory of a source copy of Eventlet.
.. _web_crawler_example:
Web Crawler
------------
.. literalinclude:: ../examples/webcrawler.py
.. _wsgi_server_example:
WSGI Server
------------
.. literalinclude:: ../examples/wsgi.py
.. _echo_server_example:
Echo Server
-----------
.. literalinclude:: ../examples/echoserver.py
.. _socket_connect_example:
Socket Connect
--------------
.. literalinclude:: ../examples/connect.py
.. _chat_server_example:
Multi-User Chat Server
-----------------------

View File

@@ -8,7 +8,7 @@ Eventlet is different from other event-based frameworks out there because it doe
Simple Example
-------------------
This is a simple web crawler that fetches a bunch of urls concurrently.
This is a simple web crawler that fetches a bunch of urls concurrently::
urls = ["http://www.google.com/intl/en_ALL/images/logo.gif",
"https://wiki.secondlife.com/w/images/secondlife.jpg",
@@ -23,7 +23,7 @@ This is a simple web crawler that fetches a bunch of urls concurrently.
pool = eventlet.GreenPool()
for body in pool.imap(fetch, urls):
print "got body", len(body)
Contents
=========
@@ -32,6 +32,7 @@ Contents
:maxdepth: 2
basic_usage
patching
examples
ssl
threading

View File

@@ -1,6 +0,0 @@
:mod:`api` -- General purpose functions
==========================================
.. automodule:: eventlet.api
:members:
:undoc-members:

View File

@@ -1,5 +0,0 @@
:mod:`greenio` -- Cooperative network primitives
=================================================
.. automodule:: eventlet.greenio
:members:

49
doc/patching.rst Normal file
View File

@@ -0,0 +1,49 @@
Greening The World
==================
One of the challenges of writing a library like Eventlet is that the built-in networking libraries don't natively support the sort of cooperative yielding that we need. What we must do instead is patch standard library modules in certain key places so that they do cooperatively yield. We've in the past considered doing this automatically upon importing Eventlet, but have decided against that course of action because it is un-Pythonic to change the behavior of module A simply by importing module B.
Therefore, the application using Eventlet must explicitly green the world for itself, using one or both of the convenient methods provided.
Import Green
--------------
The first way of greening an application is to import networking-related libraries from the ``eventlet.green`` package. It contains libraries that have the same interfaces as common standard ones, but they are modified to behave well with green threads. Using this method is a good engineering practice, because the true dependencies are apparent in every file::
from eventlet.green import socket
from eventlet.green import threading
from eventlet.green import asyncore
This works best if every library can be imported green in this manner. If ``eventlet.green`` lacks a module (for example, non-python-standard modules), then the :mod:`eventlet.patcher` module can come to the rescue. It provides a function, :func:`eventlet.patcher.import_patched`, that greens any module on import.
.. function:: eventlet.patcher.import_patched(module_name, *additional_modules, **kw_additional_modules)
Imports a module in a greened manner, so that the module's use of networking libraries like socket will use Eventlet's green versions instead. The only required argument is the name of the module to be imported::
from eventlet import patcher
httplib2 = patcher.import_patched('httplib2')
Under the hood, it works by temporarily swapping out the "normal" versions of the libraries in sys.modules for an eventlet.green equivalent. When the import of the to-be-patched module completes, the state of sys.modules is restored. Therefore, if the patched module contains the statement 'import socket', import_patched will have it reference eventlet.green.socket. One weakness of this approach is that it doesn't work for late binding (i.e. imports that happen during runtime). Late binding of imports is fortunately rarely done (it's slow and against `PEP-8 <http://www.python.org/dev/peps/pep-0008/>`_), so in most cases import_patched will work just fine.
One other aspect of import_patched is the ability to specify exactly which modules are patched. Doing so may provide a slight performance benefit since only the needed modules are imported, whereas import_patched with no arguments imports a bunch of modules in case they're needed. The *additional_modules* and *kw_additional_modules* arguments are both sequences of name/module pairs. Either or both can be used::
from eventlet.green import socket
from eventlet.green import SocketServer
BaseHTTPServer = patcher.import_patched('BaseHTTPServer',
('socket', socket),
('SocketServer', SocketServer))
BaseHTTPServer = patcher.import_patched('BaseHTTPServer',
socket=socket, SocketServer=SocketServer)
Monkeypatching the Standard Library
----------------------------------------
The other way of greening an application is simply to monkeypatch the standard
library. This has the disadvantage of appearing quite magical, but the advantage of avoiding the late-binding problem.
.. function:: eventlet.patcher.monkey_patch(os=True, select=True, socket=True, thread=True, time=True)
By default, this function monkeypatches the key system modules by replacing their key elements with green equivalents. The keyword arguments afford some control over which modules are patched, in case that's important. For almost all of them, they patch the single module of the same name (e.g. time=True means that the time module is patched [time.sleep is patched by eventlet.sleep]). The exceptions to this rule are *socket*, which also patches the :mod:`ssl` module if present; and *thread*, which patches both :mod:`thread` and :mod:`Queue`.
It is important to call :func:`eventlet.patcher.monkey_patch` as early in the lifetime of the application as possible. Try to do it as one of the first lines in the main module. The reason for this is that sometimes there is a class that inherits from a class that needs to be greened -- e.g. a class that inherits from socket.socket -- and inheritance is done at import time, so therefore the monkeypatching should happen before the module that has the derived class is imported.

View File

@@ -100,11 +100,12 @@ easy_install eventlet
<div class="sphinxsidebarwrapper">
<h3><a href="">Links</a></h3>
<ul>
<li><a class="reference external" href="http://blog.eventlet.net/">Blog</a></li>
<li><a class="reference external" href="doc/">Documentation</a></li>
<li><a class="reference external" href="https://lists.secondlife.com/pipermail/eventletdev/">Mailing List Archives</a></li>
<li><a class="reference external" href="http://eventlet.net/hudson/">Continuous Builds</a></li>
<li><a class="reference external" href="http://eventlet.net/hudson/">Automated Builds</a></li>
<li><a class="reference external" href="http://bitbucket.org/which_linden/eventlet/issues/new/">Bug Report Form</a></li>
<li><a class="reference external" href="irc://kubrick.freenode.net/#eventlet">irc channel</a></li>
<li><a class="reference external" href="irc://chat.freenode.net/#eventlet">irc channel</a></li>
</ul>
</div>
</div>

View File

@@ -2,6 +2,8 @@ from eventlet import queue
__all__ = ['Empty', 'Full', 'LifoQueue', 'PriorityQueue', 'Queue']
__patched__ = ['LifoQueue', 'PriorityQueue', 'Queue']
# these classes exist to paper over the major operational difference between
# eventlet.queue.Queue and the stdlib equivalents
class Queue(queue.Queue):

View File

@@ -6,6 +6,8 @@ from eventlet import greenio
from eventlet import greenthread
from eventlet import hubs
__patched__ = ['fdopen', 'read', 'write', 'wait', 'waitpid']
for var in dir(os_orig):
exec "%s = os_orig.%s" % (var, var)

View File

@@ -5,6 +5,8 @@ error = __select.error
from eventlet.api import getcurrent
from eventlet.hubs import get_hub
__patched__ = ['select']
def get_fileno(obj):
# The purpose of this function is to exactly replicate
# the behavior of the select module when confronted with

View File

@@ -10,6 +10,9 @@ import os
import sys
import warnings
__patched__ = ['fromfd', 'socketpair', 'gethostbyname', 'create_connection',
'ssl', 'socket']
def fromfd(*args):
return socket(__socket.fromfd(*args))

View File

@@ -13,6 +13,7 @@ orig_socket = __import__('socket')
socket = orig_socket.socket
timeout_exc = orig_socket.timeout
__patched__ = ['SSLSocket', 'wrap_socket', 'sslwrap_simple']
class GreenSSLSocket(__ssl.SSLSocket):
""" This is a green version of the SSLSocket class from the ssl module added

View File

@@ -4,6 +4,9 @@ from eventlet.support import greenlets as greenlet
from eventlet.api import spawn
from eventlet.semaphore import Semaphore as LockType
__patched__ = ['get_ident', 'start_new_thread', 'start_new', 'allocate_lock',
'allocate', 'exit', 'interrupt_main', 'stack_size', '_local']
error = __thread.error
def get_ident(gr=None):

View File

@@ -1,4 +1,5 @@
__time = __import__('time')
for var in dir(__time):
exec "%s = __time.%s" % (var, var)
__patched__ = ['sleep']
from eventlet.api import sleep

View File

@@ -1,17 +1,33 @@
import sys
__all__ = ['inject', 'import_patched', 'monkey_patch']
__exclude = set(('__builtins__', '__file__', '__name__'))
def inject(module_name, new_globals, *additional_modules):
"""Base method for "injecting" greened modules into an imported module. It
imports the module specified in *module_name*, arranging things so
that the already-imported modules in *additional_modules* are used when
*module_name* makes its imports.
*new_globals* is either None or a globals dictionary that gets populated
with the contents of the *module_name* module. This is useful when creating
a "green" version of some other module.
*additional_modules* should be a collection of two-element tuples, of the
form (<name>, <module>). If it's not specified, a default selection of
name/module pairs is used, which should cover all use cases but may be
slower because there are inevitably redundant or unnecessary imports.
"""
if not additional_modules:
# supply some defaults
additional_modules = (
_green_os_modules() +
_green_select_modules() +
_green_socket_modules() +
_green_thread_modules())
_green_thread_modules() +
_green_time_modules())
## Put the specified modules in sys.modules for the duration of the import
saved = {}
@@ -50,6 +66,12 @@ def inject(module_name, new_globals, *additional_modules):
def import_patched(module_name, *additional_modules, **kw_additional_modules):
"""Imports a module in a way that ensures that the module uses "green"
versions of the standard library modules, so that everything works
nonblockingly.
The only required argument is the name of the module to be imported.
"""
return inject(
module_name,
None,
@@ -76,7 +98,14 @@ def patch_function(func, *additional_modules):
return patched
def monkey_patch(os=True, select=True, socket=True, thread=True):
def monkey_patch(os=True, select=True, socket=True, thread=True, time=True):
"""Globally patches certain system modules to be greenthread-friendly.
The keyword arguments afford some control over which modules are patched.
For almost all of them, they patch the single module of the same name. The
exceptions are socket, which also patches the ssl module if present; and
thread, which patches thread and Queue.
"""
modules_to_patch = []
if os:
modules_to_patch += _green_os_modules()
@@ -86,13 +115,18 @@ def monkey_patch(os=True, select=True, socket=True, thread=True):
modules_to_patch += _green_socket_modules()
if thread:
modules_to_patch += _green_thread_modules()
if time:
modules_to_patch += _green_time_modules()
for name, mod in modules_to_patch:
sys.modules[name] = mod
for attr in mod.__patched__:
patched_attr = getattr(mod, attr, None)
if patched_attr is not None:
setattr(sys.modules[name], attr, patched_attr)
def _green_os_modules():
from eventlet.green import os
return [('os', os)]
def _green_select_modules():
from eventlet.green import select
return [('select', select)]
@@ -109,4 +143,7 @@ def _green_thread_modules():
from eventlet.green import Queue
from eventlet.green import thread
return [('Queue', Queue), ('thread', thread)]
def _green_time_modules():
from eventlet.green import time
return [('time', time)]

View File

@@ -92,7 +92,7 @@ from eventlet import patcher
patcher.monkey_patch()
import socket
import urllib
print "newmod", socket, urllib.socket.socket
print "newmod", socket.socket, urllib.socket.socket
"""
self.write_to_tempfile("newmod", new_mod)
python_path = os.pathsep.join(sys.path + [self.tempdir])
@@ -104,5 +104,4 @@ print "newmod", socket, urllib.socket.socket
output = p.communicate()
lines = output[0].split("\n")
self.assert_(lines[0].startswith('newmod'))
self.assert_('eventlet.green.socket' in lines[0])
self.assert_('GreenSocket' in lines[0])
self.assertEqual(lines[0].count('GreenSocket'), 2)