Author: Armin Rigo <ar...@tunes.org>
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
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to