~ubuntu-branches/ubuntu/trusty/python-eventlet/trusty-proposed

« back to all changes in this revision

Viewing changes to .pc/eventlet-leak.patch/eventlet/patcher.py

  • Committer: Package Import Robot
  • Author(s): Dave Walker (Daviey)
  • Date: 2012-05-18 13:36:26 UTC
  • mfrom: (4.1.4 sid)
  • Revision ID: package-import@ubuntu.com-20120518133626-v2hvgj9cza44ub0r
Tags: 0.9.16-2ubuntu1
* Merge from Debian, remaining changes:
  - Drop python-zmq from build depends, it's currently in universe.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import sys
2
 
import imp
3
 
 
4
 
__all__ = ['inject', 'import_patched', 'monkey_patch', 'is_monkey_patched']
5
 
 
6
 
__exclude = set(('__builtins__', '__file__', '__name__'))
7
 
 
8
 
class SysModulesSaver(object):
9
 
    """Class that captures some subset of the current state of
10
 
    sys.modules.  Pass in an iterator of module names to the
11
 
    constructor."""
12
 
    def __init__(self, module_names=()):
13
 
        self._saved = {}
14
 
        imp.acquire_lock()
15
 
        self.save(*module_names)
16
 
 
17
 
    def save(self, *module_names):
18
 
        """Saves the named modules to the object."""
19
 
        for modname in module_names:
20
 
            self._saved[modname] = sys.modules.get(modname, None)
21
 
 
22
 
    def restore(self):
23
 
        """Restores the modules that the saver knows about into
24
 
        sys.modules.
25
 
        """
26
 
        try:
27
 
            for modname, mod in self._saved.iteritems():
28
 
                if mod is not None:
29
 
                    sys.modules[modname] = mod
30
 
                else:
31
 
                    try:
32
 
                        del sys.modules[modname]
33
 
                    except KeyError:
34
 
                        pass
35
 
        finally:
36
 
            imp.release_lock()
37
 
                
38
 
 
39
 
def inject(module_name, new_globals, *additional_modules):
40
 
    """Base method for "injecting" greened modules into an imported module.  It
41
 
    imports the module specified in *module_name*, arranging things so
42
 
    that the already-imported modules in *additional_modules* are used when
43
 
    *module_name* makes its imports.
44
 
 
45
 
    *new_globals* is either None or a globals dictionary that gets populated
46
 
    with the contents of the *module_name* module.  This is useful when creating
47
 
    a "green" version of some other module.
48
 
 
49
 
    *additional_modules* should be a collection of two-element tuples, of the
50
 
    form (<name>, <module>).  If it's not specified, a default selection of
51
 
    name/module pairs is used, which should cover all use cases but may be
52
 
    slower because there are inevitably redundant or unnecessary imports.
53
 
    """
54
 
    patched_name = '__patched_module_' + module_name
55
 
    if patched_name in sys.modules:
56
 
        # returning already-patched module so as not to destroy existing
57
 
        # references to patched modules
58
 
        return sys.modules[patched_name]
59
 
 
60
 
    if not additional_modules:
61
 
        # supply some defaults
62
 
        additional_modules = (
63
 
            _green_os_modules() +
64
 
            _green_select_modules() +
65
 
            _green_socket_modules() +
66
 
            _green_thread_modules() +
67
 
            _green_time_modules()) 
68
 
            #_green_MySQLdb()) # enable this after a short baking-in period
69
 
    
70
 
    # after this we are gonna screw with sys.modules, so capture the
71
 
    # state of all the modules we're going to mess with, and lock
72
 
    saver = SysModulesSaver([name for name, m in additional_modules])
73
 
    saver.save(module_name)
74
 
 
75
 
    # Cover the target modules so that when you import the module it
76
 
    # sees only the patched versions
77
 
    for name, mod in additional_modules:
78
 
        sys.modules[name] = mod
79
 
 
80
 
    ## Remove the old module from sys.modules and reimport it while
81
 
    ## the specified modules are in place
