github.com/demonoid81/containerd@v1.3.4/gc/gc.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package gc experiments with providing central gc tooling to ensure 18 // deterministic resource removal within containerd. 19 // 20 // For now, we just have a single exported implementation that can be used 21 // under certain use cases. 22 package gc 23 24 import ( 25 "context" 26 "sync" 27 "time" 28 ) 29 30 // ResourceType represents type of resource at a node 31 type ResourceType uint8 32 33 // ResourceMax represents the max resource. 34 // Upper bits are stripped out during the mark phase, allowing the upper 3 bits 35 // to be used by the caller reference function. 36 const ResourceMax = ResourceType(0x1F) 37 38 // Node presents a resource which has a type and key, 39 // this node can be used to lookup other nodes. 40 type Node struct { 41 Type ResourceType 42 Namespace string 43 Key string 44 } 45 46 // Stats about a garbage collection run 47 type Stats interface { 48 Elapsed() time.Duration 49 } 50 51 // Tricolor implements basic, single-thread tri-color GC. Given the roots, the 52 // complete set and a refs function, this function returns a map of all 53 // reachable objects. 54 // 55 // Correct usage requires that the caller not allow the arguments to change 56 // until the result is used to delete objects in the system. 57 // 58 // It will allocate memory proportional to the size of the reachable set. 59 // 60 // We can probably use this to inform a design for incremental GC by injecting 61 // callbacks to the set modification algorithms. 62 func Tricolor(roots []Node, refs func(ref Node) ([]Node, error)) (map[Node]struct{}, error) { 63 var ( 64 grays []Node // maintain a gray "stack" 65 seen = map[Node]struct{}{} // or not "white", basically "seen" 66 reachable = map[Node]struct{}{} // or "black", in tri-color parlance 67 ) 68 69 grays = append(grays, roots...) 70 71 for len(grays) > 0 { 72 // Pick any gray object 73 id := grays[len(grays)-1] // effectively "depth first" because first element 74 grays = grays[:len(grays)-1] 75 seen[id] = struct{}{} // post-mark this as not-white 76 rs, err := refs(id) 77 if err != nil { 78 return nil, err 79 } 80 81 // mark all the referenced objects as gray 82 for _, target := range rs { 83 if _, ok := seen[target]; !ok { 84 grays = append(grays, target) 85 } 86 } 87 88 // strip bits above max resource type 89 id.Type = id.Type & ResourceMax 90 // mark as black when done 91 reachable[id] = struct{}{} 92 } 93 94 return reachable, nil 95 } 96 97 // ConcurrentMark implements simple, concurrent GC. All the roots are scanned 98 // and the complete set of references is formed by calling the refs function 99 // for each seen object. This function returns a map of all object reachable 100 // from a root. 101 // 102 // Correct usage requires that the caller not allow the arguments to change 103 // until the result is used to delete objects in the system. 104 // 105 // It will allocate memory proportional to the size of the reachable set. 106 func ConcurrentMark(ctx context.Context, root <-chan Node, refs func(context.Context, Node, func(Node)) error) (map[Node]struct{}, error) { 107 ctx, cancel := context.WithCancel(ctx) 108 defer cancel() 109 110 var ( 111 grays = make(chan Node) 112 seen = map[Node]struct{}{} // or not "white", basically "seen" 113 wg sync.WaitGroup 114 115 errOnce sync.Once 116 refErr error 117 ) 118 119 go func() { 120 for gray := range grays { 121 if _, ok := seen[gray]; ok { 122 wg.Done() 123 continue 124 } 125 seen[gray] = struct{}{} // post-mark this as non-white 126 127 go func(gray Node) { 128 defer wg.Done() 129 130 send := func(n Node) { 131 wg.Add(1) 132 select { 133 case grays <- n: 134 case <-ctx.Done(): 135 wg.Done() 136 } 137 } 138 139 if err := refs(ctx, gray, send); err != nil { 140 errOnce.Do(func() { 141 refErr = err 142 cancel() 143 }) 144 } 145 146 }(gray) 147 } 148 }() 149 150 for r := range root { 151 wg.Add(1) 152 select { 153 case grays <- r: 154 case <-ctx.Done(): 155 wg.Done() 156 } 157 158 } 159 160 // Wait for outstanding grays to be processed 161 wg.Wait() 162 163 close(grays) 164 165 if refErr != nil { 166 return nil, refErr 167 } 168 if cErr := ctx.Err(); cErr != nil { 169 return nil, cErr 170 } 171 172 return seen, nil 173 } 174 175 // Sweep removes all nodes returned through the channel which are not in 176 // the reachable set by calling the provided remove function. 177 func Sweep(reachable map[Node]struct{}, all []Node, remove func(Node) error) error { 178 // All black objects are now reachable, and all white objects are 179 // unreachable. Free those that are white! 180 for _, node := range all { 181 if _, ok := reachable[node]; !ok { 182 if err := remove(node); err != nil { 183 return err 184 } 185 } 186 } 187 188 return nil 189 }