github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/internal/cache/cache.go (about) 1 // Copyright 2018 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package cache contains caching structures using internally by the Fleetspeak 16 // server. 17 package cache 18 19 import ( 20 "context" 21 "sync" 22 "time" 23 24 "github.com/google/fleetspeak/fleetspeak/src/common" 25 "github.com/google/fleetspeak/fleetspeak/src/server/db" 26 ) 27 28 var ( 29 // MaxAge defines how long client data should be considered valid 30 // for. Variable to support unit testing. 31 MaxAge = 45 * time.Second 32 33 // We occasionally expunge old client data records, to be tidy 34 // with RAM and prevent what would effectively be a slow memory leak as 35 // clients come and go. Variable to support unit testing. 36 expireInterval = 5 * time.Minute 37 ) 38 39 // Clients is a cache of recently connected clients. 40 type Clients struct { 41 m map[common.ClientID]*clientEntry 42 l sync.RWMutex 43 stop chan struct{} 44 } 45 46 type clientEntry struct { 47 u time.Time 48 d *db.ClientData 49 } 50 51 // NewClients returns a new cache of client data. 52 func NewClients() *Clients { 53 ret := &Clients{ 54 m: make(map[common.ClientID]*clientEntry), 55 stop: make(chan struct{}), 56 } 57 go ret.expireLoop() 58 return ret 59 } 60 61 // Get returns the cached client data, if there is sufficiently fresh data in 62 // the cache, otherwise nil. 63 func (c *Clients) Get(id common.ClientID) *db.ClientData { 64 c.l.RLock() 65 defer c.l.RUnlock() 66 67 e := c.m[id] 68 if e == nil || db.Now().Sub(e.u) > MaxAge { 69 return nil 70 } 71 return e.d.Clone() 72 } 73 74 // GetOrRead returns the cached client data, if there is sufficiently fresh data 75 // in the cache. Otherwise it attempts to read the data from the provided 76 // datastore. It returns data if it finds any, hit if it was found in the cache. 77 func (c *Clients) GetOrRead(ctx context.Context, id common.ClientID, db db.ClientStore) (data *db.ClientData, hit bool, err error) { 78 ret := c.Get(id) 79 if ret != nil { 80 // cloned by Get 81 return ret, true, nil 82 } 83 84 if ret, err = db.GetClientData(ctx, id); err != nil { 85 return nil, false, err 86 } 87 c.Update(id, ret) 88 return ret, false, nil 89 } 90 91 // Update updates or sets the cached data for a particular client. If data is 92 // nil, it clears the data for the client. 93 func (c *Clients) Update(id common.ClientID, data *db.ClientData) { 94 c.l.Lock() 95 defer c.l.Unlock() 96 97 if data == nil { 98 delete(c.m, id) 99 } else { 100 c.m[id] = &clientEntry{ 101 u: db.Now(), 102 d: data.Clone(), 103 } 104 } 105 } 106 107 // Clear empties the cache, removing all entries. 108 func (c *Clients) Clear() { 109 c.l.Lock() 110 defer c.l.Unlock() 111 112 c.m = make(map[common.ClientID]*clientEntry) 113 } 114 115 // Stop releases the resources required for background cache maintenance. The 116 // cache should not be used once Stop has been called. 117 func (c *Clients) Stop() { 118 close(c.stop) 119 } 120 121 // Size returns the current size taken up by the cache, this is a count of 122 // client records, some of which may no longer be up to date. 123 func (c *Clients) Size() int { 124 c.l.RLock() 125 defer c.l.RUnlock() 126 return len(c.m) 127 } 128 129 func (c *Clients) expireLoop() { 130 t := time.NewTicker(expireInterval) 131 defer t.Stop() 132 133 for { 134 select { 135 case <-t.C: 136 c.expire() 137 case <-c.stop: 138 return 139 } 140 } 141 } 142 143 // expire prunes the cache to clean out clients that are no longer up to date. 144 func (c *Clients) expire() { 145 c.l.Lock() 146 defer c.l.Unlock() 147 148 for k, e := range c.m { 149 if db.Now().Sub(e.u) > MaxAge { 150 delete(c.m, k) 151 } 152 } 153 }