123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702 |
- # Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
- # pylint: disable=redefined-outer-name
- """
- Make the standard library cooperative.
- Patching
- ========
- The primary purpose of this module is to carefully patch, in place,
- portions of the standard library with gevent-friendly functions that
- behave in the same way as the original (at least as closely as possible).
- The primary interface to this is the :func:`patch_all` function, which
- performs all the available patches. It accepts arguments to limit the
- patching to certain modules, but most programs **should** use the
- default values as they receive the most wide-spread testing, and some monkey
- patches have dependencies on others.
- Patching **should be done as early as possible** in the lifecycle of the
- program. For example, the main module (the one that tests against
- ``__main__`` or is otherwise the first imported) should begin with
- this code, ideally before any other imports::
- from gevent import monkey
- monkey.patch_all()
- .. tip::
- Some frameworks, such as gunicorn, handle monkey-patching for you.
- Check their documentation to be sure.
- Querying
- --------
- Sometimes it is helpful to know if objects have been monkey-patched, and in
- advanced cases even to have access to the original standard library functions. This
- module provides functions for that purpose.
- - :func:`is_module_patched`
- - :func:`is_object_patched`
- - :func:`get_original`
- Use as a module
- ===============
- Sometimes it is useful to run existing python scripts or modules that
- were not built to be gevent aware under gevent. To do so, this module
- can be run as the main module, passing the script and its arguments.
- For details, see the :func:`main` function.
- Functions
- =========
- """
- from __future__ import absolute_import
- from __future__ import print_function
- import sys
- __all__ = [
- 'patch_all',
- 'patch_builtins',
- 'patch_dns',
- 'patch_os',
- 'patch_select',
- 'patch_signal',
- 'patch_socket',
- 'patch_ssl',
- 'patch_subprocess',
- 'patch_sys',
- 'patch_thread',
- 'patch_time',
- # query functions
- 'get_original',
- 'is_module_patched',
- 'is_object_patched',
- # module functions
- 'main',
- ]
- if sys.version_info[0] >= 3:
- string_types = (str,)
- PY3 = True
- else:
- import __builtin__ # pylint:disable=import-error
- string_types = (__builtin__.basestring,)
- PY3 = False
- WIN = sys.platform.startswith("win")
- # maps module name -> {attribute name: original item}
- # e.g. "time" -> {"sleep": built-in function sleep}
- saved = {}
- def is_module_patched(modname):
- """Check if a module has been replaced with a cooperative version."""
- return modname in saved
- def is_object_patched(modname, objname):
- """Check if an object in a module has been replaced with a cooperative version."""
- return is_module_patched(modname) and objname in saved[modname]
- def _get_original(name, items):
- d = saved.get(name, {})
- values = []
- module = None
- for item in items:
- if item in d:
- values.append(d[item])
- else:
- if module is None:
- module = __import__(name)
- values.append(getattr(module, item))
- return values
- def get_original(mod_name, item_name):
- """Retrieve the original object from a module.
- If the object has not been patched, then that object will still be retrieved.
- :param item_name: A string or sequence of strings naming the attribute(s) on the module
- ``mod_name`` to return.
- :return: The original value if a string was given for ``item_name`` or a sequence
- of original values if a sequence was passed.
- """
- if isinstance(item_name, string_types):
- return _get_original(mod_name, [item_name])[0]
- return _get_original(mod_name, item_name)
- _NONE = object()
- def patch_item(module, attr, newitem):
- olditem = getattr(module, attr, _NONE)
- if olditem is not _NONE:
- saved.setdefault(module.__name__, {}).setdefault(attr, olditem)
- setattr(module, attr, newitem)
- def remove_item(module, attr):
- olditem = getattr(module, attr, _NONE)
- if olditem is _NONE:
- return
- saved.setdefault(module.__name__, {}).setdefault(attr, olditem)
- delattr(module, attr)
- def patch_module(name, items=None):
- gevent_module = getattr(__import__('gevent.' + name), name)
- module_name = getattr(gevent_module, '__target__', name)
- module = __import__(module_name)
- if items is None:
- items = getattr(gevent_module, '__implements__', None)
- if items is None:
- raise AttributeError('%r does not have __implements__' % gevent_module)
- for attr in items:
- patch_item(module, attr, getattr(gevent_module, attr))
- return module
- def _queue_warning(message, _warnings):
- # Queues a warning to show after the monkey-patching process is all done.
- # Done this way to avoid extra imports during the process itself, just
- # in case. If we're calling a function one-off (unusual) go ahead and do it
- if _warnings is None:
- _process_warnings([message])
- else:
- _warnings.append(message)
- def _process_warnings(_warnings):
- import warnings
- for warning in _warnings:
- warnings.warn(warning, RuntimeWarning, stacklevel=3)
- def _patch_sys_std(name):
- from gevent.fileobject import FileObjectThread
- orig = getattr(sys, name)
- if not isinstance(orig, FileObjectThread):
- patch_item(sys, name, FileObjectThread(orig))
- def patch_sys(stdin=True, stdout=True, stderr=True):
- """Patch sys.std[in,out,err] to use a cooperative IO via a threadpool.
- This is relatively dangerous and can have unintended consequences such as hanging
- the process or `misinterpreting control keys`_ when ``input`` and ``raw_input``
- are used.
- This method does nothing on Python 3. The Python 3 interpreter wants to flush
- the TextIOWrapper objects that make up stderr/stdout at shutdown time, but
- using a threadpool at that time leads to a hang.
- .. _`misinterpreting control keys`: https://github.com/gevent/gevent/issues/274
- """
- # test__issue6.py demonstrates the hang if these lines are removed;
- # strangely enough that test passes even without monkey-patching sys
- if PY3:
- return
- if stdin:
- _patch_sys_std('stdin')
- if stdout:
- _patch_sys_std('stdout')
- if stderr:
- _patch_sys_std('stderr')
- def patch_os():
- """
- Replace :func:`os.fork` with :func:`gevent.fork`, and, on POSIX,
- :func:`os.waitpid` with :func:`gevent.os.waitpid` (if the
- environment variable ``GEVENT_NOWAITPID`` is not defined). Does
- nothing if fork is not available.
- .. caution:: This method must be used with :func:`patch_signal` to have proper SIGCHLD
- handling and thus correct results from ``waitpid``.
- :func:`patch_all` calls both by default.
- .. caution:: For SIGCHLD handling to work correctly, the event loop must run.
- The easiest way to help ensure this is to use :func:`patch_all`.
- """
- patch_module('os')
- def patch_time():
- """Replace :func:`time.sleep` with :func:`gevent.sleep`."""
- from gevent.hub import sleep
- import time
- patch_item(time, 'sleep', sleep)
- def _patch_existing_locks(threading):
- if len(list(threading.enumerate())) != 1:
- return
- try:
- tid = threading.get_ident()
- except AttributeError:
- tid = threading._get_ident()
- rlock_type = type(threading.RLock())
- try:
- import importlib._bootstrap
- except ImportError:
- class _ModuleLock(object):
- pass
- else:
- _ModuleLock = importlib._bootstrap._ModuleLock # python 2 pylint: disable=no-member
- # It might be possible to walk up all the existing stack frames to find
- # locked objects...at least if they use `with`. To be sure, we look at every object
- # Since we're supposed to be done very early in the process, there shouldn't be
- # too many.
- # By definition there's only one thread running, so the various
- # owner attributes were the old (native) thread id. Make it our
- # current greenlet id so that when it wants to unlock and compare
- # self.__owner with _get_ident(), they match.
- gc = __import__('gc')
- for o in gc.get_objects():
- if isinstance(o, rlock_type):
- if hasattr(o, '_owner'): # Py3
- if o._owner is not None:
- o._owner = tid
- else:
- if o._RLock__owner is not None:
- o._RLock__owner = tid
- elif isinstance(o, _ModuleLock):
- if o.owner is not None:
- o.owner = tid
- def patch_thread(threading=True, _threading_local=True, Event=False, logging=True,
- existing_locks=True,
- _warnings=None):
- """
- Replace the standard :mod:`thread` module to make it greenlet-based.
- - If *threading* is true (the default), also patch ``threading``.
- - If *_threading_local* is true (the default), also patch ``_threading_local.local``.
- - If *logging* is True (the default), also patch locks taken if the logging module has
- been configured.
- - If *existing_locks* is True (the default), and the process is still single threaded,
- make sure than any :class:`threading.RLock` (and, under Python 3, :class:`importlib._bootstrap._ModuleLock`)
- instances that are currently locked can be properly unlocked.
- .. caution::
- Monkey-patching :mod:`thread` and using
- :class:`multiprocessing.Queue` or
- :class:`concurrent.futures.ProcessPoolExecutor` (which uses a
- ``Queue``) will hang the process.
- .. versionchanged:: 1.1b1
- Add *logging* and *existing_locks* params.
- """
- # XXX: Simplify
- # pylint:disable=too-many-branches,too-many-locals
- # Description of the hang:
- # There is an incompatibility with patching 'thread' and the 'multiprocessing' module:
- # The problem is that multiprocessing.queues.Queue uses a half-duplex multiprocessing.Pipe,
- # which is implemented with os.pipe() and _multiprocessing.Connection. os.pipe isn't patched
- # by gevent, as it returns just a fileno. _multiprocessing.Connection is an internal implementation
- # class implemented in C, which exposes a 'poll(timeout)' method; under the covers, this issues a
- # (blocking) select() call: hence the need for a real thread. Except for that method, we could
- # almost replace Connection with gevent.fileobject.SocketAdapter, plus a trivial
- # patch to os.pipe (below). Sigh, so close. (With a little work, we could replicate that method)
- # import os
- # import fcntl
- # os_pipe = os.pipe
- # def _pipe():
- # r, w = os_pipe()
- # fcntl.fcntl(r, fcntl.F_SETFL, os.O_NONBLOCK)
- # fcntl.fcntl(w, fcntl.F_SETFL, os.O_NONBLOCK)
- # return r, w
- # os.pipe = _pipe
- # The 'threading' module copies some attributes from the
- # thread module the first time it is imported. If we patch 'thread'
- # before that happens, then we store the wrong values in 'saved',
- # So if we're going to patch threading, we either need to import it
- # before we patch thread, or manually clean up the attributes that
- # are in trouble. The latter is tricky because of the different names
- # on different versions.
- if threading:
- threading_mod = __import__('threading')
- # Capture the *real* current thread object before
- # we start returning DummyThread objects, for comparison
- # to the main thread.
- orig_current_thread = threading_mod.current_thread()
- else:
- threading_mod = None
- orig_current_thread = None
- patch_module('thread')
- if threading:
- patch_module('threading')
- if Event:
- from gevent.event import Event
- patch_item(threading_mod, 'Event', Event)
- if existing_locks:
- _patch_existing_locks(threading_mod)
- if logging and 'logging' in sys.modules:
- logging = __import__('logging')
- patch_item(logging, '_lock', threading_mod.RLock())
- for wr in logging._handlerList:
- # In py26, these are actual handlers, not weakrefs
- handler = wr() if callable(wr) else wr
- if handler is None:
- continue
- if not hasattr(handler, 'lock'):
- raise TypeError("Unknown/unsupported handler %r" % handler)
- handler.lock = threading_mod.RLock()
- if _threading_local:
- _threading_local = __import__('_threading_local')
- from gevent.local import local
- patch_item(_threading_local, 'local', local)
- def make_join_func(thread, thread_greenlet):
- from gevent.hub import sleep
- from time import time
- def join(timeout=None):
- end = None
- if threading_mod.current_thread() is thread:
- raise RuntimeError("Cannot join current thread")
- if thread_greenlet is not None and thread_greenlet.dead:
- return
- if not thread.is_alive():
- return
- if timeout:
- end = time() + timeout
- while thread.is_alive():
- if end is not None and time() > end:
- return
- sleep(0.01)
- return join
- if threading:
- from gevent.threading import main_native_thread
- for thread in threading_mod._active.values():
- if thread == main_native_thread():
- continue
- thread.join = make_join_func(thread, None)
- if sys.version_info[:2] >= (3, 4):
- # Issue 18808 changes the nature of Thread.join() to use
- # locks. This means that a greenlet spawned in the main thread
- # (which is already running) cannot wait for the main thread---it
- # hangs forever. We patch around this if possible. See also
- # gevent.threading.
- greenlet = __import__('greenlet')
- if orig_current_thread == threading_mod.main_thread():
- main_thread = threading_mod.main_thread()
- _greenlet = main_thread._greenlet = greenlet.getcurrent()
- main_thread.join = make_join_func(main_thread, _greenlet)
- # Patch up the ident of the main thread to match. This
- # matters if threading was imported before monkey-patching
- # thread
- oldid = main_thread.ident
- main_thread._ident = threading_mod.get_ident()
- if oldid in threading_mod._active:
- threading_mod._active[main_thread.ident] = threading_mod._active[oldid]
- if oldid != main_thread.ident:
- del threading_mod._active[oldid]
- else:
- _queue_warning("Monkey-patching not on the main thread; "
- "threading.main_thread().join() will hang from a greenlet",
- _warnings)
- def patch_socket(dns=True, aggressive=True):
- """Replace the standard socket object with gevent's cooperative sockets.
- If ``dns`` is true, also patch dns functions in :mod:`socket`.
- """
- from gevent import socket
- # Note: although it seems like it's not strictly necessary to monkey patch 'create_connection',
- # it's better to do it. If 'create_connection' was not monkey patched, but the rest of socket module
- # was, create_connection would still use "green" getaddrinfo and "green" socket.
- # However, because gevent.socket.socket.connect is a Python function, the exception raised by it causes
- # _socket object to be referenced by the frame, thus causing the next invocation of bind(source_address) to fail.
- if dns:
- items = socket.__implements__ # pylint:disable=no-member
- else:
- items = set(socket.__implements__) - set(socket.__dns__) # pylint:disable=no-member
- patch_module('socket', items=items)
- if aggressive:
- if 'ssl' not in socket.__implements__: # pylint:disable=no-member
- remove_item(socket, 'ssl')
- def patch_dns():
- """Replace DNS functions in :mod:`socket` with cooperative versions.
- This is only useful if :func:`patch_socket` has been called and is done automatically
- by that method if requested.
- """
- from gevent import socket
- patch_module('socket', items=socket.__dns__) # pylint:disable=no-member
- def patch_ssl():
- """Replace SSLSocket object and socket wrapping functions in :mod:`ssl` with cooperative versions.
- This is only useful if :func:`patch_socket` has been called.
- """
- patch_module('ssl')
- def patch_select(aggressive=True):
- """
- Replace :func:`select.select` with :func:`gevent.select.select`
- and :func:`select.poll` with :class:`gevent.select.poll` (where available).
- If ``aggressive`` is true (the default), also remove other
- blocking functions from :mod:`select` and (on Python 3.4 and
- above) :mod:`selectors`:
- - :func:`select.epoll`
- - :func:`select.kqueue`
- - :func:`select.kevent`
- - :func:`select.devpoll` (Python 3.5+)
- - :class:`selectors.EpollSelector`
- - :class:`selectors.KqueueSelector`
- - :class:`selectors.DevpollSelector` (Python 3.5+)
- """
- patch_module('select')
- if aggressive:
- select = __import__('select')
- # since these are blocking we're removing them here. This makes some other
- # modules (e.g. asyncore) non-blocking, as they use select that we provide
- # when none of these are available.
- remove_item(select, 'epoll')
- remove_item(select, 'kqueue')
- remove_item(select, 'kevent')
- remove_item(select, 'devpoll')
- if sys.version_info[:2] >= (3, 4):
- # Python 3 wants to use `select.select` as a member function,
- # leading to this error in selectors.py (because gevent.select.select is
- # not a builtin and doesn't get the magic auto-static that they do)
- # r, w, _ = self._select(self._readers, self._writers, [], timeout)
- # TypeError: select() takes from 3 to 4 positional arguments but 5 were given
- # Note that this obviously only happens if selectors was imported after we had patched
- # select; but there is a code path that leads to it being imported first (but now we've
- # patched select---so we can't compare them identically)
- select = __import__('select') # Should be gevent-patched now
- orig_select_select = get_original('select', 'select')
- assert select.select is not orig_select_select
- selectors = __import__('selectors')
- if selectors.SelectSelector._select in (select.select, orig_select_select):
- def _select(self, *args, **kwargs): # pylint:disable=unused-argument
- return select.select(*args, **kwargs)
- selectors.SelectSelector._select = _select
- _select._gevent_monkey = True
- if aggressive:
- # If `selectors` had already been imported before we removed
- # select.epoll|kqueue|devpoll, these may have been defined in terms
- # of those functions. They'll fail at runtime.
- remove_item(selectors, 'EpollSelector')
- remove_item(selectors, 'KqueueSelector')
- remove_item(selectors, 'DevpollSelector')
- selectors.DefaultSelector = selectors.SelectSelector
- def patch_subprocess():
- """
- Replace :func:`subprocess.call`, :func:`subprocess.check_call`,
- :func:`subprocess.check_output` and :class:`subprocess.Popen` with
- :mod:`cooperative versions <gevent.subprocess>`.
- .. note::
- On Windows under Python 3, the API support may not completely match
- the standard library.
- """
- patch_module('subprocess')
- def patch_builtins():
- """
- Make the builtin __import__ function `greenlet safe`_ under Python 2.
- .. note::
- This does nothing under Python 3 as it is not necessary. Python 3 features
- improved import locks that are per-module, not global.
- .. _greenlet safe: https://github.com/gevent/gevent/issues/108
- """
- if sys.version_info[:2] < (3, 3):
- patch_module('builtins')
- def patch_signal():
- """
- Make the signal.signal function work with a monkey-patched os.
- .. caution:: This method must be used with :func:`patch_os` to have proper SIGCHLD
- handling. :func:`patch_all` calls both by default.
- .. caution:: For proper SIGCHLD handling, you must yield to the event loop.
- Using :func:`patch_all` is the easiest way to ensure this.
- .. seealso:: :mod:`gevent.signal`
- """
- patch_module("signal")
- def _check_repatching(**module_settings):
- _warnings = []
- key = '_gevent_saved_patch_all'
- if saved.get(key, module_settings) != module_settings:
- _queue_warning("Patching more than once will result in the union of all True"
- " parameters being patched",
- _warnings)
- first_time = key not in saved
- saved[key] = module_settings
- return _warnings, first_time
- def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True, httplib=False,
- subprocess=True, sys=False, aggressive=True, Event=False,
- builtins=True, signal=True):
- """
- Do all of the default monkey patching (calls every other applicable
- function in this module).
- .. versionchanged:: 1.1
- Issue a :mod:`warning <warnings>` if this function is called multiple times
- with different arguments. The second and subsequent calls will only add more
- patches, they can never remove existing patches by setting an argument to ``False``.
- .. versionchanged:: 1.1
- Issue a :mod:`warning <warnings>` if this function is called with ``os=False``
- and ``signal=True``. This will cause SIGCHLD handlers to not be called. This may
- be an error in the future.
- """
- # pylint:disable=too-many-locals,too-many-branches
- # Check to see if they're changing the patched list
- _warnings, first_time = _check_repatching(**locals())
- if not _warnings and not first_time:
- # Nothing to do, identical args to what we just
- # did
- return
- # order is important
- if os:
- patch_os()
- if time:
- patch_time()
- if thread:
- patch_thread(Event=Event, _warnings=_warnings)
- # sys must be patched after thread. in other cases threading._shutdown will be
- # initiated to _MainThread with real thread ident
- if sys:
- patch_sys()
- if socket:
- patch_socket(dns=dns, aggressive=aggressive)
- if select:
- patch_select(aggressive=aggressive)
- if ssl:
- patch_ssl()
- if httplib:
- raise ValueError('gevent.httplib is no longer provided, httplib must be False')
- if subprocess:
- patch_subprocess()
- if builtins:
- patch_builtins()
- if signal:
- if not os:
- _queue_warning('Patching signal but not os will result in SIGCHLD handlers'
- ' installed after this not being called and os.waitpid may not'
- ' function correctly if gevent.subprocess is used. This may raise an'
- ' error in the future.',
- _warnings)
- patch_signal()
- _process_warnings(_warnings)
- def main():
- args = {}
- argv = sys.argv[1:]
- verbose = False
- script_help, patch_all_args, modules = _get_script_help()
- while argv and argv[0].startswith('--'):
- option = argv[0][2:]
- if option == 'verbose':
- verbose = True
- elif option.startswith('no-') and option.replace('no-', '') in patch_all_args:
- args[option[3:]] = False
- elif option in patch_all_args:
- args[option] = True
- if option in modules:
- for module in modules:
- args.setdefault(module, False)
- else:
- sys.exit(script_help + '\n\n' + 'Cannot patch %r' % option)
- del argv[0]
- # TODO: break on --
- if verbose:
- import pprint
- import os
- print('gevent.monkey.patch_all(%s)' % ', '.join('%s=%s' % item for item in args.items()))
- print('sys.version=%s' % (sys.version.strip().replace('\n', ' '), ))
- print('sys.path=%s' % pprint.pformat(sys.path))
- print('sys.modules=%s' % pprint.pformat(sorted(sys.modules.keys())))
- print('cwd=%s' % os.getcwd())
- patch_all(**args)
- if argv:
- sys.argv = argv
- __package__ = None
- assert __package__ is None
- globals()['__file__'] = sys.argv[0] # issue #302
- globals()['__package__'] = None # issue #975: make script be its own package
- with open(sys.argv[0]) as f:
- # Be sure to exec in globals to avoid import pollution. Also #975.
- exec(f.read(), globals())
- else:
- print(script_help)
- def _get_script_help():
- from inspect import getargspec
- patch_all_args = getargspec(patch_all)[0] # pylint:disable=deprecated-method
- modules = [x for x in patch_all_args if 'patch_' + x in globals()]
- script_help = """gevent.monkey - monkey patch the standard modules to use gevent.
- USAGE: python -m gevent.monkey [MONKEY OPTIONS] script [SCRIPT OPTIONS]
- If no OPTIONS present, monkey patches all the modules it can patch.
- You can exclude a module with --no-module, e.g. --no-thread. You can
- specify a module to patch with --module, e.g. --socket. In the latter
- case only the modules specified on the command line will be patched.
- MONKEY OPTIONS: --verbose %s""" % ', '.join('--[no-]%s' % m for m in modules)
- return script_help, patch_all_args, modules
- main.__doc__ = _get_script_help()[0]
- if __name__ == '__main__':
- main()
|