Author: Armin Rigo <[email protected]>
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
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit