@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
77package lazyref
88
99import (
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.)
6569type 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
7986func 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.
153167func (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+
176212func (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+
217323func (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
241347func (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