82
 
    sys.modules.pop(module_name, None)
83
 
    try:
84
 
        module = __import__(module_name, {}, {}, module_name.split('.')[:-1])
85
 
 
86
 
        if new_globals is not None:
87
 
            ## Update the given globals dictionary with everything from this new module
88
 
            for name in dir(module):
89
 
                if name not in __exclude:
90
 
                    new_globals[name] = getattr(module, name)
91
 
 
92
 
        ## Keep a reference to the new module to prevent it from dying
93
 
        sys.modules[patched_name] = module
94
 
    finally:
95
 
        saver.restore()  ## Put the original modules back
96
 
 
97
 
    return module
98
 
 
99
 
 
100
 
def import_patched(module_name, *additional_modules, **kw_additional_modules):
101
 
    """Imports a module in a way that ensures that the module uses "green"
102
 
    versions of the standard library modules, so that everything works
103
 
    nonblockingly.
104
 
 
105
 
    The only required argument is the name of the module to be imported.
106
 
    """
107
 
    return inject(
108
 
        module_name,
109
 
        None,
110
 
        *additional_modules + tuple(kw_additional_modules.items()))
111
 
 
112
 
 
113
 
def patch_function(func, *additional_modules):
114
 
    """Decorator that returns a version of the function that patches
115
 
    some modules for the duration of the function call.  This is
116
 
    deeply gross and should only be used for functions that import
117
 
    network libraries within their function bodies that there is no
118
 
    way of getting around."""
119
 
    if not additional_modules:
120
 
        # supply some defaults
121
 
        additional_modules = (
122
 
            _green_os_modules() +
123
 
            _green_select_modules() +
124
 
            _green_socket_modules() +
125
 
            _green_thread_modules() + 
126
 
            _green_time_modules())
127
 
 
128
 
    def patched(*args, **kw):
129
 
        saver = SysModulesSaver()
130
 
        for name, mod in additional_modules:
131
 
            saver.save(name)
132
 
            sys.modules[name] = mod
133
 
        try:
134
 
            return func(*args, **kw)
135
 
        finally:
136
 
            saver.restore()
137
 
    return patched
138
 
 
139
 
def _original_patch_function(func, *module_names):
140
 
    """Kind of the contrapositive of patch_function: decorates a
141
 
    function such that when it's called, sys.modules is populated only
142
 
    with the unpatched versions of the specified modules.  Unlike
143
 
    patch_function, only the names of the modules need be supplied,
144
 
    and there are no defaults.  This is a gross hack; tell your kids not
145
 
    to import inside function bodies!"""
146
 
    def patched(*args, **kw):
147
 
        saver = SysModulesSaver(module_names)
148
 
        for name in module_names:
149
 
            sys.modules[name] = original(name)
150
 
        try:
151
 
            return func(*args, **kw)
152
 
        finally:
153
 
            saver.restore()
154
 
    return patched
155
 
 
156
 
 
157
 
def original(modname):
158
 
    """ This returns an unpatched version of a module; this is useful for 
159
 
    Eventlet itself (i.e. tpool)."""
160
 
    # note that it's not necessary to temporarily install unpatched
161
 
    # versions of all patchable modules during the import of the
162
 
    # module; this is because none of them import each other, except
163
 
    # for threading which imports thread
164
 
    original_name = '__original_module_' + modname
165
 
    if original_name in sys.modules:
166
 
        return sys.modules.get(original_name)
167
 
 
168
 
    # re-import the "pure" module and store it in the global _originals
169
 
    # dict; be sure to restore whatever module had that name already
170
 
    saver = SysModulesSaver((modname,))
171
 
    sys.modules.pop(modname, None)
172
 
    # some rudimentary dependency checking -- fortunately the modules
173
 
    # we're working on don't have many dependencies so we can just do
174
 
    # some special-casing here
175
 
    deps = {'threading':'thread', 'Queue':'threading'}
176
 
    if modname in deps:
177
 
        dependency = deps[modname]
178
 
        saver.save(dependency)
179
 
        sys.modules[dependency] = original(dependency)
