1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
# Copyright (c) 2015 gevent contributors. See LICENSE for details.
"""gevent friendly implementations of builtin functions."""
from __future__ import absolute_import
import imp # deprecated since 3.4; issues PendingDeprecationWarning in 3.5
import sys
import weakref
from gevent.lock import RLock
# Normally we'd have the "expected" case inside the try
# (Python 3, because Python 3 is the way forward). But
# under Python 2, the popular `future` library *also* provides
# a `builtins` module---which lacks the __import__ attribute.
# So we test for the old, deprecated version first
try: # Py2
import __builtin__ as builtins
_allowed_module_name_types = (basestring,) # pylint:disable=undefined-variable
__target__ = '__builtin__'
except ImportError:
import builtins # pylint: disable=import-error
_allowed_module_name_types = (str,)
__target__ = 'builtins'
_import = builtins.__import__
# We need to protect imports both across threads and across greenlets.
# And the order matters. Note that under 3.4, the global import lock
# and imp module are deprecated. It seems that in all Py3 versions, a
# module lock is used such that this fix is not necessary.
# We emulate the per-module locking system under Python 2 in order to
# avoid issues acquiring locks in multiple-level-deep imports
# that attempt to use the gevent blocking API at runtime; using one lock
# could lead to a LoopExit error as a greenlet attempts to block on it while
# it's already held by the main greenlet (issue #798).
# We base this approach on a simplification of what `importlib._bootstrap`
# does; notably, we don't check for deadlocks
_g_import_locks = {} # name -> wref of RLock
__lock_imports = True
def __module_lock(name):
# Return the lock for the given module, creating it if necessary.
# It will be removed when no longer needed.
# Nothing in this function yields, so we're multi-greenlet safe
# (But not multi-threading safe.)
# XXX: What about on PyPy, where the GC is asynchronous (not ref-counting)?
# (Does it stop-the-world first?)
lock = None
try:
lock = _g_import_locks[name]()
except KeyError:
pass
if lock is None:
lock = RLock()
def cb(_):
# We've seen a KeyError on PyPy on RPi2
_g_import_locks.pop(name, None)
_g_import_locks[name] = weakref.ref(lock, cb)
return lock
def __import__(*args, **kwargs):
"""
__import__(name, globals=None, locals=None, fromlist=(), level=0) -> object
Normally python protects imports against concurrency by doing some locking
at the C level (at least, it does that in CPython). This function just
wraps the normal __import__ functionality in a recursive lock, ensuring that
we're protected against greenlet import concurrency as well.
"""
if args and not issubclass(type(args[0]), _allowed_module_name_types):
# if a builtin has been acquired as a bound instance method,
# python knows not to pass 'self' when the method is called.
# No such protection exists for monkey-patched builtins,
# however, so this is necessary.
args = args[1:]
if not __lock_imports:
return _import(*args, **kwargs)
module_lock = __module_lock(args[0]) # Get a lock for the module name
imp.acquire_lock()
try:
module_lock.acquire()
try:
result = _import(*args, **kwargs)
finally:
module_lock.release()
finally:
imp.release_lock()
return result
def _unlock_imports():
"""
Internal function, called when gevent needs to perform imports
lazily, but does not know the state of the system. It may be impossible
to take the import lock because there are no other running greenlets, for
example. This causes a monkey-patched __import__ to avoid taking any locks.
until the corresponding call to lock_imports. This should only be done for limited
amounts of time and when the set of imports is statically known to be "safe".
"""
global __lock_imports
# This could easily become a list that we push/pop from or an integer
# we increment if we need to do this recursively, but we shouldn't get
# that complex.
__lock_imports = False
def _lock_imports():
global __lock_imports
__lock_imports = True
if sys.version_info[:2] >= (3, 3):
__implements__ = []
else:
__implements__ = ['__import__']
__all__ = __implements__
|