@@ -10,6 +10,11 @@ import (
1010
1111type ExpireAtSecs int64
1212
13+ type entry struct {
14+ expiresAt []int64
15+ liveCount int
16+ }
17+
1318// Tree is a a thread-safe data structure for tracking expirable items. It automatically expires old entries and keys.
1419// It does not garbage collect. Items are only expired when interacting with the data structure.
1520type Tree struct {
@@ -60,20 +65,24 @@ func (n *Tree) Count(entryKey string) int {
6065 n .Lock ()
6166 defer n .Unlock ()
6267
63- datesSecs := n .getAndCleanupUnsafe (entryKey )
64- if datesSecs == nil {
68+ item , found := n .getAndCleanupUnsafe (entryKey )
69+ if ! found {
70+ return 0
71+ }
72+
73+ if item .liveCount == 0 {
74+ n .tree .Remove (entryKey )
6575 return 0
6676 }
6777
68- if len (* datesSecs ) == 0 {
78+ item = removeExpired (item )
79+ if item .liveCount == 0 {
6980 n .tree .Remove (entryKey )
7081 return 0
7182 }
7283
73- datesSecs = removeExpired (datesSecs )
74- count := len (* datesSecs )
75- n .tree .Put (entryKey , * datesSecs )
76- return count
84+ n .tree .Put (entryKey , item )
85+ return item .liveCount
7786}
7887
7988// KeyMatch crawls the subtree to return keys starting with the `keyPattern` string.
@@ -110,47 +119,52 @@ func (n *Tree) KeyMatch(keyPattern string) []string {
110119 return out
111120}
112121
113- func (n * Tree ) Put (entryKey string ) {
122+ func (n * Tree ) Put (entryKey string ) int {
114123 n .Lock ()
115124 defer n .Unlock ()
116125
117- datesSecs := n .getAndCleanupUnsafe (entryKey )
118- if datesSecs == nil {
119- datesSecs = & [] int64 {}
126+ item , found := n .getAndCleanupUnsafe (entryKey )
127+ if ! found {
128+ item = entry {}
120129 }
121- datesSecs = removeExpired (datesSecs )
130+ item = removeExpired (item )
122131 secs := time .Now ().Unix ()
123- nextDatesSecs := append (* datesSecs , secs + n .defaultExpireAfterSecs )
124- n .tree .Put (entryKey , nextDatesSecs )
132+ item .expiresAt = append (item .expiresAt , secs + n .defaultExpireAfterSecs )
133+ item .liveCount = len (item .expiresAt )
134+ n .tree .Put (entryKey , item )
135+ return item .liveCount
125136}
126137
127138// getAndCleanupUnsafe does not lock the mutex, so it can be used inside a lock
128- func (n * Tree ) getAndCleanupUnsafe (entryKey string ) * [] int64 {
139+ func (n * Tree ) getAndCleanupUnsafe (entryKey string ) ( entry , bool ) {
129140 val , found := n .tree .Get (entryKey )
130141 if ! found {
131- return nil
142+ return entry {}, false
132143 }
133- dates := val .([] int64 )
134- if len ( dates ) == 0 {
144+ item := val .(entry )
145+ if item . liveCount == 0 {
135146 // cleanup empty entry
136147 n .tree .Remove (entryKey )
137- return nil
148+ return entry {}, false
138149 }
139- return & dates // not extra copy
150+ return item , true
140151}
141152
142- func removeExpired (datesSecs * []int64 ) * []int64 {
143- if len (* datesSecs ) == 0 {
144- return datesSecs
153+ func removeExpired (item entry ) entry {
154+ if len (item .expiresAt ) == 0 {
155+ item .liveCount = 0
156+ return item
145157 }
146158 currentTime := time .Now ().Unix ()
147159 var out []int64
148160 // TODO: these are already sorted, so we can discard earlier entries
149- for _ , removeAt := range * datesSecs {
161+ for _ , removeAt := range item . expiresAt {
150162 if removeAt > currentTime {
151163 // KEEP - not expired
152164 out = append (out , removeAt )
153165 }
154166 }
155- return & out
167+ item .expiresAt = out
168+ item .liveCount = len (out )
169+ return item
156170}
0 commit comments