From 2c11b76a49e97b2420fd056984bc9320f8ef35ae Mon Sep 17 00:00:00 2001 From: Julien Muchembled <jm@nexedi.com> Date: Fri, 15 Feb 2013 15:58:59 +0100 Subject: [PATCH] Fix commit order of CMFActivity SQL connection on nodes with several zserver threads When a ZODB connection is closed, it usually returns to a ZODB pool and may be reused by another thread. If the SQL connection was open and is still in ZODB cache, the _v_database_connection attribute is still there: ActivityConnection.connect() is not called and a new instance of ZMySQLDA.db.DB is created for the new thread without initializing its sort key. --- product/CMFActivity/ActivityConnection.py | 23 +++++++++----------- product/CMFActivity/tests/testCMFActivity.py | 22 +++++++++++++++++++ product/ZMySQLDA/db.py | 6 ++--- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/product/CMFActivity/ActivityConnection.py b/product/CMFActivity/ActivityConnection.py index 75a05d3fe7..67687e374a 100644 --- a/product/CMFActivity/ActivityConnection.py +++ b/product/CMFActivity/ActivityConnection.py @@ -26,13 +26,13 @@ # ############################################################################## -from Products.ZMySQLDA.DA import Connection +from Products.ZMySQLDA.DA import Connection, ThreadedDB from Products.ERP5Type.Globals import InitializeClass from App.special_dtml import HTMLFile from Acquisition import aq_parent -# If the sort order below doesn't work, we cannot guarantee the setSortKey() -# call below will actually result in the activity connection being committed +# If the sort order below doesn't work, we cannot guarantee the sort key +# used below will actually result in the activity connection being committed # after the ZODB and Catalog data. assert None < 0 < '' < (), "Cannot guarantee commit of activities comes after the appropriate data" @@ -58,15 +58,12 @@ class ActivityConnection(Connection): # reuse the permission from ZMySQLDA permission_type = 'Add Z MySQL Database Connections' - def connect(self, s): - result = Connection.connect(self, s) - if aq_parent(self) is None: - # Connection.connect() doesn't set _v_database_connection if there - # are no acquisition wrappers - return result - # the call above will set self._v_database_connection, and it won't - # have disappeared by now. - self._v_database_connection.setSortKey( (0,) ) - return result + def factory(self): + return ActivityThreadedDB InitializeClass(ActivityConnection) + + +class ActivityThreadedDB(ThreadedDB): + + _sort_key = (0,) diff --git a/product/CMFActivity/tests/testCMFActivity.py b/product/CMFActivity/tests/testCMFActivity.py index 7e50e73b18..fc3fd05862 100644 --- a/product/CMFActivity/tests/testCMFActivity.py +++ b/product/CMFActivity/tests/testCMFActivity.py @@ -3504,6 +3504,28 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor): newconn = portal.cmf_activity_sql_connection self.assertEquals(newconn.meta_type, 'CMFActivity Database Connection') + def test_connection_sortkey(self): + """ + Check that SQL connection has properly initialized sort key, + even when its container (ZODB connection) is reused by another thread. + """ + def sortKey(): + app = ZopeTestCase.app() + try: + c = app[self.getPortalName()].cmf_activity_sql_connection() + return app._p_jar, c._access_db('sortKey', (), {}) + finally: + ZopeTestCase.close(app) + jar, sort_key = sortKey() + self.assertNotEqual(1, sort_key) + result = [] + t = threading.Thread(target=lambda: result.extend(sortKey())) + t.daemon = True + t.start() + t.join() + self.assertTrue(result[0] is jar) + self.assertEqual(result[1], sort_key) + def test_onErrorCallback(self): activity_tool = self.portal.portal_activities obj = activity_tool.newActiveProcess() diff --git a/product/ZMySQLDA/db.py b/product/ZMySQLDA/db.py index b04c4715c3..b223bd38b4 100644 --- a/product/ZMySQLDA/db.py +++ b/product/ZMySQLDA/db.py @@ -184,6 +184,8 @@ class ThreadedDB: conv[FIELD_TYPE.BIT] = ord_or_None del conv[FIELD_TYPE.TIME] + _sort_key = TM._sort_key + def __init__(self,connection): """ Parse the connection string. @@ -290,6 +292,7 @@ class ThreadedDB: db = DB(kw_args=self._kw_args, use_TM=self._use_TM, mysql_lock=self._mysql_lock, transactions=self._transactions) + db.setSortKey(self._sort_key) self._pool_set(ident, db) return getattr(db, method_id)(*args, **kw) @@ -305,9 +308,6 @@ class ThreadedDB: def string_literal(self, *args, **kw): return self._access_db(method_id='string_literal', args=args, kw=kw) - def setSortKey(self, *args, **kw): - return self._access_db(method_id='setSortKey', args=args, kw=kw) - class DB(TM): -- 2.30.9