180
 
    try:
181
 
        real_mod = __import__(modname, {}, {}, modname.split('.')[:-1])
182
 
        if modname == 'Queue' and not hasattr(real_mod, '_threading'):
183
 
            # tricky hack: Queue's constructor in <2.7 imports
184
 
            # threading on every instantiation; therefore we wrap
185
 
            # it so that it always gets the original threading
186
 
            real_mod.Queue.__init__ = _original_patch_function(
187
 
                real_mod.Queue.__init__, 
188
 
                'threading')
189
 
        # save a reference to the unpatched module so it doesn't get lost
190
 
        sys.modules[original_name] = real_mod
191
 
    finally:
192
 
        saver.restore()
193
 
 
194
 
    return sys.modules[original_name]
195
 
 
196
 
already_patched = {}
197
 
def monkey_patch(**on):
198
 
    """Globally patches certain system modules to be greenthread-friendly.
199
 
 
200
 
    The keyword arguments afford some control over which modules are patched.
201
 
    If no keyword arguments are supplied, all possible modules are patched.
202
 
    If keywords are set to True, only the specified modules are patched.  E.g.,
203
 
    ``monkey_patch(socket=True, select=True)`` patches only the select and 
204
 
    socket modules.  Most arguments patch the single module of the same name 
205
 
    (os, time, select).  The exceptions are socket, which also patches the ssl 
206
 
    module if present; and thread, which patches thread, threading, and Queue.
207
 
 
208
 
    It's safe to call monkey_patch multiple times.
209
 
    """    
210
 
    accepted_args = set(('os', 'select', 'socket', 
211
 
                         'thread', 'time', 'psycopg', 'MySQLdb'))
212
 
    default_on = on.pop("all",None)
213
 
    for k in on.iterkeys():
214
 
        if k not in accepted_args:
215
 
            raise TypeError("monkey_patch() got an unexpected "\
216
 
                                "keyword argument %r" % k)
217
 
    if default_on is None:
218
 
        default_on = not (True in on.values())
219
 
    for modname in accepted_args:
220
 
        if modname == 'MySQLdb':
221
 
            # MySQLdb is only on when explicitly patched for the moment
222
 
            on.setdefault(modname, False)
223
 
        on.setdefault(modname, default_on)
224
 
        
225
 
    modules_to_patch = []
226
 
    patched_thread = False
227
 
    if on['os'] and not already_patched.get('os'):
228
 
        modules_to_patch += _green_os_modules()
229
 
        already_patched['os'] = True
230
 
    if on['select'] and not already_patched.get('select'):
231
 
        modules_to_patch += _green_select_modules()
232
 
        already_patched['select'] = True
233
 
    if on['socket'] and not already_patched.get('socket'):
234
 
        modules_to_patch += _green_socket_modules()
235
 
        already_patched['socket'] = True
236
 
    if on['thread'] and not already_patched.get('thread'):
237
 
        patched_thread = True
238
 
        modules_to_patch += _green_thread_modules()
239
 
        already_patched['thread'] = True
240
 
    if on['time'] and not already_patched.get('time'):
241
 
        modules_to_patch += _green_time_modules()
242
 
        already_patched['time'] = True
243
 
    if on.get('MySQLdb') and not already_patched.get('MySQLdb'):
244
 
        modules_to_patch += _green_MySQLdb()
245
 
        already_patched['MySQLdb'] = True
246
 
    if on['psycopg'] and not already_patched.get('psycopg'):
247
 
        try:
248
 
            from eventlet.support import psycopg2_patcher
249
 
            psycopg2_patcher.make_psycopg_green()
250
 
            already_patched['psycopg'] = True
251
 
        except ImportError:
252
 
            # note that if we get an importerror from trying to
253
 
            # monkeypatch psycopg, we will continually retry it
254
 
            # whenever monkey_patch is called; this should not be a
255
 
            # performance problem but it allows is_monkey_patched to
256
 
            # tell us whether or not we succeeded
257
 
            pass
258
 
 
259
 
    imp.acquire_lock()
