Commits

Mike Bayer committed bcece3d

- Backported the fix for [ticket:2317] introduced
in 0.7.4, which ensures that the connection
is in a valid state before attempting to call
rollback()/prepare()/release() on savepoint
and two-phase transactions.

Comments (0)

Files changed (3)

      a tuple is inadvertently passed to session.query()
      [ticket:2297].
 
+- engine
+  - Backported the fix for [ticket:2317] introduced
+    in 0.7.4, which ensures that the connection
+    is in a valid state before attempting to call
+    rollback()/prepare()/release() on savepoint
+    and two-phase transactions.
+
 - sql
   - Fixed two subtle bugs involving column 
     correspondence in a selectable,

lib/sqlalchemy/engine/base.py

         return getattr(self.__connection, 'is_valid', False)
 
     @property
+    def _still_open_and_connection_is_valid(self):
+        return \
+            not self.closed and \
+            not self.invalidated and \
+            getattr(self.__connection, 'is_valid', False)
+
+    @property
     def info(self):
         """A collection of per-DB-API connection instance properties."""
 
             raise
 
     def _rollback_impl(self):
-        if not self.closed and not self.invalidated and \
-                        self._connection_is_valid:
+        if self._still_open_and_connection_is_valid:
             if self._echo:
                 self.engine.logger.info("ROLLBACK")
             try:
             return name
 
     def _rollback_to_savepoint_impl(self, name, context):
-        if self._connection_is_valid:
+        if self._still_open_and_connection_is_valid:
             self.engine.dialect.do_rollback_to_savepoint(self, name)
         self.__transaction = context
 
     def _release_savepoint_impl(self, name, context):
-        if self._connection_is_valid:
+        if self._still_open_and_connection_is_valid:
             self.engine.dialect.do_release_savepoint(self, name)
         self.__transaction = context
 
     def _begin_twophase_impl(self, xid):
-        if self._connection_is_valid:
+        if self._still_open_and_connection_is_valid:
             self.engine.dialect.do_begin_twophase(self, xid)
 
     def _prepare_twophase_impl(self, xid):
-        if self._connection_is_valid:
+        if self._still_open_and_connection_is_valid:
             assert isinstance(self.__transaction, TwoPhaseTransaction)
             self.engine.dialect.do_prepare_twophase(self, xid)
 
     def _rollback_twophase_impl(self, xid, is_prepared):
-        if self._connection_is_valid:
+        if self._still_open_and_connection_is_valid:
             assert isinstance(self.__transaction, TwoPhaseTransaction)
             self.engine.dialect.do_rollback_twophase(self, xid, is_prepared)
         self.__transaction = None
 
     def _commit_twophase_impl(self, xid, is_prepared):
-        if self._connection_is_valid:
+        if self._still_open_and_connection_is_valid:
             assert isinstance(self.__transaction, TwoPhaseTransaction)
             self.engine.dialect.do_commit_twophase(self, xid, is_prepared)
         self.__transaction = None
                 context.handle_dbapi_exception(e)
 
             is_disconnect = self.dialect.is_disconnect(e)
+
             if is_disconnect:
                 self.invalidate(e)
                 self.engine.dispose()

test/engine/test_reconnect.py

 
         conn.close()
 
+    def test_rollback_on_invalid_plain(self):
+        conn = engine.connect()
+        trans = conn.begin()
+        conn.invalidate()
+        trans.rollback()
+
+    @testing.requires.two_phase_transactions
+    def test_rollback_on_invalid_twophase(self):
+        conn = engine.connect()
+        trans = conn.begin_twophase()
+        conn.invalidate()
+        trans.rollback()
+
+    @testing.requires.savepoints
+    def test_rollback_on_invalid_savepoint(self):
+        conn = engine.connect()
+        trans = conn.begin()
+        trans2 = conn.begin_nested()
+        conn.invalidate()
+        trans2.rollback()
+
     def test_invalidate_twice(self):
         conn = engine.connect()
         conn.invalidate()