Skip to content

Commit 4a52ac3

Browse files
committed
Use our own Python implementation of thread-local data even if a faster implementation is available, since the C implementation does not work with mod_wsgi, but added a parameter to make this configurable.
1 parent 52e5910 commit 4a52ac3

10 files changed

Lines changed: 262 additions & 173 deletions

DBUtils/Docs/RelNotes-1.0.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ <h2>Changes:</h2>
2424
<li>Added a <tt>version</tt> attribute to all exported classes.</li>
2525
<li>Where <tt>0</tt> has the meaning "unlimited", parameters can now be also
2626
set to <tt>None</tt> instead.</li>
27+
<li>It turned out that <tt>threading.local</tt> does not work properly with
28+
<tt>mod_wsgi</tt>, so we use the Python implementation for thread-local data
29+
even when a faster <tt>threading.local</tt> implementation is available.
30+
A new parameter <tt>threadlocal</tt> allows you to pass an arbitrary class
31+
such as <tt>threading.local</tt> if you know it works in your environment.</li>
2732
</ul>
2833

2934
<h2>Bugfixes and Improvements:</h2>

DBUtils/Docs/UsersGuide.de.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@ Parameter angeben müssen:
278278
* ``closeable``: wenn dies auf ``True`` gesetzt wird, dann wird das Schließen
279279
von Verbindungen erlaubt, normalerweise wird es jedoch ignoriert
280280

281+
* ``threadlocal``: eine optionale Klasse zur Speicherung thread-lokaler Daten,
282+
die anstelle unserer Python-Implementierung benutzt wird (threading.local
283+
ist schneller, kann aber nicht in allen Fällen verwendet werden)
284+
281285
* Die als ``creator`` angegebene Funktion oder die Funktion ``connect``
282286
des DB-API-2-Datenbankadapter-Moduls erhalten alle weiteren Parameter,
283287
wie ``host``, ``database``, ``user``, ``password`` usw. Sie können einige
@@ -308,6 +312,11 @@ Stattdessen wird die Verbindung automatisch dann geschlossen, wenn der Thread
308312
endet. Sie können dieses Verhalten ändern, indem Sie den Parameter namens
309313
``closeable`` setzen.
310314

315+
Beachten Sie, dass das Holen einer Verbindung etwas beschleunigt werden kann,
316+
indem Sie den Parameter ``threadlocal`` auf ``threading.local`` setzen; dies
317+
könnte aber in einigen Umgebungen nicht funktionieren (es ist zum Beispiel
318+
bekannt, dass ``mod_wsgi`` hier Probleme bereitet, da es Daten, die mit ``threading.local`` gespeichert wurden, zwischen Requests löscht).
319+
311320
PooledDB
312321
--------
313322
Wenn Sie das ``PooledDB``-Modul einsetzen möchten, müssen Sie zuerst einen

DBUtils/Docs/UsersGuide.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ of ``PersistentDB``, passing the following parameters:
257257
* ``closeable``: if this is set to true, then closing connections will
258258
be allowed, but by default this will be silently ignored
259259

260+
* ``threadlocal``: an optional class for representing thread-local data
261+
that will be used instead of our Python implementation
262+
(threading.local is faster, but cannot be used in all cases)
263+
260264
* The creator function or the connect function of the DB-API 2 compliant
261265
database module specified as the creator will receive any additional
262266
parameters such as the host, database, user, password etc. You may
@@ -285,6 +289,11 @@ contrary to the intent of having persistent connections. Instead,
285289
the connection will be automatically closed when the thread dies.
286290
You can change this behavior be setting the ``closeable`` parameter.
287291

292+
Note that by setting the ``threadlocal`` parameter to ``threading.local``,
293+
getting connections may become a bit faster, but this may not work in
294+
all environments (for instance, ``mod_wsgi`` is known to cause problems
295+
here since it clears the ``threading.local`` data between requests).
296+
288297
PooledDB
289298
--------
290299
In order to make use of the ``PooledDB`` module, you first need to set up the

DBUtils/PersistentDB.py