260
 
    try:
261
 
        for name, mod in modules_to_patch:
262
 
            orig_mod = sys.modules.get(name)
263
 
            if orig_mod is None:
264
 
                orig_mod = __import__(name)
265
 
            for attr_name in mod.__patched__:
266
 
                patched_attr = getattr(mod, attr_name, None)
267
 
                if patched_attr is not None:
268
 
                    setattr(orig_mod, attr_name, patched_attr)
269
 
 
270
 
        # hacks ahead; this is necessary to prevent a KeyError on program exit
271
 
        if patched_thread:
272
 
            _patch_main_thread(sys.modules['threading'])
273
 
    finally:
274
 
        imp.release_lock()
275
 
 
276
 
def _patch_main_thread(mod):
277
 
    """This is some gnarly patching specific to the threading module;
278
 
    threading will always be initialized prior to monkeypatching, and
279
 
    its _active dict will have the wrong key (it uses the real thread
280
 
    id but once it's patched it will use the greenlet ids); so what we
281
 
    do is rekey the _active dict so that the main thread's entry uses
282
 
    the greenthread key.  Other threads' keys are ignored."""
283
 
    thread = original('thread')
284
 
    curthread = mod._active.pop(thread.get_ident(), None)
285
 
    if curthread:
286
 
        import eventlet.green.thread
287
 
        mod._active[eventlet.green.thread.get_ident()] = curthread
288
 
 
289
 
 
290
 
def is_monkey_patched(module):
291
 
    """Returns True if the given module is monkeypatched currently, False if
292
 
    not.  *module* can be either the module itself or its name.
293
 
 
294
 
    Based entirely off the name of the module, so if you import a
295
 
    module some other way than with the import keyword (including
296
 
    import_patched), this might not be correct about that particular
297
 
    module."""
298
 
    return module in already_patched or \
299
 
           getattr(module, '__name__', None) in already_patched
300
 
 
301
 
def _green_os_modules():
302
 
    from eventlet.green import os
303
 
    return [('os', os)]
304
 
 
305
 
def _green_select_modules():
306
 
    from eventlet.green import select
307
 
    return [('select', select)]
308
 
 
309
 
def _green_socket_modules():
310
 
    from eventlet.green import socket
311
 
    try:
312
 
        from eventlet.green import ssl
313
 
        return [('socket', socket), ('ssl', ssl)]
314
 
    except ImportError:
315
 
        return [('socket', socket)]
316
 
 
317
 
def _green_thread_modules():
318
 
    from eventlet.green import Queue
319
 
    from eventlet.green import thread
320
 
    from eventlet.green import threading
321
 
    return [('Queue', Queue), ('thread', thread), ('threading', threading)]
322
 
 
323
 
def _green_time_modules():
324
 
    from eventlet.green import time
325
 
    return [('time', time)]
326
 
 
327
 
def _green_MySQLdb():
328
 
    try:
329
 
        from eventlet.green import MySQLdb
330
 
        return [('MySQLdb', MySQLdb)]
331
 
    except ImportError:
332
 
        return []
333
 
 
334
 
 
335
 
def slurp_properties(source, destination, ignore=[], srckeys=None):
336
 
    """Copy properties from *source* (assumed to be a module) to
337
 
    *destination* (assumed to be a dict).
338
 
    
339
 
    *ignore* lists properties that should not be thusly copied.
340
 
    *srckeys* is a list of keys to copy, if the source's __all__ is 
341
 
    untrustworthy.
342
 
    """
343
 
    if srckeys is None:
344
 
        srckeys = source.__all__
345
 
    destination.update(dict([(name, getattr(source, name))
346
 
                              for name in srckeys
347
 
                              if not (
348
 
                                name.startswith('__') or 
349
 
                                name in ignore)
350
 
                            ]))
351
 
 
352
 
 
353
 
if __name__ == "__main__":
354
 
    import sys
355
 
    sys.argv.pop(0)
356
 
    monkey_patch()
357
 
    execfile(sys.argv[0])