@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
7
7
package lazyref
8
8
9
9
import (
10
+ "errors"
10
11
"fmt"
11
12
"sync"
12
13
"sync/atomic"
@@ -49,6 +50,9 @@ const (
49
50
// LastInitialized specifies that the expiration time is calculated
50
51
// from the time the reference was initialized
51
52
LastInitialized
53
+
54
+ // Refreshing indicates that the reference should be periodically refreshed
55
+ Refreshing
52
56
)
53
57
54
58
// Reference holds a value that is initialized on first access using the provided
@@ -63,7 +67,7 @@ const (
63
67
// is closed (via a call to Close) or if it expires. (Note: The Finalizer function
64
68
// is not called every time the value is refreshed with the periodic refresh feature.)
65
69
type Reference struct {
66
- sync.RWMutex
70
+ lock sync.RWMutex
67
71
ref unsafe.Pointer
68
72
lastTimeAccessed unsafe.Pointer
69
73
initializer Initializer
@@ -72,15 +76,17 @@ type Reference struct {
72
76
expirationProvider ExpirationProvider
73
77
initialInit time.Duration
74
78
expiryType ExpirationType
75
- closed chan bool
79
+ closed bool
80
+ closech chan bool
81
+ running bool
82
+ wg sync.WaitGroup
76
83
}
77
84
78
85
// New creates a new reference
79
86
func New (initializer Initializer , opts ... Opt ) * Reference {
80
87
lazyRef := & Reference {
81
88
initializer : initializer ,
82
89
initialInit : InitOnFirstAccess ,
83
- closed : make (chan bool , 1 ),
84
90
}
85
91
86
92
for _ , opt := range opts {
@@ -91,19 +97,23 @@ func New(initializer Initializer, opts ...Opt) *Reference {
91
97
// This is an expiring reference. After the initializer is
92
98
// called, set a timer that will call the expiration handler.
93
99
initializer := lazyRef .initializer
100
+ initialExpiration := lazyRef .expirationProvider ()
94
101
lazyRef .initializer = func () (interface {}, error ) {
95
102
value , err := initializer ()
96
103
if err == nil {
97
- lazyRef .startTimer ( lazyRef . expirationProvider () )
104
+ lazyRef .ensureTimerStarted ( initialExpiration )
98
105
}
99
106
return value , err
100
107
}
108
+
109
+ lazyRef .closech = make (chan bool , 1 )
110
+
101
111
if lazyRef .expirationHandler == nil {
102
112
// Set a default expiration handler
103
113
lazyRef .expirationHandler = lazyRef .resetValue
104
114
}
105
115
if lazyRef .initialInit >= 0 {
106
- lazyRef .startTimer (lazyRef .initialInit )
116
+ lazyRef .ensureTimerStarted (lazyRef .initialInit )
107
117
}
108
118
}
109
119
@@ -117,8 +127,12 @@ func (r *Reference) Get() (interface{}, error) {
117
127
return value , nil
118
128
}
119
129
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
+ }
122
136
123
137
// Try again inside the lock
124
138
if value , ok := r .get (); ok {
@@ -151,16 +165,34 @@ func (r *Reference) MustGet() interface{} {
151
165
// Close should be called for expiring references and
152
166
// rerences that specify finalizers.
153
167
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" )
156
174
157
- logger .Debug ("Closing reference" )
175
+ r .notifyClosing ()
176
+ r .wg .Wait ()
177
+ r .finalize ()
178
+ }
158
179
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
161
185
}
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
164
196
}
165
197
}
166
198
@@ -173,6 +205,10 @@ func (r *Reference) get() (interface{}, bool) {
173
205
return (* valueHolder )(p ).value , true
174
206
}
175
207
208
+ func (r * Reference ) isSet () bool {
209
+ return atomic .LoadPointer (& r .ref ) != nil
210
+ }
211
+
176
212
func (r * Reference ) set (value interface {}) {
177
213
atomic .StorePointer (& r .ref , unsafe .Pointer (& valueHolder {value : value }))
178
214
}
@@ -187,36 +223,106 @@ func (r *Reference) lastAccessed() time.Time {
187
223
return * (* time .Time )(p )
188
224
}
189
225
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
+
191
261
r .setLastAccessed ()
192
262
193
263
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
195
273
for {
196
274
select {
197
- case <- r .closed :
275
+ case <- r .closech :
276
+ logger .Debugf ("Got closed event. Exiting timer." )
277
+ return
278
+
198
279
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
202
286
}
203
287
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..." )
207
290
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
+ }
209
307
}
210
- // Set another expiry for the remainder of the time
211
- expiry = expiration - durSinceLastAccess
212
308
}
213
309
}
214
310
}()
215
311
}
216
312
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
+
217
323
func (r * Reference ) handleExpiration () {
218
- r .Lock ()
219
- defer r .Unlock ()
324
+ r .lock . Lock ()
325
+ defer r .lock . Unlock ()
220
326
221
327
logger .Debug ("Invoking expiration handler" )
222
328
r .expirationHandler ()
@@ -240,10 +346,7 @@ func (r *Reference) resetValue() {
240
346
// lock so there's no need to lock
241
347
func (r * Reference ) refreshValue () {
242
348
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 )
247
350
} else {
248
351
r .set (value )
249
352
}
0 commit comments