Author: Armin Rigo <ar...@tunes.org> Branch: stm-thread-2 Changeset: r61492:7239bb513111 Date: 2013-02-20 11:24 +0100 http://bitbucket.org/pypy/pypy/changeset/7239bb513111/
Log: Tweak thread-locals to change the approach, at least with STM: store the dict inside a weak-key-dictionary on the executioncontext. diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -37,10 +37,9 @@ self.profilefunc = None # if not None, no JIT self.w_profilefuncarg = None # - config = self.space.config - if config.translation.stm and config.objspace.std.withmethodcache: - from pypy.objspace.std.typeobject import MethodCache - self._methodcache = MethodCache(self.space) + if self.space.config.translation.stm: + from pypy.module.thread.stm import initialize_execution_context + initialize_execution_context(self) def gettopframe(self): return self.topframeref() diff --git a/pypy/module/thread/__init__.py b/pypy/module/thread/__init__.py --- a/pypy/module/thread/__init__.py +++ b/pypy/module/thread/__init__.py @@ -18,7 +18,6 @@ 'allocate_lock': 'os_lock.allocate_lock', 'allocate': 'os_lock.allocate_lock', # obsolete synonym 'LockType': 'os_lock.Lock', - '_local': 'os_local.Local', 'error': 'space.fromcache(error.Cache).w_error', } @@ -38,3 +37,12 @@ from pypy.module.posix.interp_posix import add_fork_hook from pypy.module.thread.os_thread import reinit_threads add_fork_hook('child', reinit_threads) + + def setup_after_space_initialization(self): + "NOT_RPYTHON" + if self.space.config.translation.stm: + self.extra_interpdef('_local', 'stm.STMLocal') + else: + self.extra_interpdef('_local', 'os_local.Local') + if not self.space.config.translating: + self.extra_interpdef('_untranslated_stmlocal', 'stm.STMLocal') diff --git a/pypy/module/thread/os_local.py b/pypy/module/thread/os_local.py --- a/pypy/module/thread/os_local.py +++ b/pypy/module/thread/os_local.py @@ -32,13 +32,11 @@ self.dicts[ec] = w_dict self._register_in_ec(ec) # cache the last seen dict, works because we are protected by the GIL - if self.can_cache(): - self.last_dict = w_dict - self.last_ec = ec - - def can_cache(self): - # can't cache with STM! The cache causes conflicts - return not self.space.config.translation.stm + self.last_dict = w_dict + self.last_ec = ec + # note that this class can't be used with STM! + # The cache causes conflicts. See STMLocal instead. + assert not self.space.config.translation.stm def _register_in_ec(self, ec): if not self.space.config.translation.rweakref: @@ -69,15 +67,14 @@ def getdict(self, space): ec = space.getexecutioncontext() - if self.can_cache() and ec is self.last_ec: + if ec is self.last_ec: return self.last_dict try: w_dict = self.dicts[ec] except KeyError: w_dict = self.create_new_dict(ec) - if self.can_cache(): - self.last_ec = ec - self.last_dict = w_dict + self.last_ec = ec + self.last_dict = w_dict return w_dict def descr_local__new__(space, w_subtype, __args__): diff --git a/pypy/module/thread/stm.py b/pypy/module/thread/stm.py --- a/pypy/module/thread/stm.py +++ b/pypy/module/thread/stm.py @@ -2,20 +2,32 @@ Software Transactional Memory emulation of the GIL. """ -from pypy.module.thread.threadlocals import OSThreadLocals +from pypy.module.thread.threadlocals import BaseThreadLocals from pypy.module.thread.error import wrap_thread_error from pypy.interpreter.executioncontext import ExecutionContext +from pypy.interpreter.gateway import Wrappable, W_Root, interp2app +from pypy.interpreter.typedef import TypeDef, GetSetProperty, descr_get_dict from rpython.rlib import rthread from rpython.rlib import rstm -from rpython.rlib.objectmodel import invoke_around_extcall +from rpython.rlib import rweakref +from rpython.rlib import jit +from rpython.rlib.objectmodel import invoke_around_extcall, we_are_translated ec_cache = rstm.ThreadLocalReference(ExecutionContext) +def initialize_execution_context(ec): + ec._thread_local_dicts = rweakref.RWeakKeyDictionary(STMLocal, W_Root) + if ec.space.config.objspace.std.withmethodcache: + from pypy.objspace.std.typeobject import MethodCache + ec._methodcache = MethodCache(ec.space) -class STMThreadLocals(OSThreadLocals): +def _fill_untranslated(ec): + if not we_are_translated() and not hasattr(ec, '_thread_local_dicts'): + initialize_execution_context(ec) - use_dict = False + +class STMThreadLocals(BaseThreadLocals): def initialize(self, space): """NOT_RPYTHON: set up a mechanism to send to the C code the value @@ -46,6 +58,9 @@ def getallvalues(self): raise ValueError + def leave_thread(self, space): + self.setvalue(None) + def setup_threads(self, space): self.threads_running = True self.configure_transaction_length(space) @@ -67,6 +82,8 @@ interval = space.actionflag.getcheckinterval() rstm.set_transaction_length(interval) +# ____________________________________________________________ + class STMLock(rthread.Lock): def __init__(self, space, ll_lock): @@ -86,3 +103,66 @@ def allocate_stm_lock(space): return STMLock(space, rthread.allocate_ll_lock()) + +# ____________________________________________________________ + + +class STMLocal(Wrappable): + """Thread-local data""" + + @jit.dont_look_inside + def __init__(self, space, initargs): + self.space = space + self.initargs = initargs + # The app-level __init__() will be called by the general + # instance-creation logic. It causes getdict() to be + # immediately called. If we don't prepare and set a w_dict + # for the current thread, then this would in cause getdict() + # to call __init__() a second time. + ec = space.getexecutioncontext() + _fill_untranslated(ec) + w_dict = space.newdict(instance=True) + ec._thread_local_dicts.set(self, w_dict) + + @jit.dont_look_inside + def create_new_dict(self, ec): + # create a new dict for this thread + space = self.space + w_dict = space.newdict(instance=True) + ec._thread_local_dicts.set(self, w_dict) + # call __init__ + try: + w_self = space.wrap(self) + w_type = space.type(w_self) + w_init = space.getattr(w_type, space.wrap("__init__")) + space.call_obj_args(w_init, w_self, self.initargs) + except: + # failed, forget w_dict and propagate the exception + ec._thread_local_dicts.set(self, None) + raise + # ready + return w_dict + + def getdict(self, space): + ec = space.getexecutioncontext() + _fill_untranslated(ec) + w_dict = ec._thread_local_dicts.get(self) + if w_dict is None: + w_dict = self.create_new_dict(ec) + return w_dict + + def descr_local__new__(space, w_subtype, __args__): + local = space.allocate_instance(STMLocal, w_subtype) + STMLocal.__init__(local, space, __args__) + return space.wrap(local) + + def descr_local__init__(self, space): + # No arguments allowed + pass + +STMLocal.typedef = TypeDef("thread._local", + __doc__ = "Thread-local data", + __new__ = interp2app(STMLocal.descr_local__new__.im_func), + __init__ = interp2app(STMLocal.descr_local__init__), + __dict__ = GetSetProperty(descr_get_dict, cls=STMLocal), + ) diff --git a/pypy/module/thread/test/test_local.py b/pypy/module/thread/test/test_local.py --- a/pypy/module/thread/test/test_local.py +++ b/pypy/module/thread/test/test_local.py @@ -3,9 +3,16 @@ class AppTestLocal(GenericTestThread): + def setup_class(cls): + GenericTestThread.setup_class.im_func(cls) + cls.w__local = cls.space.appexec([], """(): + import thread + return thread._local + """) + def test_local_1(self): import thread - from thread import _local as tlsobject + tlsobject = self._local freed = [] class X: def __del__(self): @@ -51,10 +58,10 @@ tags = ['???', 1, 2, 3, 4, 5, 54321] seen = [] - raises(TypeError, thread._local, a=1) - raises(TypeError, thread._local, 1) + raises(TypeError, self._local, a=1) + raises(TypeError, self._local, 1) - class X(thread._local): + class X(self._local): def __init__(self, n): assert n == 42 self.tag = tags.pop() @@ -74,7 +81,7 @@ def test_local_setdict(self): import thread - x = thread._local() + x = self._local() # XXX: On Cpython these are AttributeErrors raises(TypeError, "x.__dict__ = 42") raises(TypeError, "x.__dict__ = {}") @@ -91,7 +98,7 @@ def test_local_is_not_immortal(self): import thread, gc, time - class Local(thread._local): + class Local(self._local): def __del__(self): done.append('del') done = [] diff --git a/pypy/module/thread/test/test_stm.py b/pypy/module/thread/test/test_stm.py new file mode 100644 --- /dev/null +++ b/pypy/module/thread/test/test_stm.py @@ -0,0 +1,11 @@ +from pypy.module.thread.test import test_local + + +class AppTestSTMLocal(test_local.AppTestLocal): + + def setup_class(cls): + test_local.AppTestLocal.setup_class.im_func(cls) + cls.w__local = cls.space.appexec([], """(): + import thread + return thread._untranslated_stmlocal + """) diff --git a/pypy/module/thread/threadlocals.py b/pypy/module/thread/threadlocals.py --- a/pypy/module/thread/threadlocals.py +++ b/pypy/module/thread/threadlocals.py @@ -6,59 +6,14 @@ ExecutionContext._signals_enabled = 0 # default value -class OSThreadLocals: - """Thread-local storage for OS-level threads. - For memory management, this version depends on explicit notification when - a thread finishes. This works as long as the thread was started by - os_thread.bootstrap().""" +class BaseThreadLocals(object): + _mainthreadident = 0 - use_dict = True + def initialize(self, space): + pass - def __init__(self): - if self.use_dict: - self._valuedict = {} # {thread_ident: ExecutionContext()} - self._cleanup_() - - def _cleanup_(self): - if self.use_dict: - self._valuedict.clear() - self.clear_cache() - self._mainthreadident = 0 - - def clear_cache(self): - # Cache function: fast minicaching for the common case. Relies - # on the GIL; overridden in stm.py. - self._mostrecentkey = 0 - self._mostrecentvalue = None - - def getvalue(self): - # Overridden in stm.py. - ident = rthread.get_ident() - if ident == self._mostrecentkey: - result = self._mostrecentvalue - else: - value = self._valuedict.get(ident, None) - # slow path: update the minicache - self._mostrecentkey = ident - self._mostrecentvalue = value - result = value - return result - - def setvalue(self, value): - # Overridden in stm.py. - ident = rthread.get_ident() - if value is not None: - if len(self._valuedict) == 0: - value._signals_enabled = 1 # the main thread is enabled - self._mainthreadident = ident - self._valuedict[ident] = value - else: - try: - del self._valuedict[ident] - except KeyError: - pass - # clear the minicache to prevent it from containing an outdated value - self.clear_cache() + def setup_threads(self, space): + pass def signals_enabled(self): ec = self.getvalue() @@ -76,8 +31,56 @@ "cannot disable signals in thread not enabled for signals") ec._signals_enabled = new + +class OSThreadLocals(BaseThreadLocals): + """Thread-local storage for OS-level threads. + For memory management, this version depends on explicit notification when + a thread finishes. This works as long as the thread was started by + os_thread.bootstrap().""" + + def __init__(self): + self._valuedict = {} # {thread_ident: ExecutionContext()} + self._cleanup_() + + def _cleanup_(self): + self._valuedict.clear() + self._clear_cache() + self._mainthreadident = 0 + + def _clear_cache(self): + # Cache function: fast minicaching for the common case. Relies + # on the GIL. + self._mostrecentkey = 0 + self._mostrecentvalue = None + + def getvalue(self): + ident = rthread.get_ident() + if ident == self._mostrecentkey: + result = self._mostrecentvalue + else: + value = self._valuedict.get(ident, None) + # slow path: update the minicache + self._mostrecentkey = ident + self._mostrecentvalue = value + result = value + return result + + def setvalue(self, value): + ident = rthread.get_ident() + if value is not None: + if len(self._valuedict) == 0: + value._signals_enabled = 1 # the main thread is enabled + self._mainthreadident = ident + self._valuedict[ident] = value + else: + try: + del self._valuedict[ident] + except KeyError: + pass + # clear the minicache to prevent it from containing an outdated value + self._clear_cache() + def getallvalues(self): - # Overridden in stm.py. return self._valuedict def leave_thread(self, space): _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit