Skip to content

Commit

Permalink
Better ThreaedDict implementation using threadlocal (tx Ben Hoyt)
Browse files Browse the repository at this point in the history
Using threading.local instead of managing thread-local state manually
improved the performance by 50x. Also added a threadlocal implementation
for python 2.3 as it doesn't have threading.local.
  • Loading branch information
anandology committed Feb 16, 2011
1 parent 895ef7f commit 9e927c4
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 33 deletions.
12 changes: 3 additions & 9 deletions web/application.py
Expand Up @@ -107,15 +107,9 @@ def _unload(self):
web.ctx.fullpath = oldctx.fullpath

def _cleanup(self):
#@@@
# Since the CherryPy Webserver uses thread pool, the thread-local state is never cleared.
# This interferes with the other requests.
# clearing the thread-local storage to avoid that.
# see utils.ThreadedDict for details
import threading
t = threading.currentThread()
if hasattr(t, '_d'):
del t._d
# Threads can be recycled by WSGI servers.
# Clearing up all thread-local state to avoid interefereing with subsequent requests.
utils.ThreadedDict.clear_all()

def add_mapping(self, pattern, classname):
self.mapping += (pattern, classname)
Expand Down
46 changes: 46 additions & 0 deletions web/python23.py
@@ -0,0 +1,46 @@
"""Python 2.3 compatabilty"""
import threading

class threadlocal(object):
"""Implementation of threading.local for python2.3.
"""
def __getattribute__(self, name):
if name == "__dict__":
return threadlocal._getd(self)
elif name.startswith("__"):
# for handling special atrributes like __class__ etc.
return object.__getattribute__(self, name)
else:
try:
return self.__dict__[name]
except KeyError:
raise AttributeError, name

def __setattr__(self, name, value):
self.__dict__[name] = value

def __delattr__(self, name):
try:
del self.__dict__[name]
except KeyError:
raise AttributeError, name

def _getd(self):
t = threading.currentThread()
if not hasattr(t, '_d'):
# using __dict__ of thread as thread local storage
t._d = {}

_id = id(self)
# there could be multiple instances of threadlocal.
# use id(self) as key
if _id not in t._d:
t._d[_id] = {}
return t._d[_id]

if __name__ == '__main__':
d = threadlocal()
d.x = 1
print d.__dict__
print d.x

11 changes: 6 additions & 5 deletions web/session.py
Expand Up @@ -41,12 +41,13 @@ def __init__(self, message):
class Session(utils.ThreadedDict):
"""Session management for web.py
"""
__slots__ = ["store", "_initializer", "_last_cleanup_time", "_config"]

def __init__(self, app, store, initializer=None):
self.__dict__['store'] = store
self.__dict__['_initializer'] = initializer
self.__dict__['_last_cleanup_time'] = 0
self.__dict__['_config'] = utils.storage(web.config.session_parameters)
self.store = store
self._initializer = initializer
self._last_cleanup_time = 0
self._config = utils.storage(web.config.session_parameters)

if app:
app.add_processor(self._processor)
Expand Down Expand Up @@ -137,7 +138,7 @@ def _cleanup(self):
timeout = self._config.timeout
if current_time - self._last_cleanup_time > timeout:
self.store.cleanup(timeout)
self.__dict__['_last_cleanup_time'] = current_time
self._last_cleanup_time = current_time

def expired(self):
"""Called when an expired session is atime"""
Expand Down
100 changes: 81 additions & 19 deletions web/utils.py
Expand Up @@ -43,6 +43,11 @@
try: set
except NameError:
from sets import Set as set

try:
from threading import local as threadlocal
except ImportError:
from python23 import threadlocal

class Storage(dict):
"""
Expand Down Expand Up @@ -1153,7 +1158,7 @@ def tryall(context, prefix=None):
for (key, value) in results.iteritems():
print ' '*2, str(key)+':', value

class ThreadedDict:
class ThreadedDict(threadlocal):
"""
Thread local storage.
Expand All @@ -1170,30 +1175,87 @@ class ThreadedDict:
>>> d.x
1
"""
def __getattr__(self, key):
return getattr(self._getd(), key)
_instances = set()

def __init__(self):
ThreadedDict._instances.add(self)

def __del__(self):
ThreadedDict._instances.remove(self)

def __hash__(self):
return id(self)

def clear_all():
"""Clears all ThreadedDict instances.
"""
for t in ThreadedDict._instances:
t.clear()
clear_all = staticmethod(clear_all)

# Define all these methods to more or less fully emulate dict -- attribute access
# is built into threading.local.

def __setattr__(self, key, value):
return setattr(self._getd(), key, value)
def __getitem__(self, key):
return self.__dict__[key]

def __delattr__(self, key):
return delattr(self._getd(), key)
def __setitem__(self, key, value):
self.__dict__[key] = value

def __hash__(self):
return id(self)
def __delitem__(self, key):
del self.__dict__[key]

def __contains__(self, key):
return key in self.__dict__

has_key = __contains__

def clear(self):
self.__dict__.clear()

def _getd(self):
t = threading.currentThread()
if not hasattr(t, '_d'):
# using __dict__ of thread as thread local storage
t._d = {}
def copy(self):
return self.__dict__.copy()

# there could be multiple instances of ThreadedDict.
# use self as key
if self not in t._d:
t._d[self] = storage()
return t._d[self]
def get(self, key, default=None):
return self.__dict__.get(key, default)

def items(self):
return self.__dict__.items()

def iteritems(self):
return self.__dict__.iteritems()

def keys(self):
return self.__dict__.keys()

def iterkeys(self):
return self.__dict__.iterkeys()

iter = iterkeys

def values(self):
return self.__dict__.values()

def itervalues(self):
return self.__dict__.itervalues()

def pop(self, key, *args):
return self.__dict__.pop(key, *args)

def popitem(self):
return self.__dict__.popitem()

def setdefault(self, key, default=None):
return self.__dict__.setdefault(key, default)

def update(self, *args, **kwargs):
self.__dict__.update(*args, **kwargs)

def __repr__(self):
return '<ThreadedDict %r>' % self.__dict__

__str__ = __repr__

threadeddict = ThreadedDict

def autoassign(self, locals):
Expand Down

2 comments on commit 9e927c4

@ashokrkm
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you know if this fixes/causes any memory leak ?

@anandology
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there are any memory leaks involved in ThreadedDict implementation. Were you facing any memory leaks in your webpy app?

Please sign in to comment.