github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/gc/gc.go (about)

     1  /*
     2  Copyright 2014 The Camlistore 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 defines a generic garbage collector.
    18  package gc
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  
    24  	"camlistore.org/pkg/context"
    25  	"camlistore.org/pkg/syncutil"
    26  )
    27  
    28  const buffered = 32 // arbitrary
    29  
    30  // Item is something that exists that may or may not survive a GC collection.
    31  type Item interface{}
    32  
    33  // A Collector performs a garbage collection.
    34  type Collector struct {
    35  	// World specifies a World that should be stopped before a
    36  	// collection and started again after.
    37  	World World
    38  
    39  	Marker         Marker
    40  	Roots          Enumerator
    41  	Sweeper        Enumerator
    42  	ItemEnumerator ItemEnumerator
    43  	Deleter        Deleter
    44  }
    45  
    46  type Marker interface {
    47  	// Mark marks that an item should exist.
    48  	// It must be safe for calls from concurrent goroutines.
    49  	Mark(Item) error
    50  
    51  	// IsMarked returns whether the item is marked.
    52  	// It must be safe for calls from concurrent goroutines.
    53  	IsMarked(Item) (bool, error)
    54  }
    55  
    56  // World defines the thing that should be stopped before GC and started after.
    57  type World interface {
    58  	Stop() error
    59  	Start() error
    60  }
    61  
    62  type Deleter interface {
    63  	// Delete deletes an item that was deemed unreachable via
    64  	// the garbage collector.
    65  	// It must be safe for calls from concurrent goroutines.
    66  	Delete(Item) error
    67  }
    68  
    69  // Enumerator enumerates items.
    70  type Enumerator interface {
    71  	// Enumerate enumerates items (which items depends on usage)
    72  	// and sends them to the provided channel. Regardless of return
    73  	// value, the channel should be closed.
    74  	//
    75  	// If the provided context is closed, Enumerate should return
    76  	// with an error (typically context.ErrCanceled)
    77  	Enumerate(*context.Context, chan<- Item) error
    78  }
    79  
    80  // ItemEnumerator enumerates all the edges out from an item.
    81  type ItemEnumerator interface {
    82  	// EnumerateItme is like Enuerator's Enumerate, but specific
    83  	// to the provided item.
    84  	EnumerateItem(*context.Context, Item, chan<- Item) error
    85  }
    86  
    87  // ctx will be canceled on failure
    88  func (c *Collector) markItem(ctx *context.Context, it Item, isRoot bool) error {
    89  	if !isRoot {
    90  		marked, err := c.Marker.IsMarked(it)
    91  		if err != nil {
    92  			return err
    93  		}
    94  		if marked {
    95  			return nil
    96  		}
    97  	}
    98  	if err := c.Marker.Mark(it); err != nil {
    99  		return err
   100  	}
   101  
   102  	ch := make(chan Item, buffered)
   103  	var grp syncutil.Group
   104  	grp.Go(func() error {
   105  		return c.ItemEnumerator.EnumerateItem(ctx, it, ch)
   106  	})
   107  	grp.Go(func() error {
   108  		for it := range ch {
   109  			if err := c.markItem(ctx, it, false); err != nil {
   110  				return err
   111  			}
   112  		}
   113  		return nil
   114  	})
   115  	if err := grp.Err(); err != nil {
   116  		ctx.Cancel()
   117  		return err
   118  	}
   119  	return nil
   120  }
   121  
   122  // Collect performs a garbage collection.
   123  func (c *Collector) Collect(ctx *context.Context) (err error) {
   124  	if c.World == nil {
   125  		return errors.New("no World")
   126  	}
   127  	if c.Marker == nil {
   128  		return errors.New("no Marker")
   129  	}
   130  	if c.Roots == nil {
   131  		return errors.New("no Roots")
   132  	}
   133  	if c.Sweeper == nil {
   134  		return errors.New("no Sweeper")
   135  	}
   136  	if c.ItemEnumerator == nil {
   137  		return errors.New("no ItemEnumerator")
   138  	}
   139  	if c.Deleter == nil {
   140  		return errors.New("no Deleter")
   141  	}
   142  	if err := c.World.Stop(); err != nil {
   143  		return err
   144  	}
   145  	defer func() {
   146  		startErr := c.World.Start()
   147  		if err == nil {
   148  			err = startErr
   149  		}
   150  	}()
   151  
   152  	// Mark.
   153  	roots := make(chan Item, buffered)
   154  	markCtx := ctx.New()
   155  	var marker syncutil.Group
   156  	marker.Go(func() error {
   157  		defer markCtx.Cancel()
   158  		for it := range roots {
   159  			if err := c.markItem(markCtx, it, true); err != nil {
   160  				return err
   161  			}
   162  		}
   163  		return nil
   164  	})
   165  	marker.Go(func() error {
   166  		return c.Roots.Enumerate(markCtx, roots)
   167  	})
   168  	if err := marker.Err(); err != nil {
   169  		return fmt.Errorf("Mark failure: %v", err)
   170  	}
   171  
   172  	// Sweep.
   173  	all := make(chan Item, buffered)
   174  	sweepCtx := ctx.New()
   175  	var sweeper syncutil.Group
   176  	sweeper.Go(func() error {
   177  		return c.Sweeper.Enumerate(sweepCtx, all)
   178  	})
   179  	sweeper.Go(func() error {
   180  		defer sweepCtx.Done()
   181  		for it := range all {
   182  			ok, err := c.Marker.IsMarked(it)
   183  			if err != nil {
   184  				return err
   185  			}
   186  			if !ok {
   187  				if err := c.Deleter.Delete(it); err != nil {
   188  					return err
   189  				}
   190  			}
   191  		}
   192  		return nil
   193  	})
   194  	if err := sweeper.Err(); err != nil {
   195  		return fmt.Errorf("Sweep failure: %v", err)
   196  	}
   197  	return nil
   198  }