Author: Armin Rigo <[email protected]>
Branch: ec-keepalive
Changeset: r81539:0c470d6715d7
Date: 2016-01-04 10:32 +0100
http://bitbucket.org/pypy/pypy/changeset/0c470d6715d7/
Log: Add custom trace hooks in order to walk all threadlocalrefs, which
are now chained in a doubly-linked list. Of course it only works
with our own GCs, not Boehm.
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
@@ -94,7 +94,7 @@
old_sig = ec._signals_enabled
if ident != self._mainthreadident:
old_sig += 1
- self._cleanup_()
+ self._cleanup_() # clears self._valuedict
self._mainthreadident = ident
self._set_ec(ec)
ec._signals_enabled = old_sig
diff --git a/rpython/rlib/rthread.py b/rpython/rlib/rthread.py
--- a/rpython/rlib/rthread.py
+++ b/rpython/rlib/rthread.py
@@ -291,8 +291,6 @@
# ____________________________________________________________
#
# Thread-locals.
-# KEEP THE REFERENCE ALIVE, THE GC DOES NOT FOLLOW THEM SO FAR!
-# We use _make_sure_does_not_move() to make sure the pointer will not move.
class ThreadLocalField(object):
@@ -351,6 +349,9 @@
class ThreadLocalReference(ThreadLocalField):
+ # A thread-local that points to an object. The object stored in such
+ # a thread-local is kept alive as long as the thread is not finished
+ # (but only with our own GCs! it seems not to work with Boehm...)
_COUNT = 1
def __init__(self, Cls, loop_invariant=False):
@@ -378,19 +379,39 @@
assert isinstance(value, Cls) or value is None
if we_are_translated():
from rpython.rtyper.annlowlevel import cast_instance_to_gcref
- from rpython.rlib.rgc import _make_sure_does_not_move
- from rpython.rlib.objectmodel import running_on_llinterp
gcref = cast_instance_to_gcref(value)
- if not running_on_llinterp:
- if gcref:
- _make_sure_does_not_move(gcref)
value = lltype.cast_ptr_to_int(gcref)
setraw(value)
+ rgc.register_custom_trace_hook(TRACETLREF, _lambda_trace_tlref)
+ rgc.ll_writebarrier(_tracetlref_obj)
else:
self.local.value = value
self.get = get
self.set = set
+ self.automatic_keepalive = _automatic_keepalive
+
+ def _trace_tlref(gc, obj, callback, arg):
+ p = llmemory.NULL
+ while True:
+ p = llop.threadlocalref_enum(llmemory.Address, p)
+ if not p:
+ break
+ gc._trace_callback(callback, arg, p + offset)
+ _lambda_trace_tlref = lambda: _trace_tlref
+ TRACETLREF = lltype.GcStruct('TRACETLREF')
+ _tracetlref_obj = lltype.malloc(TRACETLREF, immortal=True)
+
+
+def _automatic_keepalive():
+ """Returns True if translated with a GC that keeps alive
+ the set() value until the end of the thread. Returns False
+ if you need to keep it alive yourself.
+ """
+ from rpython.rlib import objectmodel
+ config = objectmodel.fetch_translated_config()
+ return (config is not None and
+ config.translation.gctransformer == "framework")
tlfield_thread_ident = ThreadLocalField(lltype.Signed, "thread_ident",
diff --git a/rpython/rlib/test/test_rthread.py
b/rpython/rlib/test/test_rthread.py
--- a/rpython/rlib/test/test_rthread.py
+++ b/rpython/rlib/test/test_rthread.py
@@ -240,3 +240,35 @@
class TestUsingFramework(AbstractThreadTests):
gcpolicy = 'minimark'
+
+ def test_tlref_keepalive(self):
+ import weakref
+ from rpython.config.translationoption import SUPPORT__THREAD
+
+ class FooBar(object):
+ pass
+ t = ThreadLocalReference(FooBar)
+ assert t.automatic_keepalive() is False
+
+ def tset():
+ x1 = FooBar()
+ t.set(x1)
+ return weakref.ref(x1)
+ tset._dont_inline_ = True
+
+ def f():
+ assert t.automatic_keepalive() is True
+ wr = tset()
+ import gc; gc.collect() # 'x1' should not be collected
+ x2 = t.get()
+ assert x2 is not None
+ assert wr() is not None
+ assert wr() is x2
+ return 42
+
+ for no__thread in (True, False):
+ if SUPPORT__THREAD or no__thread:
+ extra_options = {'no__thread': no__thread}
+ fn = self.getcompiled(f, [], extra_options=extra_options)
+ res = fn()
+ assert res == 42
diff --git a/rpython/rtyper/llinterp.py b/rpython/rtyper/llinterp.py
--- a/rpython/rtyper/llinterp.py
+++ b/rpython/rtyper/llinterp.py
@@ -950,6 +950,9 @@
return self.op_raw_load(RESTYPE, _address_of_thread_local(), offset)
op_threadlocalref_get.need_result_type = True
+ def op_threadlocalref_enum(self, prev):
+ raise NotImplementedError
+
# __________________________________________________________
# operations on addresses
diff --git a/rpython/rtyper/lltypesystem/lloperation.py
b/rpython/rtyper/lltypesystem/lloperation.py
--- a/rpython/rtyper/lltypesystem/lloperation.py
+++ b/rpython/rtyper/lltypesystem/lloperation.py
@@ -545,8 +545,9 @@
'getslice': LLOp(canraise=(Exception,)),
'check_and_clear_exc': LLOp(),
- 'threadlocalref_addr': LLOp(sideeffects=False), # get (or make) addr of
tl
+ 'threadlocalref_addr': LLOp(), # get (or make) addr of
tl
'threadlocalref_get': LLOp(sideeffects=False), # read field (no check)
+ 'threadlocalref_enum': LLOp(sideeffects=False), # enum all
threadlocalrefs
# __________ debugging __________
'debug_view': LLOp(),
diff --git a/rpython/translator/c/genc.py b/rpython/translator/c/genc.py
--- a/rpython/translator/c/genc.py
+++ b/rpython/translator/c/genc.py
@@ -733,6 +733,7 @@
print >> f, 'struct pypy_threadlocal_s {'
print >> f, '\tint ready;'
print >> f, '\tchar *stack_end;'
+ print >> f, '\tstruct pypy_threadlocal_s *prev, *next;'
for field in fields:
typename = database.gettype(field.FIELDTYPE)
print >> f, '\t%s;' % cdecl(typename, field.fieldname)
diff --git a/rpython/translator/c/src/threadlocal.c
b/rpython/translator/c/src/threadlocal.c
--- a/rpython/translator/c/src/threadlocal.c
+++ b/rpython/translator/c/src/threadlocal.c
@@ -9,14 +9,31 @@
#include "src/threadlocal.h"
+static struct pypy_threadlocal_s linkedlist_head = {
+ .prev = &linkedlist_head,
+ .next = &linkedlist_head };
+
+struct pypy_threadlocal_s *
+_RPython_ThreadLocals_Enum(struct pypy_threadlocal_s *prev)
+{
+ if (prev == NULL)
+ prev = &linkedlist_head;
+ if (prev->next == &linkedlist_head)
+ return NULL;
+ return prev->next;
+}
+
static void _RPy_ThreadLocals_Init(void *p)
{
+ struct pypy_threadlocal_s *tls = (struct pypy_threadlocal_s *)p;
+ struct pypy_threadlocal_s *oldnext;
memset(p, 0, sizeof(struct pypy_threadlocal_s));
+
#ifdef RPY_TLOFS_p_errno
- ((struct pypy_threadlocal_s *)p)->p_errno = &errno;
+ tls->p_errno = &errno;
#endif
#ifdef RPY_TLOFS_thread_ident
- ((struct pypy_threadlocal_s *)p)->thread_ident =
+ tls->thread_ident =
# ifdef _WIN32
GetCurrentThreadId();
# else
@@ -26,7 +43,21 @@
where it is not the case are rather old nowadays. */
# endif
#endif
- ((struct pypy_threadlocal_s *)p)->ready = 42;
+ oldnext = linkedlist_head.next;
+ tls->prev = &linkedlist_head;
+ tls->next = oldnext;
+ linkedlist_head.next = tls;
+ oldnext->prev = tls;
+ tls->ready = 42;
+}
+
+static void threadloc_unlink(struct pypy_threadlocal_s *tls)
+{
+ assert(tls->ready == 42);
+ tls->next->prev = tls->prev;
+ tls->prev->next = tls->next;
+ memset(tls, 0xDD, sizeof(struct pypy_threadlocal_s)); /* debug */
+ tls->ready = 0;
}
@@ -53,9 +84,8 @@
void RPython_ThreadLocals_ThreadDie(void)
{
- memset(&pypy_threadlocal, 0xDD,
- sizeof(struct pypy_threadlocal_s)); /* debug */
- pypy_threadlocal.ready = 0;
+ if (pypy_threadlocal.ready == 42)
+ threadloc_unlink(&pypy_threadlocal);
}
@@ -105,7 +135,7 @@
void *p = _RPy_ThreadLocals_Get();
if (p != NULL) {
_RPy_ThreadLocals_Set(NULL);
- memset(p, 0xDD, sizeof(struct pypy_threadlocal_s)); /* debug */
+ threadloc_unlink((struct pypy_threadlocal_s *)p);
free(p);
}
}
diff --git a/rpython/translator/c/src/threadlocal.h
b/rpython/translator/c/src/threadlocal.h
--- a/rpython/translator/c/src/threadlocal.h
+++ b/rpython/translator/c/src/threadlocal.h
@@ -13,14 +13,18 @@
to die. */
RPY_EXTERN void RPython_ThreadLocals_ThreadDie(void);
-/* There are two llops: 'threadlocalref_addr' and 'threadlocalref_make'.
- They both return the address of the thread-local structure (of the
- C type 'struct pypy_threadlocal_s'). The difference is that
- OP_THREADLOCALREF_MAKE() checks if we have initialized this thread-
- local structure in the current thread, and if not, calls the following
- helper. */
+/* 'threadlocalref_addr' returns the address of the thread-local
+ structure (of the C type 'struct pypy_threadlocal_s'). It first
+ checks if we have initialized this thread-local structure in the
+ current thread, and if not, calls the following helper. */
RPY_EXTERN char *_RPython_ThreadLocals_Build(void);
+RPY_EXTERN struct pypy_threadlocal_s *
+_RPython_ThreadLocals_Enum(struct pypy_threadlocal_s *prev);
+
+#define OP_THREADLOCALREF_ENUM(p, r) \
+ r = _RPython_ThreadLocals_Enum(p)
+
/* ------------------------------------------------------------ */
#ifdef USE___THREAD
diff --git a/rpython/translator/c/test/test_boehm.py
b/rpython/translator/c/test/test_boehm.py
--- a/rpython/translator/c/test/test_boehm.py
+++ b/rpython/translator/c/test/test_boehm.py
@@ -23,6 +23,7 @@
class AbstractGCTestClass(object):
gcpolicy = "boehm"
use_threads = False
+ extra_options = {}
# deal with cleanups
def setup_method(self, meth):
@@ -33,8 +34,10 @@
#print "CLEANUP"
self._cleanups.pop()()
- def getcompiled(self, func, argstypelist=[], annotatorpolicy=None):
- return compile(func, argstypelist, gcpolicy=self.gcpolicy,
thread=self.use_threads)
+ def getcompiled(self, func, argstypelist=[], annotatorpolicy=None,
+ extra_options={}):
+ return compile(func, argstypelist, gcpolicy=self.gcpolicy,
+ thread=self.use_threads, **extra_options)
class TestUsingBoehm(AbstractGCTestClass):
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit