Simplified monkey_patch interface by removing the need for the all keyword parameter (though it still works), and adding many tests to verify that it works as intended. Simplified the tests in patcher_test overall.

This commit is contained in:
Ryan Williams
2010-03-27 14:47:35 -07:00
parent 4205490329
commit 4a2df235f8
3 changed files with 137 additions and 64 deletions

View File

@@ -45,13 +45,18 @@ 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(all=True, os=False, select=False, socket=False, thread=False, time=False)
.. function:: eventlet.patcher.monkey_patch(os=None, select=None, socket=None, thread=None, time=None)
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. If *all* is True, then all modules are patched regardless of the other arguments. If it's False, then the rest of the keyword arguments control patching of specific subsections of the standard library. Most 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 :mod:`thread`, :mod:`threading`, and :mod:`Queue`.
This function monkeypatches the key system modules by replacing their key elements with green equivalents. If no arguments are specified, everything is patched::
import eventlet
eventlet.monkey_patch()
The keyword arguments afford some control over which modules are patched, in case that's important. Most 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 :mod:`thread`, :mod:`threading`, and :mod:`Queue`.
Here's an example of using monkey_patch to patch only a few modules::
import eventlet
eventlet.monkey_patch(all=False, socket=True, select=True)
eventlet.monkey_patch(socket=True, select=True)
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 derived class is defined. It's safe to call monkey_patch multiple times.

View File

@@ -123,46 +123,54 @@ def original(modname):
return _originals.get(modname)
already_patched = {}
def monkey_patch(all=True, os=False, select=False,
socket=False, thread=False, time=False):
def monkey_patch(**on):
"""Globally patches certain system modules to be greenthread-friendly.
The keyword arguments afford some control over which modules are patched.
If *all* is True, then all modules are patched regardless of the other
arguments. If it's False, then the rest of the keyword arguments control
patching of specific subsections of the standard library.
Most patch the single module of the same name (os, time,
select). The exceptions are socket, which also patches the ssl module if
present; and thread, which patches thread, threading, and Queue.
If no keyword arguments are supplied, all possible modules are patched.
If keywords are set to True, only the specified modules are patched. E.g.,
``monkey_patch(socket=True, select=True)`` patches only the select and
socket modules. Most arguments patch the single module of the same name
(os, time, select). The exceptions are socket, which also patches the ssl
module if present; and thread, which patches thread, threading, and Queue.
It's safe to call monkey_patch multiple times.
"""
accepted_args = set(('os', 'select', 'socket', 'thread', 'time'))
default_on = on.pop("all",None)
for k in on.iterkeys():
if k not in accepted_args:
raise TypeError("monkey_patch() got an unexpected "\
"keyword argument %r" % k)
if default_on is None:
default_on = not (True in on.values())
for modname in accepted_args:
on.setdefault(modname, default_on)
modules_to_patch = []
if all or os and not already_patched.get('os'):
if on['os'] and not already_patched.get('os'):
modules_to_patch += _green_os_modules()
already_patched['os'] = True
if all or select and not already_patched.get('select'):
if on['select'] and not already_patched.get('select'):
modules_to_patch += _green_select_modules()
already_patched['select'] = True
if all or socket and not already_patched.get('socket'):
if on['socket'] and not already_patched.get('socket'):
modules_to_patch += _green_socket_modules()
already_patched['socket'] = True
if all or thread and not already_patched.get('thread'):
if on['thread'] and not already_patched.get('thread'):
# hacks ahead
threading = original('threading')
import eventlet.green.threading as greenthreading
greenthreading._patch_main_thread(threading)
modules_to_patch += _green_thread_modules()
already_patched['thread'] = True
if all or time and not already_patched.get('time'):
if on['time'] and not already_patched.get('time'):
modules_to_patch += _green_time_modules()
already_patched['time'] = True
for name, mod in modules_to_patch:
orig_mod = sys.modules.get(name)
for attr_name in mod.__patched__:
#orig_attr = getattr(orig_mod, attr_name, None)
# @@tavis: line above wasn't used, not sure what author intended
patched_attr = getattr(mod, attr_name, None)
if patched_attr is not None:
setattr(orig_mod, attr_name, patched_attr)

