[pypy-svn] pypy default: Free memory allocated to shadow stacks after a fork().

arigo commits-noreply at bitbucket.org
Wed Feb 2 16:33:32 CET 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r41563:d2d23acf30a9
Date: 2011-02-01 15:00 +0100
http://bitbucket.org/pypy/pypy/changeset/d2d23acf30a9/

Log:	Free memory allocated to shadow stacks after a fork(). (not tested
	so far...)

diff --git a/pypy/module/thread/ll_thread.py b/pypy/module/thread/ll_thread.py
--- a/pypy/module/thread/ll_thread.py
+++ b/pypy/module/thread/ll_thread.py
@@ -1,6 +1,6 @@
 
 from pypy.rpython.lltypesystem import rffi
-from pypy.rpython.lltypesystem import lltype
+from pypy.rpython.lltypesystem import lltype, llmemory
 from pypy.rpython.tool import rffi_platform as platform
 from pypy.translator.tool.cbuild import ExternalCompilationInfo
 import py, os
@@ -152,7 +152,7 @@
 # ____________________________________________________________
 #
 # Thread integration.
-# These are three completely ad-hoc operations at the moment.
+# These are five completely ad-hoc operations at the moment.
 
 def gc_thread_prepare():
     """To call just before thread.start_new_thread().  This
@@ -179,3 +179,20 @@
     if we_are_translated():
         llop.gc_thread_die(lltype.Void)
 gc_thread_die._always_inline_ = True
+
+def gc_thread_before_fork():
+    """To call just before fork().  Prepares for forking, after
+    which only the current thread will be alive.
+    """
+    if we_are_translated():
+        return llop.gc_thread_before_fork(llmemory.Address)
+    else:
+        return llmemory.NULL
+
+def gc_thread_after_fork(result_of_fork, opaqueaddr):
+    """To call just after fork().
+    """
+    if we_are_translated():
+        llop.gc_thread_after_fork(lltype.Void, result_of_fork, opaqueaddr)
+    else:
+        assert opaqueaddr == llmemory.NULL

diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py
--- a/pypy/rpython/lltypesystem/lloperation.py
+++ b/pypy/rpython/lltypesystem/lloperation.py
@@ -466,6 +466,8 @@
                                  # allocating non-GC structures only
     'gc_thread_run'       : LLOp(),
     'gc_thread_die'       : LLOp(),
+    'gc_thread_before_fork':LLOp(),   # returns an opaque address
+    'gc_thread_after_fork': LLOp(),   # arguments: (result_of_fork, opaqueaddr)
     'gc_assume_young_pointers': LLOp(canrun=True),
     'gc_writebarrier_before_copy': LLOp(canrun=True),
     'gc_heap_stats'       : LLOp(canunwindgc=True),

diff --git a/pypy/rpython/module/ll_os.py b/pypy/rpython/module/ll_os.py
--- a/pypy/rpython/module/ll_os.py
+++ b/pypy/rpython/module/ll_os.py
@@ -1531,14 +1531,17 @@
 
     @registering_if(os, 'fork')
     def register_os_fork(self):
+        from pypy.module.thread import ll_thread
         eci = self.gcc_profiling_bug_workaround('pid_t _noprof_fork(void)',
                                                 'return fork();')
         os_fork = self.llexternal('_noprof_fork', [], rffi.PID_T,
                                   compilation_info = eci,
-                                  threadsafe = False)
+                                  _nowrapper = True)
 
         def fork_llimpl():
+            opaqueaddr = ll_thread.gc_thread_before_fork()
             childpid = rffi.cast(lltype.Signed, os_fork())
+            ll_thread.gc_thread_after_fork(childpid, opaqueaddr)
             if childpid == -1:
                 raise OSError(rposix.get_errno(), "os_fork failed")
             return rffi.cast(lltype.Signed, childpid)

diff --git a/pypy/translator/c/gc.py b/pypy/translator/c/gc.py
--- a/pypy/translator/c/gc.py
+++ b/pypy/translator/c/gc.py
@@ -92,6 +92,12 @@
     def OP_GC_THREAD_DIE(self, funcgen, op):
         return ''
 
+    def OP_GC_THREAD_BEFORE_FORK(self, funcgen, op):
+        return '%s = NULL;' % funcgen.expr(op.result)
+
+    def OP_GC_THREAD_AFTER_FORK(self, funcgen, op):
+        return ''
+
     def OP_GC_ASSUME_YOUNG_POINTERS(self, funcgen, op):
         return ''
 

diff --git a/pypy/rpython/memory/gctransform/framework.py b/pypy/rpython/memory/gctransform/framework.py
--- a/pypy/rpython/memory/gctransform/framework.py
+++ b/pypy/rpython/memory/gctransform/framework.py
@@ -960,6 +960,22 @@
         if hasattr(self.root_walker, 'thread_die_ptr'):
             hop.genop("direct_call", [self.root_walker.thread_die_ptr])
 
+    def gct_gc_thread_before_fork(self, hop):
+        assert self.translator.config.translation.thread
+        if hasattr(self.root_walker, 'thread_before_fork_ptr'):
+            hop.genop("direct_call", [self.root_walker.thread_before_fork_ptr],
+                      resultvar=hop.spaceop.result)
+        else:
+            c_null = rmodel.inputconst(llmemory.Address, llmemory.NULL)
+            hop.genop("same_as", [c_null],
+                      resultvar=hop.spaceop.result)
+
+    def gct_gc_thread_after_fork(self, hop):
+        assert self.translator.config.translation.thread
+        if hasattr(self.root_walker, 'thread_after_fork_ptr'):
+            hop.genop("direct_call", [self.root_walker.thread_after_fork_ptr]
+                                     + hop.spaceop.args)
+
     def gct_gc_get_type_info_group(self, hop):
         return hop.cast_result(self.c_type_info_group)
 
@@ -1435,7 +1451,7 @@
             gcdata.active_thread = new_aid
 
         def collect_stack(aid, stacktop, callback):
-            if stacktop != llmemory.NULL and aid != get_aid():
+            if stacktop != llmemory.NULL and aid != gcdata.active_thread:
                 # collect all valid stacks from the dict (the entry
                 # corresponding to the current thread is not valid)
                 gc = self.gc
@@ -1447,11 +1463,41 @@
                     addr += sizeofaddr
 
         def collect_more_stacks(callback):
+            ll_assert(get_aid() == gcdata.active_thread,
+                      "collect_more_stacks(): invalid active_thread")
             gcdata.thread_stacks.foreach(collect_stack, callback)
 
+        def _free_if_not_current(aid, stacktop, _):
+            if stacktop != llmemory.NULL and aid != gcdata.active_thread:
+                end = stacktop - sizeofaddr
+                base = end.address[0]
+                llmemory.raw_free(base)
+
+        def thread_after_fork(result_of_fork, opaqueaddr):
+            # we don't need a thread_before_fork in this case, so
+            # opaqueaddr == NULL.  This is called after fork().
+            if result_of_fork == 0:
+                # We are in the child process.  Assumes that only the
+                # current thread survived, so frees the shadow stacks
+                # of all the other ones.
+                gcdata.thread_stacks.foreach(_free_if_not_current, None)
+                # Clears the dict (including the current thread, which
+                # was an invalid entry anyway and will be recreated by
+                # the next call to save_away_current_stack()).
+                gcdata.thread_stacks.clear()
+                # Finally, reset the stored thread IDs, in case it
+                # changed because of fork().
+                aid = get_aid()
+                gcdata.main_thread = aid
+                gcdata.active_thread = aid
+
         self.thread_setup = thread_setup
         self.thread_prepare_ptr = getfn(thread_prepare, [], annmodel.s_None)
         self.thread_run_ptr = getfn(thread_run, [], annmodel.s_None,
                                     inline=True)
         self.thread_die_ptr = getfn(thread_die, [], annmodel.s_None)
+        self.thread_after_fork_ptr = getfn(thread_after_fork,
+                                           [annmodel.SomeInteger(),
+                                            annmodel.SomeAddress()],
+                                           annmodel.s_None)
         self.collect_stacks_from_other_threads = collect_more_stacks


More information about the Pypy-commit mailing list