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