View File

@@ -50,23 +50,25 @@ class Patcher(LimitedTestCase):
p = subprocess.Popen([sys.executable,
os.path.join(self.tempdir, filename)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=new_env)
return p
output, _ = p.communicate()
lines = output.split("\n")
return output, lines
class ImportPatched(Patcher):
def test_patch_a_module(self):
self.write_to_tempfile("base", base_module_contents)
self.write_to_tempfile("patching", patching_module_contents)
self.write_to_tempfile("importing", import_module_contents)
p = self.launch_subprocess('importing.py')
output = p.communicate()
lines = output[0].split("\n")
self.assert_(lines[0].startswith('patcher'), repr(output[0]))
self.assert_(lines[1].startswith('base'), repr(output[0]))
self.assert_(lines[2].startswith('importing'), repr(output[0]))
self.assert_('eventlet.green.socket' in lines[1], repr(output[0]))
self.assert_('eventlet.green.urllib' in lines[1], repr(output[0]))
self.assert_('eventlet.green.socket' in lines[2], repr(output[0]))
self.assert_('eventlet.green.urllib' in lines[2], repr(output[0]))
self.assert_('eventlet.green.httplib' not in lines[2], repr(output[0]))
output, lines = self.launch_subprocess('importing.py')
self.assert_(lines[0].startswith('patcher'), repr(output))
self.assert_(lines[1].startswith('base'), repr(output))
self.assert_(lines[2].startswith('importing'), repr(output))
self.assert_('eventlet.green.socket' in lines[1], repr(output))
self.assert_('eventlet.green.urllib' in lines[1], repr(output))
self.assert_('eventlet.green.socket' in lines[2], repr(output))
self.assert_('eventlet.green.urllib' in lines[2], repr(output))
self.assert_('eventlet.green.httplib' not in lines[2], repr(output))
def test_import_patched_defaults(self):
self.write_to_tempfile("base", base_module_contents)
@@ -76,15 +78,15 @@ base = patcher.import_patched('base')
print "newmod", base, base.socket, base.urllib.socket.socket
"""
self.write_to_tempfile("newmod", new_mod)
p = self.launch_subprocess('newmod.py')
output = p.communicate()
lines = output[0].split("\n")
self.assert_(lines[0].startswith('base'), repr(output[0]))
self.assert_(lines[1].startswith('newmod'), repr(output[0]))
self.assert_('eventlet.green.socket' in lines[1], repr(output[0]))
self.assert_('GreenSocket' in lines[1], repr(output[0]))
output, lines = self.launch_subprocess('newmod.py')
self.assert_(lines[0].startswith('base'), repr(output))
self.assert_(lines[1].startswith('newmod'), repr(output))
self.assert_('eventlet.green.socket' in lines[1], repr(output))
self.assert_('GreenSocket' in lines[1], repr(output))
def test_monkey_patch(self):
class MonkeyPatch(Patcher):
def test_patched_modules(self):
new_mod = """
from eventlet import patcher
patcher.monkey_patch()
@@ -93,11 +95,9 @@ import urllib
print "newmod", socket.socket, urllib.socket.socket
"""
self.write_to_tempfile("newmod", new_mod)
p = self.launch_subprocess('newmod.py')
output = p.communicate()
lines = output[0].split("\n")
self.assert_(lines[0].startswith('newmod'), repr(output[0]))
self.assertEqual(lines[0].count('GreenSocket'), 2, repr(output[0]))
output, lines = self.launch_subprocess('newmod.py')
self.assert_(lines[0].startswith('newmod'), repr(output))
self.assertEqual(lines[0].count('GreenSocket'), 2, repr(output))
def test_early_patching(self):
new_mod = """
@@ -108,11 +108,9 @@ eventlet.sleep(0.01)
print "newmod"
"""
self.write_to_tempfile("newmod", new_mod)
p = self.launch_subprocess('newmod.py')
output = p.communicate()
lines = output[0].split("\n")
self.assertEqual(len(lines), 2, repr(output[0]))
self.assert_(lines[0].startswith('newmod'), repr(output[0]))
output, lines = self.launch_subprocess('newmod.py')
self.assertEqual(len(lines), 2, repr(output))
self.assert_(lines[0].startswith('newmod'), repr(output))
def test_late_patching(self):
new_mod = """
@@ -124,11 +122,9 @@ eventlet.sleep(0.01)
print "newmod"
"""
self.write_to_tempfile("newmod", new_mod)
p = self.launch_subprocess('newmod.py')
output = p.communicate()
lines = output[0].split("\n")
self.assertEqual(len(lines), 2, repr(output[0]))
self.assert_(lines[0].startswith('newmod'), repr(output[0]))
output, lines = self.launch_subprocess('newmod.py')
self.assertEqual(len(lines), 2, repr(output))
self.assert_(lines[0].startswith('newmod'), repr(output))
def test_tpool(self):
new_mod = """
@@ -140,11 +136,75 @@ print "newmod", tpool.execute(len, "hi")
print "newmod", tpool.execute(len, "hi2")
"""
self.write_to_tempfile("newmod", new_mod)
p = self.launch_subprocess('newmod.py')
output = p.communicate()
lines = output[0].split("\n")
self.assertEqual(len(lines), 3, repr(output[0]))
self.assert_(lines[0].startswith('newmod'), repr(output[0]))
self.assert_('2' in lines[0], repr(output[0]))
self.assert_('3' in lines[1], repr(output[0]))
output, lines = self.launch_subprocess('newmod.py')
self.assertEqual(len(lines), 3, repr(output))
self.assert_(lines[0].startswith('newmod'), repr(output))
self.assert_('2' in lines[0], repr(output))
self.assert_('3' in lines[1], repr(output))
def test_typeerror(self):
new_mod = """
from eventlet import patcher
patcher.monkey_patch(finagle=True)
"""
self.write_to_tempfile("newmod", new_mod)
output, lines = self.launch_subprocess('newmod.py')
self.assert_(lines[-2].startswith('TypeError'), repr(output))
self.assert_('finagle' in lines[-2], repr(output))
def assert_boolean_logic(self, call, expected):
new_mod = """
from eventlet import patcher
%s
print "already_patched", ",".join(sorted(patcher.already_patched.keys()))
""" % call
self.write_to_tempfile("newmod", new_mod)
output, lines = self.launch_subprocess('newmod.py')
ap = 'already_patched'
self.assert_(lines[0].startswith(ap), repr(output))
patched_modules = lines[0][len(ap):].strip()
self.assertEqual(patched_modules, expected,
"Logic:%s\nExpected: %s != %s" %(call, expected,
patched_modules))
def test_boolean(self):
self.assert_boolean_logic("patcher.monkey_patch()",
'os,select,socket,thread,time')
def test_boolean_all(self):
self.assert_boolean_logic("patcher.monkey_patch(all=True)",
'os,select,socket,thread,time')
def test_boolean_all_single(self):
self.assert_boolean_logic("patcher.monkey_patch(all=True, socket=True)",
'os,select,socket,thread,time')
def test_boolean_all_negative(self):
self.assert_boolean_logic("patcher.monkey_patch(all=False, "\
"socket=False, select=True)",
'select')
def test_boolean_single(self):
self.assert_boolean_logic("patcher.monkey_patch(socket=True)",
'socket')
def test_boolean_double(self):
self.assert_boolean_logic("patcher.monkey_patch(socket=True,"\
" select=True)",
'select,socket')
def test_boolean_negative(self):
self.assert_boolean_logic("patcher.monkey_patch(socket=False)",
'os,select,thread,time')
def test_boolean_negative2(self):
self.assert_boolean_logic("patcher.monkey_patch(socket=False,"\
"time=False)",
'os,select,thread')
def test_conflicting_specifications(self):
self.assert_boolean_logic("patcher.monkey_patch(socket=False, "\
"select=True)",
'select')