Windows: Use 32-bit distribution of python
This commit is contained in:
@@ -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(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 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'``.
|
||||
|
||||
"""
|
||||
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):
|
||||
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(source_module, '__implements__', None)
|
||||
if items is None:
|
||||
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(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)
|
||||
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
|
||||
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()
|
||||
|
||||
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_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.
|
||||
"""
|
||||
patch_module('ssl')
|
||||
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
|
||||
return _warnings, first_time, module_settings
|
||||
|
||||
|
||||
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):
|
||||
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]
|
||||
|
||||
Reference in New Issue
Block a user