X Tutup
Skip to content

Commit b542cc1

Browse files
committed
gc: add toy tri-color GC implementation
As a minimum, we would like to maintain generic garbage collection of containerd resources. This includes images, bundles, containers and arbitrary content. The included implementation is just a textbook toy that we can use to inform the requirements of gc. It implements a simple, stack-based tricolor algorithm. Nothing special. Mostly, this is to ensure we think about garbage collection from the start, rather than as an afterthought or follow on feature. Signed-off-by: Stephen J Day <stephen.day@docker.com>
1 parent df9c093 commit b542cc1

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

gc/2

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package gc
2+
3+
type color uint8
4+
5+
const (
6+
white = iota
7+
gray
8+
black
9+
)
10+
11+
func tricolor(roots []string, all []string, refs map[string][]string) []string {
12+
// all objects white -> default value of color
13+
state := make(map[string]color, len(all))
14+
var (
15+
grays []string
16+
reachable []string
17+
)
18+
19+
grays = append(grays, roots...)
20+
// root-set objects gray.
21+
for _, ro := range roots {
22+
state[ro] = gray
23+
}
24+
25+
// (Repeat this as long as there are gray coloured objects) Pick a gray
26+
// object. Colour all objects referenced to from that gray object gray too.
27+
// (Except those who are black). And colour itself black.
28+
29+
// Pick any gray object
30+
for id := findcolor(state, gray); id != ""; id = findcolor(state, gray) {
31+
// mark all the referenced objects as gray
32+
for _, target := range refs[id] {
33+
if state[target] == white {
34+
state[target] = gray
35+
}
36+
}
37+
state[id] = black
38+
}
39+
40+
// All black objects are now reachable, and all white objects are
41+
// unreachable. Free those that are white!
42+
var whites []string
43+
for _, obj := range all {
44+
if state[obj] == white {
45+
whites = append(whites, obj)
46+
}
47+
}
48+
49+
return whites
50+
}
51+
52+
func findcolor(cs map[string]color, q color) string {
53+
// TODO(stevvooe): Super-inefficient!
54+
for id, c := range cs {
55+
if c == q {
56+
return id
57+
}
58+
}
59+
60+
return ""
61+
}
62+
63+
// type colorset struct {
64+
// state map[string]color
65+
// }
66+
67+
// func (cs *colorset) mark(id string, c color) {
68+
// cs.state[id] = c
69+
// }

gc/gc.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Package gc experiments with providing central gc tooling to ensure
2+
// deterministic resource removaol within containerd.
3+
//
4+
// For now, we just have a single exported implementation that can be used
5+
// under certain use cases.
6+
package gc
7+
8+
// Tricolor implements basic, single-thread tri-color GC. Given the roots, the
9+
// complete set and a refs function, this returns the unreachable objects.
10+
//
11+
// Correct usage requires that the caller not allow the arguments to change
12+
// until the result is used to delete objects in the system.
13+
//
14+
// It will allocate memory proportional to the size of the reachable set.
15+
//
16+
// We can probably use this to inform a design for incremental GC by injecting
17+
// callbacks to the set modification algorithms.
18+
func Tricolor(roots []string, all []string, refs func(ref string) []string) []string {
19+
var (
20+
grays []string // maintain a gray "stack"
21+
seen = map[string]struct{}{} // or not "white", basically "seen"
22+
reachable = map[string]struct{}{} // or "block", in tri-color parlance
23+
)
24+
25+
grays = append(grays, roots...)
26+
27+
for len(grays) > 0 {
28+
// Pick any gray object
29+
id := grays[len(grays)-1] // effectively "depth first" because first element
30+
grays = grays[:len(grays)-1]
31+
seen[id] = struct{}{} // post-mark this as not-white
32+
33+
// mark all the referenced objects as gray
34+
for _, target := range refs(id) {
35+
if _, ok := seen[target]; !ok {
36+
grays = append(grays, target)
37+
}
38+
}
39+
40+
// mark as black when done
41+
reachable[id] = struct{}{}
42+
}
43+
44+
// All black objects are now reachable, and all white objects are
45+
// unreachable. Free those that are white!
46+
var whites []string
47+
for _, obj := range all {
48+
if _, ok := reachable[obj]; !ok {
49+
whites = append(whites, obj)
50+
}
51+
}
52+
53+
return whites
54+
}

gc/gc_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package gc
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestTricolorBasic(t *testing.T) {
9+
roots := []string{"A", "C"}
10+
all := []string{"A", "B", "C", "D", "E", "F", "G"}
11+
refs := map[string][]string{
12+
"A": {"B"},
13+
"B": {"A"},
14+
"C": {"D", "F", "B"},
15+
"E": {"F", "G"},
16+
}
17+
18+
unreachable := Tricolor(roots, all, lookup(refs))
19+
expected := []string{"E", "G"}
20+
21+
if !reflect.DeepEqual(unreachable, expected) {
22+
t.Fatalf("incorrect unreachable set: %v != %v", unreachable, expected)
23+
}
24+
}
25+
26+
func lookup(refs map[string][]string) func(id string) []string {
27+
return func(ref string) []string {
28+
return refs[ref]
29+
}
30+
}

0 commit comments

Comments
 (0)
X Tutup