Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(670)

Side by Side Diff: common/lazyslot/lazyslot.go

Issue 1748933002: Retry RPC deadlines when fetching GAE settings. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-go@simplify-lazyslot
Patch Set: "rebase" Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | common/lazyslot/lazyslot_test.go » ('j') | server/settings/settings.go » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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.
11 package lazyslot 11 package lazyslot
12 12
13 import ( 13 import (
14 "sync" 14 "sync"
15 "time" 15 "time"
16 16
17 "golang.org/x/net/context" 17 "golang.org/x/net/context"
18 18
19 "github.com/luci/luci-go/common/clock" 19 "github.com/luci/luci-go/common/clock"
20 "github.com/luci/luci-go/common/retry"
20 ) 21 )
21 22
22 // Value is what's stored in a Slot. It is treated as immutable value. 23 // Value is what's stored in a Slot. It is treated as immutable value.
23 type Value struct { 24 type Value struct {
24 // Value is whatever fetcher returned. 25 // Value is whatever fetcher returned.
25 Value interface{} 26 Value interface{}
26 // Expiration is time when this value expires and should be refetched. 27 // Expiration is time when this value expires and should be refetched.
27 Expiration time.Time 28 Expiration time.Time
28 } 29 }
29 30
30 // Fetcher knows how to load a new value. 31 // Fetcher knows how to load a new value.
31 // 32 //
32 // If it returns no errors, it MUST return non-nil Value.Value or Slot.Get will 33 // If it returns no errors, it MUST return non-nil Value.Value or Slot.Get will
33 // panic. 34 // panic.
34 type Fetcher func(c context.Context, prev Value) (Value, error) 35 type Fetcher func(c context.Context, prev Value) (Value, error)
35 36
36 // Slot holds a cached Value and refreshes it when it expires. 37 // Slot holds a cached Value and refreshes it when it expires.
37 // 38 //
38 // Only one goroutine will be busy refreshing, all others will see a slightly 39 // Only one goroutine will be busy refreshing, all others will see a slightly
39 // stale copy of the value during the refresh. 40 // stale copy of the value during the refresh.
40 type Slot struct { 41 type Slot struct {
41 » Fetcher Fetcher // used to actually load the value on demand 42 » Fetcher Fetcher // used to actually load the value on deman d
42 » Timeout time.Duration // how long to allow to fetch, 5 sec by default. 43 » Timeout time.Duration // how long to allow to fetch, 15 sec by de fault.
44 » RetryFactory retry.Factory // if non-nil, defines how to retry fetch e rrors
45 » RetryCallback retry.Callback // called before retries, useful in tests
43 46
44 lock sync.Mutex // protects the guts below 47 lock sync.Mutex // protects the guts below
45 current *Value // currently known value or nil if not fetched 48 current *Value // currently known value or nil if not fetched
46 currentFetcherCtx context.Context // non-nil if some goroutine is fetchi ng now 49 currentFetcherCtx context.Context // non-nil if some goroutine is fetchi ng now
47 } 50 }
48 51
49 // Get returns stored value if it is still fresh. 52 // Get returns stored value if it is still fresh.
50 // 53 //
51 // It may return slightly stale copy if some other goroutine is fetching a new 54 // 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. 55 // copy now. If there's no cached copy at all, blocks until it is retrieved.
53 // 56 //
54 // Returns an error only when Fetcher returns an error. Panics if fetcher 57 // Returns an error only when Fetcher returns an error. Panics if fetcher
55 // doesn't produce a value, and doesn't return an error. 58 // doesn't produce a value, and doesn't return an error.
56 func (s *Slot) Get(c context.Context) (result Value, err error) { 59 func (s *Slot) Get(c context.Context) (result Value, err error) {
57 // state is populate in the anonymous function below. 60 // state is populate in the anonymous function below.
58 var state struct { 61 var state struct {
59 » » C context.Context 62 » » C context.Context
60 » » Fetcher Fetcher 63 » » Fetcher Fetcher
61 » » PrevValue Value 64 » » PrevValue Value
65 » » RetryFactory retry.Factory
66 » » RetryCallback retry.Callback
62 } 67 }
63 68
64 result, err, done := func() (result Value, err error, done bool) { 69 result, err, done := func() (result Value, err error, done bool) {
65 now := clock.Now(c) 70 now := clock.Now(c)
66 71
67 // This lock protects the guts of the slot and makes sure only o ne goroutine 72 // This lock protects the guts of the slot and makes sure only o ne goroutine
68 // is doing an initial fetch. 73 // is doing an initial fetch.
69 s.lock.Lock() 74 s.lock.Lock()
70 defer s.lock.Unlock() 75 defer s.lock.Unlock()
71 76
72 // A cached value exists and it is still fresh? Return it right away. 77 // A cached value exists and it is still fresh? Return it right away.
73 if s.current != nil && now.Before(s.current.Expiration) { 78 if s.current != nil && now.Before(s.current.Expiration) {
74 result = *s.current 79 result = *s.current
75 done = true 80 done = true
76 return 81 return
77 } 82 }
78 83
79 // Fetching the value for the first time ever? Do it under the l ock because 84 // 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 85 // 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. 86 // initial fetch to complete. They'll all block on s.lock.Lock() above.
82 if s.current == nil { 87 if s.current == nil {
83 » » » result, err = doFetch(c, s.Fetcher, Value{}) 88 » » » result, err = fetchWithRetries(s.makeFetcherCtx(c), s.Fe tcher, s.RetryFactory, s.RetryCallback, Value{})
84 if err == nil { 89 if err == nil {
85 s.current = &result 90 s.current = &result
86 } 91 }
87 done = true 92 done = true
88 return 93 return
89 } 94 }
90 95
91 // We have a cached copy but it has expired. Maybe some other go routine is 96 // 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. 97 // fetching it already? Returns the cached stale copy if so.
93 if s.currentFetcherCtx != nil { 98 if s.currentFetcherCtx != nil {
94 result = *s.current 99 result = *s.current
95 done = true 100 done = true
96 return 101 return
97 } 102 }
98 103
99 // No one is fetching the value now, we should do it. Prepare a new context 104 // 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. 105 // that will be used to do the fetch once lock is released.
101 » » timeout := 5 * time.Second 106 » » s.currentFetcherCtx = s.makeFetcherCtx(c)
102 » » if s.Timeout != 0 {
103 » » » timeout = s.Timeout
104 » » }
105 » » s.currentFetcherCtx, _ = context.WithTimeout(c, timeout)
106 107
107 // Copy lock-protected guts into local variables before releasin g the lock. 108 // Copy lock-protected guts into local variables before releasin g the lock.
108 state.C = s.currentFetcherCtx 109 state.C = s.currentFetcherCtx
109 state.Fetcher = s.Fetcher 110 state.Fetcher = s.Fetcher
110 state.PrevValue = *s.current 111 state.PrevValue = *s.current
112 state.RetryFactory = s.RetryFactory
113 state.RetryCallback = s.RetryCallback
111 return 114 return
112 }() 115 }()
113 if done { 116 if done {
114 return 117 return
115 } 118 }
116 119
117 // Finish the fetch and update the cached value. 120 // Finish the fetch and update the cached value.
118 return func() (result Value, err error) { 121 return func() (result Value, err error) {
119 defer func() { 122 defer func() {
120 s.lock.Lock() 123 s.lock.Lock()
121 defer s.lock.Unlock() 124 defer s.lock.Unlock()
122 s.currentFetcherCtx = nil 125 s.currentFetcherCtx = nil
123 // result.Value is not nil iff fetch succeeded and didn' t panic. 126 // result.Value is not nil iff fetch succeeded and didn' t panic.
124 if result.Value != nil { 127 if result.Value != nil {
125 s.current = &result 128 s.current = &result
126 } 129 }
127 }() 130 }()
128 » » return doFetch(state.C, state.Fetcher, state.PrevValue) 131 » » return fetchWithRetries(state.C, state.Fetcher, state.RetryFacto ry, state.RetryCallback, state.PrevValue)
129 }() 132 }()
130 } 133 }
131 134
135 // makeFetcherCtx prepares a context to use for fetch operation.
136 //
137 // Must be called under the lock.
138 func (s *Slot) makeFetcherCtx(c context.Context) context.Context {
139 timeout := 15 * time.Second
140 if s.Timeout != 0 {
141 timeout = s.Timeout
142 }
143 fetcherCtx, _ := clock.WithTimeout(c, timeout)
144 return fetcherCtx
145 }
146
147 // fetchWithRetries calls doFetch, retrying on errors.
148 func fetchWithRetries(ctx context.Context, cb Fetcher, factory retry.Factory, re tryCb retry.Callback, prev Value) (result Value, err error) {
149 if factory == nil {
150 return doFetch(ctx, cb, prev)
151 }
152 err = retry.Retry(clock.Tag(ctx, "retry"), factory, func() error {
153 result, err = doFetch(ctx, cb, prev)
154 return err
155 }, retryCb)
156 if err != nil {
157 result = Value{}
158 }
159 return
160 }
161
132 // doFetch calls fetcher callback and validates return value. 162 // doFetch calls fetcher callback and validates return value.
133 func doFetch(ctx context.Context, cb Fetcher, prev Value) (result Value, err err or) { 163 func doFetch(ctx context.Context, cb Fetcher, prev Value) (result Value, err err or) {
134 result, err = cb(ctx, prev) 164 result, err = cb(ctx, prev)
135 switch { 165 switch {
136 case err == nil && result.Value == nil: 166 case err == nil && result.Value == nil:
137 panic("lazyslot.Slot Fetcher returned nil value") 167 panic("lazyslot.Slot Fetcher returned nil value")
138 case err != nil: 168 case err != nil:
139 result = Value{} 169 result = Value{}
140 } 170 }
141 return 171 return
142 } 172 }
OLDNEW
« no previous file with comments | « no previous file | common/lazyslot/lazyslot_test.go » ('j') | server/settings/settings.go » ('J')

Powered by Google App Engine
This is Rietveld 408576698