Commit c13c591d authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components: Fix deadlock between import lock and aq_method_lock.

parent 5ad424bf
...@@ -241,102 +241,76 @@ class ComponentDynamicPackage(ModuleType): ...@@ -241,102 +241,76 @@ class ComponentDynamicPackage(ModuleType):
As per PEP-302, raise an ImportError if the Loader could not load the As per PEP-302, raise an ImportError if the Loader could not load the
module for any reason... module for any reason...
""" """
# In Python < 3.3, the import lock is a global lock for all modules: site = getSite()
# http://bugs.python.org/issue9260 name = fullname[len(self._namespace_prefix):]
#
# So, release the import lock acquired by import statement on all hooks to
# load objects from ZODB. When an object is requested from ZEO, it sends a
# RPC request and lets the asyncore thread gets the reply. This reply may
# be a tuple (PICKLE, TID), sent directly to the first thread, or an
# Exception, which tries to import a ZODB module and thus creates a
# deadlock because of the global import lock
#
# Also, handle the case where find_module() may be called without import
# statement as it does change anything in sys.modules
import_lock_held = True
try:
imp.release_lock()
except RuntimeError:
import_lock_held = False
try:
site = getSite()
name = fullname[len(self._namespace_prefix):]
# if only Version package (erp5.component.XXX.VERSION_version) is # if only Version package (erp5.component.XXX.VERSION_version) is
# requested to be loaded, then create it if necessary # requested to be loaded, then create it if necessary
if name.endswith('_version'): if name.endswith('_version'):
version = name[:-self.__version_suffix_len] version = name[:-self.__version_suffix_len]
return (version in site.getVersionPriorityNameList() and return (version in site.getVersionPriorityNameList() and
self._getVersionPackage(version) or None) self._getVersionPackage(version) or None)
module_fullname_alias = None module_fullname_alias = None
version_package_name = name[:-self.__version_suffix_len] version_package_name = name[:-self.__version_suffix_len]
# If a specific version of the Component has been requested # If a specific version of the Component has been requested
if '.' in name: if '.' in name:
try: try:
version, name = name.split('.') version, name = name.split('.')
version = version[:-self.__version_suffix_len] version = version[:-self.__version_suffix_len]
except ValueError, error: except ValueError, error:
raise ImportError("%s: should be %s.VERSION.COMPONENT_REFERENCE (%s)" % \ raise ImportError("%s: should be %s.VERSION.COMPONENT_REFERENCE (%s)" % \
(fullname, self._namespace, error)) (fullname, self._namespace, error))
try: try:
component_id = self._registry_dict[name][version][0] component_id = self._registry_dict[name][version][0]
except KeyError: except KeyError:
raise ImportError("%s: version %s of Component %s could not be found" % \ raise ImportError("%s: version %s of Component %s could not be found" % \
(fullname, version, name)) (fullname, version, name))
# Otherwise, find the Component with the highest version priority # Otherwise, find the Component with the highest version priority
else:
try:
component_version_dict = self._registry_dict[name]
except KeyError:
raise ImportError("%s: Component %s could not be found" % (fullname,
name))
# Version priority name list is ordered in descending order
for version in site.getVersionPriorityNameList():
component_id_uid_tuple = component_version_dict.get(version)
if component_id_uid_tuple is not None:
component_id = component_id_uid_tuple[0]
break
else: else:
try: raise ImportError("%s: no version of Component %s in Site priority" % \
component_version_dict = self._registry_dict[name] (fullname, name))
except KeyError:
raise ImportError("%s: Component %s could not be found" % (fullname,
name))
# Version priority name list is ordered in descending order
for version in site.getVersionPriorityNameList():
component_id_uid_tuple = component_version_dict.get(version)
if component_id_uid_tuple is not None:
component_id = component_id_uid_tuple[0]
break
else:
raise ImportError("%s: no version of Component %s in Site priority" % \
(fullname, name))
# Check whether this module has already been loaded before for a
# specific version, if so, just add it to the upper level
try:
module = getattr(getattr(self, version + '_version'), name)
except AttributeError:
pass
else:
setattr(self, name, module)
return module
module_fullname_alias = self._namespace + '.' + name # Check whether this module has already been loaded before for a
# specific version, if so, just add it to the upper level
try:
module = getattr(getattr(self, version + '_version'), name)
except AttributeError:
pass
else:
setattr(self, name, module)
return module
component = getattr(site.portal_components, component_id) module_fullname_alias = self._namespace + '.' + name
relative_url = component.getRelativeUrl()
module_fullname = '%s.%s_version.%s' % (self._namespace, version, name) component = getattr(site.portal_components, component_id)
module = ModuleType(module_fullname, component.getDescription()) relative_url = component.getRelativeUrl()
source_code_str = component.getTextContent(validated_only=True) module_fullname = '%s.%s_version.%s' % (self._namespace, version, name)
version_package = self._getVersionPackage(version) module = ModuleType(module_fullname, component.getDescription())
finally: source_code_str = component.getTextContent(validated_only=True)
# Internal release of import lock at the end of import machinery will version_package = self._getVersionPackage(version)
# fail if the hook is not acquired
if import_lock_held:
imp.acquire_lock()
# All the required objects have been loaded, acquire import lock to modify # All the required objects have been loaded, acquire import lock to modify
# sys.modules and execute PEP302 requisites # sys.modules and execute PEP302 requisites
if not import_lock_held: imp.acquire_lock()
imp.acquire_lock()
try: try:
# The module *must* be in sys.modules before executing the code in case # The module *must* be in sys.modules before executing the code in case
# the module code imports (directly or indirectly) itself (see PEP 302) # the module code imports (directly or indirectly) itself (see PEP 302)
...@@ -393,19 +367,41 @@ class ComponentDynamicPackage(ModuleType): ...@@ -393,19 +367,41 @@ class ComponentDynamicPackage(ModuleType):
return module return module
finally: finally:
# load_module() can be called outside of import machinery, for example imp.release_lock()
# to check first if the module can be handled by Component and then try
# to load it without going through the same code again
if not import_lock_held:
imp.release_lock()
def load_module(self, fullname): def load_module(self, fullname):
""" """
Make sure that loading module is thread-safe using aq_method_lock to make Make sure that loading module is thread-safe using aq_method_lock to make
sure that modules do not disappear because of an ongoing reset sure that modules do not disappear because of an ongoing reset
""" """
with aq_method_lock: # In Python < 3.3, the import lock is a global lock for all modules:
# http://bugs.python.org/issue9260
#
# So, release the import lock acquired by import statement on all hooks to
# load objects from ZODB. When an object is requested from ZEO, it sends a
# RPC request and lets the asyncore thread gets the reply. This reply may
# be a tuple (PICKLE, TID), sent directly to the first thread, or an
# Exception, which tries to import a ZODB module and thus creates a
# deadlock because of the global import lock
#
# Also, handle the case where find_module() may be called without import
# statement as it does change anything in sys.modules
import_lock_held = True
try:
imp.release_lock()
except RuntimeError:
import_lock_held = False
aq_method_lock.acquire()
try:
return self.__load_module(fullname) return self.__load_module(fullname)
finally:
aq_method_lock.release()
# Internal release of import lock at the end of import machinery will
# fail if the hook is not acquired
if import_lock_held:
imp.acquire_lock()
def find_load_module(self, name): def find_load_module(self, name):
""" """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment