]> git.llucax.com Git - software/dgc/cdgc.git/commitdiff
Run the mark phase in a fork()ed process
authorLeandro Lucarella <llucax@gmail.com>
Thu, 26 Aug 2010 01:21:32 +0000 (22:21 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Thu, 26 Aug 2010 01:23:37 +0000 (22:23 -0300)
This is the first big step towards a concurrent GC. The mark phase is ran
in a fork()ed process and the world is only stopped to do the fork()
because we need each thread to dump the CPU registers into the stack to
be scanned.

Forking is controlled via the option "no_fork" (which is false by default).
If not enough support from the underlying OS is found (i.e. no fork() or
no shared memory) or if fork() fails, the mark phase fallback to run in
the same process as the mutator (as it was done before this patch).

The mark and freebits bitmaps are shared between the two processes to
communicate the results of the mark phase. The freebits could not be
shared, but in that case the freebits should be set in the mutator process,
making pauses longer. Freebits should be revisited though.

rt/gc/cdgc/gc.d
rt/gc/cdgc/opts.d

index db6396cf07fc61432ae14d4eae24e85257d63d05..78f673c4335c59811026b20378c41f83e64e4a3f 100644 (file)
@@ -768,7 +768,40 @@ size_t fullcollectshell()
 size_t fullcollect(void *stackTop)
 {
     debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n");
+
+    // we always need to stop the world to make threads save the CPU registers
+    // in the stack and prepare themselves for thread_scanAll()
+    thread_suspendAll();
+    gc.stats.world_stopped();
+
+    if (opts.options.fork) {
+        os.pid_t child_pid = os.fork();
+        assert (child_pid != -1); // don't accept errors in non-release mode
+        switch (child_pid) {
+        case -1: // if fork() fails, fallback to stop-the-world
+            opts.options.fork = false;
+            break;
+        case 0: // child process (i.e. the collectors mark phase)
+            mark(stackTop);
+            cstdlib.exit(0);
+            break; // bogus, will never reach here
+        default: // parent process (i.e. the mutator)
+            // start the world again and wait for the mark phase to finish
+            thread_resumeAll();
+            gc.stats.world_started();
+            int status = void;
+            os.pid_t wait_pid = os.waitpid(child_pid, &status, 0);
+            assert (wait_pid == child_pid);
+            return sweep();
+        }
+
+    }
+
+    // if we reach here, we are using the standard stop-the-world collection
     mark(stackTop);
+    thread_resumeAll();
+    gc.stats.world_started();
+
     return sweep();
 }
 
@@ -780,9 +813,6 @@ void mark(void *stackTop)
 {
     debug(COLLECT_PRINTF) printf("\tmark()\n");
 
-    thread_suspendAll();
-    gc.stats.world_stopped();
-
     gc.p_cache = null;
     gc.size_cache = 0;
 
@@ -917,9 +947,6 @@ void mark(void *stackTop)
             }
         }
     }
-
-    thread_resumeAll();
-    gc.stats.world_started();
 }
 
 
@@ -1176,6 +1203,9 @@ void initialize()
     int dummy;
     gc.stack_bottom = cast(char*)&dummy;
     opts.parse(cstdlib.getenv("D_GC_OPTS"));
+    // If we are going to fork, make sure we have the needed OS support
+    if (opts.options.fork)
+        opts.options.fork = os.HAVE_SHARED && os.HAVE_FORK;
     gc.lock = GCLock.classinfo;
     gc.inited = 1;
     setStackBottom(rt_stackBottom());
@@ -1866,10 +1896,18 @@ struct Pool
         //assert(baseAddr);
         topAddr = baseAddr + poolsize;
 
-        mark.alloc(cast(size_t)poolsize / 16);
-        scan.alloc(cast(size_t)poolsize / 16);
-        freebits.alloc(cast(size_t)poolsize / 16);
-        noscan.alloc(cast(size_t)poolsize / 16);
+        size_t nbits = cast(size_t)poolsize / 16;
+
+        // if the GC will run in parallel in a fork()ed process, we need to
+        // share the mark bits
+        os.Vis vis = os.Vis.PRIV;
+        if (opts.options.fork)
+            vis = os.Vis.SHARED;
+        mark.alloc(nbits, vis); // shared between mark and sweep
+        freebits.alloc(nbits, vis); // ditto
+        scan.alloc(nbits); // only used in the mark phase
+        finals.alloc(nbits); // mark phase *MUST* have a snapshot
+        noscan.alloc(nbits); // ditto
 
         pagetable = cast(ubyte*) cstdlib.malloc(npages);
         if (!pagetable)
@@ -1900,9 +1938,12 @@ struct Pool
         if (pagetable)
             cstdlib.free(pagetable);
 
-        mark.Dtor();
+        os.Vis vis = os.Vis.PRIV;
+        if (opts.options.fork)
+            vis = os.Vis.SHARED;
+        mark.Dtor(vis);
+        freebits.Dtor(vis);
         scan.Dtor();
-        freebits.Dtor();
         finals.Dtor();
         noscan.Dtor();
     }
index bb6529a6cf7e4fa5a48ae7bad72b54a91d999e8d..728119fdd9bc88dae6a9fade30f37a77fbf98343 100644 (file)
@@ -52,6 +52,7 @@ struct Options
     bool sentinel = false;
     bool mem_stomp = false;
     bool conservative = false;
+    bool fork = true;
 }
 
 package Options options;
@@ -87,6 +88,8 @@ void process_option(char* opt_name, char* opt_value)
         options.mem_stomp = parse_bool(opt_value);
     else if (cstr_eq(opt_name, "conservative"))
         options.conservative = parse_bool(opt_value);
+    else if (cstr_eq(opt_name, "no_fork"))
+        options.fork = !parse_bool(opt_value);
 }
 
 
@@ -145,6 +148,7 @@ unittest
         assert (sentinel == false);
         assert (mem_stomp == false);
         assert (conservative == false);
+        assert (fork == true);
     }
     parse("mem_stomp");
     with (options) {
@@ -153,14 +157,16 @@ unittest
         assert (sentinel == false);
         assert (mem_stomp == true);
         assert (conservative == false);
+        assert (fork == true);
     }
-    parse("mem_stomp=0:verbose=2:conservative");
+    parse("mem_stomp=0:verbose=2:conservative:no_fork=10");
     with (options) {
         assert (verbose == 2);
         assert (log_file[0] == '\0');
         assert (sentinel == false);
         assert (mem_stomp == false);
         assert (conservative == true);
+        assert (fork == false);
     }
     parse("log_file=12345 67890:verbose=1:sentinel=4:mem_stomp=1");
     with (options) {
@@ -169,6 +175,7 @@ unittest
         assert (sentinel == true);
         assert (mem_stomp == true);
         assert (conservative == true);
+        assert (fork == false);
     }
     parse(null);
     with (options) {
@@ -177,6 +184,7 @@ unittest
         assert (sentinel == true);
         assert (mem_stomp == true);
         assert (conservative == true);
+        assert (fork == false);
     }
 }