Commits

Armin Rigo committed 7239bb5

Tweak thread-locals to change the approach, at least with STM:
store the dict inside a weak-key-dictionary on the executioncontext.

Comments (0)

Files changed (7)

pypy/interpreter/executioncontext.py

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

pypy/module/thread/__init__.py

         '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',
     }
 
         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')

pypy/module/thread/os_local.py

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

pypy/module/thread/stm.py

 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
     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)
             interval = space.actionflag.getcheckinterval()
             rstm.set_transaction_length(interval)
 
+# ____________________________________________________________
+
 
 class STMLock(rthread.Lock):
     def __init__(self, space, ll_lock):
 
 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),
+                     )

pypy/module/thread/test/test_local.py

 
 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):
         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()
 
     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__ = {}")
 
     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 = []

pypy/module/thread/test/test_stm.py

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

pypy/module/thread/threadlocals.py

 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()
                 "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):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.