diff -r e8e7f6a0e7b6 -r 63128e8b9af9 server/hooks.py --- a/server/hooks.py Mon Feb 08 09:57:25 2010 +0100 +++ b/server/hooks.py Mon Feb 08 10:06:40 2010 +0100 @@ -8,6 +8,7 @@ """ __docformat__ = "restructuredtext en" +from threading import Lock from datetime import datetime from cubicweb import UnknownProperty, ValidationError, BadConnectionId @@ -25,6 +26,41 @@ DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of', 'wf_info_for', 'from_state', 'to_state')) +_UNIQUE_CONSTRAINTS_LOCK = Lock() +_UNIQUE_CONSTRAINTS_HOLDER = None + +class _ReleaseUniqueConstraintsHook(Operation): + def commit_event(self): + pass + def postcommit_event(self): + _release_unique_cstr_lock(self.session) + def rollback_event(self): + _release_unique_cstr_lock(self.session) + +def _acquire_unique_cstr_lock(session): + """acquire the _UNIQUE_CONSTRAINTS_LOCK for the session. + + This lock used to avoid potential integrity pb when checking + RQLUniqueConstraint in two different transactions, as explained in + http://intranet.logilab.fr/jpl/ticket/36564 + """ + global _UNIQUE_CONSTRAINTS_HOLDER + asession = session.actual_session() + if _UNIQUE_CONSTRAINTS_HOLDER is asession: + return + _UNIQUE_CONSTRAINTS_LOCK.acquire() + _UNIQUE_CONSTRAINTS_HOLDER = asession + # register operation responsible to release the lock on commit/rollback + _ReleaseUniqueConstraintsHook(asession) + +def _release_unique_cstr_lock(session): + global _UNIQUE_CONSTRAINTS_HOLDER + if _UNIQUE_CONSTRAINTS_HOLDER is session: + _UNIQUE_CONSTRAINTS_HOLDER = None + _UNIQUE_CONSTRAINTS_LOCK.release() + else: + assert _UNIQUE_CONSTRAINTS_HOLDER is None + def relation_deleted(session, eidfrom, rtype, eidto): session.transaction_data.setdefault('pendingrelations', []).append( @@ -216,6 +252,12 @@ if eidto in pending: return for constraint in self.constraints: + # XXX + # * lock RQLConstraint as well? + # * use a constraint id to use per constraint lock and avoid + # unnecessary commit serialization ? + if isinstance(constraint, RQLUniqueConstraint): + _acquire_unique_cstr_lock(self.session) try: constraint.repo_check(self.session, eidfrom, rtype, eidto) except NotImplementedError: