Skip to content

cmd/compile: statictmps can prevent heap objects from being collected #29068

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
randall77 opened this issue Dec 3, 2018 · 1 comment
Open
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. GarbageCollector
Milestone

Comments

@randall77
Copy link
Contributor

package main

import (
	"fmt"
	"runtime"
)

var x = [][]int{[]int{1}}

func main() {
	s := make([]int, 1e8)
	runtime.SetFinalizer(&s[0], func(p *int) { fmt.Println("finalized") })
	x[0] = s
	x = nil
	runtime.GC()
	runtime.GC()
	runtime.GC()
}

When I run this program, the finalizer never executes. The big slice will live in the heap forever.

The original state of the program has x pointing to a compiler-allocated global variable statictmp1. That variable has one []int slot in it, which in turn points to a second statictmp2 variable holding a single 1.

When we do x[0]=s, we set statictmp1 to point to the heap instead. Then when we do x = nil, statictmp1 is now unreachable. But statictmp1 now points to an object in the heap, and we still scan statictmp1 at every garbage collection, because it is a global.

If instead we allocated statictmp1 on the heap, this problem would go away. There's a tradeoff here which I'm not sure how to resolve. The current situation prioritizes fast startup and preferring global data over heap data, but it can result in imprecise retention of objects.

A better fix would be to include a global "object" in the GC marking phase for each global variable. (This is similar to how stack objects work.) Named globals would be in the root set, but unnamed ones like the statictmps would only be live if a heap object or other live global pointed to it.

I'm not sure it is worth fixing this problem. I sort of stumbled on it while working on #29013 but I haven't seen any instances in the wild. Thought it would be worth documenting it here in case someone had a better idea or someone found an actual real-world instance.

Note that a similar situation also applies to statictmp2. Afterx[0]=s, statictmp2 is dead. We can't collect statictmp2 because it is allocated in the globals area. We could collect it if it was allocated on the heap. But because it doesn't hold a pointer to heap objects, it isn't a big deal either way. (This also has a parallel to stack objects, which we can't collect directly - we can only ignore their contents.)

@aclements @RLH

@aclements
Copy link
Member

@cherrymui and I were just discussing this and came up with some interesting ideas:

First, and somewhat obviously, we'd split up the "true" globals that are always roots from the coincidental globals that might become unreachable. True globals we could continue to scan exactly as we do today.

For coincidental globals, the GC would need to be able to find object boundaries and pointer/scalar information, and keep mark bits to avoid infinite scan loops (though we wouldn't need to use the mark bits for sweeping).

@cherrymui suggested that we could structure this global space literally the same way we do heap spans. The linker already sorts globals by size; it would just have to round them up to size classes and arrange them in pages appropriately. Then we can find object boundaries in the same way we do for regular heap spans. We could also lay out pointer/scalar information exactly as the heap does, either embedding it at the end of these spans, or as allocation headers. Finally, the linker could write out some minimal description of these spans, which the runtime could realize into literal mspan objects at startup. This would let us reuse a lot of code paths around object finding, pointer/scalar bitmaps, and mark bits. It would also naturally help with a lot of weird corner cases, like what happens if the program attaches a finalizer to one of these objects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. GarbageCollector
Projects
None yet
Development

No branches or pull requests

3 participants