SAOC: fork() based garbage collector#2604
Conversation
This is everything necessary to write the core of the logic for the concurrent mark.
Also changed some functions call to reflect their signature
|
Thanks for your pull request and interest in making D better, @FraMecca! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub fetch digger
dub run digger -- build "master + druntime#2604" |
|
Remove the trailing whitespace for the CI to pass: |
rainers
left a comment
There was a problem hiding this comment.
Good stuff! See comments for some nits/questions.
src/core/gc/config.d
Outdated
|
|
||
| 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) |
There was a problem hiding this comment.
The user will probably not care how it's implemented (i.e. that is using fork), but selects the GC to be concurrent. So maybe the option should be the latter. Alternatively, it could also just be another GC named concurrent, but how to combine that with precise?
There was a problem hiding this comment.
I think is not crazy to think that other concurrent GC implementations could come in the future, specially since fork doesn't exist in Windows and such, so maybe it is more future proof to keep it specific. This will also hint the user that the OS where it is used must support fork to work :)
| @@ -43,6 +43,7 @@ import core.gc.gcinterface; | |||
| import rt.util.container.treap; | |||
There was a problem hiding this comment.
I think you should define a version (e.g. COLLECT_FORKED) that is set only for supporting platforms. Most code should only be enabled with that version.
src/gc/impl/conservative/gc.d
Outdated
|
|
||
| // minimize() should be called only after a call to fullcollect | ||
| // terminates with a sweep | ||
| if (doMinimize || lowMem) |
There was a problem hiding this comment.
Can doMinimize be converted to a function parameter instead of a field? It's unlikely to actually find completely unused pool, so it's probably not too bad if it is skipped sometimes.
There was a problem hiding this comment.
I don;t think it can because it needs to be set when:
- lowMem
- tryAllocNewPool fails
we could avoid minimizing after a failed tryAllocNewPool and call minimize() only on lowMem (at least for the forking GC only)
There was a problem hiding this comment.
Maybe it just needs a better name, e.g. minimizeAfterNextCollection.
src/gc/impl/conservative/gc.d
Outdated
| freebits.alloc(nbits); | ||
| freebits.setRange(0, nbits); | ||
| } | ||
| freebits.setAll(); |
There was a problem hiding this comment.
This is already done with the freebits.setRange(0, nbits); call above.
There was a problem hiding this comment.
I get a sigsegv if I replace that call with freebits.setRange(0, nbits);
setAll does this: memset(data, 0xFF, nwords * wordtype.sizeof)
There was a problem hiding this comment.
freebits is only allocated for SmallObjectPool, but setAll is called unconditionally. I suspect setRange doesn't work as expected with nbits == 0.
There was a problem hiding this comment.
Remove this line or replace the freebits.setRange call above with it.
There was a problem hiding this comment.
Why do you want me to remove that line?
It leaks without it while setRange generates a sigsegv because it does a different thing.
I can put it behind an else statement so that it gets called for smallPools only.
There was a problem hiding this comment.
freebits is empty in the else case, so setAll does the same as setRange for small object pools.
parallel is enabled by default so CI test should run using markParallel that uses mark.setLocked. In any case, I can't reproduce the failures on my machine so I don't know how to proceed with debugging CI issues. |
But the |
I've setup a VM with Ubuntu 19 and observe that the std.stdio unittest is running into a ForkError on termination. Maybe this has to do with the unittest also using fork to delete temporary files. |
The gc can ignore ECHILD because that means that child termination was caught by the user already (with a call to wait(0) )
This prompted me to open a PR to phobos dlang/phobos#7111 |
|
I can now reproduce failures by executing The failures seem to go away if I disable either forking or parallel marking. Changing the single call to pool.mark.set during allocation to setLocked didn't help. |
| 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); |
There was a problem hiding this comment.
That's the wrong order, now. It's in the middle in the format string.
| } | ||
|
|
||
| mark.Dtor(); | ||
| mark.Dtor(config.fork); |
There was a problem hiding this comment.
Nit: this is an unconditional call, while mark.alloc uses version(COLLECT_FORK). They should be consistent, probably simpler to leave the evaluation of the share parameter to GCbits and remove the version condition for mark.alloc..
|
I made a couple more experiments with hospital.d:
BTW: the failure shows as a small difference in the results, not a crash. Correct run: Failed run: |
|
If this passes and we disable |
|
Ping @FraMecca. Any chance you can bring this to the finish line? |
|
@wilzbach I am still working from time to time in understanding the source of the bug. If there is something I can do in the meantime let me know. |
|
Happy Birthday to this PR :(. @FraMecca any luck with the bug? |
|
I'm going to close this since #3514 was merged. |
|
Yes, I misread the link description. Thanks! |
Full benchmarks here:
https://gist.github.com/FraMecca/ee50c9f4d1dc16d115dcddce8000cf9f
The other benchmark programs from druntime are not indicative in the same way they weren't for the threaded version.
Benchmark results for vdparser_extended:
fork enabled, average of 20 runs
standard, average of 20 runs:
Benchmark results for
Dustmite --force ../../druntime/src/ 'grep -nR HAVE_FORK':fork enabled, average of 20 runs:
standard, average of 20 runs: