github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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 slice 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  }