github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/shareds/store.go (about) 1 /* 2 * Copyright 2023 Wang Min Xiang 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package shareds 19 20 import ( 21 "github.com/aacfactory/configures" 22 "github.com/aacfactory/errors" 23 "github.com/aacfactory/fns/commons/bytex" 24 "github.com/aacfactory/fns/context" 25 "github.com/aacfactory/logs" 26 "github.com/tidwall/btree" 27 "strconv" 28 "sync" 29 "sync/atomic" 30 "time" 31 ) 32 33 type Store interface { 34 Get(ctx context.Context, key []byte) (value []byte, has bool, err error) 35 Set(ctx context.Context, key []byte, value []byte) (err error) 36 SetWithTTL(ctx context.Context, key []byte, value []byte, ttl time.Duration) (err error) 37 Remove(ctx context.Context, key []byte) (err error) 38 Incr(ctx context.Context, key []byte, delta int64) (v int64, err error) 39 Expire(ctx context.Context, key []byte, ttl time.Duration) (err error) 40 Close() 41 } 42 43 type StoreBuilder interface { 44 Build(ctx context.Context, config configures.Config) (store Store, err error) 45 } 46 47 var ( 48 localStoreBuilder LocalStoreBuild = defaultLocalStoreBuild 49 ) 50 51 func RegisterLocalStoreBuild(build LocalStoreBuild) { 52 localStoreBuilder = build 53 } 54 55 type LocalStoreBuild func(log logs.Logger, config configures.Config) (Store, error) 56 57 type DefaultLocalSharedStoreConfig struct { 58 ShrinkDuration time.Duration `json:"shrinkDuration"` 59 } 60 61 func defaultLocalStoreBuild(log logs.Logger, config configures.Config) (store Store, err error) { 62 cfg := DefaultLocalSharedStoreConfig{} 63 cfgErr := config.As(&cfg) 64 if cfgErr != nil { 65 err = errors.Warning("fns: build default local shared store failed").WithCause(cfgErr) 66 return 67 } 68 shrinkDuration := cfg.ShrinkDuration 69 if shrinkDuration == 0 { 70 shrinkDuration = 1 * time.Hour 71 } 72 s := &localStore{ 73 log: log, 74 shrinkDuration: shrinkDuration, 75 closeCh: make(chan struct{}, 1), 76 locker: sync.RWMutex{}, 77 values: btree.NewMap[string, Entry](0), 78 } 79 if shrinkDuration > 0 { 80 go s.shrink() 81 } 82 store = s 83 return 84 } 85 86 type localStore struct { 87 log logs.Logger 88 shrinkDuration time.Duration 89 closeCh chan struct{} 90 locker sync.RWMutex 91 values *btree.Map[string, Entry] 92 } 93 94 func (store *localStore) Get(_ context.Context, key []byte) (value []byte, has bool, err error) { 95 if key == nil || len(key) == 0 { 96 err = errors.Warning("fns: shared store get failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key)) 97 return 98 } 99 store.locker.RLock() 100 defer store.locker.RUnlock() 101 sk := bytex.ToString(key) 102 entry, exist := store.values.Get(sk) 103 if !exist { 104 return 105 } 106 if entry.Expired() { 107 return 108 } 109 has = true 110 switch v := entry.Value.(type) { 111 case []byte: 112 value = v 113 break 114 case *atomic.Int64: 115 value = bytex.FromString(strconv.FormatInt(v.Load(), 64)) 116 break 117 default: 118 has = false 119 break 120 } 121 return 122 } 123 124 func (store *localStore) Set(_ context.Context, key []byte, value []byte) (err error) { 125 if key == nil || len(key) == 0 { 126 err = errors.Warning("fns: shared store set failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key)) 127 return 128 } 129 store.locker.Lock() 130 defer store.locker.Unlock() 131 sk := bytex.ToString(key) 132 store.values.Set(sk, Entry{ 133 key: sk, 134 Value: value, 135 Deadline: time.Time{}, 136 }) 137 return 138 } 139 140 func (store *localStore) SetWithTTL(ctx context.Context, key []byte, value []byte, ttl time.Duration) (err error) { 141 if key == nil || len(key) == 0 { 142 err = errors.Warning("fns: shared store set failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key)) 143 return 144 } 145 if ttl < 1 { 146 err = store.Set(ctx, key, value) 147 return 148 } 149 store.locker.Lock() 150 defer store.locker.Unlock() 151 sk := bytex.ToString(key) 152 store.values.Set(sk, Entry{ 153 key: sk, 154 Value: value, 155 Deadline: time.Now().Add(ttl), 156 }) 157 return 158 } 159 160 func (store *localStore) Incr(_ context.Context, key []byte, delta int64) (v int64, err error) { 161 if key == nil || len(key) == 0 { 162 err = errors.Warning("fns: shared store incr failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key)) 163 return 164 } 165 store.locker.Lock() 166 defer store.locker.Unlock() 167 sk := bytex.ToString(key) 168 var n *atomic.Int64 169 entry, exist := store.values.Get(sk) 170 if exist { 171 if entry.Expired() { 172 n = new(atomic.Int64) 173 } else { 174 nn, ok := entry.Value.(*atomic.Int64) 175 if !ok { 176 err = errors.Warning("fns: shared store incr failed").WithCause(errors.Warning("value of key is not int64")).WithMeta("shared", "local").WithMeta("key", string(key)) 177 return 178 } 179 n = nn 180 } 181 } else { 182 n = new(atomic.Int64) 183 entry = Entry{ 184 key: sk, 185 Value: n, 186 Deadline: time.Time{}, 187 } 188 } 189 v = n.Add(delta) 190 store.values.Set(sk, entry) 191 return 192 } 193 194 func (store *localStore) Remove(_ context.Context, key []byte) (err error) { 195 if key == nil || len(key) == 0 { 196 err = errors.Warning("fns: shared store remove failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key)) 197 return 198 } 199 store.locker.Lock() 200 defer store.locker.Unlock() 201 sk := bytex.ToString(key) 202 store.values.Delete(sk) 203 return 204 } 205 206 func (store *localStore) Expire(ctx context.Context, key []byte, ttl time.Duration) (err error) { 207 if key == nil || len(key) == 0 { 208 err = errors.Warning("fns: shared store expire key failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key)) 209 return 210 } 211 store.locker.Lock() 212 defer store.locker.Unlock() 213 sk := bytex.ToString(key) 214 entry, exist := store.values.Get(sk) 215 if !exist { 216 return 217 } 218 if ttl < 1 { 219 entry.Deadline = time.Time{} 220 } else { 221 entry.Deadline = time.Now().Add(ttl) 222 } 223 store.values.Set(sk, entry) 224 return 225 } 226 227 func (store *localStore) shrink() { 228 timer := time.NewTimer(store.shrinkDuration) 229 stop := false 230 for { 231 select { 232 case <-store.closeCh: 233 stop = true 234 break 235 case <-timer.C: 236 store.locker.Lock() 237 expires := make([]string, 0, 1) 238 values := store.values.Values() 239 for _, value := range values { 240 if value.Expired() { 241 expires = append(expires, value.key) 242 } 243 } 244 for _, expire := range expires { 245 store.values.Delete(expire) 246 } 247 store.locker.Unlock() 248 timer.Reset(store.shrinkDuration) 249 break 250 } 251 if stop { 252 break 253 } 254 } 255 timer.Stop() 256 } 257 258 func (store *localStore) Close() { 259 if store.shrinkDuration > 0 { 260 store.closeCh <- struct{}{} 261 } 262 close(store.closeCh) 263 } 264 265 type Entry struct { 266 key string 267 Value any 268 Deadline time.Time 269 } 270 271 func (entry *Entry) Expired() bool { 272 if entry.Deadline.IsZero() { 273 return false 274 } 275 return time.Now().After(entry.Deadline) 276 }