From: Leandro Lucarella Date: Thu, 26 Aug 2010 01:21:32 +0000 (-0300) Subject: Run the mark phase in a fork()ed process X-Git-Url: https://git.llucax.com/software/dgc/cdgc.git/commitdiff_plain/b28fd72842fc9ce935bed74f7b2ba79f9cc59711 Run the mark phase in a fork()ed process 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. --- diff --git a/rt/gc/cdgc/gc.d b/rt/gc/cdgc/gc.d index db6396c..78f673c 100644 --- a/rt/gc/cdgc/gc.d +++ b/rt/gc/cdgc/gc.d @@ -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(); } diff --git a/rt/gc/cdgc/opts.d b/rt/gc/cdgc/opts.d index bb6529a..728119f 100644 --- a/rt/gc/cdgc/opts.d +++ b/rt/gc/cdgc/opts.d @@ -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); } }