[Checkins] SVN: zope.sqlalchemy/trunk/ Remove the session id from the SESSION_STATE just before we de-reference the

Brian Sutherland jinty at web.de
Wed Sep 17 06:31:55 EDT 2008


Log message for revision 91205:
  Remove the session id from the SESSION_STATE just before we de-reference the
  session (i.e. all work is already successfuly completed). This fixes cases
  where the transaction commit failed but SESSION_STATE was already cleared.  In
  those cases, the transaction was wedeged as abort would always error.  This
  happened on PostgreSQL where invalid SQL was used and the error caught.
  

Changed:
  U   zope.sqlalchemy/trunk/CHANGES.txt
  U   zope.sqlalchemy/trunk/src/zope/sqlalchemy/datamanager.py
  U   zope.sqlalchemy/trunk/src/zope/sqlalchemy/tests.py

-=-
Modified: zope.sqlalchemy/trunk/CHANGES.txt
===================================================================
--- zope.sqlalchemy/trunk/CHANGES.txt	2008-09-17 06:41:09 UTC (rev 91204)
+++ zope.sqlalchemy/trunk/CHANGES.txt	2008-09-17 10:31:54 UTC (rev 91205)
@@ -7,6 +7,11 @@
 Bugs fixed:
 
 * Only raise errors in tpc_abort if we have committed.
+* Remove the session id from the SESSION_STATE just before we de-reference the
+  session (i.e. all work is already successfuly completed). This fixes cases
+  where the transaction commit failed but SESSION_STATE was already cleared.  In
+  those cases, the transaction was wedeged as abort would always error.  This
+  happened on PostgreSQL where invalid SQL was used and the error caught.
 
 0.3 (2008-07-29)
 ----------------

Modified: zope.sqlalchemy/trunk/src/zope/sqlalchemy/datamanager.py
===================================================================
--- zope.sqlalchemy/trunk/src/zope/sqlalchemy/datamanager.py	2008-09-17 06:41:09 UTC (rev 91204)
+++ zope.sqlalchemy/trunk/src/zope/sqlalchemy/datamanager.py	2008-09-17 10:31:54 UTC (rev 91205)
@@ -49,31 +49,32 @@
         _SESSION_STATE[id(session)] = status
         self.state = 'init'
 
+    def _finish(self, final_state):
+        assert self.session is not None
+        del _SESSION_STATE[id(self.session)]
+        self.tx = self.session = None
+        self.state = final_state
+
     def abort(self, trans):
         if self.tx is not None: # there may have been no work to do
-            del _SESSION_STATE[id(self.session)]
             self.session.close()
-            self.tx = self.session = None
-            self.state = 'aborted'
+            self._finish('aborted')
 
     def tpc_begin(self, trans):
         self.session._autoflush()
     
     def commit(self, trans):
         status = _SESSION_STATE[id(self.session)]
-        del _SESSION_STATE[id(self.session)]
         if status is not STATUS_INVALIDATED:
             self.session.close()
-            self.tx = self.session = None
-            self.state = 'no work'
+            self._finish('no work')
 
     def tpc_vote(self, trans):
         # for a one phase data manager commit last in tpc_vote
         if self.tx is not None: # there may have been no work to do
             self.tx.commit()
             self.session.close()
-            self.tx = self.session = None
-            self.state = 'committed'
+            self._finish('committed')
                 
     def tpc_finish(self, trans):
         pass
@@ -114,15 +115,13 @@
         if self.tx is not None:
             self.tx.commit()
             self.session.close()
-            self.tx = self.session = None
-            self.state = 'committed'
+            self._finish('committed')
 
     def tpc_abort(self, trans):
         if self.tx is not None: # we may not have voted, and been aborted already
             self.tx.rollback()
             self.session.close()
-            self.tx = self.session = None
-            self.state = 'aborted commit'
+            self._finish('aborted commit')
 
     def sortKey(self):
         # Sort normally

Modified: zope.sqlalchemy/trunk/src/zope/sqlalchemy/tests.py
===================================================================
--- zope.sqlalchemy/trunk/src/zope/sqlalchemy/tests.py	2008-09-17 06:41:09 UTC (rev 91204)
+++ zope.sqlalchemy/trunk/src/zope/sqlalchemy/tests.py	2008-09-17 10:31:54 UTC (rev 91205)
@@ -32,6 +32,7 @@
 import sqlalchemy as sa
 from sqlalchemy import orm, sql
 from zope.sqlalchemy import datamanager as tx
+from zope.sqlalchemy import mark_changed
 
 TEST_TWOPHASE = bool(os.environ.get('TEST_TWOPHASE'))
 TEST_DSN = os.environ.get('TEST_DSN', 'sqlite:///:memory:')
@@ -169,6 +170,32 @@
         transaction.abort()
         metadata.drop_all(engine)
 
+    def testAbortAfterCommit(self):
+        # This is a regression test which used to wedge the transaction
+        # machinery when using PostgreSQL (and perhaps other) connections.
+        # Basically, if a commit failed, there was no way to abort the
+        # transaction. Leaving the transaction wedged.
+        transaction.begin()
+        session = Session()
+        conn = session.connection()
+        # At least PostgresSQL requires a rollback after invalid SQL is executed
+        self.assertRaises(Exception, conn.execute, "BAD SQL SYNTAX")
+        mark_changed(session)
+        try:
+            # Thus we could fail in commit
+            transaction.commit()
+        except:
+            # But abort must succed (and actually rollback the base connection)
+            transaction.abort()
+            pass
+        # Or the next transaction the next transaction will not be able to start!
+        transaction.begin()
+        session = Session()
+        conn = session.connection()
+        conn.execute("SELECT 1")
+        mark_changed(session)
+        transaction.commit()
+
     def testSimplePopulation(self):
         session = Session()
         query = session.query(User)



More information about the Checkins mailing list