...

Source file src/github.com/launchdarkly/ccache/bucket.go

Documentation: github.com/launchdarkly/ccache

     1  package ccache
     2  
     3  import (
     4  	"strings"
     5  	"sync"
     6  	"time"
     7  )
     8  
     9  type bucket struct {
    10  	sync.RWMutex
    11  	lookup map[string]*Item
    12  }
    13  
    14  func (b *bucket) itemCount() int {
    15  	b.RLock()
    16  	defer b.RUnlock()
    17  	return len(b.lookup)
    18  }
    19  
    20  func (b *bucket) forEachFunc(matches func(key string, item *Item) bool) bool {
    21  	lookup := b.lookup
    22  	b.RLock()
    23  	defer b.RUnlock()
    24  	for key, item := range lookup {
    25  		if !matches(key, item) {
    26  			return false
    27  		}
    28  	}
    29  	return true
    30  }
    31  
    32  func (b *bucket) get(key string) *Item {
    33  	b.RLock()
    34  	defer b.RUnlock()
    35  	return b.lookup[key]
    36  }
    37  
    38  func (b *bucket) set(key string, value interface{}, duration time.Duration, track bool) (*Item, *Item) {
    39  	expires := time.Now().Add(duration).UnixNano()
    40  	item := newItem(key, value, expires, track)
    41  	b.Lock()
    42  	existing := b.lookup[key]
    43  	b.lookup[key] = item
    44  	b.Unlock()
    45  	return item, existing
    46  }
    47  
    48  func (b *bucket) delete(key string) *Item {
    49  	b.Lock()
    50  	item := b.lookup[key]
    51  	delete(b.lookup, key)
    52  	b.Unlock()
    53  	return item
    54  }
    55  
    56  // This is an expensive operation, so we do what we can to optimize it and limit
    57  // the impact it has on concurrent operations. Specifically, we:
    58  // 1 - Do an initial iteration to collect matches. This allows us to do the
    59  //     "expensive" prefix check (on all values) using only a read-lock
    60  // 2 - Do a second iteration, under write lock, for the matched results to do
    61  //     the actual deletion
    62  
    63  // Also, this is the only place where the Bucket is aware of cache detail: the
    64  // deletables channel. Passing it here lets us avoid iterating over matched items
    65  // again in the cache. Further, we pass item to deletables BEFORE actually removing
    66  // the item from the map. I'm pretty sure this is 100% fine, but it is unique.
    67  // (We do this so that the write to the channel is under the read lock and not the
    68  // write lock)
    69  func (b *bucket) deleteFunc(matches func(key string, item *Item) bool, deletables chan *Item) int {
    70  	lookup := b.lookup
    71  	items := make([]*Item, 0)
    72  
    73  	b.RLock()
    74  	for key, item := range lookup {
    75  		if matches(key, item) {
    76  			deletables <- item
    77  			items = append(items, item)
    78  		}
    79  	}
    80  	b.RUnlock()
    81  
    82  	if len(items) == 0 {
    83  		// avoid the write lock if we can
    84  		return 0
    85  	}
    86  
    87  	b.Lock()
    88  	for _, item := range items {
    89  		delete(lookup, item.key)
    90  	}
    91  	b.Unlock()
    92  	return len(items)
    93  }
    94  
    95  func (b *bucket) deletePrefix(prefix string, deletables chan *Item) int {
    96  	return b.deleteFunc(func(key string, item *Item) bool {
    97  		return strings.HasPrefix(key, prefix)
    98  	}, deletables)
    99  }
   100  
   101  func (b *bucket) clear() {
   102  	b.Lock()
   103  	b.lookup = make(map[string]*Item)
   104  	b.Unlock()
   105  }
   106  

View as plain text