Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Commit ff9763c

Browse files
committed
[FAB-8945] Fix lazyref bugs
Fix the following issues: - Close() didn't wait for Go routine to shut down - Idle expiration time wasn't calculated correctly Change-Id: Iabacfad37b9d57bd53491735099f451483dcdc6f Signed-off-by: Bob Stasyszyn <Bob.Stasyszyn@securekey.com>
1 parent cf7cd8a commit ff9763c

File tree

3 files changed

+160
-66
lines changed

3 files changed

+160
-66
lines changed

pkg/util/concurrent/lazyref/lazyref.go

Lines changed: 135 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
77
package lazyref
88

99
import (
10+
"errors"
1011
"fmt"
1112
"sync"
1213
"sync/atomic"
@@ -49,6 +50,9 @@ const (
4950
// LastInitialized specifies that the expiration time is calculated
5051
// from the time the reference was initialized
5152
LastInitialized
53+
54+
// Refreshing indicates that the reference should be periodically refreshed
55+
Refreshing
5256
)
5357

5458
// Reference holds a value that is initialized on first access using the provided
@@ -63,7 +67,7 @@ const (
6367
// is closed (via a call to Close) or if it expires. (Note: The Finalizer function
6468
// is not called every time the value is refreshed with the periodic refresh feature.)
6569
type Reference struct {
66-
sync.RWMutex
70+
lock sync.RWMutex
6771
ref unsafe.Pointer
6872
lastTimeAccessed unsafe.Pointer
6973
initializer Initializer
@@ -72,15 +76,17 @@ type Reference struct {
7276
expirationProvider ExpirationProvider
7377
initialInit time.Duration
7478
expiryType ExpirationType
75-
closed chan bool
79+
closed bool
80+
closech chan bool
81+
running bool
82+
wg sync.WaitGroup
7683
}
7784

7885
// New creates a new reference
7986
func New(initializer Initializer, opts ...Opt) *Reference {
8087
lazyRef := &Reference{
8188
initializer: initializer,
8289
initialInit: InitOnFirstAccess,
83-
closed: make(chan bool, 1),
8490
}
8591

8692
for _, opt := range opts {
@@ -91,19 +97,23 @@ func New(initializer Initializer, opts ...Opt) *Reference {
9197
// This is an expiring reference. After the initializer is
9298
// called, set a timer that will call the expiration handler.
9399
initializer := lazyRef.initializer
100+
initialExpiration := lazyRef.expirationProvider()
94101
lazyRef.initializer = func() (interface{}, error) {
95102
value, err := initializer()
96103
if err == nil {
97-
lazyRef.startTimer(lazyRef.expirationProvider())
104+
lazyRef.ensureTimerStarted(initialExpiration)
98105
}
99106
return value, err
100107
}
108+
109+
lazyRef.closech = make(chan bool, 1)
110+
101111
if lazyRef.expirationHandler == nil {
102112
// Set a default expiration handler
103113
lazyRef.expirationHandler = lazyRef.resetValue
104114
}
105115
if lazyRef.initialInit >= 0 {
106-
lazyRef.startTimer(lazyRef.initialInit)
116+
lazyRef.ensureTimerStarted(lazyRef.initialInit)
107117
}
108118
}
109119

@@ -117,8 +127,12 @@ func (r *Reference) Get() (interface{}, error) {
117127
return value, nil
118128
}
119129

120-
r.Lock()
121-
defer r.Unlock()
130+
r.lock.Lock()
131+
defer r.lock.Unlock()
132+
133+
if r.closed {
134+
return nil, errors.New("reference is already closed")
135+
}
122136

123137
// Try again inside the lock
124138
if value, ok := r.get(); ok {
@@ -151,16 +165,34 @@ func (r *Reference) MustGet() interface{} {
151165
// Close should be called for expiring references and
152166
// rerences that specify finalizers.
153167
func (r *Reference) Close() {
154-
r.Lock()
155-
defer r.Unlock()
168+
if !r.setClosed() {
169+
// Already closed
170+
return
171+
}
172+
173+
logger.Info("Closing reference")
156174

157-
logger.Debug("Closing reference")
175+
r.notifyClosing()
176+
r.wg.Wait()
177+
r.finalize()
178+
}
158179

159-
if r.expirationHandler != nil {
160-
r.closed <- true
180+
func (r *Reference) setClosed() bool {
181+
r.lock.Lock()
182+
defer r.lock.Unlock()
183+
if r.closed {
184+
return false
161185
}
162-
if r.finalizer != nil {
163-
r.finalizer()
186+
r.closed = true
187+
return true
188+
}
189+
190+
func (r *Reference) notifyClosing() {
191+
r.lock.Lock()
192+
defer r.lock.Unlock()
193+
if r.running {
194+
logger.Debugf("Sending closed event...")
195+
r.closech <- true
164196
}
165197
}
166198

@@ -173,6 +205,10 @@ func (r *Reference) get() (interface{}, bool) {
173205
return (*valueHolder)(p).value, true
174206
}
175207

208+
func (r *Reference) isSet() bool {
209+
return atomic.LoadPointer(&r.ref) != nil
210+
}
211+
176212
func (r *Reference) set(value interface{}) {
177213
atomic.StorePointer(&r.ref, unsafe.Pointer(&valueHolder{value: value}))
178214
}
@@ -187,36 +223,106 @@ func (r *Reference) lastAccessed() time.Time {
187223
return *(*time.Time)(p)
188224
}
189225

190-
func (r *Reference) startTimer(expiration time.Duration) {
226+
func (r *Reference) timerRunning() bool {
227+
r.lock.RLock()
228+
defer r.lock.RUnlock()
229+
return r.running
230+
}
231+
232+
func (r *Reference) setTimerRunning() bool {
233+
r.lock.Lock()
234+
defer r.lock.Unlock()
235+
236+
if r.running || r.closed {
237+
logger.Debugf("Cannot start timer since timer is either already running or it is closed")
238+
return false
239+
}
240+
241+
r.running = true
242+
r.wg.Add(1)
243+
logger.Debugf("Timer started")
244+
return true
245+
}
246+
247+
func (r *Reference) setTimerStopped() {
248+
r.lock.Lock()
249+
defer r.lock.Unlock()
250+
logger.Debugf("Timer stopped")
251+
r.running = false
252+
r.wg.Done()
253+
}
254+
255+
func (r *Reference) ensureTimerStarted(initialExpiration time.Duration) {
256+
if r.running {
257+
logger.Debugf("Timer is already running")
258+
return
259+
}
260+
191261
r.setLastAccessed()
192262

193263
go func() {
194-
expiry := expiration
264+
if !r.setTimerRunning() {
265+
logger.Debugf("Timer is already running")
266+
return
267+
}
268+
defer r.setTimerStopped()
269+
270+
logger.Debugf("Starting timer")
271+
272+
expiry := initialExpiration
195273
for {
196274
select {
197-
case <-r.closed:
275+
case <-r.closech:
276+
logger.Debugf("Got closed event. Exiting timer.")
277+
return
278+
198279
case <-time.After(expiry):
199-
if r.expiryType == LastInitialized {
200-
r.handleExpiration()
201-
return
280+
expiration := r.expirationProvider()
281+
282+
if !r.isSet() && r.expiryType != Refreshing {
283+
expiry = expiration
284+
logger.Debugf("Reference is not set. Will expire again in %s", expiry)
285+
continue
202286
}
203287

204-
// Check how long it's been since last access
205-
durSinceLastAccess := time.Now().Sub(r.lastAccessed())
206-
if durSinceLastAccess > expiration {
288+
if r.expiryType == LastInitialized || r.expiryType == Refreshing {
289+
logger.Debugf("Handling expiration...")
207290
r.handleExpiration()
208-
return
291+
expiry = expiration
292+
logger.Debugf("... finished handling expiration. Setting expiration to %s", expiry)
293+
} else {
294+
// Check how long it's been since last access
295+
durSinceLastAccess := time.Now().Sub(r.lastAccessed())
296+
logger.Debugf("Duration since last access is %s", durSinceLastAccess)
297+
if durSinceLastAccess > expiration {
298+
logger.Debugf("... handling expiration...")
299+
r.handleExpiration()
300+
expiry = expiration
301+
logger.Debugf("... finished handling expiration. Setting expiration to %s", expiry)
302+
} else {
303+
// Set another expiry for the remainder of the time
304+
expiry = expiration - durSinceLastAccess
305+
logger.Debugf("Not expired yet. Will check again in %s", expiry)
306+
}
209307
}
210-
// Set another expiry for the remainder of the time
211-
expiry = expiration - durSinceLastAccess
212308
}
213309
}
214310
}()
215311
}
216312

313+
func (r *Reference) finalize() {
314+
if r.finalizer == nil {
315+
return
316+
}
317+
318+
r.lock.Lock()
319+
r.finalizer()
320+
r.lock.Unlock()
321+
}
322+
217323
func (r *Reference) handleExpiration() {
218-
r.Lock()
219-
defer r.Unlock()
324+
r.lock.Lock()
325+
defer r.lock.Unlock()
220326

221327
logger.Debug("Invoking expiration handler")
222328
r.expirationHandler()
@@ -240,10 +346,7 @@ func (r *Reference) resetValue() {
240346
// lock so there's no need to lock
241347
func (r *Reference) refreshValue() {
242348
if value, err := r.initializer(); err != nil {
243-
expiration := r.expirationProvider()
244-
logger.Warnf("Error - initializer returned error: %s. Will retry in %s", err, expiration)
245-
// Start the timer so that we can retry
246-
r.startTimer(expiration)
349+
logger.Warnf("Error - initializer returned error: %s. Will retry again later", err)
247350
} else {
248351
r.set(value)
249352
}

0 commit comments

Comments
 (0)