Author: Armin Rigo <ar...@tunes.org> Branch: ec-keepalive Changeset: r81540:a4175679cd80 Date: 2016-01-04 11:32 +0100 http://bitbucket.org/pypy/pypy/changeset/a4175679cd80/
Log: Finish the RPython part diff --git a/rpython/rlib/rthread.py b/rpython/rlib/rthread.py --- a/rpython/rlib/rthread.py +++ b/rpython/rlib/rthread.py @@ -352,6 +352,8 @@ # 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...) + # (also, on Windows, if you're not making a DLL but an EXE, it will + # leak the objects when a thread finishes; see threadlocal.c.) _COUNT = 1 def __init__(self, Cls, loop_invariant=False): 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 @@ -241,10 +241,13 @@ class TestUsingFramework(AbstractThreadTests): gcpolicy = 'minimark' - def test_tlref_keepalive(self): + def test_tlref_keepalive(self, no__thread=True): import weakref from rpython.config.translationoption import SUPPORT__THREAD + if not (SUPPORT__THREAD or no__thread): + py.test.skip("no __thread support here") + class FooBar(object): pass t = ThreadLocalReference(FooBar) @@ -256,6 +259,10 @@ return weakref.ref(x1) tset._dont_inline_ = True + class WrFromThread: + pass + wr_from_thread = WrFromThread() + def f(): assert t.automatic_keepalive() is True wr = tset() @@ -264,11 +271,29 @@ assert x2 is not None assert wr() is not None assert wr() is x2 + return wr + + def thread_entry_point(): + wr = f() + wr_from_thread.wr = wr + wr_from_thread.seen = True + + def main(): + wr_from_thread.seen = False + start_new_thread(thread_entry_point, ()) + wr1 = f() + time.sleep(0.5) + assert wr_from_thread.seen is True + wr2 = wr_from_thread.wr + import gc; gc.collect() # wr2() should be collected here + assert wr1() is not None # this thread, still running + assert wr2() is None # other thread, not running any more 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 + extra_options = {'no__thread': no__thread} + fn = self.getcompiled(main, [], extra_options=extra_options) + res = fn() + assert res == 42 + + def test_tlref_keepalive__thread(self): + self.test_tlref_keepalive(no__thread=False) 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 @@ -3,12 +3,15 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#ifndef _WIN32 -# include <pthread.h> -#endif #include "src/threadlocal.h" +pthread_key_t pypy_threadlocal_key +#ifdef _WIN32 += TLS_OUT_OF_INDEXES +#endif +; + static struct pypy_threadlocal_s linkedlist_head = { .prev = &linkedlist_head, .next = &linkedlist_head }; @@ -51,63 +54,62 @@ tls->ready = 42; } -static void threadloc_unlink(struct pypy_threadlocal_s *tls) +static void threadloc_unlink(void *p) { - 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; + struct pypy_threadlocal_s *tls = (struct pypy_threadlocal_s *)p; + if (tls->ready == 42) { + tls->ready = 0; + tls->next->prev = tls->prev; + tls->prev->next = tls->next; + memset(tls, 0xDD, sizeof(struct pypy_threadlocal_s)); /* debug */ + } +#ifndef USE___THREAD + free(p); +#endif } - -/* ------------------------------------------------------------ */ -#ifdef USE___THREAD -/* ------------------------------------------------------------ */ - - -/* in this situation, we always have one full 'struct pypy_threadlocal_s' - available, managed by gcc. */ -__thread struct pypy_threadlocal_s pypy_threadlocal; +#ifdef _WIN32 +/* xxx Defines a DllMain() function. It's horrible imho: it only + works if we happen to compile a DLL (not a EXE); and of course you + get link-time errors if two files in the same DLL do the same. + There are some alternatives known, but they are horrible in other + ways (e.g. using undocumented behavior). This seems to be the + simplest, but feel free to fix if you need that. + */ +BOOL WINAPI DllMain(HINSTANCE hinstDLL, + DWORD reason_for_call, + LPVOID reserved) +{ + LPVOID p; + switch (reason_for_call) { + case DLL_THREAD_DETACH: + if (pypy_threadlocal_key != TLS_OUT_OF_INDEXES) { + p = TlsGetValue(pypy_threadlocal_key); + if (p != NULL) { + TlsSetValue(pypy_threadlocal_key, NULL); + threadloc_unlink(p); + } + } + break; + default: + break; + } + return TRUE; +} +#endif void RPython_ThreadLocals_ProgramInit(void) { - _RPy_ThreadLocals_Init(&pypy_threadlocal); -} - -char *_RPython_ThreadLocals_Build(void) -{ - RPyAssert(pypy_threadlocal.ready == 0, "corrupted thread-local"); - _RPy_ThreadLocals_Init(&pypy_threadlocal); - return (char *)&pypy_threadlocal; -} - -void RPython_ThreadLocals_ThreadDie(void) -{ - if (pypy_threadlocal.ready == 42) - threadloc_unlink(&pypy_threadlocal); -} - - -/* ------------------------------------------------------------ */ -#else -/* ------------------------------------------------------------ */ - - -/* this is the case where the 'struct pypy_threadlocal_s' is allocated - explicitly, with malloc()/free(), and attached to (a single) thread- - local key using the API of Windows or pthread. */ - -pthread_key_t pypy_threadlocal_key; - - -void RPython_ThreadLocals_ProgramInit(void) -{ + /* Initialize the pypy_threadlocal_key, together with a destructor + that will be called every time a thread shuts down (if there is + a non-null thread-local value). This is needed even in the + case where we use '__thread' below, for the destructor. + */ #ifdef _WIN32 pypy_threadlocal_key = TlsAlloc(); if (pypy_threadlocal_key == TLS_OUT_OF_INDEXES) #else - if (pthread_key_create(&pypy_threadlocal_key, NULL) != 0) + if (pthread_key_create(&pypy_threadlocal_key, threadloc_unlink) != 0) #endif { fprintf(stderr, "Internal RPython error: " @@ -117,6 +119,45 @@ _RPython_ThreadLocals_Build(); } + +/* ------------------------------------------------------------ */ +#ifdef USE___THREAD +/* ------------------------------------------------------------ */ + + +/* in this situation, we always have one full 'struct pypy_threadlocal_s' + available, managed by gcc. */ +__thread struct pypy_threadlocal_s pypy_threadlocal; + +char *_RPython_ThreadLocals_Build(void) +{ + RPyAssert(pypy_threadlocal.ready == 0, "corrupted thread-local"); + _RPy_ThreadLocals_Init(&pypy_threadlocal); + + /* we also set up &pypy_threadlocal as a POSIX thread-local variable, + because we need the destructor behavior. */ + pthread_setspecific(pypy_threadlocal_key, (void *)&pypy_threadlocal); + + return (char *)&pypy_threadlocal; +} + +void RPython_ThreadLocals_ThreadDie(void) +{ + pthread_setspecific(pypy_threadlocal_key, NULL); + threadloc_unlink(&pypy_threadlocal); +} + + +/* ------------------------------------------------------------ */ +#else +/* ------------------------------------------------------------ */ + + +/* this is the case where the 'struct pypy_threadlocal_s' is allocated + explicitly, with malloc()/free(), and attached to (a single) thread- + local key using the API of Windows or pthread. */ + + char *_RPython_ThreadLocals_Build(void) { void *p = malloc(sizeof(struct pypy_threadlocal_s)); @@ -135,8 +176,7 @@ void *p = _RPy_ThreadLocals_Get(); if (p != NULL) { _RPy_ThreadLocals_Set(NULL); - threadloc_unlink((struct pypy_threadlocal_s *)p); - free(p); + threadloc_unlink(p); /* includes free(p) */ } } _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit