| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // Package lazyslot implements a caching scheme for globally shared objects that | 5 // Package lazyslot implements a caching scheme for globally shared objects that |
| 6 // take significant time to refresh. The defining property of the implementation | 6 // take significant time to refresh. |
| 7 // is that only one goroutine (can be background one) will block when refreshing | 7 // |
| 8 // such object, while all others will use a slightly stale cached copy. | 8 // The defining property of the implementation is that only one goroutine will |
| 9 // block when refreshing such object, while all others will use a slightly stale |
| 10 // cached copy. |
| 9 package lazyslot | 11 package lazyslot |
| 10 | 12 |
| 11 import ( | 13 import ( |
| 12 "sync" | 14 "sync" |
| 13 "time" | 15 "time" |
| 14 | 16 |
| 15 "golang.org/x/net/context" | 17 "golang.org/x/net/context" |
| 16 | 18 |
| 17 "github.com/luci/luci-go/common/clock" | 19 "github.com/luci/luci-go/common/clock" |
| 18 ) | 20 ) |
| 19 | 21 |
| 20 // Value is what's stored in a Slot. It is treated as immutable value. | 22 // Value is what's stored in a Slot. It is treated as immutable value. |
| 21 type Value struct { | 23 type Value struct { |
| 22 // Value is whatever fetcher returned. | 24 // Value is whatever fetcher returned. |
| 23 Value interface{} | 25 Value interface{} |
| 24 // Expiration is time when this value expires and should be refetched. | 26 // Expiration is time when this value expires and should be refetched. |
| 25 Expiration time.Time | 27 Expiration time.Time |
| 26 } | 28 } |
| 27 | 29 |
| 28 // Fetcher knows how to load new value. | 30 // Fetcher knows how to load a new value. |
| 31 // |
| 32 // If it returns no errors, it MUST return non-nil Value.Value or Slot.Get will |
| 33 // panic. |
| 29 type Fetcher func(c context.Context, prev Value) (Value, error) | 34 type Fetcher func(c context.Context, prev Value) (Value, error) |
| 30 | 35 |
| 31 // Slot holds a cached Value and refreshes it when it expires. Only one | 36 // Slot holds a cached Value and refreshes it when it expires. |
| 32 // goroutine will be busy refreshing, all others will see a slightly stale | 37 // |
| 33 // copy of the value during the refresh. | 38 // Only one goroutine will be busy refreshing, all others will see a slightly |
| 39 // stale copy of the value during the refresh. |
| 34 type Slot struct { | 40 type Slot struct { |
| 35 Fetcher Fetcher // used to actually load the value on demand | 41 Fetcher Fetcher // used to actually load the value on demand |
| 36 Timeout time.Duration // how long to allow to fetch, 5 sec by default. | 42 Timeout time.Duration // how long to allow to fetch, 5 sec by default. |
| 37 Async bool // if true do fetches in background goroutine | |
| 38 | 43 |
| 39 » lock sync.Mutex // protects the guts below | 44 » lock sync.Mutex // protects the guts below |
| 40 » current *Value // currently known value or nil if not fe
tched | 45 » current *Value // currently known value or nil if not
fetched |
| 41 » currentFetcher context.Context // non nil if some goroutine is fetching
now | 46 » currentFetcherCtx context.Context // non-nil if some goroutine is fetchi
ng now |
| 42 } | 47 } |
| 43 | 48 |
| 44 // Peek returns currently cached value if there's one or zero Value{} if not. | 49 // Get returns stored value if it is still fresh. |
| 45 // It doesn't try to fetch a value. | 50 // |
| 46 func (s *Slot) Peek() Value { | 51 // It may return slightly stale copy if some other goroutine is fetching a new |
| 47 » if s.current == nil { | 52 // copy now. If there's no cached copy at all, blocks until it is retrieved. |
| 48 » » return Value{} | 53 // |
| 54 // Returns an error only when Fetcher returns an error. Panics if fetcher |
| 55 // doesn't produce a value, and doesn't return an error. |
| 56 func (s *Slot) Get(c context.Context) (result Value, err error) { |
| 57 » // state is populate in the anonymous function below. |
| 58 » var state struct { |
| 59 » » C context.Context |
| 60 » » Fetcher Fetcher |
| 61 » » PrevValue Value |
| 49 } | 62 } |
| 50 return *s.current | |
| 51 } | |
| 52 | 63 |
| 53 // Get returns stored value if it is still fresh. It may return slightly stale | 64 » result, err, done := func() (result Value, err error, done bool) { |
| 54 // copy if some other goroutine is fetching a new copy now. If there's no cached | 65 » » now := clock.Now(c) |
| 55 // copy at all, blocks until it is retrieved (even if slot is configured with | |
| 56 // Async = true). Returns an error only when Fetcher returns an error. | |
| 57 func (s *Slot) Get(c context.Context) (Value, error) { | |
| 58 » now := clock.Now(c) | |
| 59 | 66 |
| 60 » // Set in the local function below, used it fetch is needed. | 67 » » // This lock protects the guts of the slot and makes sure only o
ne goroutine |
| 61 » var ( | 68 » » // is doing an initial fetch. |
| 62 » » ctx context.Context | |
| 63 » » fetchCb Fetcher | |
| 64 » » prevVal Value | |
| 65 » » async bool | |
| 66 » ) | |
| 67 | |
| 68 » // If done is true, val and err are returned right away. | |
| 69 » done, val, err := func() (bool, Value, error) { | |
| 70 s.lock.Lock() | 69 s.lock.Lock() |
| 71 defer s.lock.Unlock() | 70 defer s.lock.Unlock() |
| 72 | 71 |
| 73 » » // Still fresh? Return right away. | 72 » » // A cached value exists and it is still fresh? Return it right
away. |
| 74 if s.current != nil && now.Before(s.current.Expiration) { | 73 if s.current != nil && now.Before(s.current.Expiration) { |
| 75 » » » return true, *s.current, nil | 74 » » » result = *s.current |
| 75 » » » done = true |
| 76 » » » return |
| 76 } | 77 } |
| 77 | 78 |
| 78 // Fetching the value for the first time ever? Do it under the l
ock because | 79 // Fetching the value for the first time ever? Do it under the l
ock because |
| 79 // there's nothing to return yet. All goroutines would have to w
ait for this | 80 // there's nothing to return yet. All goroutines would have to w
ait for this |
| 80 // initial fetch to complete. They'll all block on s.lock.Lock()
above. | 81 // initial fetch to complete. They'll all block on s.lock.Lock()
above. |
| 81 if s.current == nil { | 82 if s.current == nil { |
| 82 » » » val, err := s.Fetcher(c, Value{}) | 83 » » » result, err = doFetch(c, s.Fetcher, Value{}) |
| 83 » » » if err != nil { | 84 » » » if err == nil { |
| 84 » » » » return true, Value{}, err | 85 » » » » s.current = &result |
| 85 } | 86 } |
| 86 » » » s.current = &val | 87 » » » done = true |
| 87 » » » return true, val, nil | 88 » » » return |
| 88 } | 89 } |
| 89 | 90 |
| 90 » » // We have a cached copy and it has expired. Maybe some other go
routine is | 91 » » // We have a cached copy but it has expired. Maybe some other go
routine is |
| 91 » » // fetching it already? Returns the stale copy if so. | 92 » » // fetching it already? Returns the cached stale copy if so. |
| 92 » » if s.currentFetcher != nil { | 93 » » if s.currentFetcherCtx != nil { |
| 93 » » » return true, *s.current, nil | 94 » » » result = *s.current |
| 95 » » » done = true |
| 96 » » » return |
| 94 } | 97 } |
| 95 | 98 |
| 96 » » // No one is fetching the value now, we should do it. Release th
e lock while | 99 » » // No one is fetching the value now, we should do it. Prepare a
new context |
| 97 » » // fetching to allow other goroutines to grab the stale copy. | 100 » » // that will be used to do the fetch once lock is released. |
| 98 timeout := 5 * time.Second | 101 timeout := 5 * time.Second |
| 99 if s.Timeout != 0 { | 102 if s.Timeout != 0 { |
| 100 timeout = s.Timeout | 103 timeout = s.Timeout |
| 101 } | 104 } |
| 102 » » s.currentFetcher, _ = context.WithTimeout(c, timeout) | 105 » » s.currentFetcherCtx, _ = context.WithTimeout(c, timeout) |
| 103 » » ctx = s.currentFetcher | 106 |
| 104 » » fetchCb = s.Fetcher | 107 » » // Copy lock-protected guts into local variables before releasin
g the lock. |
| 105 » » prevVal = *s.current | 108 » » state.C = s.currentFetcherCtx |
| 106 » » async = s.Async | 109 » » state.Fetcher = s.Fetcher |
| 107 » » return false, Value{}, nil | 110 » » state.PrevValue = *s.current |
| 111 » » return |
| 108 }() | 112 }() |
| 109 if done { | 113 if done { |
| 110 » » return val, err | 114 » » return |
| 111 } | 115 } |
| 112 | 116 |
| 113 » fetch := func() (val Value, err error) { | 117 » // Finish the fetch and update the cached value. |
| 118 » return func() (result Value, err error) { |
| 114 defer func() { | 119 defer func() { |
| 115 s.lock.Lock() | 120 s.lock.Lock() |
| 116 defer s.lock.Unlock() | 121 defer s.lock.Unlock() |
| 117 » » » s.currentFetcher = nil | 122 » » » s.currentFetcherCtx = nil |
| 118 » » » if err == nil && val.Value != nil { | 123 » » » // result.Value is not nil iff fetch succeeded and didn'
t panic. |
| 119 » » » » s.current = &val | 124 » » » if result.Value != nil { |
| 125 » » » » s.current = &result |
| 120 } | 126 } |
| 121 }() | 127 }() |
| 122 » » return fetchCb(ctx, prevVal) | 128 » » return doFetch(state.C, state.Fetcher, state.PrevValue) |
| 129 » }() |
| 130 } |
| 131 |
| 132 // doFetch calls fetcher callback and validates return value. |
| 133 func doFetch(ctx context.Context, cb Fetcher, prev Value) (result Value, err err
or) { |
| 134 » result, err = cb(ctx, prev) |
| 135 » switch { |
| 136 » case err == nil && result.Value == nil: |
| 137 » » panic("lazyslot.Slot Fetcher returned nil value") |
| 138 » case err != nil: |
| 139 » » result = Value{} |
| 123 } | 140 } |
| 124 | 141 » return |
| 125 » if async { | |
| 126 » » go fetch() | |
| 127 » » return prevVal, nil | |
| 128 » } | |
| 129 » return fetch() | |
| 130 } | 142 } |
| OLD | NEW |