diff options
Diffstat (limited to 'python/gevent/monkey.py')
-rw-r--r-- | python/gevent/monkey.py | 595 |
1 files changed, 486 insertions, 109 deletions
diff --git a/python/gevent/monkey.py b/python/gevent/monkey.py index 1f04455..6c1fa00 100644 --- a/python/gevent/monkey.py +++ b/python/gevent/monkey.py @@ -3,9 +3,6 @@ """ 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). @@ -24,13 +21,21 @@ this code, ideally before any other imports:: from gevent import monkey monkey.patch_all() +A corollary of the above is that patching **should be done on the main +thread** and **should be done while the program is single-threaded**. + .. tip:: Some frameworks, such as gunicorn, handle monkey-patching for you. Check their documentation to be sure. +.. warning:: + + Patching too late can lead to unreliable behaviour (for example, some + modules may still use blocking sockets) or even errors. + 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 @@ -40,6 +45,64 @@ module provides functions for that purpose. - :func:`is_object_patched` - :func:`get_original` +Plugins +======= + +Beginning in gevent 1.3, events are emitted during the monkey patching process. +These events are delivered first to :mod:`gevent.events` subscribers, and then +to `setuptools entry points`_. + +The following events are defined. They are listed in (roughly) the order +that a call to :func:`patch_all` will emit them. + +- :class:`gevent.events.GeventWillPatchAllEvent` +- :class:`gevent.events.GeventWillPatchModuleEvent` +- :class:`gevent.events.GeventDidPatchModuleEvent` +- :class:`gevent.events.GeventDidPatchBuiltinModulesEvent` +- :class:`gevent.events.GeventDidPatchAllEvent` + +Each event class documents the corresponding setuptools entry point name. The +entry points will be called with a single argument, the same instance of +the class that was sent to the subscribers. + +You can subscribe to the events to monitor the monkey-patching process and +to manipulate it, for example by raising :exc:`gevent.events.DoNotPatch`. + +You can also subscribe to the events to provide additional patching beyond what +gevent distributes, either for additional standard library modules, or +for third-party packages. The suggested time to do this patching is in +the subscriber for :class:`gevent.events.GeventDidPatchBuiltinModulesEvent`. +For example, to automatically patch `psycopg2`_ using `psycogreen`_ +when the call to :func:`patch_all` is made, you could write code like this:: + + # mypackage.py + def patch_psycopg(event): + from psycogreen.gevent import patch_psycopg + patch_psycopg() + +In your ``setup.py`` you would register it like this:: + + from setuptools import setup + setup( + ... + entry_points={ + 'gevent.plugins.monkey.did_patch_builtins': [ + 'psycopg2 = mypackage:patch_psycopg', + ], + }, + ... + ) + +For more complex patching, gevent provides a helper method +that you can call to replace attributes of modules with attributes of your +own modules. This function also takes care of emitting the appropriate events. + +- :func:`patch_module` + +.. _setuptools entry points: http://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins +.. _psycopg2: https://pypi.python.org/pypi/psycopg2 +.. _psycogreen: https://pypi.python.org/pypi/psycogreen + Use as a module =============== @@ -48,8 +111,8 @@ 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 -========= +.. versionchanged:: 1.3b1 + Added support for plugins and began emitting will/did patch events. """ from __future__ import absolute_import from __future__ import print_function @@ -60,6 +123,7 @@ __all__ = [ 'patch_builtins', 'patch_dns', 'patch_os', + 'patch_queue', 'patch_select', 'patch_signal', 'patch_socket', @@ -72,6 +136,8 @@ __all__ = [ 'get_original', 'is_module_patched', 'is_object_patched', + # plugin API + 'patch_module', # module functions 'main', ] @@ -87,19 +153,63 @@ else: WIN = sys.platform.startswith("win") +class MonkeyPatchWarning(RuntimeWarning): + """ + The type of warnings we issue. + + .. versionadded:: 1.3a2 + """ + +def _notify_patch(event, _warnings=None): + # Raises DoNotPatch if we're not supposed to patch + from gevent.events import notify_and_call_entry_points + + event._warnings = _warnings + notify_and_call_entry_points(event) + +def _ignores_DoNotPatch(func): + + from functools import wraps + + @wraps(func) + def ignores(*args, **kwargs): + from gevent.events import DoNotPatch + try: + return func(*args, **kwargs) + except DoNotPatch: + return False + + return ignores + + # 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_module_patched(mod_name): + """ + Check if a module has been replaced with a cooperative version. + + :param str mod_name: The name of the standard library module, + e.g., ``'socket'``. + + """ + return mod_name in saved + + +def is_object_patched(mod_name, item_name): + """ + Check if an object in a module has been replaced with a + cooperative version. + :param str mod_name: The name of the standard library module, + e.g., ``'socket'``. + :param str item_name: The name of the attribute in the module, + e.g., ``'create_connection'``. -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] + """ + return is_module_patched(mod_name) and item_name in saved[mod_name] def _get_original(name, items): @@ -117,19 +227,26 @@ def _get_original(name, items): def get_original(mod_name, item_name): - """Retrieve the original object from a module. + """ + Retrieve the original object from a module. - If the object has not been patched, then that object will still be retrieved. + 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. + :param str mod_name: The name of the standard library module, + e.g., ``'socket'``. + :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() @@ -148,17 +265,94 @@ def remove_item(module, attr): 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) +def __call_module_hook(gevent_module, name, module, items, _warnings): + # This function can raise DoNotPatch on 'will' + + def warn(message): + _queue_warning(message, _warnings) + + func_name = '_gevent_' + name + '_monkey_patch' + try: + func = getattr(gevent_module, func_name) + except AttributeError: + func = lambda *args: None + + + func(module, items, warn) + + +def patch_module(target_module, source_module, items=None, + _warnings=None, + _notify_did_subscribers=True): + """ + patch_module(target_module, source_module, items=None) + + Replace attributes in *target_module* with the attributes of the + same name in *source_module*. + + The *source_module* can provide some attributes to customize the process: + + * ``__implements__`` is a list of attribute names to copy; if not present, + the *items* keyword argument is mandatory. + * ``_gevent_will_monkey_patch(target_module, items, warn, **kwargs)`` + * ``_gevent_did_monkey_patch(target_module, items, warn, **kwargs)`` + These two functions in the *source_module* are called *if* they exist, + before and after copying attributes, respectively. The "will" function + may modify *items*. The value of *warn* is a function that should be called + with a single string argument to issue a warning to the user. If the "will" + function raises :exc:`gevent.events.DoNotPatch`, no patching will be done. These functions + are called before any event subscribers or plugins. + + :keyword list items: A list of attribute names to replace. If + not given, this will be taken from the *source_module* ``__implements__`` + attribute. + :return: A true value if patching was done, a false value if patching was canceled. + + .. versionadded:: 1.3b1 + """ + from gevent import events + if items is None: - items = getattr(gevent_module, '__implements__', None) + items = getattr(source_module, '__implements__', None) if items is None: - raise AttributeError('%r does not have __implements__' % gevent_module) + raise AttributeError('%r does not have __implements__' % source_module) + + try: + __call_module_hook(source_module, 'will', target_module, items, _warnings) + _notify_patch( + events.GeventWillPatchModuleEvent(target_module.__name__, source_module, + target_module, items), + _warnings) + except events.DoNotPatch: + return False + for attr in items: - patch_item(module, attr, getattr(gevent_module, attr)) - return module + patch_item(target_module, attr, getattr(source_module, attr)) + + __call_module_hook(source_module, 'did', target_module, items, _warnings) + + if _notify_did_subscribers: + # We allow turning off the broadcast of the 'did' event for the benefit + # of our internal functions which need to do additional work (besides copying + # attributes) before their patch can be considered complete. + _notify_patch( + events.GeventDidPatchModuleEvent(target_module.__name__, source_module, + target_module) + ) + + return True + +def _patch_module(name, items=None, _warnings=None, _notify_did_subscribers=True): + + gevent_module = getattr(__import__('gevent.' + name), name) + module_name = getattr(gevent_module, '__target__', name) + target_module = __import__(module_name) + + patch_module(target_module, gevent_module, items=items, + _warnings=_warnings, + _notify_did_subscribers=_notify_did_subscribers) + + return gevent_module, target_module def _queue_warning(message, _warnings): @@ -174,7 +368,7 @@ def _queue_warning(message, _warnings): def _process_warnings(_warnings): import warnings for warning in _warnings: - warnings.warn(warning, RuntimeWarning, stacklevel=3) + warnings.warn(warning, MonkeyPatchWarning, stacklevel=3) def _patch_sys_std(name): @@ -183,33 +377,48 @@ def _patch_sys_std(name): if not isinstance(orig, FileObjectThread): patch_item(sys, name, FileObjectThread(orig)) - +@_ignores_DoNotPatch def patch_sys(stdin=True, stdout=True, stderr=True): - """Patch sys.std[in,out,err] to use a cooperative IO via a threadpool. + """ + 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 is relatively dangerous and can have unintended consequences + such as hanging the process or `misinterpreting control keys`_ + when :func:`input` and :func:`raw_input` are used. :func:`patch_all` + does *not* call this function by default. - 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. + 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: + items = None + else: + items = set([('stdin' if stdin else None), + ('stdout' if stdout else None), + ('stderr' if stderr else None)]) + items.discard(None) + items = list(items) + + if not items: return - if stdin: - _patch_sys_std('stdin') - if stdout: - _patch_sys_std('stdout') - if stderr: - _patch_sys_std('stderr') + from gevent import events + _notify_patch(events.GeventWillPatchModuleEvent('sys', None, sys, + items)) + + for item in items: + _patch_sys_std(item) + _notify_patch(events.GeventDidPatchModuleEvent('sys', None, sys)) +@_ignores_DoNotPatch def patch_os(): """ Replace :func:`os.fork` with :func:`gevent.fork`, and, on POSIX, @@ -217,21 +426,36 @@ def patch_os(): 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 + .. 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. + .. 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') + _patch_module('os') + +@_ignores_DoNotPatch +def patch_queue(): + """ + On Python 3.7 and above, replace :class:`queue.SimpleQueue` (implemented + in C) with its Python counterpart. + + .. versionadded:: 1.3.5 + """ + import gevent.queue + if 'SimpleQueue' in gevent.queue.__all__: + _patch_module('queue', items=['SimpleQueue']) + + +@_ignores_DoNotPatch def patch_time(): - """Replace :func:`time.sleep` with :func:`gevent.sleep`.""" - from gevent.hub import sleep - import time - patch_item(time, 'sleep', sleep) + """ + Replace :func:`time.sleep` with :func:`gevent.sleep`. + """ + _patch_module('time') def _patch_existing_locks(threading): @@ -271,20 +495,26 @@ def _patch_existing_locks(threading): if o.owner is not None: o.owner = tid - -def patch_thread(threading=True, _threading_local=True, Event=False, logging=True, +@_ignores_DoNotPatch +def patch_thread(threading=True, _threading_local=True, Event=True, logging=True, existing_locks=True, _warnings=None): """ + patch_thread(threading=True, _threading_local=True, Event=True, logging=True, existing_locks=True) -> 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. + :keyword bool threading: When True (the default), + also patch :mod:`threading`. + :keyword bool _threading_local: When True (the default), + also patch :class:`_threading_local.local`. + :keyword bool logging: When True (the default), also patch locks + taken if the logging module has been configured. + + :keyword bool existing_locks: When True (the default), and the + process is still single threaded, make sure that 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 @@ -294,9 +524,11 @@ def patch_thread(threading=True, _threading_local=True, Event=False, logging=Tru .. versionchanged:: 1.1b1 Add *logging* and *existing_locks* params. + .. versionchanged:: 1.3a2 + ``Event`` defaults to True. """ # XXX: Simplify - # pylint:disable=too-many-branches,too-many-locals + # pylint:disable=too-many-branches,too-many-locals,too-many-statements # Description of the hang: # There is an incompatibility with patching 'thread' and the 'multiprocessing' module: @@ -333,16 +565,24 @@ def patch_thread(threading=True, _threading_local=True, Event=False, logging=Tru orig_current_thread = threading_mod.current_thread() else: threading_mod = None + gevent_threading_mod = None orig_current_thread = None - patch_module('thread') + gevent_thread_mod, thread_mod = _patch_module('thread', + _warnings=_warnings, _notify_did_subscribers=False) if threading: - patch_module('threading') + gevent_threading_mod, _ = _patch_module('threading', + _warnings=_warnings, _notify_did_subscribers=False) if Event: from gevent.event import Event patch_item(threading_mod, 'Event', Event) + # Python 2 had `Event` as a function returning + # the private class `_Event`. Some code may be relying + # on that. + if hasattr(threading_mod, '_Event'): + patch_item(threading_mod, '_Event', Event) if existing_locks: _patch_existing_locks(threading_mod) @@ -423,11 +663,18 @@ def patch_thread(threading=True, _threading_local=True, Event=False, logging=Tru "threading.main_thread().join() will hang from a greenlet", _warnings) + from gevent import events + _notify_patch(events.GeventDidPatchModuleEvent('thread', gevent_thread_mod, thread_mod)) + _notify_patch(events.GeventDidPatchModuleEvent('threading', gevent_threading_mod, threading_mod)) +@_ignores_DoNotPatch def patch_socket(dns=True, aggressive=True): - """Replace the standard socket object with gevent's cooperative sockets. + """ + Replace the standard socket object with gevent's cooperative + sockets. - If ``dns`` is true, also patch dns functions in :mod:`socket`. + :keyword bool dns: When true (the default), also patch address + resolution functions in :mod:`socket`. See :doc:`/dns` for details. """ from gevent import socket # Note: although it seems like it's not strictly necessary to monkey patch 'create_connection', @@ -439,30 +686,104 @@ def patch_socket(dns=True, aggressive=True): items = socket.__implements__ # pylint:disable=no-member else: items = set(socket.__implements__) - set(socket.__dns__) # pylint:disable=no-member - patch_module('socket', items=items) + _patch_module('socket', items=items) if aggressive: if 'ssl' not in socket.__implements__: # pylint:disable=no-member remove_item(socket, 'ssl') - +@_ignores_DoNotPatch def patch_dns(): - """Replace DNS functions in :mod:`socket` with cooperative versions. + """ + Replace :doc:`DNS functions </dns>` 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. + 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 + _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. +def _find_module_refs(to, excluding_names=()): + # Looks specifically for module-level references, + # i.e., 'from foo import Bar'. We define a module reference + # as a dict (subclass) that also has a __name__ attribute. + # This does not handle subclasses, but it does find them. + # Returns two sets. The first is modules (name, file) that were + # found. The second is subclasses that were found. + gc = __import__('gc') + direct_ref_modules = set() + subclass_modules = set() - This is only useful if :func:`patch_socket` has been called. + def report(mod): + return mod['__name__'], mod.get('__file__', '<unknown>') + + for r in gc.get_referrers(to): + if isinstance(r, dict) and '__name__' in r: + if r['__name__'] in excluding_names: + continue + + for v in r.values(): + if v is to: + direct_ref_modules.add(report(r)) + elif isinstance(r, type) and to in r.__bases__ and 'gevent.' not in r.__module__: + subclass_modules.add(r) + + return direct_ref_modules, subclass_modules + +@_ignores_DoNotPatch +def patch_ssl(_warnings=None, _first_time=True): """ - patch_module('ssl') + patch_ssl() -> None + Replace :class:`ssl.SSLSocket` object and socket wrapping functions in + :mod:`ssl` with cooperative versions. + This is only useful if :func:`patch_socket` has been called. + """ + may_need_warning = ( + _first_time + and sys.version_info[:2] >= (3, 6) + and 'ssl' in sys.modules + and hasattr(sys.modules['ssl'], 'SSLContext')) + # Previously, we didn't warn on Python 2 if pkg_resources has been imported + # because that imports ssl and it's commonly used for namespace packages, + # which typically means we're still in some early part of the import cycle. + # However, with our new more discriminating check, that no longer seems to be a problem. + # Prior to 3.6, we don't have the RecursionError problem, and prior to 3.7 we don't have the + # SSLContext.sslsocket_class/SSLContext.sslobject_class problem. + + gevent_mod, _ = _patch_module('ssl', _warnings=_warnings) + if may_need_warning: + direct_ref_modules, subclass_modules = _find_module_refs( + gevent_mod.orig_SSLContext, + excluding_names=('ssl', 'gevent.ssl', 'gevent._ssl3', 'gevent._sslgte279')) + if direct_ref_modules or subclass_modules: + # Normally you don't want to have dynamic warning strings, because + # the cache in the warning module is based on the string. But we + # specifically only do this the first time we patch ourself, so it's + # ok. + direct_ref_mod_str = subclass_str = '' + if direct_ref_modules: + direct_ref_mod_str = 'Modules that had direct imports (NOT patched): %s. ' % ([ + "%s (%s)" % (name, fname) + for name, fname in direct_ref_modules + ]) + if subclass_modules: + subclass_str = 'Subclasses (NOT patched): %s. ' % ([ + str(t) for t in subclass_modules + ]) + _queue_warning( + 'Monkey-patching ssl after ssl has already been imported ' + 'may lead to errors, including RecursionError on Python 3.6. ' + 'It may also silently lead to incorrect behaviour on Python 3.7. ' + 'Please monkey-patch earlier. ' + 'See https://github.com/gevent/gevent/issues/1016. ' + + direct_ref_mod_str + subclass_str, + _warnings) + + +@_ignores_DoNotPatch def patch_select(aggressive=True): """ Replace :func:`select.select` with :func:`gevent.select.select` @@ -480,9 +801,10 @@ def patch_select(aggressive=True): - :class:`selectors.KqueueSelector` - :class:`selectors.DevpollSelector` (Python 3.5+) """ - patch_module('select') + + source_mod, target_mod = _patch_module('select', _notify_did_subscribers=False) if aggressive: - select = __import__('select') + select = target_mod # 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. @@ -500,7 +822,7 @@ def patch_select(aggressive=True): # 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 + select = target_mod # Should be gevent-patched now orig_select_select = get_original('select', 'select') assert select.select is not orig_select_select selectors = __import__('selectors') @@ -510,6 +832,14 @@ def patch_select(aggressive=True): selectors.SelectSelector._select = _select _select._gevent_monkey = True + # Python 3.7 refactors the poll-like selectors to use a common + # base class and capture a reference to select.poll, etc, at + # import time. selectors tends to get imported early + # (importing 'platform' does it: platform -> subprocess -> selectors), + # so we need to clean that up. + if hasattr(selectors, 'PollSelector') and hasattr(selectors.PollSelector, '_selector_cls'): + selectors.PollSelector._selector_cls = select.poll + if aggressive: # If `selectors` had already been imported before we removed # select.epoll|kqueue|devpoll, these may have been defined in terms @@ -519,7 +849,10 @@ def patch_select(aggressive=True): remove_item(selectors, 'DevpollSelector') selectors.DefaultSelector = selectors.SelectSelector + from gevent import events + _notify_patch(events.GeventDidPatchModuleEvent('select', source_mod, target_mod)) +@_ignores_DoNotPatch def patch_subprocess(): """ Replace :func:`subprocess.call`, :func:`subprocess.check_call`, @@ -531,12 +864,12 @@ def patch_subprocess(): the standard library. """ - patch_module('subprocess') - + _patch_module('subprocess') +@_ignores_DoNotPatch def patch_builtins(): """ - Make the builtin __import__ function `greenlet safe`_ under Python 2. + Make the builtin :func:`__import__` function `greenlet safe`_ under Python 2. .. note:: This does nothing under Python 3 as it is not necessary. Python 3 features @@ -546,27 +879,28 @@ def patch_builtins(): """ if sys.version_info[:2] < (3, 3): - patch_module('builtins') - + _patch_module('builtins') +@_ignores_DoNotPatch def patch_signal(): """ - Make the signal.signal function work with a monkey-patched os. + Make the :func:`signal.signal` function work with a :func:`monkey-patched os <patch_os>`. - .. caution:: This method must be used with :func:`patch_os` to have proper SIGCHLD + .. 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. + .. 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") + _patch_module("signal") def _check_repatching(**module_settings): _warnings = [] key = '_gevent_saved_patch_all' + del module_settings['kwargs'] 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", @@ -574,16 +908,31 @@ def _check_repatching(**module_settings): 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): + return _warnings, first_time, module_settings + + +def _subscribe_signal_os(will_patch_all): + if will_patch_all.will_patch_module('signal') and not will_patch_all.will_patch_module('os'): + warnings = will_patch_all._warnings # Internal + _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) + +def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True, + httplib=False, # Deprecated, to be removed. + subprocess=True, sys=False, aggressive=True, Event=True, + builtins=True, signal=True, + queue=True, + **kwargs): """ Do all of the default monkey patching (calls every other applicable function in this module). + :return: A true value if patching all modules wasn't cancelled, a false + value if it was. + .. 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 @@ -592,16 +941,32 @@ def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=Tru 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. + .. versionchanged:: 1.3a2 + ``Event`` defaults to True. + .. versionchanged:: 1.3b1 + Defined the return values. + .. versionchanged:: 1.3b1 + Add ``**kwargs`` for the benefit of event subscribers. CAUTION: gevent may add + and interpret additional arguments in the future, so it is suggested to use prefixes + for kwarg values to be interpreted by plugins, for example, `patch_all(mylib_futures=True)`. + .. versionchanged:: 1.3.5 + Add *queue*, defaulting to True, for Python 3.7. """ # pylint:disable=too-many-locals,too-many-branches # Check to see if they're changing the patched list - _warnings, first_time = _check_repatching(**locals()) + _warnings, first_time, modules_to_patch = _check_repatching(**locals()) if not _warnings and not first_time: # Nothing to do, identical args to what we just # did return + from gevent import events + try: + _notify_patch(events.GeventWillPatchAllEvent(modules_to_patch, kwargs), _warnings) + except events.DoNotPatch: + return False + # order is important if os: patch_os() @@ -618,7 +983,7 @@ def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=Tru if select: patch_select(aggressive=aggressive) if ssl: - patch_ssl() + patch_ssl(_warnings=_warnings, _first_time=first_time) if httplib: raise ValueError('gevent.httplib is no longer provided, httplib must be False') if subprocess: @@ -626,15 +991,15 @@ def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=Tru 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() + if queue: + patch_queue() + + _notify_patch(events.GeventDidPatchBuiltinModulesEvent(modules_to_patch, kwargs), _warnings) + _notify_patch(events.GeventDidPatchAllEvent(modules_to_patch, kwargs), _warnings) _process_warnings(_warnings) + return True def main(): @@ -669,31 +1034,43 @@ def main(): 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()) + import runpy + # Use runpy.run_path to closely (exactly) match what the + # interpreter does given 'python <path>'. This includes allowing + # passing .pyc/.pyo files and packages with a __main__ and + # potentially even zip files. Previously we used exec, which only + # worked if we directly read a python source file. + runpy.run_path(sys.argv[0], + run_name='__main__') else: print(script_help) def _get_script_help(): - from inspect import getargspec - patch_all_args = getargspec(patch_all)[0] # pylint:disable=deprecated-method + # pylint:disable=deprecated-method + import inspect + try: + getter = inspect.getfullargspec # deprecated in 3.5, un-deprecated in 3.6 + except AttributeError: + getter = inspect.getargspec + patch_all_args = getter(patch_all)[0] 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] +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) +.. versionchanged:: 1.3b1 + The *script* argument can now be any argument that can be passed to `runpy.run_path`, + just like the interpreter itself does, for example a package directory containing ``__main__.py``. + Previously it had to be the path to + a .py source file. + +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] |