From a1d2f1f7b76d434072c35b55678ce8a3bbfb4649 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Fri, 16 Dec 2011 17:02:41 +0000 Subject: [PATCH] Ensured that thread-shareability gets validated when closing a PostgreSQL or SQLite connection. Refs #17258. git-svn-id: http://code.djangoproject.com/svn/django/trunk@17206 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/__init__.py | 2 +- .../db/backends/postgresql_psycopg2/base.py | 1 + django/db/backends/sqlite3/base.py | 1 + tests/regressiontests/backends/tests.py | 45 +++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 59c0992af5..8002a70f8b 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -130,7 +130,7 @@ class BaseDatabaseWrapper(object): if (not self.allow_thread_sharing and self._thread_ident != thread.get_ident()): raise DatabaseError("DatabaseWrapper objects created in a " - "thread can only be used in that same thread. The object" + "thread can only be used in that same thread. The object " "with alias '%s' was created in thread id %s and this is " "thread id %s." % (self.alias, self._thread_ident, thread.get_ident())) diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 2f020f46cf..9b25737ea0 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -129,6 +129,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.cursor().execute('SET CONSTRAINTS ALL DEFERRED') def close(self): + self.validate_thread_sharing() if self.connection is None: return diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 75e8fa027d..b5d38bb367 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -300,6 +300,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): referenced_table_name, referenced_column_name)) def close(self): + self.validate_thread_sharing() # If database is in memory, closing the connection destroys the # database. To prevent accidental data loss, ignore close requests on # an in-memory db. diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 82c21c8c7b..bda604e00b 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -487,6 +487,9 @@ class ThreadTests(TestCase): def runner(): from django.db import connections for conn in connections.all(): + # Allow thread sharing so the connection can be closed by the + # main thread. + conn.allow_thread_sharing = True connections_set.add(conn) for x in xrange(2): t = threading.Thread(target=runner) @@ -537,4 +540,46 @@ class ThreadTests(TestCase): exceptions = [] do_thread() # All good + self.assertEqual(len(exceptions), 0) + + def test_closing_non_shared_connections(self): + """ + Ensure that a connection that is not explicitly shareable cannot be + closed by another thread. + Refs #17258. + """ + # First, without explicitly enabling the connection for sharing. + exceptions = set() + def runner1(): + def runner2(other_thread_connection): + try: + other_thread_connection.close() + except DatabaseError, e: + exceptions.add(e) + t2 = threading.Thread(target=runner2, args=[connections['default']]) + t2.start() + t2.join() + t1 = threading.Thread(target=runner1) + t1.start() + t1.join() + # The exception was raised + self.assertEqual(len(exceptions), 1) + + # Then, with explicitly enabling the connection for sharing. + exceptions = set() + def runner1(): + def runner2(other_thread_connection): + try: + other_thread_connection.close() + except DatabaseError, e: + exceptions.add(e) + # Enable thread sharing + connections['default'].allow_thread_sharing = True + t2 = threading.Thread(target=runner2, args=[connections['default']]) + t2.start() + t2.join() + t1 = threading.Thread(target=runner1) + t1.start() + t1.join() + # No exception was raised self.assertEqual(len(exceptions), 0) \ No newline at end of file