summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorJulien Phalip <jphalip@gmail.com>2011-12-16 13:40:19 +0000
committerJulien Phalip <jphalip@gmail.com>2011-12-16 13:40:19 +0000
commit34e248efeca272a2c0f2b15a5347dd66fc0340c9 (patch)
treefd073f3b8a6132b002375ff454665bba090458fd /django
parent5df31c0164e9477a3ebb6b1bbde8604e06fbefd4 (diff)
Fixed #17258 -- Moved `threading.local` from `DatabaseWrapper` to the `django.db.connections` dictionary. This allows connections to be explicitly shared between multiple threads and is particularly useful for enabling the sharing of in-memory SQLite connections. Many thanks to Anssi Kääriäinen for the excellent suggestions and feedback, and to Alex Gaynor for the reviews. Refs #2879.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17205 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
-rw-r--r--django/db/__init__.py16
-rw-r--r--django/db/backends/__init__.py33
-rw-r--r--django/db/backends/sqlite3/base.py17
-rw-r--r--django/db/utils.py12
4 files changed, 68 insertions, 10 deletions
diff --git a/django/db/__init__.py b/django/db/__init__.py
index 8395468b8c..26c7add0af 100644
--- a/django/db/__init__.py
+++ b/django/db/__init__.py
@@ -22,9 +22,21 @@ router = ConnectionRouter(settings.DATABASE_ROUTERS)
# we manually create the dictionary from the settings, passing only the
# settings that the database backends care about. Note that TIME_ZONE is used
# by the PostgreSQL backends.
-# we load all these up for backwards compatibility, you should use
+# We load all these up for backwards compatibility, you should use
# connections['default'] instead.
-connection = connections[DEFAULT_DB_ALIAS]
+class DefaultConnectionProxy(object):
+ """
+ Proxy for accessing the default DatabaseWrapper object's attributes. If you
+ need to access the DatabaseWrapper object itself, use
+ connections[DEFAULT_DB_ALIAS] instead.
+ """
+ def __getattr__(self, item):
+ return getattr(connections[DEFAULT_DB_ALIAS], item)
+
+ def __setattr__(self, name, value):
+ return setattr(connections[DEFAULT_DB_ALIAS], name, value)
+
+connection = DefaultConnectionProxy()
backend = load_backend(connection.settings_dict['ENGINE'])
# Register an event that closes the database connection
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index f2bde840d7..59c0992af5 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -1,8 +1,9 @@
+from django.db.utils import DatabaseError
+
try:
import thread
except ImportError:
import dummy_thread as thread
-from threading import local
from contextlib import contextmanager
from django.conf import settings
@@ -13,14 +14,15 @@ from django.utils.importlib import import_module
from django.utils.timezone import is_aware
-class BaseDatabaseWrapper(local):
+class BaseDatabaseWrapper(object):
"""
Represents a database connection.
"""
ops = None
vendor = 'unknown'
- def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS):
+ def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS,
+ allow_thread_sharing=False):
# `settings_dict` should be a dictionary containing keys such as
# NAME, USER, etc. It's called `settings_dict` instead of `settings`
# to disambiguate it from Django settings modules.
@@ -34,6 +36,8 @@ class BaseDatabaseWrapper(local):
self.transaction_state = []
self.savepoint_state = 0
self._dirty = None
+ self._thread_ident = thread.get_ident()
+ self.allow_thread_sharing = allow_thread_sharing
def __eq__(self, other):
return self.alias == other.alias
@@ -116,6 +120,21 @@ class BaseDatabaseWrapper(local):
"pending COMMIT/ROLLBACK")
self._dirty = False
+ def validate_thread_sharing(self):
+ """
+ Validates that the connection isn't accessed by another thread than the
+ one which originally created it, unless the connection was explicitly
+ authorized to be shared between threads (via the `allow_thread_sharing`
+ property). Raises an exception if the validation fails.
+ """
+ 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"
+ "with alias '%s' was created in thread id %s and this is "
+ "thread id %s."
+ % (self.alias, self._thread_ident, thread.get_ident()))
+
def is_dirty(self):
"""
Returns True if the current transaction requires a commit for changes to
@@ -179,6 +198,7 @@ class BaseDatabaseWrapper(local):
"""
Commits changes if the system is not in managed transaction mode.
"""
+ self.validate_thread_sharing()
if not self.is_managed():
self._commit()
self.clean_savepoints()
@@ -189,6 +209,7 @@ class BaseDatabaseWrapper(local):
"""
Rolls back changes if the system is not in managed transaction mode.
"""
+ self.validate_thread_sharing()
if not self.is_managed():
self._rollback()
else:
@@ -198,6 +219,7 @@ class BaseDatabaseWrapper(local):
"""
Does the commit itself and resets the dirty flag.
"""
+ self.validate_thread_sharing()
self._commit()
self.set_clean()
@@ -205,6 +227,7 @@ class BaseDatabaseWrapper(local):
"""
This function does the rollback itself and resets the dirty flag.
"""
+ self.validate_thread_sharing()
self._rollback()
self.set_clean()
@@ -228,6 +251,7 @@ class BaseDatabaseWrapper(local):
Rolls back the most recent savepoint (if one exists). Does nothing if
savepoints are not supported.
"""
+ self.validate_thread_sharing()
if self.savepoint_state:
self._savepoint_rollback(sid)
@@ -236,6 +260,7 @@ class BaseDatabaseWrapper(local):
Commits the most recent savepoint (if one exists). Does nothing if
savepoints are not supported.
"""
+ self.validate_thread_sharing()
if self.savepoint_state:
self._savepoint_commit(sid)
@@ -269,11 +294,13 @@ class BaseDatabaseWrapper(local):
pass
def close(self):
+ self.validate_thread_sharing()
if self.connection is not None:
self.connection.close()
self.connection = None
def cursor(self):
+ self.validate_thread_sharing()
if (self.use_debug_cursor or
(self.use_debug_cursor is None and settings.DEBUG)):
cursor = self.make_debug_cursor(self._cursor())
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index a610606635..75e8fa027d 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -7,10 +7,10 @@ standard library.
import datetime
import decimal
+import warnings
import re
import sys
-from django.conf import settings
from django.db import utils
from django.db.backends import *
from django.db.backends.signals import connection_created
@@ -241,6 +241,21 @@ class DatabaseWrapper(BaseDatabaseWrapper):
'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
}
kwargs.update(settings_dict['OPTIONS'])
+ # Always allow the underlying SQLite connection to be shareable
+ # between multiple threads. The safe-guarding will be handled at a
+ # higher level by the `BaseDatabaseWrapper.allow_thread_sharing`
+ # property. This is necessary as the shareability is disabled by
+ # default in pysqlite and it cannot be changed once a connection is
+ # opened.
+ if 'check_same_thread' in kwargs and kwargs['check_same_thread']:
+ warnings.warn(
+ 'The `check_same_thread` option was provided and set to '
+ 'True. It will be overriden with False. Use the '
+ '`DatabaseWrapper.allow_thread_sharing` property instead '
+ 'for controlling thread shareability.',
+ RuntimeWarning
+ )
+ kwargs.update({'check_same_thread': False})
self.connection = Database.connect(**kwargs)
# Register extract, date_trunc, and regexp functions.
self.connection.create_function("django_extract", 2, _sqlite_extract)
diff --git a/django/db/utils.py b/django/db/utils.py
index f0c13e3aac..41ad6df728 100644
--- a/django/db/utils.py
+++ b/django/db/utils.py
@@ -1,4 +1,5 @@
import os
+from threading import local
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
@@ -50,7 +51,7 @@ class ConnectionDoesNotExist(Exception):
class ConnectionHandler(object):
def __init__(self, databases):
self.databases = databases
- self._connections = {}
+ self._connections = local()
def ensure_defaults(self, alias):
"""
@@ -73,16 +74,19 @@ class ConnectionHandler(object):
conn.setdefault(setting, None)
def __getitem__(self, alias):
- if alias in self._connections:
- return self._connections[alias]
+ if hasattr(self._connections, alias):
+ return getattr(self._connections, alias)
self.ensure_defaults(alias)
db = self.databases[alias]
backend = load_backend(db['ENGINE'])
conn = backend.DatabaseWrapper(db, alias)
- self._connections[alias] = conn
+ setattr(self._connections, alias, conn)
return conn
+ def __setitem__(self, key, value):
+ setattr(self._connections, key, value)
+
def __iter__(self):
return iter(self.databases)