4
4
"container/heap"
5
5
"slices"
6
6
"sync"
7
+ "sync/atomic"
7
8
"time"
8
9
9
10
"github.com/negrel/assert"
@@ -13,7 +14,7 @@ import (
13
14
"github.com/rs/zerolog"
14
15
)
15
16
16
- // Service define an in memory session storage.
17
+ // Service define an in- memory session storage.
17
18
type Service interface {
18
19
// InsertSession stores given session in memory. If number of visitor session
19
20
// exceed configured max session per visitor, this function returns false and
@@ -26,7 +27,7 @@ type Service interface {
26
27
// IdentifySession updates stored session visitor id. Updated session and
27
28
// boolean found flag are returned.
28
29
IdentifySession (deviceId uint64 , pageUri uri.Uri , visitorId string ) (event.Session , bool )
29
- // WaitForSession retrieves stored session and returns it. If session is not
30
+ // WaitSession retrieves stored session and returns it. If session is not
30
31
// found, it waits until it is created or timeout.
31
32
// Returned boolean flag is false if wait timed out and returned an empty
32
33
// session.
@@ -46,12 +47,12 @@ func (e *sessionEntry) hasWaiter() bool {
46
47
return e .wait != nil
47
48
}
48
49
49
- func (e * sessionEntry ) isExpired () bool {
50
- return uint32 (time . Now () .Unix ()) >= e .expiry
50
+ func (e * sessionEntry ) isExpired (now time. Time ) bool {
51
+ return uint32 (now .Unix ()) >= e .expiry
51
52
}
52
53
53
- func (e * sessionEntry ) isValid () bool {
54
- return ! e .hasWaiter () && ! e .isExpired ()
54
+ func (e * sessionEntry ) isValid (now time. Time ) bool {
55
+ return ! e .hasWaiter () && ! e .isExpired (now )
55
56
}
56
57
57
58
// deviceData holds sessions entries and gc metadata associated to a single
@@ -74,6 +75,9 @@ type service struct {
74
75
75
76
// GC priority queue.
76
77
gcQueue gcQueue
78
+
79
+ // Internal clock.
80
+ now atomic.Pointer [time.Time ]
77
81
}
78
82
79
83
// ProvideService is a wire provider for in memory session storage.
@@ -90,13 +94,15 @@ func ProvideService(
90
94
Dur ("session_inactive_ttl" , cfg .sessionInactiveTtl ).
91
95
Logger ()
92
96
97
+ now := time .Now ()
93
98
service := & service {
94
99
logger : logger ,
95
100
cfg : cfg ,
96
101
metrics : newMetrics (promRegistry ),
97
102
mu : sync.Mutex {},
98
103
devices : make (map [uint64 ]* deviceData ),
99
104
gcQueue : gcQueue {},
105
+ now : & now
100
106
}
101
107
heap .Init (& service .gcQueue )
102
108
@@ -145,7 +151,7 @@ func (s *service) getSession(deviceId uint64, latestPath string) (*sessionEntry,
145
151
func (s * service ) getValidSessionEntry (deviceId uint64 , latestPath string ) * sessionEntry {
146
152
assert .Locked (& s .mu )
147
153
session , device , i := s .getSession (deviceId , latestPath )
148
- if session == nil || ! session .isValid () {
154
+ if session == nil || ! session .isValid (* s . now . Load () ) {
149
155
return nil
150
156
}
151
157
@@ -314,7 +320,7 @@ func (s *service) WaitSession(deviceId uint64, pageUri uri.Uri, timeout time.Dur
314
320
currentSession , deviceData , sessionIndex := s .getSession (deviceId , pageUri .Path ())
315
321
316
322
// Valid session.
317
- if currentSession != nil && currentSession .isValid () {
323
+ if currentSession != nil && currentSession .isValid (* s . now . Load () ) {
318
324
s .mu .Unlock ()
319
325
return currentSession .Session , true
320
326
} else if timeout == time .Duration (0 ) { // Entry not found and timeout is 0s.
@@ -377,18 +383,21 @@ func (s *service) WaitSession(deviceId uint64, pageUri uri.Uri, timeout time.Dur
377
383
378
384
// session garbage collector loop.
379
385
func (s * service ) gcLoop () {
386
+ tick := time .NewTicker (s .cfg .gcInterval )
387
+
380
388
for {
389
+ now := <- tick .C
390
+
391
+ s .now .Store (& now )
381
392
s .metrics .gcCycle .Inc ()
382
393
383
394
// Wait until there is job in gcQueue.
384
395
s .mu .Lock ()
385
396
if len (s .gcQueue ) == 0 {
386
397
s .mu .Unlock ()
387
- time .Sleep (s .cfg .gcInterval )
388
398
continue
389
399
}
390
400
391
- now := time .Now ()
392
401
nowTs := uint32 (now .Unix ())
393
402
394
403
// Peek job.
@@ -397,7 +406,6 @@ func (s *service) gcLoop() {
397
406
// Job hasn't expired yet.
398
407
if job .pExpiry > nowTs {
399
408
s .mu .Unlock ()
400
- time .Sleep (s .cfg .gcInterval )
401
409
continue
402
410
}
403
411
@@ -443,5 +451,5 @@ func (s *service) gcLoop() {
443
451
}
444
452
445
453
func (s * service ) newExpiry () uint32 {
446
- return uint32 (time . Now ().Add (s .cfg .sessionInactiveTtl ).Unix ())
454
+ return uint32 (s . now . Load ().Add (s .cfg .sessionInactiveTtl ).Unix ())
447
455
}
0 commit comments