
Marks and sweeps unreferenced objects. Add snapshot cleanup to metadata. Add content garbage collection Add dirty flags for snapshotters and content store which are set on deletion and used during the next garbage collection. Cleanup content store backend when content metadata is removed. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
155 lines
2.8 KiB
Go
155 lines
2.8 KiB
Go
package gc
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestTricolorBasic(t *testing.T) {
|
|
roots := []string{"A", "C"}
|
|
all := []string{"A", "B", "C", "D", "E", "F", "G", "H"}
|
|
refs := map[string][]string{
|
|
"A": {"B"},
|
|
"B": {"A"},
|
|
"C": {"D", "F", "B"},
|
|
"E": {"F", "G"},
|
|
"F": {"H"},
|
|
}
|
|
|
|
expected := toNodes([]string{"A", "B", "C", "D", "F", "H"})
|
|
|
|
reachable, err := Tricolor(toNodes(roots), lookup(refs))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var sweeped []Node
|
|
for _, a := range toNodes(all) {
|
|
if _, ok := reachable[a]; ok {
|
|
sweeped = append(sweeped, a)
|
|
}
|
|
}
|
|
|
|
if !reflect.DeepEqual(sweeped, expected) {
|
|
t.Fatalf("incorrect unreachable set: %v != %v", sweeped, expected)
|
|
}
|
|
}
|
|
|
|
func TestConcurrentBasic(t *testing.T) {
|
|
roots := []string{"A", "C"}
|
|
all := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I"}
|
|
refs := map[string][]string{
|
|
"A": {"B"},
|
|
"B": {"A"},
|
|
"C": {"D", "F", "B"},
|
|
"E": {"F", "G"},
|
|
"F": {"H"},
|
|
"G": {"I"},
|
|
}
|
|
|
|
expected := toNodes([]string{"A", "B", "C", "D", "F", "H"})
|
|
|
|
ctx := context.Background()
|
|
rootC := make(chan Node)
|
|
go func() {
|
|
writeNodes(ctx, rootC, toNodes(roots))
|
|
close(rootC)
|
|
}()
|
|
|
|
reachable, err := ConcurrentMark(ctx, rootC, lookupc(refs))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var sweeped []Node
|
|
for _, a := range toNodes(all) {
|
|
if _, ok := reachable[a]; ok {
|
|
sweeped = append(sweeped, a)
|
|
}
|
|
}
|
|
|
|
if !reflect.DeepEqual(sweeped, expected) {
|
|
t.Fatalf("incorrect unreachable set: %v != %v", sweeped, expected)
|
|
}
|
|
}
|
|
|
|
func writeNodes(ctx context.Context, nc chan<- Node, nodes []Node) {
|
|
for _, n := range nodes {
|
|
select {
|
|
case nc <- n:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func lookup(refs map[string][]string) func(id Node) ([]Node, error) {
|
|
return func(ref Node) ([]Node, error) {
|
|
return toNodes(refs[ref.Key]), nil
|
|
}
|
|
}
|
|
|
|
func lookupc(refs map[string][]string) func(context.Context, Node, func(Node)) error {
|
|
return func(ctx context.Context, ref Node, fn func(Node)) error {
|
|
for _, n := range toNodes(refs[ref.Key]) {
|
|
fn(n)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func toNodes(s []string) []Node {
|
|
n := make([]Node, len(s))
|
|
for i := range s {
|
|
n[i] = Node{
|
|
Key: s[i],
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func newScanner(refs []string) *stringScanner {
|
|
return &stringScanner{
|
|
i: -1,
|
|
s: refs,
|
|
}
|
|
}
|
|
|
|
type stringScanner struct {
|
|
i int
|
|
s []string
|
|
}
|
|
|
|
func (ss *stringScanner) Next() bool {
|
|
ss.i++
|
|
return ss.i < len(ss.s)
|
|
}
|
|
|
|
func (ss *stringScanner) Node() Node {
|
|
return Node{
|
|
Key: ss.s[ss.i],
|
|
}
|
|
}
|
|
|
|
func (ss *stringScanner) Cleanup() error {
|
|
ss.s[ss.i] = ""
|
|
return nil
|
|
}
|
|
|
|
func (ss *stringScanner) Err() error {
|
|
return nil
|
|
}
|
|
|
|
func (ss *stringScanner) All() []Node {
|
|
remaining := make([]Node, 0, len(ss.s))
|
|
for _, s := range ss.s {
|
|
if s != "" {
|
|
remaining = append(remaining, Node{
|
|
Key: s,
|
|
})
|
|
}
|
|
}
|
|
return remaining
|
|
}
|