| 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. | 6 // take significant time to refresh. |
| 7 // | 7 // |
| 8 // The defining property of the implementation is that only one goroutine will | 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 | 9 // block when refreshing such object, while all others will use a slightly stale |
| 10 // cached copy. | 10 // cached copy. |
| (...skipping 21 matching lines...) Expand all Loading... |
| 32 // If it returns no errors, it MUST return non-nil Value.Value or Slot.Get will | 32 // If it returns no errors, it MUST return non-nil Value.Value or Slot.Get will |
| 33 // panic. | 33 // panic. |
| 34 type Fetcher func(c context.Context, prev Value) (Value, error) | 34 type Fetcher func(c context.Context, prev Value) (Value, error) |
| 35 | 35 |
| 36 // Slot holds a cached Value and refreshes it when it expires. | 36 // Slot holds a cached Value and refreshes it when it expires. |
| 37 // | 37 // |
| 38 // Only one goroutine will be busy refreshing, all others will see a slightly | 38 // Only one goroutine will be busy refreshing, all others will see a slightly |
| 39 // stale copy of the value during the refresh. | 39 // stale copy of the value during the refresh. |
| 40 type Slot struct { | 40 type Slot struct { |
| 41 Fetcher Fetcher // used to actually load the value on demand | 41 Fetcher Fetcher // used to actually load the value on demand |
| 42 » Timeout time.Duration // how long to allow to fetch, 5 sec by default. | 42 » Timeout time.Duration // how long to allow to fetch, 15 sec by default. |
| 43 | 43 |
| 44 lock sync.Mutex // protects the guts below | 44 lock sync.Mutex // protects the guts below |
| 45 current *Value // currently known value or nil if not
fetched | 45 current *Value // currently known value or nil if not
fetched |
| 46 currentFetcherCtx context.Context // non-nil if some goroutine is fetchi
ng now | 46 currentFetcherCtx context.Context // non-nil if some goroutine is fetchi
ng now |
| 47 } | 47 } |
| 48 | 48 |
| 49 // Get returns stored value if it is still fresh. | 49 // Get returns stored value if it is still fresh. |
| 50 // | 50 // |
| 51 // It may return slightly stale copy if some other goroutine is fetching a new | 51 // It may return slightly stale copy if some other goroutine is fetching a new |
| 52 // copy now. If there's no cached copy at all, blocks until it is retrieved. | 52 // copy now. If there's no cached copy at all, blocks until it is retrieved. |
| (...skipping 20 matching lines...) Expand all Loading... |
| 73 if s.current != nil && now.Before(s.current.Expiration) { | 73 if s.current != nil && now.Before(s.current.Expiration) { |
| 74 result = *s.current | 74 result = *s.current |
| 75 done = true | 75 done = true |
| 76 return | 76 return |
| 77 } | 77 } |
| 78 | 78 |
| 79 // 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 |
| 80 // 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 |
| 81 // 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. |
| 82 if s.current == nil { | 82 if s.current == nil { |
| 83 » » » result, err = doFetch(c, s.Fetcher, Value{}) | 83 » » » result, err = doFetch(s.makeFetcherCtx(c), s.Fetcher, Va
lue{}) |
| 84 if err == nil { | 84 if err == nil { |
| 85 s.current = &result | 85 s.current = &result |
| 86 } | 86 } |
| 87 done = true | 87 done = true |
| 88 return | 88 return |
| 89 } | 89 } |
| 90 | 90 |
| 91 // We have a cached copy but 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 |
| 92 // fetching it already? Returns the cached stale copy if so. | 92 // fetching it already? Returns the cached stale copy if so. |
| 93 if s.currentFetcherCtx != nil { | 93 if s.currentFetcherCtx != nil { |
| 94 result = *s.current | 94 result = *s.current |
| 95 done = true | 95 done = true |
| 96 return | 96 return |
| 97 } | 97 } |
| 98 | 98 |
| 99 // No one is fetching the value now, we should do it. Prepare a
new context | 99 // No one is fetching the value now, we should do it. Prepare a
new context |
| 100 // that will be used to do the fetch once lock is released. | 100 // that will be used to do the fetch once lock is released. |
| 101 » » timeout := 5 * time.Second | 101 » » s.currentFetcherCtx = s.makeFetcherCtx(c) |
| 102 » » if s.Timeout != 0 { | |
| 103 » » » timeout = s.Timeout | |
| 104 » » } | |
| 105 » » s.currentFetcherCtx, _ = context.WithTimeout(c, timeout) | |
| 106 | 102 |
| 107 // Copy lock-protected guts into local variables before releasin
g the lock. | 103 // Copy lock-protected guts into local variables before releasin
g the lock. |
| 108 state.C = s.currentFetcherCtx | 104 state.C = s.currentFetcherCtx |
| 109 state.Fetcher = s.Fetcher | 105 state.Fetcher = s.Fetcher |
| 110 state.PrevValue = *s.current | 106 state.PrevValue = *s.current |
| 111 return | 107 return |
| 112 }() | 108 }() |
| 113 if done { | 109 if done { |
| 114 return | 110 return |
| 115 } | 111 } |
| 116 | 112 |
| 117 // Finish the fetch and update the cached value. | 113 // Finish the fetch and update the cached value. |
| 118 return func() (result Value, err error) { | 114 return func() (result Value, err error) { |
| 119 defer func() { | 115 defer func() { |
| 120 s.lock.Lock() | 116 s.lock.Lock() |
| 121 defer s.lock.Unlock() | 117 defer s.lock.Unlock() |
| 122 s.currentFetcherCtx = nil | 118 s.currentFetcherCtx = nil |
| 123 // result.Value is not nil iff fetch succeeded and didn'
t panic. | 119 // result.Value is not nil iff fetch succeeded and didn'
t panic. |
| 124 if result.Value != nil { | 120 if result.Value != nil { |
| 125 s.current = &result | 121 s.current = &result |
| 126 } | 122 } |
| 127 }() | 123 }() |
| 128 return doFetch(state.C, state.Fetcher, state.PrevValue) | 124 return doFetch(state.C, state.Fetcher, state.PrevValue) |
| 129 }() | 125 }() |
| 130 } | 126 } |
| 131 | 127 |
| 128 // makeFetcherCtx prepares a context to use for fetch operation. |
| 129 // |
| 130 // Must be called under the lock. |
| 131 func (s *Slot) makeFetcherCtx(c context.Context) context.Context { |
| 132 timeout := 15 * time.Second |
| 133 if s.Timeout != 0 { |
| 134 timeout = s.Timeout |
| 135 } |
| 136 fetcherCtx, _ := clock.WithTimeout(c, timeout) |
| 137 return fetcherCtx |
| 138 } |
| 139 |
| 132 // doFetch calls fetcher callback and validates return value. | 140 // doFetch calls fetcher callback and validates return value. |
| 133 func doFetch(ctx context.Context, cb Fetcher, prev Value) (result Value, err err
or) { | 141 func doFetch(ctx context.Context, cb Fetcher, prev Value) (result Value, err err
or) { |
| 134 result, err = cb(ctx, prev) | 142 result, err = cb(ctx, prev) |
| 135 switch { | 143 switch { |
| 136 case err == nil && result.Value == nil: | 144 case err == nil && result.Value == nil: |
| 137 panic("lazyslot.Slot Fetcher returned nil value") | 145 panic("lazyslot.Slot Fetcher returned nil value") |
| 138 case err != nil: | 146 case err != nil: |
| 139 result = Value{} | 147 result = Value{} |
| 140 } | 148 } |
| 141 return | 149 return |
| 142 } | 150 } |
| OLD | NEW |