~juju-qa/ubuntu/xenial/juju/2.0-rc2

« back to all changes in this revision

Viewing changes to src/github.com/juju/utils/cache/cache.go

  • Committer: Nicholas Skaggs
  • Date: 2016-09-30 14:39:30 UTC
  • mfrom: (1.8.1)
  • Revision ID: nicholas.skaggs@canonical.com-20160930143930-vwwhrefh6ftckccy
import upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
40
40
        // Instead, we move items from old to new when they're accessed
41
41
        // and throw away the old map at refresh time.
42
42
        old, new map[Key]entry
 
43
 
 
44
        inFlight map[Key]*fetchCall
 
45
}
 
46
 
 
47
// fetch represents an in-progress fetch call. If a cache Get request
 
48
// is made for an item that is currently being fetched, this will
 
49
// be used to avoid an extra call to the fetch function.
 
50
type fetchCall struct {
 
51
        wg  sync.WaitGroup
 
52
        val interface{}
 
53
        err error
43
54
}
44
55
 
45
56
// New returns a new Cache that will cache items for
50
61
        // time, so will expire immediately, causing the new
51
62
        // map to be created.
52
63
        return &Cache{
53
 
                maxAge: maxAge,
 
64
                maxAge:   maxAge,
 
65
                inFlight: make(map[Key]*fetchCall),
54
66
        }
55
67
}
56
68
 
91
103
        if val, ok := c.cachedValue(key, now); ok {
92
104
                return val, nil
93
105
        }
 
106
        c.mu.Lock()
 
107
        if f, ok := c.inFlight[key]; ok {
 
108
                // There's already an in-flight request for the key, so wait
 
109
                // for that to complete and use its results.
 
110
                c.mu.Unlock()
 
111
                f.wg.Wait()
 
112
                // The value will have been added to the cache by the first fetch,
 
113
                // so no need to add it here.
 
114
                if f.err == nil {
 
115
                        return f.val, nil
 
116
                }
 
117
                return nil, errgo.Mask(f.err, errgo.Any)
 
118
        }
 
119
        var f fetchCall
 
120
        f.wg.Add(1)
 
121
        c.inFlight[key] = &f
 
122
        // Mark the request as done when we return, and after
 
123
        // the value has been added to the cache.
 
124
        defer f.wg.Done()
 
125
 
94
126
        // Fetch the data without the mutex held
95
127
        // so that one slow fetch doesn't hold up
96
128
        // all the other cache accesses.
 
129
        c.mu.Unlock()
97
130
        val, err := fetch()
98
 
        if err != nil {
99
 
                // TODO consider caching cache misses.
100
 
                return nil, errgo.Mask(err, errgo.Any)
101
 
        }
102
 
        if c.maxAge < 2*time.Nanosecond {
 
131
        c.mu.Lock()
 
132
        defer c.mu.Unlock()
 
133
 
 
134
        // Set the result in the fetchCall so that other calls can see it.
 
135
        f.val, f.err = val, err
 
136
        if err == nil && c.maxAge >= 2*time.Nanosecond {
103
137
                // If maxAge is < 2ns then the expiry code will panic because the
104
138
                // actual expiry time will be maxAge - a random value in the
105
139
                // interval [0, maxAge/2). If maxAge is < 2ns then this requires
108
142
                // This value is so small that there's no need to cache anyway,
109
143
                // which makes tests more obviously deterministic when using
110
144
                // a zero expiry time.
111
 
                return val, nil
112
 
        }
113
 
        c.mu.Lock()
114
 
        defer c.mu.Unlock()
115
 
        // Add the new cache entry. Because it's quite likely that a
116
 
        // large number of cache entries will be initially fetched at
117
 
        // the same time, we want to avoid a thundering herd of fetches
118
 
        // when they all expire at the same time, so we set the expiry
119
 
        // time to a random interval between [now + t.maxAge/2, now +
120
 
        // t.maxAge] and so they'll be spread over time without
121
 
        // compromising the maxAge value.
122
 
        c.new[key] = entry{
123
 
                value:  val,
124
 
                expire: now.Add(c.maxAge - time.Duration(rand.Int63n(int64(c.maxAge/2)))),
125
 
        }
126
 
        return val, nil
 
145
                c.new[key] = entry{
 
146
                        value:  val,
 
147
                        expire: now.Add(c.maxAge - time.Duration(rand.Int63n(int64(c.maxAge/2)))),
 
148
                }
 
149
        }
 
150
        delete(c.inFlight, key)
 
151
        if err == nil {
 
152
                return f.val, nil
 
153
        }
 
154
        return nil, errgo.Mask(f.err, errgo.Any)
127
155
}
128
156
 
129
157
// cachedValue returns any cached value for the given key