Commit 7874888a authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components: Fix deadlock on import lock.

Since a29456bc, only import lock is used instead of aq_method_lock, dynamic
modules locks and import lock. However, it was creating another deadlock:

when the import lock is held in one thread (for instance when trying to
perform migration to Portal Type as Classes and ZODB Property Sheets), and an
object is loaded from ZODB, a request is sent to ZEO, and this blocks until
another thread (asyncore) gets the ZEO reply and sends it back to the first
thread.

However, if the asyncore thread receives an Exception, it will tries to import
its module and thus create a deadlock as the import lock is still held by the
first thread.
parent 35ad08a2
...@@ -86,7 +86,6 @@ from Products.ERP5Type.Accessor.TypeDefinition import asDate ...@@ -86,7 +86,6 @@ from Products.ERP5Type.Accessor.TypeDefinition import asDate
from Products.ERP5Type.Message import Message from Products.ERP5Type.Message import Message
from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from Products.ERP5Type.dynamic.import_lock import ImportLock
from zope.interface import classImplementsOnly, implementedBy from zope.interface import classImplementsOnly, implementedBy
...@@ -711,7 +710,6 @@ class Base( CopyContainer, ...@@ -711,7 +710,6 @@ class Base( CopyContainer,
isTempDocument = ConstantGetter('isTempDocument', value=False) isTempDocument = ConstantGetter('isTempDocument', value=False)
# Dynamic method acquisition system (code generation) # Dynamic method acquisition system (code generation)
aq_method_lock = ImportLock()
aq_method_generated = set() aq_method_generated = set()
aq_method_generating = [] aq_method_generating = []
aq_portal_type = {} aq_portal_type = {}
......
...@@ -37,7 +37,7 @@ from AccessControl import ClassSecurityInfo ...@@ -37,7 +37,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from AccessControl.Permission import Permission from AccessControl.Permission import Permission
from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type.Base import Base from Products.ERP5Type.dynamic import aq_method_lock
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from zLOG import LOG, INFO, WARNING from zLOG import LOG, INFO, WARNING
...@@ -147,7 +147,7 @@ class ComponentTool(BaseTool): ...@@ -147,7 +147,7 @@ class ComponentTool(BaseTool):
# class when Components are reset through aq_method_lock # class when Components are reset through aq_method_lock
import erp5.component import erp5.component
from Products.ERP5Type.dynamic.component_package import ComponentDynamicPackage from Products.ERP5Type.dynamic.component_package import ComponentDynamicPackage
with Base.aq_method_lock: with aq_method_lock:
for package in erp5.component.__dict__.itervalues(): for package in erp5.component.__dict__.itervalues():
if isinstance(package, ComponentDynamicPackage): if isinstance(package, ComponentDynamicPackage):
package.reset() package.reset()
......
import threading
aq_method_lock = threading.RLock()
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
############################################################################## ##############################################################################
from types import ModuleType from types import ModuleType
from . import aq_method_lock
import sys import sys
from Products.ERP5Type.dynamic.import_lock import ImportLock
class DynamicModule(ModuleType): class DynamicModule(ModuleType):
"""This module may generate new objects at runtime.""" """This module may generate new objects at runtime."""
...@@ -41,13 +41,13 @@ class DynamicModule(ModuleType): ...@@ -41,13 +41,13 @@ class DynamicModule(ModuleType):
def __init__(self, name, factory, doc=None): def __init__(self, name, factory, doc=None):
super(DynamicModule, self).__init__(name, doc=doc) super(DynamicModule, self).__init__(name, doc=doc)
self._factory = factory self._factory = factory
self._lock = ImportLock()
def __getattr__(self, name): def __getattr__(self, name):
if name[:2] == '__': if name[:2] == '__':
raise AttributeError('%r module has no attribute %r' raise AttributeError('%r module has no attribute %r'
% (self.__name__, name)) % (self.__name__, name))
with self._lock:
with aq_method_lock:
try: try:
return super(DynamicModule, self).__getattribute__(name) return super(DynamicModule, self).__getattribute__(name)
except AttributeError: except AttributeError:
......
# -*- coding: utf-8 -*-
##############################################################################
# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
import imp
class ImportLock(object):
"""
This class provides the interpreter's import lock.
It is intended to use in ERP5Type.dynamic to avoid possible dead lock.
It can be used in two ways :
1) 'with' statement
lock = ImportLock()
with lock:
...
2) traditional 'try' and 'finally'
lock = ImportLock()
lock.acquire()
try:
...
finally:
lock.release()
"""
def __enter__(self):
imp.acquire_lock()
def __exit__(self, type, value, tb):
imp.release_lock()
def acquire(self):
imp.acquire_lock()
def release(self):
imp.release_lock()
...@@ -6,6 +6,7 @@ from Products.ERP5Type import Permissions ...@@ -6,6 +6,7 @@ from Products.ERP5Type import Permissions
from Products.ERP5Type.Accessor.Constant import Getter as ConstantGetter from Products.ERP5Type.Accessor.Constant import Getter as ConstantGetter
from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.Base import Base as ERP5Base from Products.ERP5Type.Base import Base as ERP5Base
from . import aq_method_lock
from Products.ERP5Type.Base import PropertyHolder, initializePortalTypeDynamicWorkflowMethods from Products.ERP5Type.Base import PropertyHolder, initializePortalTypeDynamicWorkflowMethods
from Products.ERP5Type.Utils import UpperCase from Products.ERP5Type.Utils import UpperCase
from Products.ERP5Type.Core.CategoryProperty import CategoryProperty from Products.ERP5Type.Core.CategoryProperty import CategoryProperty
...@@ -319,7 +320,7 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder): ...@@ -319,7 +320,7 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder):
portal_type = klass.__name__ portal_type = klass.__name__
from Products.ERP5.ERP5Site import getSite from Products.ERP5.ERP5Site import getSite
site = getSite() site = getSite()
ERP5Base.aq_method_lock.acquire() aq_method_lock.acquire()
try: try:
try: try:
class_definition = generatePortalTypeClass(site, portal_type) class_definition = generatePortalTypeClass(site, portal_type)
...@@ -363,7 +364,7 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder): ...@@ -363,7 +364,7 @@ class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder):
import traceback; traceback.print_exc() import traceback; traceback.print_exc()
raise raise
finally: finally:
ERP5Base.aq_method_lock.release() aq_method_lock.release()
def generateLazyPortalTypeClass(portal_type_name): def generateLazyPortalTypeClass(portal_type_name):
return PortalTypeMetaClass(portal_type_name, return PortalTypeMetaClass(portal_type_name,
......
...@@ -33,7 +33,8 @@ import inspect ...@@ -33,7 +33,8 @@ import inspect
import transaction import transaction
from Products.ERP5Type.mixin.temporary import TemporaryDocumentMixin from Products.ERP5Type.mixin.temporary import TemporaryDocumentMixin
from Products.ERP5Type.Base import Base, resetRegisteredWorkflowMethod from Products.ERP5Type.Base import resetRegisteredWorkflowMethod
from . import aq_method_lock
from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.Utils import setDefaultClassProperties from Products.ERP5Type.Utils import setDefaultClassProperties
from Products.ERP5Type import document_class_registry, mixin_class_registry from Products.ERP5Type import document_class_registry, mixin_class_registry
...@@ -318,7 +319,7 @@ def synchronizeDynamicModules(context, force=False): ...@@ -318,7 +319,7 @@ def synchronizeDynamicModules(context, force=False):
last_sync = cookie last_sync = cookie
import erp5 import erp5
with Base.aq_method_lock: with aq_method_lock:
# Thanks to TransactionalResource, the '_bootstrapped' global variable # Thanks to TransactionalResource, the '_bootstrapped' global variable
# is updated in a transactional way. Without it, it would be required to # is updated in a transactional way. Without it, it would be required to
# restart the instance if anything went wrong. # restart the instance if anything went wrong.
......
...@@ -310,6 +310,9 @@ class ComponentMixin(PropertyRecordableMixin, Base): ...@@ -310,6 +310,9 @@ class ComponentMixin(PropertyRecordableMixin, Base):
Initially, namespace_dict default parameter value was an empty dict to Initially, namespace_dict default parameter value was an empty dict to
allow checking the source code before validate, but this is completely allow checking the source code before validate, but this is completely
wrong as the object reference is kept accross each call wrong as the object reference is kept accross each call
TODO-arnau: Not used anymore in component_package, so this could be
removed as soon as pyflakes is used instead
""" """
if text_content is None: if text_content is None:
text_content = self.getTextContent(validated_only=validated_only) text_content = self.getTextContent(validated_only=validated_only)
......
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