Lines changed: 16 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
if the default (OperationalError, InternalError) is not adequate
4242
closeable: if this is set to true, then closing connections will
4343
be allowed, but by default this will be silently ignored
44+
threadlocal: an optional class for representing thread-local data
45+
that will be used instead of our Python implementation
46+
(threading.local is faster, but cannot be used in all cases)
4447
4548
The creator function or the connect function of the DB-API 2 compliant
4649
database module specified as the creator will receive any additional
@@ -70,10 +73,15 @@
7073
the connection will be automatically closed when the thread dies.
7174
You can change this behavior be setting the closeable parameter.
7275
76+
Note that by setting the threadlocal parameter to threading.local,
77+
getting connections may become a bit faster, but this may not work in
78+
all environments (for instance, mod_wsgi is known to cause problems
79+
since it clears the threading.local data between requests).
80+
7381
7482
Requirements:
7583
76-
Minimum requirement: Python 2.2. Recommended: Python 2.4.3.
84+
Python >= 2.2, < 3.0.
7785
7886
7987
Ideas for improvement:
@@ -99,6 +107,7 @@
99107
__date__ = "$Date$"
100108

101109

110+
import ThreadingLocal
102111
from DBUtils.SteadyDB import connect
103112

104113

@@ -120,8 +129,8 @@ class PersistentDB:
120129
version = __version__
121130

