From 16ac6d9efbc85d7565ac672a014a17d0cc7c4067 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Sat, 11 May 2019 15:01:52 +0200 Subject: [PATCH 01/47] Toggle forking gc on/off --- src/core/gc/config.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/gc/config.d b/src/core/gc/config.d index cf6ec189a4..39dea8d041 100644 --- a/src/core/gc/config.d +++ b/src/core/gc/config.d @@ -15,6 +15,7 @@ __gshared Config config; struct Config { bool disable; // start disabled + bool fork = false; // optional concurrent behaviour ubyte profile; // enable profiling with summary when terminating program string gc = "conservative"; // select gc implementation conservative|precise|manual @@ -39,6 +40,7 @@ struct Config printf("GC options are specified as white space separated assignments: disable:0|1 - start disabled (%d) + fork:0|1 - set fork behaviour (disabled by default) (%d) profile:0|1|2 - enable profiling with summary when terminating program (%d) gc:".ptr, disable, profile); foreach (i, entry; registeredGCFactories) From b5e5109d2c84cd99d336ff25d2f8c631e2b32c8e Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Sat, 11 May 2019 15:12:15 +0200 Subject: [PATCH 02/47] Annotate used functions with @nogc --- src/gc/bits.d | 12 ++++++------ src/gc/os.d | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/gc/bits.d b/src/gc/bits.d index caf8416a32..2e6c93d3a2 100644 --- a/src/gc/bits.d +++ b/src/gc/bits.d @@ -38,7 +38,7 @@ struct GCBits wordtype* data; size_t nbits; - void Dtor() nothrow + void Dtor() nothrow @nogc { if (data) { @@ -55,7 +55,7 @@ struct GCBits onOutOfMemoryError(); } - wordtype test(size_t i) const nothrow + wordtype test(size_t i) const nothrow @nogc in { assert(i < nbits); @@ -65,7 +65,7 @@ struct GCBits return core.bitop.bt(data, i); } - int set(size_t i) nothrow + int set(size_t i) nothrow @nogc in { assert(i < nbits); @@ -75,7 +75,7 @@ struct GCBits return core.bitop.bts(data, i); } - int clear(size_t i) nothrow + int clear(size_t i) nothrow @nogc in { assert(i <= nbits); @@ -432,7 +432,7 @@ struct GCBits testCopyRange(2, 3, 166); // failed with assert } - void zero() nothrow + void zero() nothrow @nogc { memset(data, 0, nwords * wordtype.sizeof); } @@ -447,7 +447,7 @@ struct GCBits memcpy(data, f.data, nwords * wordtype.sizeof); } - @property size_t nwords() const pure nothrow + @property size_t nwords() const pure nothrow @nogc { return (nbits + (BITS_PER_WORD - 1)) >> BITS_SHIFT; } diff --git a/src/gc/os.d b/src/gc/os.d index baef98c17f..95d625cacc 100644 --- a/src/gc/os.d +++ b/src/gc/os.d @@ -75,7 +75,7 @@ static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) /** * Map memory. */ - void *os_mem_map(size_t nbytes) nothrow + void *os_mem_map(size_t nbytes) nothrow @nogc { return VirtualAlloc(null, nbytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); @@ -88,7 +88,7 @@ static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) * 0 success * !=0 failure */ - int os_mem_unmap(void *base, size_t nbytes) nothrow + int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc { return cast(int)(VirtualFree(base, 0, MEM_RELEASE) == 0); } @@ -103,7 +103,7 @@ else static if (is(typeof(mmap))) // else version (GC_Use_Alloc_MMap) } - int os_mem_unmap(void *base, size_t nbytes) nothrow + int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc { return munmap(base, nbytes); } @@ -116,7 +116,7 @@ else static if (is(typeof(valloc))) // else version (GC_Use_Alloc_Valloc) } - int os_mem_unmap(void *base, size_t nbytes) nothrow + int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc { free(base); return 0; @@ -136,7 +136,7 @@ else static if (is(typeof(malloc))) // else version (GC_Use_Alloc_Malloc) const size_t PAGE_MASK = PAGESIZE - 1; - void *os_mem_map(size_t nbytes) nothrow + void *os_mem_map(size_t nbytes) nothrow @nogc { byte *p, q; p = cast(byte *) malloc(nbytes + PAGESIZE); q = p + ((PAGESIZE - ((cast(size_t) p & PAGE_MASK))) & PAGE_MASK); @@ -145,7 +145,7 @@ else static if (is(typeof(malloc))) // else version (GC_Use_Alloc_Malloc) } - int os_mem_unmap(void *base, size_t nbytes) nothrow + int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc { free( *cast(void**)( cast(byte*) base + nbytes ) ); return 0; From 10162243e0fa54b1effb81f3953425c9341d2375 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Sat, 11 May 2019 15:14:50 +0200 Subject: [PATCH 03/47] C functions and imports This is everything necessary to write the core of the logic for the concurrent mark. --- src/gc/bits.d | 22 +++++++++--- src/gc/impl/conservative/gc.d | 6 ++++ src/gc/os.d | 63 +++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/gc/bits.d b/src/gc/bits.d index 2e6c93d3a2..df6ded0fb2 100644 --- a/src/gc/bits.d +++ b/src/gc/bits.d @@ -13,6 +13,7 @@ */ module gc.bits; +import gc.os : os_mem_map, os_mem_unmap; import core.bitop; import core.stdc.string; @@ -33,6 +34,7 @@ struct GCBits enum BITS_MASK = (BITS_PER_WORD - 1); enum BITS_0 = cast(wordtype)0; enum BITS_1 = cast(wordtype)1; + bool mmap; enum BITS_2 = cast(wordtype)2; wordtype* data; @@ -42,17 +44,24 @@ struct GCBits { if (data) { - free(data); + if (mmap) + os_mem_unmap(data, nwords * data[0].sizeof); + else + free(data); data = null; } } - void alloc(size_t nbits) nothrow + void alloc(size_t nbits, bool mmap = false) nothrow { this.nbits = nbits; - data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); + if (mmap) + data = cast(typeof(data[0])*)os_mem_map(nwords * data[0].sizeof, true); // Allocate as MAP_SHARED + else + data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); if (!data) onOutOfMemoryError(); + this.mmap = mmap; } wordtype test(size_t i) const nothrow @nogc @@ -437,7 +446,12 @@ struct GCBits memset(data, 0, nwords * wordtype.sizeof); } - void copy(GCBits *f) nothrow + void setAll() nothrow @nogc + { + memset(data, 0xFF, nwords * wordtype.sizeof); + } + + void copy(GCBits *f) nothrow @nogc in { assert(nwords == f.nwords); diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 06548777e7..ca91c1f03b 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -43,6 +43,7 @@ import core.gc.gcinterface; import rt.util.container.treap; import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc; +import core.stdc.stdio : fflush; import core.stdc.string : memcpy, memset, memmove; import core.bitop; import core.thread; @@ -56,6 +57,11 @@ else import core.stdc.stdio : sprintf, printf; // needed to ou import core.time; alias currTime = MonoTime.currTime; +extern (C) nothrow @nogc +{ + void _Exit(int); +} + // Track total time spent preparing for GC, // marking, sweeping and recovering pages. __gshared Duration prepTime; diff --git a/src/gc/os.d b/src/gc/os.d index 95d625cacc..2d4cc8ac55 100644 --- a/src/gc/os.d +++ b/src/gc/os.d @@ -49,6 +49,48 @@ else version (Posix) version (CRuntime_UClibc) import core.sys.linux.sys.mman : MAP_ANON; import core.stdc.stdlib; + + /** + * Possible results for the wait_pid() function. + */ + enum WRes + { + DONE, /// The process has finished successfully + RUNNING, /// The process is still running + ERROR /// There was an error waiting for the process + } + + /** + * Wait for a process with PID pid to finish. + * + * If block is false, this function will not block, and return WRes.RUNNING if + * the process is still running. Otherwise it will return always WRes.DONE + * (unless there is an error, in which case WRes.ERROR is returned). + */ + WRes wait_pid(pid_t pid, bool block = true) nothrow @nogc + { + int status = void; + pid_t waited_pid = void; + // In the case where we are blocking, we need to consider signals + // arriving while we wait, and resume the waiting if EINTR is returned + do { + errno = 0; + waited_pid = waitpid(pid, &status, block ? 0 : WNOHANG); + } + while (waited_pid == -1 && errno == EINTR); + if (waited_pid == 0) + return WRes.RUNNING; + assert (waited_pid == pid); + assert (status == 0); + if (waited_pid != pid || status != 0) + return WRes.ERROR; + return WRes.DONE; + } + + public import core.sys.posix.unistd: pid_t, fork; + import core.sys.posix.sys.wait: waitpid, WNOHANG; + import core.stdc.errno: errno, EINTR; + //version = GC_Use_Alloc_MMap; } else @@ -69,9 +111,18 @@ else static if (is(typeof(malloc))) version = GC_Use_Alloc_Malloc; else static assert(false, "No supported allocation methods available."); +/ +/** + * Indicates if an implementation support fork(). + * + * The value shown here is just demostrative, the real value is defined based + * on the OS it's being compiled in. + * enum HAVE_FORK = true; +*/ static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) { + enum { HAVE_FORK = false } + /** * Map memory. */ @@ -95,10 +146,13 @@ static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) } else static if (is(typeof(mmap))) // else version (GC_Use_Alloc_MMap) { - void *os_mem_map(size_t nbytes) nothrow + enum { HAVE_FORK = true } + + void *os_mem_map(size_t nbytes, bool share = false) nothrow @nogc { void *p; - p = mmap(null, nbytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + auto map_f = share ? MAP_SHARED : MAP_PRIVATE; + p = mmap(null, nbytes, PROT_READ | PROT_WRITE, map_f | MAP_ANON, -1, 0); return (p == MAP_FAILED) ? null : p; } @@ -110,7 +164,9 @@ else static if (is(typeof(mmap))) // else version (GC_Use_Alloc_MMap) } else static if (is(typeof(valloc))) // else version (GC_Use_Alloc_Valloc) { - void *os_mem_map(size_t nbytes) nothrow + enum { HAVE_FORK = false } + + void *os_mem_map(size_t nbytes) nothrow @nogc { return valloc(nbytes); } @@ -129,6 +185,7 @@ else static if (is(typeof(malloc))) // else version (GC_Use_Alloc_Malloc) // to PAGESIZE alignment, there will be space for a void* at the end // after PAGESIZE bytes used by the GC. + enum { HAVE_FORK = false } import gc.gc; From 89af9697b941b4a8836509a47d1a9e69d2e61e3c Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Sat, 11 May 2019 15:17:27 +0200 Subject: [PATCH 04/47] Core logic of the fork() based gc. Also changed some functions call to reflect their signature --- src/gc/impl/conservative/gc.d | 171 +++++++++++++++++++++++++++++++--- 1 file changed, 156 insertions(+), 15 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index ca91c1f03b..c2ba8faefb 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -1003,6 +1003,7 @@ class ConservativeGC : GC /** * Do full garbage collection. + The collection is done concurrently only if block is false. * Return number of pages free'd. */ size_t fullCollect() nothrow @@ -1013,7 +1014,7 @@ class ConservativeGC : GC // when collecting. static size_t go(Gcx* gcx) nothrow { - return gcx.fullcollect(); + return gcx.fullcollect(false, true); // standard stop the world } immutable result = runLocked!go(gcx); @@ -1040,7 +1041,7 @@ class ConservativeGC : GC // when collecting. static size_t go(Gcx* gcx) nothrow { - return gcx.fullcollect(true); + return gcx.fullcollect(true, true); // standard stop the world } runLocked!go(gcx); } @@ -1230,6 +1231,8 @@ struct Gcx auto rangesLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); Treap!Root roots; Treap!Range ranges; + private pid_t markProcPid = 0; + private bool doMinimize = false; debug(INVARIANT) bool initialized; debug(INVARIANT) bool inCollection; @@ -1374,6 +1377,11 @@ struct Gcx } } + @property bool collectInProgress() const nothrow + { + return markProcPid != 0; + } + /** * @@ -1648,7 +1656,7 @@ struct Gcx if (!newPool(1, false)) { // out of memory => try to free some memory - fullcollect(); + fullcollect(false, true); // stop the worlds if (lowMem) minimize(); recoverNextPage(bin); @@ -1671,8 +1679,10 @@ struct Gcx // Return next item from free list bucket[bin] = (cast(List*)p).next; auto pool = (cast(List*)p).pool; + auto biti = (p - pool.baseAddr) >> pool.shiftBy; assert(pool.freebits.test(biti)); + pool.mark.set(biti); // be sure that the child is aware of the page being used pool.freebits.clear(biti); if (bits) pool.setBits(biti, bits); @@ -1735,14 +1745,14 @@ struct Gcx if (!tryAllocNewPool()) { // disabled but out of memory => try to free some memory - fullcollect(); - minimize(); + doMinimize = true; + fullcollect(false, true); } } else { + doMinimize = true; fullcollect(); - minimize(); } // If alloc didn't yet succeed retry now that we collected/minimized if (!pool && !tryAlloc() && !tryAllocNewPool()) @@ -1752,7 +1762,12 @@ struct Gcx assert(pool); debug(PRINTF) printFreeInfo(&pool.base); + pool.pagetable[pn] = B_PAGE; + if (npages > 1) + memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1); + pool.mark.set(pn); usedLargePages += npages; + pool.freepages -= npages; debug(PRINTF) printFreeInfo(&pool.base); @@ -1820,6 +1835,8 @@ struct Gcx if (pool) { pool.initialize(npages, isLargeObject); + if (collectInProgress) + pool.mark.setAll(); if (!pool.baseAddr || !pooltable.insert(pool)) { pool.Dtor(); @@ -2563,11 +2580,17 @@ struct Gcx return recoverPool[bin] = poolIndex < npools ? cast(SmallObjectPool*)pool : null; } + void disableFork() nothrow + { + config.fork = false; + markProcPid = 0; + } + /** * Return number of full pages free'd. */ - size_t fullcollect(bool nostack = false) nothrow + size_t fullcollect(bool nostack = false, bool block = false) nothrow { // It is possible that `fullcollect` will be called from a thread which // is not yet registered in runtime (because allocating `new Thread` is @@ -2581,9 +2604,54 @@ struct Gcx begin = start = currTime; debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n"); + bool doFork = config.fork; + version (COLLECT_PARALLEL) + bool doParallel = config.parallel > 0; + else + enum doParallel = false; + + assert(!(doFork && doParallel), "No reason in mixing threads and fork"); //printf("\tpool address range = %p .. %p\n", minAddr, maxAddr); + // If there is a mark process running, check if it already finished. + // If that is the case, we move to the sweep phase. + // If it's still running, either we block until the mark phase is + // done (and then sweep to finish the collection), or in case of error + // we redo the mark phase without forking. + if (collectInProgress && doFork) { + WRes rc = wait_pid(markProcPid, block); + switch (rc) + { + case WRes.DONE: + debug(COLLECT_PRINTF) printf("\t\tmark proc DONE (block=%d)\n", + cast(int) block); + markProcPid = 0; + // process GC marks then sweep + thread_suspendAll(); + thread_processGCMarks(&isMarked); + thread_resumeAll(); + break; + case WRes.RUNNING: + debug(COLLECT_PRINTF) printf("\t\tmark proc RUNNING\n"); + if (!block) + return 0; + // Something went wrong, if block is true, wait() should never + // returned RUNNING. + goto case WRes.ERROR; + case WRes.ERROR: + debug(COLLECT_PRINTF) printf("\t\tmark proc ERROR\n"); + // Try to keep going without forking + // and do the marking in this thread + disableFork(); + goto Lmark; + default: + assert(false, "Unknown wait_pid() result"); + } + } + else + { +Lmark: // lock roots and ranges around suspending threads b/c they're not reentrant safe rangesLock.lock(); rootsLock.lock(); @@ -2602,12 +2670,65 @@ struct Gcx prepTime += (stop - start); start = stop; - version (COLLECT_PARALLEL) - bool doParallel = config.parallel > 0; - else - enum doParallel = false; - - if (doParallel) + // Forking is enabled, so we fork() and start a new concurrent mark phase + // in the child. If the collection should not block, the parent process + // tells the caller no memory could be recycled immediately (if this collection + // was triggered by an allocation, the caller should allocate more memory + // to fulfill the request). + // If the collection should block, the parent will wait for the mark phase + // to finish before returning control to the mutator, + // but other threads are restarted and may run in parallel with the mark phase + // (unless they allocate or use the GC themselves, in which case + // the global GC lock will stop them). + if (doFork) + { + // fork now and sweep later + fflush(null); // avoid duplicated FILE* output + auto pid = fork(); + assert(pid != -1); // TODO handle case + switch (pid) + { + case -1: // fork() failed, retry without forking + disableFork(); + goto Lmark; + case 0: // child process + if (ConservativeGC.isPrecise) + markAll!markPrecise(nostack); + else + markAll!markConservative(nostack); + _Exit(0); + break; // bogus + default: // the parent + thread_resumeAll(); + if (!block) + { + markProcPid = pid; + // update profiling informations + stop = currTime; + markTime += (stop - start); + Duration pause = stop - begin; + if (pause > maxPauseTime) + maxPauseTime = pause; + pauseTime += pause; + return 0; + } + WRes r = wait_pid(pid); // block until marking is done + assert(r == WRes.DONE); + assert(r != WRes.RUNNING); + if (r == WRes.ERROR) + { + thread_suspendAll(); + // there was an error + // do the marking in this thread + disableFork(); + if (ConservativeGC.isPrecise) + markAll!markPrecise(nostack); + else + markAll!markConservative(nostack); + } + } + } + else if (doParallel) { version (COLLECT_PARALLEL) markParallel(nostack); @@ -2620,10 +2741,19 @@ struct Gcx markAll!markConservative(nostack); } + // if we get here, forking failed and a standard STW collection got issued + // threads were suspended again, restart them + if(doFork) + thread_suspendAll(); + thread_processGCMarks(&isMarked); thread_resumeAll(); } + // If we reach here, the child process has finished the marking phase + // or block == true and we are using standard stop the world collection. + // It is time to sweep + stop = currTime; markTime += (stop - start); Duration pause = stop - begin; @@ -2640,6 +2770,14 @@ struct Gcx ConservativeGC._inFinalizer = false; } + // minimize() should be called only after a call to fullcollect + // terminates with a sweep + if (doMinimize || lowMem) + { + doMinimize = false; + minimize(); + } + // init bucket lists bucket[] = null; foreach (Bins bin; 0..B_NUMSMALL) @@ -2655,7 +2793,6 @@ struct Gcx ++numCollections; updateCollectThresholds(); - return freedPages; } @@ -2991,7 +3128,7 @@ struct Pool topAddr = baseAddr + poolsize; auto nbits = cast(size_t)poolsize >> shiftBy; - mark.alloc(nbits); + mark.alloc(nbits, true); if (ConservativeGC.isPrecise) { if (isLargeObject) @@ -3015,6 +3152,7 @@ struct Pool freebits.alloc(nbits); freebits.setRange(0, nbits); } + freebits.setAll(); noscan.alloc(nbits); appendable.alloc(nbits); @@ -3825,6 +3963,9 @@ struct SmallObjectPool // Convert page to free list size_t size = binsize[bin]; void* p = baseAddr + pn * PAGESIZE; + size_t biti = pn * (PAGESIZE / 16); + base.freebits.set(biti); + auto first = cast(List*) p; // ensure 2 bytes blocks are available below ptop, one From dbab396344d53421c1fead65cad7237b21d13792 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Sat, 11 May 2019 16:00:20 +0200 Subject: [PATCH 05/47] whitespace --- src/gc/impl/conservative/gc.d | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index c2ba8faefb..b3d0208494 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2697,8 +2697,8 @@ Lmark: else markAll!markConservative(nostack); _Exit(0); - break; // bogus - default: // the parent + break; // bogus + default: // the parent thread_resumeAll(); if (!block) { @@ -2720,7 +2720,7 @@ Lmark: thread_suspendAll(); // there was an error // do the marking in this thread - disableFork(); + disableFork(); if (ConservativeGC.isPrecise) markAll!markPrecise(nostack); else From 412e6ecbb4d30ed4565eb2df6fd1fc49294bf3d6 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Sat, 11 May 2019 16:05:58 +0200 Subject: [PATCH 06/47] whitespace before parenthesis --- src/gc/impl/conservative/gc.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index b3d0208494..81fd4e813a 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2743,7 +2743,7 @@ Lmark: // if we get here, forking failed and a standard STW collection got issued // threads were suspended again, restart them - if(doFork) + if (doFork) thread_suspendAll(); thread_processGCMarks(&isMarked); From 5ebfe3d8e12fc470b761ba61423cad34176dde25 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:03:46 +0200 Subject: [PATCH 07/47] mmap as parameter not attribute --- src/gc/bits.d | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gc/bits.d b/src/gc/bits.d index df6ded0fb2..b908486dc9 100644 --- a/src/gc/bits.d +++ b/src/gc/bits.d @@ -34,13 +34,12 @@ struct GCBits enum BITS_MASK = (BITS_PER_WORD - 1); enum BITS_0 = cast(wordtype)0; enum BITS_1 = cast(wordtype)1; - bool mmap; enum BITS_2 = cast(wordtype)2; wordtype* data; size_t nbits; - void Dtor() nothrow @nogc + void Dtor(bool mmap) nothrow @nogc { if (data) { From 89b9c485d01f46803a83702aaed350348353934f Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:04:59 +0200 Subject: [PATCH 08/47] rebase --- src/gc/bits.d | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gc/bits.d b/src/gc/bits.d index b908486dc9..4992518ee4 100644 --- a/src/gc/bits.d +++ b/src/gc/bits.d @@ -55,12 +55,11 @@ struct GCBits { this.nbits = nbits; if (mmap) - data = cast(typeof(data[0])*)os_mem_map(nwords * data[0].sizeof, true); // Allocate as MAP_SHARED + data = cast(typeof(data[0])*)os_mem_map(nwords * data[0].sizeof, true); // Allocate as MAP_SHARED else data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); if (!data) onOutOfMemoryError(); - this.mmap = mmap; } wordtype test(size_t i) const nothrow @nogc From 24b4b4ecbd7d3ef13937a91b15ce299f1cdb2995 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:09:15 +0200 Subject: [PATCH 09/47] dashes and comments on same line --- src/core/gc/config.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/gc/config.d b/src/core/gc/config.d index 39dea8d041..48ca48b74a 100644 --- a/src/core/gc/config.d +++ b/src/core/gc/config.d @@ -15,7 +15,7 @@ __gshared Config config; struct Config { bool disable; // start disabled - bool fork = false; // optional concurrent behaviour + bool fork = false; // optional concurrent behaviour ubyte profile; // enable profiling with summary when terminating program string gc = "conservative"; // select gc implementation conservative|precise|manual @@ -40,7 +40,7 @@ struct Config printf("GC options are specified as white space separated assignments: disable:0|1 - start disabled (%d) - fork:0|1 - set fork behaviour (disabled by default) (%d) + fork:0|1 - set fork behaviour (disabled by default) (%d) profile:0|1|2 - enable profiling with summary when terminating program (%d) gc:".ptr, disable, profile); foreach (i, entry; registeredGCFactories) From b71bc05196d77d44a4b267dc0112fa025a7869e4 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:09:51 +0200 Subject: [PATCH 10/47] style changes as per review --- src/gc/os.d | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/gc/os.d b/src/gc/os.d index 2d4cc8ac55..b035f43b5e 100644 --- a/src/gc/os.d +++ b/src/gc/os.d @@ -50,24 +50,22 @@ else version (Posix) import core.stdc.stdlib; - /** - * Possible results for the wait_pid() function. - */ - enum WRes + /// Possible results for the wait_pid() function. + enum ChildStatus { - DONE, /// The process has finished successfully - RUNNING, /// The process is still running - ERROR /// There was an error waiting for the process + done, /// The process has finished successfully + running, /// The process is still running + error /// There was an error waiting for the process } /** * Wait for a process with PID pid to finish. * - * If block is false, this function will not block, and return WRes.RUNNING if - * the process is still running. Otherwise it will return always WRes.DONE - * (unless there is an error, in which case WRes.ERROR is returned). + * If block is false, this function will not block, and return ChildStatus.RUNNING if + * the process is still running. Otherwise it will return always ChildStatus.DONE + * (unless there is an error, in which case ChildStatus.ERROR is returned). */ - WRes wait_pid(pid_t pid, bool block = true) nothrow @nogc + ChildStatus wait_pid(pid_t pid, bool block = true) nothrow @nogc { int status = void; pid_t waited_pid = void; @@ -79,12 +77,12 @@ else version (Posix) } while (waited_pid == -1 && errno == EINTR); if (waited_pid == 0) - return WRes.RUNNING; + return ChildStatus.running; assert (waited_pid == pid); assert (status == 0); if (waited_pid != pid || status != 0) - return WRes.ERROR; - return WRes.DONE; + return ChildStatus.error; + return ChildStatus.done; } public import core.sys.posix.unistd: pid_t, fork; @@ -116,12 +114,12 @@ else static assert(false, "No supported allocation methods available."); * * The value shown here is just demostrative, the real value is defined based * on the OS it's being compiled in. - * enum HAVE_FORK = true; + * enum HaveFork = true; */ static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) { - enum { HAVE_FORK = false } + enum HaveFork = false; /** * Map memory. @@ -146,7 +144,7 @@ static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) } else static if (is(typeof(mmap))) // else version (GC_Use_Alloc_MMap) { - enum { HAVE_FORK = true } + enum HaveFork = true; void *os_mem_map(size_t nbytes, bool share = false) nothrow @nogc { void *p; @@ -164,7 +162,7 @@ else static if (is(typeof(mmap))) // else version (GC_Use_Alloc_MMap) } else static if (is(typeof(valloc))) // else version (GC_Use_Alloc_Valloc) { - enum { HAVE_FORK = false } + enum HaveFork = false; void *os_mem_map(size_t nbytes) nothrow @nogc { @@ -185,7 +183,7 @@ else static if (is(typeof(malloc))) // else version (GC_Use_Alloc_Malloc) // to PAGESIZE alignment, there will be space for a void* at the end // after PAGESIZE bytes used by the GC. - enum { HAVE_FORK = false } + enum HaveFork = false; import gc.gc; From a8053906aa9d048b9da9da154d3a1c7a99c2df5f Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:10:29 +0200 Subject: [PATCH 11/47] import _Exit from core.posix --- src/gc/impl/conservative/gc.d | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 81fd4e813a..fe7335d5bc 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -57,11 +57,6 @@ else import core.stdc.stdio : sprintf, printf; // needed to ou import core.time; alias currTime = MonoTime.currTime; -extern (C) nothrow @nogc -{ - void _Exit(int); -} - // Track total time spent preparing for GC, // marking, sweeping and recovering pages. __gshared Duration prepTime; From 53c6786418c25086318fadec2040e42d4e2c1c9d Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:10:50 +0200 Subject: [PATCH 12/47] markbits are mmapped --- src/gc/impl/conservative/gc.d | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index fe7335d5bc..1692b00735 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -3213,26 +3213,26 @@ struct Pool bPageOffsets = null; } - mark.Dtor(); + mark.Dtor(true); if (ConservativeGC.isPrecise) { if (isLargeObject) cstdlib.free(rtinfo); else - is_pointer.Dtor(); + is_pointer.Dtor(false); } if (isLargeObject) { - nointerior.Dtor(); + nointerior.Dtor(true); } else { - freebits.Dtor(); + freebits.Dtor(true); } - finals.Dtor(); - structFinals.Dtor(); - noscan.Dtor(); - appendable.Dtor(); + finals.Dtor(false); + structFinals.Dtor(false); + noscan.Dtor(false); + appendable.Dtor(false); } /** From 708cd4bc5a48adfc935e335fa1cb34742f9d5dbd Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:11:26 +0200 Subject: [PATCH 13/47] redundant code --- src/gc/impl/conservative/gc.d | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 1692b00735..a8bb67c57a 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -1757,7 +1757,6 @@ struct Gcx assert(pool); debug(PRINTF) printFreeInfo(&pool.base); - pool.pagetable[pn] = B_PAGE; if (npages > 1) memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1); pool.mark.set(pn); @@ -3958,8 +3957,6 @@ struct SmallObjectPool // Convert page to free list size_t size = binsize[bin]; void* p = baseAddr + pn * PAGESIZE; - size_t biti = pn * (PAGESIZE / 16); - base.freebits.set(biti); auto first = cast(List*) p; From afacea0de52de6b1551aff575d014815a3742288 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:12:22 +0200 Subject: [PATCH 14/47] style changes --- src/gc/impl/conservative/gc.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index a8bb67c57a..f8fc14587f 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -998,7 +998,7 @@ class ConservativeGC : GC /** * Do full garbage collection. - The collection is done concurrently only if block is false. + * The collection is done concurrently only if block is false. * Return number of pages free'd. */ size_t fullCollect() nothrow @@ -1651,7 +1651,7 @@ struct Gcx if (!newPool(1, false)) { // out of memory => try to free some memory - fullcollect(false, true); // stop the worlds + fullcollect(false, true); // stop the world if (lowMem) minimize(); recoverNextPage(bin); From d3fe6ad574d09c8facc70172f9162b1d3c256fc6 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:13:00 +0200 Subject: [PATCH 15/47] moved functions as per review --- src/gc/impl/conservative/gc.d | 179 ++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 74 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index f8fc14587f..7c897b9c57 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2580,6 +2580,98 @@ struct Gcx markProcPid = 0; } + ChildStatus collectFork(bool block) nothrow + { + typeof(return) rc = wait_pid(markProcPid, block); + final switch (rc) + { + case ChildStatus.done: + debug(COLLECT_PRINTF) printf("\t\tmark proc DONE (block=%d)\n", + cast(int) block); + markProcPid = 0; + // process GC marks then sweep + thread_suspendAll(); + thread_processGCMarks(&isMarked); + thread_resumeAll(); + break; + case ChildStatus.running: + debug(COLLECT_PRINTF) printf("\t\tmark proc RUNNING\n"); + if (!block) + break; + // Something went wrong, if block is true, wait() should never + // return RUNNING. + goto case ChildStatus.error; + case ChildStatus.error: + debug(COLLECT_PRINTF) printf("\t\tmark proc ERROR\n"); + // Try to keep going without forking + // and do the marking in this thread + break; + } + return rc; + } + + ChildStatus markFork(bool nostack, bool block, const MonoTime begin, ref MonoTime start, ref MonoTime stop, ref Duration markTime) nothrow + { + // Forking is enabled, so we fork() and start a new concurrent mark phase + // in the child. If the collection should not block, the parent process + // tells the caller no memory could be recycled immediately (if this collection + // was triggered by an allocation, the caller should allocate more memory + // to fulfill the request). + // If the collection should block, the parent will wait for the mark phase + // to finish before returning control to the mutator, + // but other threads are restarted and may run in parallel with the mark phase + // (unless they allocate or use the GC themselves, in which case + // the global GC lock will stop them). + // fork now and sweep later + import core.stdc.stdio; + import core.stdc.stdlib : _Exit; // TODO VERSION COLLECT_FORK + + fflush(null); // avoid duplicated FILE* output + auto pid = fork(); + assert(pid != -1); // TODO handle case + switch (pid) + { + case -1: // fork() failed, retry without forking + return ChildStatus.error; + case 0: // child process + if (ConservativeGC.isPrecise) + markAll!markPrecise(nostack); + else + markAll!markConservative(nostack); + _Exit(0); + return ChildStatus.done; // bogus + default: // the parent + thread_resumeAll(); + if (!block) + { + markProcPid = pid; + // update profiling informations + stop = currTime; + markTime += (stop - start); + Duration pause = stop - begin; + if (pause > maxPauseTime) + maxPauseTime = pause; + pauseTime += pause; + + return ChildStatus.running; + } + ChildStatus r = wait_pid(pid); // block until marking is done + assert(r == ChildStatus.done); + assert(r != ChildStatus.running); + if (r == ChildStatus.error) + { + thread_suspendAll(); + // there was an error + // do the marking in this thread + disableFork(); + if (ConservativeGC.isPrecise) + markAll!markPrecise(nostack); + else + markAll!markConservative(nostack); + } + } + return ChildStatus.done; // waited for the child + } /** * Return number of full pages free'd. @@ -2614,33 +2706,16 @@ struct Gcx // we redo the mark phase without forking. if (collectInProgress && doFork) { - WRes rc = wait_pid(markProcPid, block); - switch (rc) + ChildStatus rc = collectFork(block); + final switch (rc) { - case WRes.DONE: - debug(COLLECT_PRINTF) printf("\t\tmark proc DONE (block=%d)\n", - cast(int) block); - markProcPid = 0; - // process GC marks then sweep - thread_suspendAll(); - thread_processGCMarks(&isMarked); - thread_resumeAll(); + case ChildStatus.done: break; - case WRes.RUNNING: - debug(COLLECT_PRINTF) printf("\t\tmark proc RUNNING\n"); - if (!block) - return 0; - // Something went wrong, if block is true, wait() should never - // returned RUNNING. - goto case WRes.ERROR; - case WRes.ERROR: - debug(COLLECT_PRINTF) printf("\t\tmark proc ERROR\n"); - // Try to keep going without forking - // and do the marking in this thread + case ChildStatus.running: + return 0; + case ChildStatus.error: disableFork(); goto Lmark; - default: - assert(false, "Unknown wait_pid() result"); } } else @@ -2664,62 +2739,18 @@ Lmark: prepTime += (stop - start); start = stop; - // Forking is enabled, so we fork() and start a new concurrent mark phase - // in the child. If the collection should not block, the parent process - // tells the caller no memory could be recycled immediately (if this collection - // was triggered by an allocation, the caller should allocate more memory - // to fulfill the request). - // If the collection should block, the parent will wait for the mark phase - // to finish before returning control to the mutator, - // but other threads are restarted and may run in parallel with the mark phase - // (unless they allocate or use the GC themselves, in which case - // the global GC lock will stop them). if (doFork) { - // fork now and sweep later - fflush(null); // avoid duplicated FILE* output - auto pid = fork(); - assert(pid != -1); // TODO handle case - switch (pid) + auto forkResult = markFork(nostack, block, begin, start, stop, markTime); + final switch (forkResult) { - case -1: // fork() failed, retry without forking + case ChildStatus.error: disableFork(); goto Lmark; - case 0: // child process - if (ConservativeGC.isPrecise) - markAll!markPrecise(nostack); - else - markAll!markConservative(nostack); - _Exit(0); - break; // bogus - default: // the parent - thread_resumeAll(); - if (!block) - { - markProcPid = pid; - // update profiling informations - stop = currTime; - markTime += (stop - start); - Duration pause = stop - begin; - if (pause > maxPauseTime) - maxPauseTime = pause; - pauseTime += pause; - return 0; - } - WRes r = wait_pid(pid); // block until marking is done - assert(r == WRes.DONE); - assert(r != WRes.RUNNING); - if (r == WRes.ERROR) - { - thread_suspendAll(); - // there was an error - // do the marking in this thread - disableFork(); - if (ConservativeGC.isPrecise) - markAll!markPrecise(nostack); - else - markAll!markConservative(nostack); - } + case ChildStatus.running: + return 0; + case ChildStatus.done: + break; } } else if (doParallel) From c634dfe1f280eadd1b41cbff53fd396e9e696cf1 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 16 May 2019 23:13:21 +0200 Subject: [PATCH 16/47] throws an error, still recursive locking --- src/gc/impl/conservative/gc.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 7c897b9c57..fdba9cfb07 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -165,6 +165,8 @@ class ConservativeGC : GC this() { //config is assumed to have already been initialized + if (config.fork && config.parallel > 0) + throw new Error("No reason in mixing threads and fork"); gcx = cast(Gcx*)cstdlib.calloc(1, Gcx.sizeof); if (!gcx) From e7117b2690a46959eb4820c3119fcff78760af7c Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Mon, 17 Jun 2019 11:19:36 +0200 Subject: [PATCH 17/47] redundant code --- src/gc/impl/conservative/gc.d | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index fdba9cfb07..f2cc2615fb 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -1759,8 +1759,6 @@ struct Gcx assert(pool); debug(PRINTF) printFreeInfo(&pool.base); - if (npages > 1) - memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1); pool.mark.set(pn); usedLargePages += npages; pool.freepages -= npages; From 8f396e0383a324d19e400212b86b4395ebc1a07c Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Mon, 17 Jun 2019 11:24:46 +0200 Subject: [PATCH 18/47] indentation and lowercasing --- src/gc/os.d | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/gc/os.d b/src/gc/os.d index b035f43b5e..f65c3c432c 100644 --- a/src/gc/os.d +++ b/src/gc/os.d @@ -53,17 +53,17 @@ else version (Posix) /// Possible results for the wait_pid() function. enum ChildStatus { - done, /// The process has finished successfully - running, /// The process is still running - error /// There was an error waiting for the process + done, /// The process has finished successfully + running, /// The process is still running + error /// There was an error waiting for the process } /** * Wait for a process with PID pid to finish. * - * If block is false, this function will not block, and return ChildStatus.RUNNING if - * the process is still running. Otherwise it will return always ChildStatus.DONE - * (unless there is an error, in which case ChildStatus.ERROR is returned). + * If block is false, this function will not block, and return ChildStatus.running if + * the process is still running. Otherwise it will return always ChildStatus.done + * (unless there is an error, in which case ChildStatus.error is returned). */ ChildStatus wait_pid(pid_t pid, bool block = true) nothrow @nogc { @@ -98,27 +98,27 @@ else //version = GC_Use_Alloc_Malloc; } -/+ -static if (is(typeof(VirtualAlloc))) - version = GC_Use_Alloc_Win32; -else static if (is(typeof(mmap))) - version = GC_Use_Alloc_MMap; -else static if (is(typeof(valloc))) - version = GC_Use_Alloc_Valloc; -else static if (is(typeof(malloc))) - version = GC_Use_Alloc_Malloc; -else static assert(false, "No supported allocation methods available."); -+/ -/** - * Indicates if an implementation support fork(). - * - * The value shown here is just demostrative, the real value is defined based - * on the OS it's being compiled in. - * enum HaveFork = true; -*/ static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) { + /+ + static if (is(typeof(VirtualAlloc))) + version = GC_Use_Alloc_Win32; + else static if (is(typeof(mmap))) + version = GC_Use_Alloc_MMap; + else static if (is(typeof(valloc))) + version = GC_Use_Alloc_Valloc; + else static if (is(typeof(malloc))) + version = GC_Use_Alloc_Malloc; + else static assert(false, "No supported allocation methods available."); + +/ + /** + * Indicates if an implementation supports fork(). + * + * The value shown here is just demostrative, the real value is defined based + * on the OS it's being compiled in. + * enum HaveFork = true; + */ enum HaveFork = false; /** From 54aeb921e507d2653d1c26e6e9f8395a9c5628ea Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Mon, 17 Jun 2019 11:30:48 +0200 Subject: [PATCH 19/47] renamed bool mmap to share --- src/gc/bits.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gc/bits.d b/src/gc/bits.d index 4992518ee4..c4992fcad2 100644 --- a/src/gc/bits.d +++ b/src/gc/bits.d @@ -39,7 +39,7 @@ struct GCBits wordtype* data; size_t nbits; - void Dtor(bool mmap) nothrow @nogc + void Dtor(bool share = false) nothrow @nogc { if (data) { @@ -51,7 +51,7 @@ struct GCBits } } - void alloc(size_t nbits, bool mmap = false) nothrow + void alloc(size_t nbits, bool share = false) nothrow { this.nbits = nbits; if (mmap) From 0d9251548cc65a5462d6143ef6292c8a20c214c9 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Mon, 17 Jun 2019 14:29:01 +0200 Subject: [PATCH 20/47] throws in case of failed fork --- src/core/exception.d | 15 +++++++++++++++ src/gc/os.d | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/core/exception.d b/src/core/exception.d index fc9bab597e..0daed36a5c 100644 --- a/src/core/exception.d +++ b/src/core/exception.d @@ -594,6 +594,21 @@ extern (C) void onInvalidMemoryOperationError(void* pretend_sideffect = null) @t throw staticError!InvalidMemoryOperationError(); } +/** + * A callback for errors in the case of a failed fork in D. A $(LREF ForkError) will be thrown. + * + * Params: + * file = The name of the file that signaled this error. + * line = The line number on which this error occurred. + * + * Throws: + * $(LREF ConfigurationError). + */ +extern (C) void onForkError( string file = __FILE__, size_t line = __LINE__ ) @trusted pure nothrow @nogc +{ + throw staticError!ForkError( file, line, null ); +} + /** * A callback for unicode errors in D. A $(LREF UnicodeException) will be thrown. * diff --git a/src/gc/os.d b/src/gc/os.d index f65c3c432c..9e0041de99 100644 --- a/src/gc/os.d +++ b/src/gc/os.d @@ -67,6 +67,8 @@ else version (Posix) */ ChildStatus wait_pid(pid_t pid, bool block = true) nothrow @nogc { + import core.exception : onForkError; + int status = void; pid_t waited_pid = void; // In the case where we are blocking, we need to consider signals @@ -78,10 +80,11 @@ else version (Posix) while (waited_pid == -1 && errno == EINTR); if (waited_pid == 0) return ChildStatus.running; - assert (waited_pid == pid); - assert (status == 0); if (waited_pid != pid || status != 0) + { + onForkError(); return ChildStatus.error; + } return ChildStatus.done; } From 91e8a26b9331dafd2450544b40d7f8ac22d19025 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Mon, 17 Jun 2019 14:29:46 +0200 Subject: [PATCH 21/47] added configuration error: can't mix parallel and fork --- src/core/exception.d | 43 +++++++++++++++++++++++++++++++++++ src/gc/impl/conservative/gc.d | 3 ++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/core/exception.d b/src/core/exception.d index 0daed36a5c..bc754a96b4 100644 --- a/src/core/exception.d +++ b/src/core/exception.d @@ -19,6 +19,31 @@ void __switch_errorT()(string file = __FILE__, size_t line = __LINE__) @trusted assert(0, "No appropriate switch clause found"); } + +/** + * Thrown on a configuration error. + */ +class ConfigurationError : Error +{ + this( string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null ) @nogc nothrow pure @safe + { + super( msg, file, line, next ); + } +} + + +/** + * Thrown on a configuration error. + */ +class ForkError : Error +{ + this( string file = __FILE__, size_t line = __LINE__, Throwable next = null ) @nogc nothrow pure @safe + { + super( "fork() failed", file, line, next ); + } +} + + version (D_BetterC) { // When compiling with -betterC we use template functions so if they are @@ -594,6 +619,24 @@ extern (C) void onInvalidMemoryOperationError(void* pretend_sideffect = null) @t throw staticError!InvalidMemoryOperationError(); } + +/** + * A callback for runtime configuration errors in D. A $(LREF ConfigurationError) will be thrown. + * + * Params: + * msg = information about the error + * file = The name of the file that signaled this error. + * line = The line number on which this error occurred. + * + * Throws: + * $(LREF ConfigurationError). + */ +extern (C) void onConfigurationError( string msg, string file = __FILE__, size_t line = __LINE__ ) @trusted pure nothrow @nogc +{ + throw staticError!ConfigurationError( msg, file, line, null ); +} + + /** * A callback for errors in the case of a failed fork in D. A $(LREF ForkError) will be thrown. * diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index f2cc2615fb..0e7614c46a 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -164,9 +164,10 @@ class ConservativeGC : GC this() { + import core.exception : onConfigurationError; //config is assumed to have already been initialized if (config.fork && config.parallel > 0) - throw new Error("No reason in mixing threads and fork"); + onConfigurationError("Can't mix threads and fork strategies for GC marking"); gcx = cast(Gcx*)cstdlib.calloc(1, Gcx.sizeof); if (!gcx) From a44273773748261bf5abb538d81b0f28746ee697 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Mon, 17 Jun 2019 14:35:07 +0200 Subject: [PATCH 22/47] added a version switch for the fork() behaviour --- src/gc/impl/conservative/gc.d | 106 ++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 37 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 0e7614c46a..ed74d3e702 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -34,6 +34,9 @@ module gc.impl.conservative.gc; /***************************************************/ version = COLLECT_PARALLEL; // parallel scanning +version (Posix) + version = COLLECT_FORK; +version = COLLECT_FORK; import gc.bits; import gc.os; @@ -1229,8 +1232,12 @@ struct Gcx auto rangesLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); Treap!Root roots; Treap!Range ranges; - private pid_t markProcPid = 0; private bool doMinimize = false; + version (COLLECT_FORK) + { + private pid_t markProcPid = 0; + bool shouldFork; + } debug(INVARIANT) bool initialized; debug(INVARIANT) bool inCollection; @@ -1266,6 +1273,9 @@ struct Gcx mappedPages = 0; //printf("gcx = %p, self = %x\n", &this, self); debug(INVARIANT) initialized = true; + version (COLLECT_FORK) + shouldFork = config.fork; + } @@ -1377,7 +1387,10 @@ struct Gcx @property bool collectInProgress() const nothrow { - return markProcPid != 0; + version (COLLECT_FORK) + return markProcPid != 0; + else + return false; } @@ -2577,10 +2590,14 @@ struct Gcx void disableFork() nothrow { - config.fork = false; - markProcPid = 0; + version (COLLECT_FORK) + { + markProcPid = 0; + shouldFork = false; + } } + version (COLLECT_FORK) ChildStatus collectFork(bool block) nothrow { typeof(return) rc = wait_pid(markProcPid, block); @@ -2611,7 +2628,9 @@ struct Gcx return rc; } - ChildStatus markFork(bool nostack, bool block, const MonoTime begin, ref MonoTime start, ref MonoTime stop, ref Duration markTime) nothrow + version (COLLECT_FORK) + ChildStatus markFork(bool nostack, bool block, const MonoTime begin, ref MonoTime start, ref MonoTime stop) nothrow + { // Forking is enabled, so we fork() and start a new concurrent mark phase // in the child. If the collection should not block, the parent process @@ -2625,11 +2644,12 @@ struct Gcx // the global GC lock will stop them). // fork now and sweep later import core.stdc.stdio; - import core.stdc.stdlib : _Exit; // TODO VERSION COLLECT_FORK + version (COLLECT_FORK) + import core.stdc.stdlib : _Exit; fflush(null); // avoid duplicated FILE* output auto pid = fork(); - assert(pid != -1); // TODO handle case + assert(pid != -1); switch (pid) { case -1: // fork() failed, retry without forking @@ -2684,6 +2704,17 @@ struct Gcx // part of `thread_attachThis` implementation). In that case it is // better not to try actually collecting anything + version (COLLECT_FORK) + bool doFork = shouldFork; + else + enum doFork = false; + + version (COLLECT_PARALLEL) + bool doParallel = config.parallel > 0; + else + enum doParallel = false; + + if (Thread.getThis() is null) return 0; @@ -2691,13 +2722,6 @@ struct Gcx begin = start = currTime; debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n"); - bool doFork = config.fork; - version (COLLECT_PARALLEL) - bool doParallel = config.parallel > 0; - else - enum doParallel = false; - - assert(!(doFork && doParallel), "No reason in mixing threads and fork"); //printf("\tpool address range = %p .. %p\n", minAddr, maxAddr); // If there is a mark process running, check if it already finished. @@ -2705,18 +2729,20 @@ struct Gcx // If it's still running, either we block until the mark phase is // done (and then sweep to finish the collection), or in case of error // we redo the mark phase without forking. - if (collectInProgress && doFork) + if (doFork && collectInProgress) { - ChildStatus rc = collectFork(block); - final switch (rc) - { - case ChildStatus.done: - break; - case ChildStatus.running: - return 0; - case ChildStatus.error: - disableFork(); - goto Lmark; + version (COLLECT_FORK){ + ChildStatus rc = collectFork(block); + final switch (rc) + { + case ChildStatus.done: + break; + case ChildStatus.running: + return 0; + case ChildStatus.error: + disableFork(); + goto Lmark; + } } } else @@ -2742,16 +2768,19 @@ Lmark: if (doFork) { - auto forkResult = markFork(nostack, block, begin, start, stop, markTime); - final switch (forkResult) + version (COLLECT_FORK) { - case ChildStatus.error: - disableFork(); - goto Lmark; - case ChildStatus.running: - return 0; - case ChildStatus.done: - break; + auto forkResult = markFork(nostack, block, begin, start, stop); + final switch (forkResult) + { + case ChildStatus.error: + disableFork(); + goto Lmark; + case ChildStatus.running: + return 0; + case ChildStatus.done: + break; + } } } else if (doParallel) @@ -3154,7 +3183,10 @@ struct Pool topAddr = baseAddr + poolsize; auto nbits = cast(size_t)poolsize >> shiftBy; - mark.alloc(nbits, true); + version (COLLECT_FORK) + mark.alloc(nbits, true); + else + mark.alloc(nbits); if (ConservativeGC.isPrecise) { if (isLargeObject) @@ -3254,11 +3286,11 @@ struct Pool } if (isLargeObject) { - nointerior.Dtor(true); + nointerior.Dtor(); } else { - freebits.Dtor(true); + freebits.Dtor(); } finals.Dtor(false); structFinals.Dtor(false); From b6620eacdfc2f20565aaf583f4c6f0333121fd3f Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 08:58:37 +0200 Subject: [PATCH 23/47] use mem_map and mem_unmap only when requested --- src/gc/bits.d | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gc/bits.d b/src/gc/bits.d index c4992fcad2..dbd028c596 100644 --- a/src/gc/bits.d +++ b/src/gc/bits.d @@ -13,7 +13,7 @@ */ module gc.bits; -import gc.os : os_mem_map, os_mem_unmap; +import gc.os : os_mem_map, os_mem_unmap, HaveFork; import core.bitop; import core.stdc.string; @@ -43,7 +43,7 @@ struct GCBits { if (data) { - if (mmap) + if (HaveFork && share) os_mem_unmap(data, nwords * data[0].sizeof); else free(data); @@ -54,7 +54,7 @@ struct GCBits void alloc(size_t nbits, bool share = false) nothrow { this.nbits = nbits; - if (mmap) + if (HaveFork && share) data = cast(typeof(data[0])*)os_mem_map(nwords * data[0].sizeof, true); // Allocate as MAP_SHARED else data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); From 92d1223a0e3385433924ddbadf5792adee1c3d31 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 08:59:35 +0200 Subject: [PATCH 24/47] redundant version(COLLECT_FORK) --- src/gc/impl/conservative/gc.d | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index ed74d3e702..5f63d2bd91 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -36,7 +36,6 @@ module gc.impl.conservative.gc; version = COLLECT_PARALLEL; // parallel scanning version (Posix) version = COLLECT_FORK; -version = COLLECT_FORK; import gc.bits; import gc.os; From 61541c90498d00b4fd8fe85a43291da94b4c4d43 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 08:59:58 +0200 Subject: [PATCH 25/47] redundant code --- src/gc/impl/conservative/gc.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 5f63d2bd91..f254e8894e 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -1772,9 +1772,9 @@ struct Gcx assert(pool); debug(PRINTF) printFreeInfo(&pool.base); - pool.mark.set(pn); + if (collectInProgress) + pool.mark.set(pn); usedLargePages += npages; - pool.freepages -= npages; debug(PRINTF) printFreeInfo(&pool.base); From fe28004e6c45bd63d54da6486c48103c386d994c Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:00:18 +0200 Subject: [PATCH 26/47] bits dtor called implicitly and on config.fork --- src/gc/impl/conservative/gc.d | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index f254e8894e..598cef1c5e 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -3183,7 +3183,7 @@ struct Pool auto nbits = cast(size_t)poolsize >> shiftBy; version (COLLECT_FORK) - mark.alloc(nbits, true); + mark.alloc(nbits, config.fork); else mark.alloc(nbits); if (ConservativeGC.isPrecise) @@ -3275,7 +3275,7 @@ struct Pool bPageOffsets = null; } - mark.Dtor(true); + mark.Dtor(config.fork); if (ConservativeGC.isPrecise) { if (isLargeObject) @@ -3291,10 +3291,10 @@ struct Pool { freebits.Dtor(); } - finals.Dtor(false); - structFinals.Dtor(false); - noscan.Dtor(false); - appendable.Dtor(false); + finals.Dtor(); + structFinals.Dtor(); + noscan.Dtor(); + appendable.Dtor(); } /** From 28b76b8ef5635f93bda3bef3ef6347e7f3c96a9d Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:00:47 +0200 Subject: [PATCH 27/47] better imports --- src/gc/impl/conservative/gc.d | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 598cef1c5e..4ad29d0023 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2642,9 +2642,8 @@ struct Gcx // (unless they allocate or use the GC themselves, in which case // the global GC lock will stop them). // fork now and sweep later - import core.stdc.stdio; - version (COLLECT_FORK) - import core.stdc.stdlib : _Exit; + import core.stdc.stdio : fflush; + import core.stdc.stdlib : _Exit; fflush(null); // avoid duplicated FILE* output auto pid = fork(); From b95c49319a7a31383deef5e4e6e3bb76108bfc29 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:17:52 +0200 Subject: [PATCH 28/47] better naming of doMinimize --- src/gc/impl/conservative/gc.d | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 4ad29d0023..80598d819c 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -1231,7 +1231,7 @@ struct Gcx auto rangesLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); Treap!Root roots; Treap!Range ranges; - private bool doMinimize = false; + private bool minimizeAfterNextCollection = false; version (COLLECT_FORK) { private pid_t markProcPid = 0; @@ -1755,13 +1755,13 @@ struct Gcx if (!tryAllocNewPool()) { // disabled but out of memory => try to free some memory - doMinimize = true; + minimizeAfterNextCollection = true; fullcollect(false, true); } } else { - doMinimize = true; + minimizeAfterNextCollection = true; fullcollect(); } // If alloc didn't yet succeed retry now that we collected/minimized @@ -2825,9 +2825,9 @@ Lmark: // minimize() should be called only after a call to fullcollect // terminates with a sweep - if (doMinimize || lowMem) + if (minimizeAfterNextCollection || lowMem) { - doMinimize = false; + minimizeAfterNextCollection = false; minimize(); } From 4605f19201992fbddaf0537fef02f03320716b90 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:18:01 +0200 Subject: [PATCH 29/47] call freebits.setAll for smallPools only --- src/gc/impl/conservative/gc.d | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 80598d819c..4d8dfb5989 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -3208,7 +3208,8 @@ struct Pool freebits.alloc(nbits); freebits.setRange(0, nbits); } - freebits.setAll(); + else + freebits.setAll(); noscan.alloc(nbits); appendable.alloc(nbits); From 65a029d9d08c727956eeaa2e20e7e4de02d66343 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:22:25 +0200 Subject: [PATCH 30/47] update timings outside markFork --- src/gc/impl/conservative/gc.d | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 4d8dfb5989..e70027fa0c 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2628,7 +2628,7 @@ struct Gcx } version (COLLECT_FORK) - ChildStatus markFork(bool nostack, bool block, const MonoTime begin, ref MonoTime start, ref MonoTime stop) nothrow + ChildStatus markFork(bool nostack, bool block) nothrow { // Forking is enabled, so we fork() and start a new concurrent mark phase @@ -2664,14 +2664,6 @@ struct Gcx if (!block) { markProcPid = pid; - // update profiling informations - stop = currTime; - markTime += (stop - start); - Duration pause = stop - begin; - if (pause > maxPauseTime) - maxPauseTime = pause; - pauseTime += pause; - return ChildStatus.running; } ChildStatus r = wait_pid(pid); // block until marking is done @@ -2768,13 +2760,21 @@ Lmark: { version (COLLECT_FORK) { - auto forkResult = markFork(nostack, block, begin, start, stop); + auto forkResult = markFork(nostack, block); final switch (forkResult) { case ChildStatus.error: disableFork(); goto Lmark; case ChildStatus.running: + // update profiling informations + stop = currTime; + markTime += (stop - start); + Duration pause = stop - begin; + if (pause > maxPauseTime) + maxPauseTime = pause; + pauseTime += pause; + return 0; case ChildStatus.done: break; From 00b236b7df4b83fbed421a7a053061d4e0b31b67 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:23:24 +0200 Subject: [PATCH 31/47] formatting --- src/gc/impl/conservative/gc.d | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index e70027fa0c..ea20dcd6b8 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2629,7 +2629,6 @@ struct Gcx version (COLLECT_FORK) ChildStatus markFork(bool nostack, bool block) nothrow - { // Forking is enabled, so we fork() and start a new concurrent mark phase // in the child. If the collection should not block, the parent process From c734c8ef8f5f311f893111ace6a2523f099ea403 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:35:09 +0200 Subject: [PATCH 32/47] allow threaded marking in the child process --- src/core/exception.d | 29 ----------------------------- src/gc/impl/conservative/gc.d | 15 ++++++++------- 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/src/core/exception.d b/src/core/exception.d index bc754a96b4..94576b7445 100644 --- a/src/core/exception.d +++ b/src/core/exception.d @@ -20,18 +20,6 @@ void __switch_errorT()(string file = __FILE__, size_t line = __LINE__) @trusted } -/** - * Thrown on a configuration error. - */ -class ConfigurationError : Error -{ - this( string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null ) @nogc nothrow pure @safe - { - super( msg, file, line, next ); - } -} - - /** * Thrown on a configuration error. */ @@ -620,23 +608,6 @@ extern (C) void onInvalidMemoryOperationError(void* pretend_sideffect = null) @t } -/** - * A callback for runtime configuration errors in D. A $(LREF ConfigurationError) will be thrown. - * - * Params: - * msg = information about the error - * file = The name of the file that signaled this error. - * line = The line number on which this error occurred. - * - * Throws: - * $(LREF ConfigurationError). - */ -extern (C) void onConfigurationError( string msg, string file = __FILE__, size_t line = __LINE__ ) @trusted pure nothrow @nogc -{ - throw staticError!ConfigurationError( msg, file, line, null ); -} - - /** * A callback for errors in the case of a failed fork in D. A $(LREF ForkError) will be thrown. * diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index ea20dcd6b8..8164ad931b 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -166,10 +166,7 @@ class ConservativeGC : GC this() { - import core.exception : onConfigurationError; //config is assumed to have already been initialized - if (config.fork && config.parallel > 0) - onConfigurationError("Can't mix threads and fork strategies for GC marking"); gcx = cast(Gcx*)cstdlib.calloc(1, Gcx.sizeof); if (!gcx) @@ -2628,7 +2625,7 @@ struct Gcx } version (COLLECT_FORK) - ChildStatus markFork(bool nostack, bool block) nothrow + ChildStatus markFork(bool nostack, bool block, bool doParallel) nothrow { // Forking is enabled, so we fork() and start a new concurrent mark phase // in the child. If the collection should not block, the parent process @@ -2652,7 +2649,9 @@ struct Gcx case -1: // fork() failed, retry without forking return ChildStatus.error; case 0: // child process - if (ConservativeGC.isPrecise) + if (doParallel) + markParallel(nostack); + else if (ConservativeGC.isPrecise) markAll!markPrecise(nostack); else markAll!markConservative(nostack); @@ -2674,7 +2673,9 @@ struct Gcx // there was an error // do the marking in this thread disableFork(); - if (ConservativeGC.isPrecise) + if (doParallel) + markParallel(nostack); + else if (ConservativeGC.isPrecise) markAll!markPrecise(nostack); else markAll!markConservative(nostack); @@ -2759,7 +2760,7 @@ Lmark: { version (COLLECT_FORK) { - auto forkResult = markFork(nostack, block); + auto forkResult = markFork(nostack, block, doParallel); final switch (forkResult) { case ChildStatus.error: From b0f3542384de01e26e8bbbc1d988333eda139758 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:41:40 +0200 Subject: [PATCH 33/47] moved comments back --- src/gc/os.d | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/gc/os.d b/src/gc/os.d index 9e0041de99..e2e17bc929 100644 --- a/src/gc/os.d +++ b/src/gc/os.d @@ -101,20 +101,20 @@ else //version = GC_Use_Alloc_Malloc; } +/+ +static if (is(typeof(VirtualAlloc))) + version = GC_Use_Alloc_Win32; +else static if (is(typeof(mmap))) + version = GC_Use_Alloc_MMap; +else static if (is(typeof(valloc))) + version = GC_Use_Alloc_Valloc; +else static if (is(typeof(malloc))) + version = GC_Use_Alloc_Malloc; +else static assert(false, "No supported allocation methods available."); ++/ static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) { - /+ - static if (is(typeof(VirtualAlloc))) - version = GC_Use_Alloc_Win32; - else static if (is(typeof(mmap))) - version = GC_Use_Alloc_MMap; - else static if (is(typeof(valloc))) - version = GC_Use_Alloc_Valloc; - else static if (is(typeof(malloc))) - version = GC_Use_Alloc_Malloc; - else static assert(false, "No supported allocation methods available."); - +/ /** * Indicates if an implementation supports fork(). * From 0ea99daa2e6459d6c9bd175951caebe7da001243 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:41:51 +0200 Subject: [PATCH 34/47] remove freebits.setAll call --- src/gc/impl/conservative/gc.d | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 8164ad931b..2e0f9c48c8 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -3208,8 +3208,6 @@ struct Pool freebits.alloc(nbits); freebits.setRange(0, nbits); } - else - freebits.setAll(); noscan.alloc(nbits); appendable.alloc(nbits); From 8856570f1a6ae1e929c6eb842cbac71509653188 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:44:57 +0200 Subject: [PATCH 35/47] fork enabled temporarely for CI --- src/core/gc/config.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/gc/config.d b/src/core/gc/config.d index 48ca48b74a..5bcf1ee4e8 100644 --- a/src/core/gc/config.d +++ b/src/core/gc/config.d @@ -15,7 +15,7 @@ __gshared Config config; struct Config { bool disable; // start disabled - bool fork = false; // optional concurrent behaviour + bool fork = true; // optional concurrent behaviour ubyte profile; // enable profiling with summary when terminating program string gc = "conservative"; // select gc implementation conservative|precise|manual From 6a4e909431590ae9f1d0dff1ae7862e739b0bc8a Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 18 Jun 2019 09:52:02 +0200 Subject: [PATCH 36/47] style --- src/gc/impl/conservative/gc.d | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 2e0f9c48c8..25cf59b000 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2640,7 +2640,6 @@ struct Gcx // fork now and sweep later import core.stdc.stdio : fflush; import core.stdc.stdlib : _Exit; - fflush(null); // avoid duplicated FILE* output auto pid = fork(); assert(pid != -1); From d2f450686d0a4683914bf9b75ead4d28f0a3233a Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 25 Jun 2019 11:07:46 +0200 Subject: [PATCH 37/47] solve compilation error on OSes that do not support fork() --- src/gc/bits.d | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gc/bits.d b/src/gc/bits.d index dbd028c596..376d363a8b 100644 --- a/src/gc/bits.d +++ b/src/gc/bits.d @@ -43,7 +43,9 @@ struct GCBits { if (data) { - if (HaveFork && share) + static if (!HaveFork) + free(data); + else if (share) os_mem_unmap(data, nwords * data[0].sizeof); else free(data); @@ -54,7 +56,9 @@ struct GCBits void alloc(size_t nbits, bool share = false) nothrow { this.nbits = nbits; - if (HaveFork && share) + static if (!HaveFork) + data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); + if (share) data = cast(typeof(data[0])*)os_mem_map(nwords * data[0].sizeof, true); // Allocate as MAP_SHARED else data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); From c0e3539cc221abc0883ef827c101adda39daf3d2 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 25 Jun 2019 12:01:39 +0200 Subject: [PATCH 38/47] forgot else statement --- src/gc/bits.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gc/bits.d b/src/gc/bits.d index 376d363a8b..ce6b200b52 100644 --- a/src/gc/bits.d +++ b/src/gc/bits.d @@ -58,7 +58,7 @@ struct GCBits this.nbits = nbits; static if (!HaveFork) data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); - if (share) + else if (share) data = cast(typeof(data[0])*)os_mem_map(nwords * data[0].sizeof, true); // Allocate as MAP_SHARED else data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); From d7ed6bdbc1a3e76bcefd553148c64e8ff1a77d8f Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 4 Jul 2019 14:07:41 +0200 Subject: [PATCH 39/47] change RT-config description --- src/core/gc/config.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/gc/config.d b/src/core/gc/config.d index 5bcf1ee4e8..e900a710ea 100644 --- a/src/core/gc/config.d +++ b/src/core/gc/config.d @@ -40,7 +40,7 @@ struct Config printf("GC options are specified as white space separated assignments: disable:0|1 - start disabled (%d) - fork:0|1 - set fork behaviour (disabled by default) (%d) + fork:0|1 - set fork behaviour (%d) profile:0|1|2 - enable profiling with summary when terminating program (%d) gc:".ptr, disable, profile); foreach (i, entry; registeredGCFactories) From c5eedb34ef5a9595d031a32b1d0db96db2083681 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 4 Jul 2019 14:08:13 +0200 Subject: [PATCH 40/47] no need for private bool --- src/gc/impl/conservative/gc.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 25cf59b000..6c39285689 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -1228,7 +1228,7 @@ struct Gcx auto rangesLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); Treap!Root roots; Treap!Range ranges; - private bool minimizeAfterNextCollection = false; + bool minimizeAfterNextCollection = false; version (COLLECT_FORK) { private pid_t markProcPid = 0; From eab16e9416fc638598457c4f0260ed58d6c4935a Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 4 Jul 2019 14:08:37 +0200 Subject: [PATCH 41/47] pool.mark.set only if collectInprogress --- src/gc/impl/conservative/gc.d | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 6c39285689..6eac5fbe92 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -1689,7 +1689,8 @@ struct Gcx auto biti = (p - pool.baseAddr) >> pool.shiftBy; assert(pool.freebits.test(biti)); - pool.mark.set(biti); // be sure that the child is aware of the page being used + if (collectInProgress) + pool.mark.set(biti); // be sure that the child is aware of the page being used pool.freebits.clear(biti); if (bits) pool.setBits(biti, bits); From f172e9836abc958f30333d6f6a5e0991c24285ce Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 4 Jul 2019 14:08:57 +0200 Subject: [PATCH 42/47] refactoring --- src/gc/impl/conservative/gc.d | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 6eac5fbe92..f32ff21143 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2585,13 +2585,11 @@ struct Gcx return recoverPool[bin] = poolIndex < npools ? cast(SmallObjectPool*)pool : null; } + version (COLLECT_FORK) void disableFork() nothrow { - version (COLLECT_FORK) - { - markProcPid = 0; - shouldFork = false; - } + markProcPid = 0; + shouldFork = false; } version (COLLECT_FORK) @@ -3279,7 +3277,7 @@ struct Pool if (isLargeObject) cstdlib.free(rtinfo); else - is_pointer.Dtor(false); + is_pointer.Dtor(); } if (isLargeObject) { From ce10aa342a14d0a799d909b424d80ddf739a862c Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Thu, 4 Jul 2019 15:15:51 +0200 Subject: [PATCH 43/47] temporary fix for fullCollect on RT termination --- src/gc/impl/conservative/gc.d | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index f32ff21143..0531971aac 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -1038,7 +1038,7 @@ class ConservativeGC : GC // when collecting. static size_t go(Gcx* gcx) nothrow { - return gcx.fullcollect(true, true); // standard stop the world + return gcx.fullcollect(true, true, true); // standard stop the world } runLocked!go(gcx); } @@ -2685,7 +2685,7 @@ struct Gcx /** * Return number of full pages free'd. */ - size_t fullcollect(bool nostack = false, bool block = false) nothrow + size_t fullcollect(bool nostack = false, bool block = false, bool isFinal = false) nothrow { // It is possible that `fullcollect` will be called from a thread which // is not yet registered in runtime (because allocating `new Thread` is @@ -2844,6 +2844,8 @@ Lmark: ++numCollections; updateCollectThresholds(); + if (isFinal) + return fullcollect(true, true, false); return freedPages; } From d84375fa19711f9621333271280659bed188198e Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Fri, 12 Jul 2019 17:28:43 +0200 Subject: [PATCH 44/47] import fflush only in debug --- src/gc/impl/conservative/gc.d | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 0531971aac..0afab96ff8 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -45,7 +45,6 @@ import core.gc.gcinterface; import rt.util.container.treap; import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc; -import core.stdc.stdio : fflush; import core.stdc.string : memcpy, memset, memmove; import core.bitop; import core.thread; @@ -2637,9 +2636,11 @@ struct Gcx // (unless they allocate or use the GC themselves, in which case // the global GC lock will stop them). // fork now and sweep later - import core.stdc.stdio : fflush; import core.stdc.stdlib : _Exit; - fflush(null); // avoid duplicated FILE* output + debug (PRINTF_TO_FILE){ + import core.stdc.stdio : fflush; + fflush(null); // avoid duplicated FILE* output + } auto pid = fork(); assert(pid != -1); switch (pid) From df46a6aa5e891d6fb477d95de3762fffe78da37c Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Fri, 12 Jul 2019 17:28:59 +0200 Subject: [PATCH 45/47] moved asserts --- src/gc/impl/conservative/gc.d | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 0afab96ff8..b54aac5ed7 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -2664,8 +2664,6 @@ struct Gcx return ChildStatus.running; } ChildStatus r = wait_pid(pid); // block until marking is done - assert(r == ChildStatus.done); - assert(r != ChildStatus.running); if (r == ChildStatus.error) { thread_suspendAll(); @@ -2678,6 +2676,9 @@ struct Gcx markAll!markPrecise(nostack); else markAll!markConservative(nostack); + } else { + assert(r == ChildStatus.done); + assert(r != ChildStatus.running); } } return ChildStatus.done; // waited for the child From 6af3c10e87505fd243642847feee584cf6443a0d Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Mon, 15 Jul 2019 18:21:47 +0200 Subject: [PATCH 46/47] ignore echild The gc can ignore ECHILD because that means that child termination was caught by the user already (with a call to wait(0) ) --- src/gc/os.d | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gc/os.d b/src/gc/os.d index e2e17bc929..014ddf9d3f 100644 --- a/src/gc/os.d +++ b/src/gc/os.d @@ -80,7 +80,9 @@ else version (Posix) while (waited_pid == -1 && errno == EINTR); if (waited_pid == 0) return ChildStatus.running; - if (waited_pid != pid || status != 0) + else if (errno == ECHILD) + return ChildStatus.done; // someone called posix.syswait + else if (waited_pid != pid || status != 0) { onForkError(); return ChildStatus.error; @@ -90,7 +92,7 @@ else version (Posix) public import core.sys.posix.unistd: pid_t, fork; import core.sys.posix.sys.wait: waitpid, WNOHANG; - import core.stdc.errno: errno, EINTR; + import core.stdc.errno: errno, EINTR, ECHILD; //version = GC_Use_Alloc_MMap; } From 0bc32ba21fb25f62f45593b0212fe110391c9417 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Wed, 17 Jul 2019 10:53:25 +0200 Subject: [PATCH 47/47] pass fork to gc/config --- src/core/gc/config.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/gc/config.d b/src/core/gc/config.d index e900a710ea..429bd76442 100644 --- a/src/core/gc/config.d +++ b/src/core/gc/config.d @@ -42,7 +42,7 @@ struct Config disable:0|1 - start disabled (%d) fork:0|1 - set fork behaviour (%d) profile:0|1|2 - enable profiling with summary when terminating program (%d) - gc:".ptr, disable, profile); + gc:".ptr, disable, profile, fork); foreach (i, entry; registeredGCFactories) { if (i) printf("|");