190 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    Copyright The containerd Authors.
 | |
| 
 | |
|    Licensed under the Apache License, Version 2.0 (the "License");
 | |
|    you may not use this file except in compliance with the License.
 | |
|    You may obtain a copy of the License at
 | |
| 
 | |
|        http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
|    Unless required by applicable law or agreed to in writing, software
 | |
|    distributed under the License is distributed on an "AS IS" BASIS,
 | |
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|    See the License for the specific language governing permissions and
 | |
|    limitations under the License.
 | |
| */
 | |
| 
 | |
| // Package gc experiments with providing central gc tooling to ensure
 | |
| // deterministic resource removal within containerd.
 | |
| //
 | |
| // For now, we just have a single exported implementation that can be used
 | |
| // under certain use cases.
 | |
| package gc
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // ResourceType represents type of resource at a node
 | |
| type ResourceType uint8
 | |
| 
 | |
| // ResourceMax represents the max resource.
 | |
| // Upper bits are stripped out during the mark phase, allowing the upper 3 bits
 | |
| // to be used by the caller reference function.
 | |
| const ResourceMax = ResourceType(0x1F)
 | |
| 
 | |
| // Node presents a resource which has a type and key,
 | |
| // this node can be used to lookup other nodes.
 | |
| type Node struct {
 | |
| 	Type      ResourceType
 | |
| 	Namespace string
 | |
| 	Key       string
 | |
| }
 | |
| 
 | |
| // Stats about a garbage collection run
 | |
| type Stats interface {
 | |
| 	Elapsed() time.Duration
 | |
| }
 | |
| 
 | |
| // Tricolor implements basic, single-thread tri-color GC. Given the roots, the
 | |
| // complete set and a refs function, this function returns a map of all
 | |
| // reachable objects.
 | |
| //
 | |
| // Correct usage requires that the caller not allow the arguments to change
 | |
| // until the result is used to delete objects in the system.
 | |
| //
 | |
| // It will allocate memory proportional to the size of the reachable set.
 | |
| //
 | |
| // We can probably use this to inform a design for incremental GC by injecting
 | |
| // callbacks to the set modification algorithms.
 | |
| func Tricolor(roots []Node, refs func(ref Node) ([]Node, error)) (map[Node]struct{}, error) {
 | |
| 	var (
 | |
| 		grays     []Node                // maintain a gray "stack"
 | |
| 		seen      = map[Node]struct{}{} // or not "white", basically "seen"
 | |
| 		reachable = map[Node]struct{}{} // or "black", in tri-color parlance
 | |
| 	)
 | |
| 
 | |
| 	grays = append(grays, roots...)
 | |
| 
 | |
| 	for len(grays) > 0 {
 | |
| 		// Pick any gray object
 | |
| 		id := grays[len(grays)-1] // effectively "depth first" because first element
 | |
| 		grays = grays[:len(grays)-1]
 | |
| 		seen[id] = struct{}{} // post-mark this as not-white
 | |
| 		rs, err := refs(id)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		// mark all the referenced objects as gray
 | |
| 		for _, target := range rs {
 | |
| 			if _, ok := seen[target]; !ok {
 | |
| 				grays = append(grays, target)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// strip bits above max resource type
 | |
| 		id.Type = id.Type & ResourceMax
 | |
| 		// mark as black when done
 | |
| 		reachable[id] = struct{}{}
 | |
| 	}
 | |
| 
 | |
| 	return reachable, nil
 | |
| }
 | |
| 
 | |
| // ConcurrentMark implements simple, concurrent GC. All the roots are scanned
 | |
| // and the complete set of references is formed by calling the refs function
 | |
| // for each seen object. This function returns a map of all object reachable
 | |
| // from a root.
 | |
| //
 | |
| // Correct usage requires that the caller not allow the arguments to change
 | |
| // until the result is used to delete objects in the system.
 | |
| //
 | |
| // It will allocate memory proportional to the size of the reachable set.
 | |
| func ConcurrentMark(ctx context.Context, root <-chan Node, refs func(context.Context, Node, func(Node)) error) (map[Node]struct{}, error) {
 | |
| 	ctx, cancel := context.WithCancel(ctx)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	var (
 | |
| 		grays = make(chan Node)
 | |
| 		seen  = map[Node]struct{}{} // or not "white", basically "seen"
 | |
| 		wg    sync.WaitGroup
 | |
| 
 | |
| 		errOnce sync.Once
 | |
| 		refErr  error
 | |
| 	)
 | |
| 
 | |
| 	go func() {
 | |
| 		for gray := range grays {
 | |
| 			if _, ok := seen[gray]; ok {
 | |
| 				wg.Done()
 | |
| 				continue
 | |
| 			}
 | |
| 			seen[gray] = struct{}{} // post-mark this as non-white
 | |
| 
 | |
| 			go func(gray Node) {
 | |
| 				defer wg.Done()
 | |
| 
 | |
| 				send := func(n Node) {
 | |
| 					wg.Add(1)
 | |
| 					select {
 | |
| 					case grays <- n:
 | |
| 					case <-ctx.Done():
 | |
| 						wg.Done()
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if err := refs(ctx, gray, send); err != nil {
 | |
| 					errOnce.Do(func() {
 | |
| 						refErr = err
 | |
| 						cancel()
 | |
| 					})
 | |
| 				}
 | |
| 
 | |
| 			}(gray)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	for r := range root {
 | |
| 		wg.Add(1)
 | |
| 		select {
 | |
| 		case grays <- r:
 | |
| 		case <-ctx.Done():
 | |
| 			wg.Done()
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	// Wait for outstanding grays to be processed
 | |
| 	wg.Wait()
 | |
| 
 | |
| 	close(grays)
 | |
| 
 | |
| 	if refErr != nil {
 | |
| 		return nil, refErr
 | |
| 	}
 | |
| 	if cErr := ctx.Err(); cErr != nil {
 | |
| 		return nil, cErr
 | |
| 	}
 | |
| 
 | |
| 	return seen, nil
 | |
| }
 | |
| 
 | |
| // Sweep removes all nodes returned through the slice which are not in
 | |
| // the reachable set by calling the provided remove function.
 | |
| func Sweep(reachable map[Node]struct{}, all []Node, remove func(Node) error) error {
 | |
| 	// All black objects are now reachable, and all white objects are
 | |
| 	// unreachable. Free those that are white!
 | |
| 	for _, node := range all {
 | |
| 		if _, ok := reachable[node]; !ok {
 | |
| 			if err := remove(node); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | 