122131
def __init__(self, creator,
123-
maxusage=None, setsession=None, failures=None, closeable=False,
124-
*args, **kwargs):
132+
maxusage=None, setsession=None, failures=None,
133+
closeable=False, threadlocal=None, *args, **kwargs):
125134
"""Set up the persistent DB-API 2 connection generator.
126135
127136
creator: either an arbitrary function returning new DB-API 2
@@ -136,6 +145,9 @@ def __init__(self, creator,
136145
if the default (OperationalError, InternalError) is not adequate
137146
closeable: if this is set to true, then closing connections will
138147
be allowed, but by default this will be silently ignored
148+
threadlocal: an optional class for representing thread-local data
149+
that will be used instead of our Python implementation
150+
(threading.local is faster, but cannot be used in all cases)
139151
args, kwargs: the parameters that shall be passed to the creator
140152
function or the connection constructor of the DB-API 2 module
141153
@@ -158,7 +170,7 @@ def __init__(self, creator,
158170
self._failures = failures
159171
self._closeable = closeable
160172
self._args, self._kwargs = args, kwargs
161-
self.thread = local()
173+
self.thread = (threadlocal or ThreadingLocal.local)()
162174

163175
def steady_connection(self):
164176
"""Get a steady, non-persistent DB-API 2 connection."""
@@ -187,81 +199,3 @@ def connection(self, shareable=False):
187199
def dedicated_connection(self):
188200
"""Alias for connection(shareable=False)."""
189201
return self.connection()
190-
191-
192-
try: # import a class for representing thread-local objects
193-
from threading import local
194-
except ImportError: # for Python < 2.4, use the following simple implementation
195-
from threading import currentThread, enumerate, RLock
196-
class _localbase(object):
197-
__slots__ = '_local__key', '_local__args', '_local__lock'
198-
def __new__(cls, *args, **kwargs):
199-
self = object.__new__(cls)
200-
key = '_local__key', 'thread.local.' + str(id(self))
201-
object.__setattr__(self, '_local__key', key)
202-
object.__setattr__(self, '_local__args', (args, kwargs))
203-
object.__setattr__(self, '_local__lock', RLock())
204-
if args or kwargs and (cls.__init__ is object.__init__):
205-
raise TypeError("Initialization arguments are not supported")
206-
d = object.__getattribute__(self, '__dict__')
207-
currentThread().__dict__[key] = d
208-
return self
209-
def _patch(self):
210-
key = object.__getattribute__(self, '_local__key')
211-
d = currentThread().__dict__.get(key)
212-
if d is None:
213-
d = {}
214-
currentThread().__dict__[key] = d
215-
object.__setattr__(self, '__dict__', d)
216-
cls = type(self)
217-
if cls.__init__ is not object.__init__:
218-
args, kwargs = object.__getattribute__(self, '_local__args')
219-
cls.__init__(self, *args, **kwargs)
220-
else:
221-
object.__setattr__(self, '__dict__', d)
222-
class local(_localbase):
223-
def __getattribute__(self, name):
224-
lock = object.__getattribute__(self, '_local__lock')
225-
lock.acquire()
226-
try:
227-
_patch(self)
228-
return object.__getattribute__(self, name)
229-
finally:
230-
lock.release()
231-
def __setattr__(self, name, value):
232-
lock = object.__getattribute__(self, '_local__lock')
233-
lock.acquire()
234-
try:
235-
_patch(self)
236-
return object.__setattr__(self, name, value)
237-
finally:
238-
lock.release()
239-
def __delattr__(self, name):
240-
lock = object.__getattribute__(self, '_local__lock')
241-
lock.acquire()
242-
try:
243-
_patch(self)
244-
return object.__delattr__(self, name)
245-
finally:
246-
lock.release()
247-
def __del__():
248-
threading_enumerate = enumerate
249-
__getattribute__ = object.__getattribute__
250-
def __del__(self):
251-
try:
252-
key = __getattribute__(self, '_local__key')
253-
threads = list(threading_enumerate())
254-
except Exception:
255-
return
256-
for thread in threads:
257-
try:
258-
__dict__ = thread.__dict__
259-
except AttributeError:
260-
continue
261-
if key in __dict__:
262-
try:
263-
del __dict__[key]
264-
except KeyError:
265-
pass
266-
return __del__
267-
__del__ = __del__()

DBUtils/PersistentPg.py

Lines changed: 16 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
prepare the session, e.g. ["set datestyle to german", ...]
4040
closeable: if this is set to true, then closing connections will
4141
be allowed, but by default this will be silently ignored
42+
threadlocal: an optional class for representing thread-local data
43+
that will be used instead of our Python implementation
44+
(threading.local is faster, but cannot be used in all cases)
4245
4346
Additionally, you have to pass the parameters for the actual
4447
PostgreSQL connection which are passed via PyGreSQL,
@@ -65,11 +68,15 @@
6568
the connection will be automatically closed when the thread dies.
6669
You can change this behavior be setting the closeable parameter.
6770
71+
Note that by setting the threadlocal parameter to threading.local,
72+
getting connections may become a bit faster, but this may not work in
73+
all environments (for instance, mod_wsgi is known to cause problems
74+
since it clears the threading.local data between requests).
75+
6876
6977
Requirements:
7078
71-
Minimum requirement: Python 2.2 and PyGreSQL 3.4.
72-
Recommended: Python 2.4.3 and PyGreSQL 3.8.
79+
Python >= 2.2, < 3.0, PyGreSQL >= 3.4.
7380
7481
7582
Ideas for improvement:
@@ -95,6 +102,7 @@
95102
__date__ = "$Date$"
96103

97104

105+
import ThreadingLocal
98106
from DBUtils.SteadyPg import SteadyPgConnection
99107

100108

@@ -108,8 +116,8 @@ class PersistentPg:
108116

109117
version = __version__
110118

111-
def __init__(self, maxusage=None, setsession=None, closeable=False,
112-
*args, **kwargs):
119+
def __init__(self, maxusage=None, setsession=None,
120+
closeable=False, threadlocal=None, *args, **kwargs):
113121
"""Set up the persistent PostgreSQL connection generator.
114122
115123
maxusage: maximum number of reuses of a single connection
@@ -120,6 +128,9 @@ def __init__(self, maxusage=None, setsession=None, closeable=False,
120128
the session, e.g. ["set datestyle to ...", "set time zone ..."]
121129
closeable: if this is set to true, then closing connections will
122130
be allowed, but by default this will be silently ignored
131+
threadlocal: an optional class for representing thread-local data
132+
that will be used instead of our Python implementation
133+
(threading.local is faster, but cannot be used in all cases)
123134
args, kwargs: the parameters that shall be used to establish
124135
the PostgreSQL connections using class PyGreSQL pg.DB()
125136
@@ -128,7 +139,7 @@ def __init__(self, maxusage=None, setsession=None, closeable=False,
128139
self._setsession = setsession
129140
self._closeable = closeable
130141
self._args, self._kwargs = args, kwargs
131-
self.thread = local()
142+
self.thread = (threadlocal or ThreadingLocal.local)()
132143

133144
def steady_connection(self):
134145
"""Get a steady, non-persistent PyGreSQL connection."""
@@ -144,81 +155,3 @@ def connection(self):
144155
con = self.steady_connection()
145156
self.thread.connection = con
146157
return con
147-
148-
149-
try: # import a class for representing thread-local objects
150-
from threading import local
151-
except ImportError: # for Python < 2.4, use the following simple implementation
152-
from threading import currentThread, enumerate, RLock
153-
class _localbase(object):
154-
__slots__ = '_local__key', '_local__args', '_local__lock'
155-
def __new__(cls, *args, **kwargs):
156-
self = object.__new__(cls)
157-
key = '_local__key', 'thread.local.' + str(id(self))
158-
object.__setattr__(self, '_local__key', key)
159-
object.__setattr__(self, '_local__args', (args, kwargs))
160-
object.__setattr__(self, '_local__lock', RLock())
161-
if args or kwargs and (cls.__init__ is object.__init__):
162-
raise TypeError("Initialization arguments are not supported")
163-
d = object.__getattribute__(self, '__dict__')
164-
currentThread().__dict__[key] = d
165-
return self
166-
def _patch(self):
167-
key = object.__getattribute__(self, '_local__key')
168-
d = currentThread().__dict__.get(key)
169-
if d is None:
170-
d = {}
171-
currentThread().__dict__[key] = d
172-
object.__setattr__(self, '__dict__', d)
173-
cls = type(self)
174-
if cls.__init__ is not object.__init__:
175-
args, kwargs = object.__getattribute__(self, '_local__args')
176-
cls.__init__(self, *args, **kwargs)
177-
else:
178-
object.__setattr__(self, '__dict__', d)
179-
class local(_localbase):
180-
def __getattribute__(self, name):
181-
lock = object.__getattribute__(self, '_local__lock')
182-
lock.acquire()
183-
try:
184-
_patch(self)
185-
return object.__getattribute__(self, name)
186-
finally:
187-
lock.release()
188-
def __setattr__(self, name, value):
189-
lock = object.__getattribute__(self, '_local__lock')
190-
lock.acquire()
191-
try:
192-
_patch(self)
193-
return object.__setattr__(self, name, value)
194-
finally:
195-
lock.release()
196-
def __delattr__(self, name):
197-
lock = object.__getattribute__(self, '_local__lock')
198-
lock.acquire()
199-
try:
200-
_patch(self)
201-
return object.__delattr__(self, name)
202-
finally:
203-
lock.release()
204-
def __del__():
205-
threading_enumerate = enumerate
206-
__getattribute__ = object.__getattribute__
207-
def __del__(self):
208-
try:
209-
key = __getattribute__(self, '_local__key')
210-
threads = list(threading_enumerate())
211-
except Exception:
212-
return
213-
for thread in threads:
214-
try:
215-
__dict__ = thread.__dict__
216-
except AttributeError:
217-
continue
218-
if key in __dict__:
219-
try:
220-
del __dict__[key]
221-
except KeyError:
222-
pass
223-
return __del__
224-
__del__ = __del__()

DBUtils/PooledDB.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,10 @@ class PooledDB:
155155
version = __version__
156156

157157
def __init__(self, creator,
158-
mincached=0, maxcached=0,
159-
maxshared=0, maxconnections=0, blocking=False,
160-
maxusage=None, setsession=None, failures=None,
161-
*args, **kwargs):
158+
mincached=0, maxcached=0,
159+
maxshared=0, maxconnections=0, blocking=False,
160+
maxusage=None, setsession=None, failures=None,
161+
*args, **kwargs):
162162
"""Set up the DB-API 2 connection pool.
163163
164164
creator: either an arbitrary function returning new DB-API 2

DBUtils/PooledPg.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,10 @@ class PooledPg:
129129
version = __version__
130130

131131
def __init__(self,
132-
mincached=0, maxcached=0,
133-
maxconnections=0, blocking=False,
134-
maxusage=None, setsession=None,
135-
*args, **kwargs):
132+
mincached=0, maxcached=0,
133+
maxconnections=0, blocking=False,
134+
maxusage=None, setsession=None,
135+
*args, **kwargs):
136136
"""Set up the PostgreSQL connection pool.
137137
138138
mincached: initial number of connections in the pool

0 commit comments

Comments
 (0)