Commits

Anonymous committed d0f5d48

Ensured that thread-shareability gets validated when closing a PostgreSQL or SQLite connection. Refs #17258.

Comments (0)

Files changed (4)

django/db/backends/__init__.py

         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()))

django/db/backends/postgresql_psycopg2/base.py

         self.cursor().execute('SET CONSTRAINTS ALL DEFERRED')
 
     def close(self):
+        self.validate_thread_sharing()
         if self.connection is None:
             return
 

django/db/backends/sqlite3/base.py

                         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.

tests/regressiontests/backends/tests.py

         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)
         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)