Loading django/db/backends/__init__.py +8 −1 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ class BaseDatabaseWrapper(object): # Connection termination related attributes self.close_at = None self.closed_in_transaction = False self.errors_occurred = False # Thread-safety related attributes Loading Loading @@ -103,9 +104,11 @@ class BaseDatabaseWrapper(object): # In case the previous connection was closed while in an atomic block self.in_atomic_block = False self.savepoint_ids = [] self.needs_rollback = False # Reset parameters defining when to close the connection max_age = self.settings_dict['CONN_MAX_AGE'] self.close_at = None if max_age is None else time.time() + max_age self.closed_in_transaction = False self.errors_occurred = False # Establish the connection conn_params = self.get_connection_params() Loading Loading @@ -188,6 +191,10 @@ class BaseDatabaseWrapper(object): try: self._close() finally: if self.in_atomic_block: self.closed_in_transaction = True self.needs_rollback = True else: self.connection = None self.set_clean() Loading django/db/backends/postgresql_psycopg2/base.py +1 −24 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ PostgreSQL database backend for Django. Requires psycopg 2: http://initd.org/projects/psycopg2 """ import logging import sys from django.db.backends import * Loading Loading @@ -31,8 +31,6 @@ psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString) psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString) logger = logging.getLogger('django.db.backends') def utc_tzinfo_factory(offset): if offset != 0: raise AssertionError("database connection isn't set to UTC") Loading Loading @@ -140,27 +138,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None return cursor def close(self): self.validate_thread_sharing() if self.connection is None: return try: self.connection.close() self.connection = None except Database.Error: # In some cases (database restart, network connection lost etc...) # the connection to the database is lost without giving Django a # notification. If we don't set self.connection to None, the error # will occur a every request. self.connection = None logger.warning('psycopg2 error while closing the connection.', exc_info=sys.exc_info() ) raise finally: self.set_clean() def _set_isolation_level(self, isolation_level): assert isolation_level in range(1, 5) # Use set_autocommit for level = 0 if self.psycopg2_version >= (2, 4, 2): Loading django/db/transaction.py +13 −3 Original line number Diff line number Diff line Loading @@ -290,7 +290,12 @@ class Atomic(object): connection.in_atomic_block = False try: if exc_type is None and not connection.needs_rollback: if connection.closed_in_transaction: # The database will perform a rollback by itself. # Wait until we exit the outermost block. pass elif exc_type is None and not connection.needs_rollback: if connection.in_atomic_block: # Release savepoint if there is one if sid is not None: Loading Loading @@ -330,12 +335,17 @@ class Atomic(object): finally: # Outermost block exit when autocommit was enabled. if not connection.in_atomic_block: if connection.features.autocommits_when_autocommit_is_off: if connection.closed_in_transaction: connection.connection = None elif connection.features.autocommits_when_autocommit_is_off: connection.autocommit = True else: connection.set_autocommit(True) # Outermost block exit when autocommit was disabled. elif not connection.savepoint_ids and not connection.commit_on_exit: if connection.closed_in_transaction: connection.connection = None else: connection.in_atomic_block = False def __call__(self, func): Loading tests/transactions/tests.py +15 −1 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ except ImportError: import time from django.db import (connection, transaction, DatabaseError, IntegrityError, OperationalError) DatabaseError, Error, IntegrityError, OperationalError) from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import IgnorePendingDeprecationWarningsMixin from django.utils import six Loading Loading @@ -359,6 +359,20 @@ class AtomicErrorsTests(TransactionTestCase): r2.save(force_update=True) self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Calculus") @skipUnlessDBFeature('test_db_allows_multiple_connections') def test_atomic_prevents_queries_in_broken_transaction_after_client_close(self): with transaction.atomic(): Reporter.objects.create(first_name="Archibald", last_name="Haddock") connection.close() # The connection is closed and the transaction is marked as # needing rollback. This will raise an InterfaceError on databases # that refuse to create cursors on closed connections (PostgreSQL) # and a TransactionManagementError on other databases. with self.assertRaises(Error): Reporter.objects.create(first_name="Cuthbert", last_name="Calculus") # The connection is usable again . self.assertEqual(Reporter.objects.count(), 0) @skipUnless(connection.vendor == 'mysql', "MySQL-specific behaviors") class AtomicMySQLTests(TransactionTestCase): Loading Loading
django/db/backends/__init__.py +8 −1 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ class BaseDatabaseWrapper(object): # Connection termination related attributes self.close_at = None self.closed_in_transaction = False self.errors_occurred = False # Thread-safety related attributes Loading Loading @@ -103,9 +104,11 @@ class BaseDatabaseWrapper(object): # In case the previous connection was closed while in an atomic block self.in_atomic_block = False self.savepoint_ids = [] self.needs_rollback = False # Reset parameters defining when to close the connection max_age = self.settings_dict['CONN_MAX_AGE'] self.close_at = None if max_age is None else time.time() + max_age self.closed_in_transaction = False self.errors_occurred = False # Establish the connection conn_params = self.get_connection_params() Loading Loading @@ -188,6 +191,10 @@ class BaseDatabaseWrapper(object): try: self._close() finally: if self.in_atomic_block: self.closed_in_transaction = True self.needs_rollback = True else: self.connection = None self.set_clean() Loading
django/db/backends/postgresql_psycopg2/base.py +1 −24 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ PostgreSQL database backend for Django. Requires psycopg 2: http://initd.org/projects/psycopg2 """ import logging import sys from django.db.backends import * Loading Loading @@ -31,8 +31,6 @@ psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString) psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString) logger = logging.getLogger('django.db.backends') def utc_tzinfo_factory(offset): if offset != 0: raise AssertionError("database connection isn't set to UTC") Loading Loading @@ -140,27 +138,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None return cursor def close(self): self.validate_thread_sharing() if self.connection is None: return try: self.connection.close() self.connection = None except Database.Error: # In some cases (database restart, network connection lost etc...) # the connection to the database is lost without giving Django a # notification. If we don't set self.connection to None, the error # will occur a every request. self.connection = None logger.warning('psycopg2 error while closing the connection.', exc_info=sys.exc_info() ) raise finally: self.set_clean() def _set_isolation_level(self, isolation_level): assert isolation_level in range(1, 5) # Use set_autocommit for level = 0 if self.psycopg2_version >= (2, 4, 2): Loading
django/db/transaction.py +13 −3 Original line number Diff line number Diff line Loading @@ -290,7 +290,12 @@ class Atomic(object): connection.in_atomic_block = False try: if exc_type is None and not connection.needs_rollback: if connection.closed_in_transaction: # The database will perform a rollback by itself. # Wait until we exit the outermost block. pass elif exc_type is None and not connection.needs_rollback: if connection.in_atomic_block: # Release savepoint if there is one if sid is not None: Loading Loading @@ -330,12 +335,17 @@ class Atomic(object): finally: # Outermost block exit when autocommit was enabled. if not connection.in_atomic_block: if connection.features.autocommits_when_autocommit_is_off: if connection.closed_in_transaction: connection.connection = None elif connection.features.autocommits_when_autocommit_is_off: connection.autocommit = True else: connection.set_autocommit(True) # Outermost block exit when autocommit was disabled. elif not connection.savepoint_ids and not connection.commit_on_exit: if connection.closed_in_transaction: connection.connection = None else: connection.in_atomic_block = False def __call__(self, func): Loading
tests/transactions/tests.py +15 −1 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ except ImportError: import time from django.db import (connection, transaction, DatabaseError, IntegrityError, OperationalError) DatabaseError, Error, IntegrityError, OperationalError) from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import IgnorePendingDeprecationWarningsMixin from django.utils import six Loading Loading @@ -359,6 +359,20 @@ class AtomicErrorsTests(TransactionTestCase): r2.save(force_update=True) self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Calculus") @skipUnlessDBFeature('test_db_allows_multiple_connections') def test_atomic_prevents_queries_in_broken_transaction_after_client_close(self): with transaction.atomic(): Reporter.objects.create(first_name="Archibald", last_name="Haddock") connection.close() # The connection is closed and the transaction is marked as # needing rollback. This will raise an InterfaceError on databases # that refuse to create cursors on closed connections (PostgreSQL) # and a TransactionManagementError on other databases. with self.assertRaises(Error): Reporter.objects.create(first_name="Cuthbert", last_name="Calculus") # The connection is usable again . self.assertEqual(Reporter.objects.count(), 0) @skipUnless(connection.vendor == 'mysql', "MySQL-specific behaviors") class AtomicMySQLTests(TransactionTestCase): Loading