Commit 2ac367df authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components: Cache source code in the loader to avoid access to ERP5 Site.

Using DeadlockDebugguer lead to a deadlock when trying to debug a deadlock on
ZODB Component as it was trying to load the Component. Moreover, this required
accessing to ERP5 Site whereas DeadlockDebugguer is used outside.

Steps to reproduce:
  1. Create an Extension Component with a sleep and run it.
  2. Call manage_debug_threads.
parent 6b03c39e
...@@ -76,6 +76,7 @@ class ComponentDynamicPackage(ModuleType): ...@@ -76,6 +76,7 @@ class ComponentDynamicPackage(ModuleType):
self._portal_type = portal_type self._portal_type = portal_type
self.__version_suffix_len = len('_version') self.__version_suffix_len = len('_version')
self.__registry_dict = collections.defaultdict(dict) self.__registry_dict = collections.defaultdict(dict)
self.__fullname_source_code_dict = {}
# Add this module to sys.path for future imports # Add this module to sys.path for future imports
sys.modules[namespace] = self sys.modules[namespace] = self
...@@ -132,17 +133,14 @@ class ComponentDynamicPackage(ModuleType): ...@@ -132,17 +133,14 @@ class ComponentDynamicPackage(ModuleType):
def get_source(self, fullname): def get_source(self, fullname):
""" """
Get the source code of the given module name from the ID defined on the PEP-302 function to get the source code, used mainly by linecache for
dynamic module (setting getTextContent() on the module directly may not tracebacks, pdb...
work properly upon reset and there is no need for performance there as it
is only used for traceback or pdb anyway)
"""
module = __import__(fullname, fromlist=[fullname.rsplit('.', 1)[0]],
level=0)
component = getSite().unrestrictedTraverse(module.__file__[1:-1])
return component.getTextContent(validated_only=True) Use internal cache rather than accessing the Component directly as this
would require accessing ERP5 Site even though the source code may be
retrieved outside of ERP5 (eg DeadlockDebugguer).
"""
return self.__fullname_source_code_dict.get(fullname)
def find_module(self, fullname, path=None): def find_module(self, fullname, path=None):
""" """
...@@ -390,6 +388,9 @@ class ComponentDynamicPackage(ModuleType): ...@@ -390,6 +388,9 @@ class ComponentDynamicPackage(ModuleType):
module_cache_set.add(module) module_cache_set.add(module)
# Only useful for get_source()
self.__fullname_source_code_dict[module_fullname] = source_code_str
return module return module
finally: finally:
# load_module() can be called outside of import machinery, for example # load_module() can be called outside of import machinery, for example
...@@ -415,8 +416,9 @@ class ComponentDynamicPackage(ModuleType): ...@@ -415,8 +416,9 @@ class ComponentDynamicPackage(ModuleType):
if sub_package: if sub_package:
package = sub_package package = sub_package
else: else:
# Clear the Component registry only once # Clear the Component registry and source code dict only once
self.__registry_dict.clear() self.__registry_dict.clear()
self.__fullname_source_code_dict.clear()
package = self package = self
for name, module in package.__dict__.items(): for name, module in package.__dict__.items():
......
...@@ -128,9 +128,9 @@ def patch_linecache(): ...@@ -128,9 +128,9 @@ def patch_linecache():
display ZODB Components, Python Script source code and TALES Expressions display ZODB Components, Python Script source code and TALES Expressions
properly without requiring to create a temporary file on the filesystem properly without requiring to create a temporary file on the filesystem
The filename is is always '<string>' for any code executed by exec() (ZODB The filename is is always '<portal_components/*>' for ZODB Components,
Components), '(FILENAME)?Script \(Python\)' for Zope Python Scripts and '(FILENAME)?Script \(Python\)' for Zope Python Scripts and 'Python
'Python Expression "CODE"' for TALES expressions. Expression "CODE"' for TALES expressions.
linecache.cache filled by linecache.updatecache() called by the original linecache.cache filled by linecache.updatecache() called by the original
linecache.getlines() is bypassed for Python Script to avoid getting linecache.getlines() is bypassed for Python Script to avoid getting
...@@ -142,7 +142,7 @@ def patch_linecache(): ...@@ -142,7 +142,7 @@ def patch_linecache():
if module_globals is None: if module_globals is None:
module_globals = get_globals(sys._getframe(1)) module_globals = get_globals(sys._getframe(1))
# Get source code of ZODB Components (following PEP 302) # Get source code of ZODB Components following PEP 302
if (filename.startswith('<portal_components/') and if (filename.startswith('<portal_components/') and
'__loader__' in module_globals): '__loader__' in module_globals):
data = None data = None
......
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