github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/cachevalue/cache.go (about) 1 // Copyright (c) 2015-2024 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cachevalue 19 20 import ( 21 "sync" 22 "sync/atomic" 23 "time" 24 ) 25 26 // Opts contains options for the cache. 27 type Opts struct { 28 // When set to true, return the last cached value 29 // even if updating the value errors out. 30 // Returns the last good value AND the error. 31 ReturnLastGood bool 32 33 // If CacheError is set, errors will be cached as well 34 // and not continuously try to update. 35 // Should not be combined with ReturnLastGood. 36 CacheError bool 37 38 // If NoWait is set, Get() will return the last good value, 39 // if TTL has expired but 2x TTL has not yet passed, 40 // but will fetch a new value in the background. 41 NoWait bool 42 } 43 44 // Cache contains a synchronized value that is considered valid 45 // for a specific amount of time. 46 // An Update function must be set to provide an updated value when needed. 47 type Cache[T any] struct { 48 // updateFn must return an updated value. 49 // If an error is returned the cached value is not set. 50 // Only one caller will call this function at any time, others will be blocking. 51 // The returned value can no longer be modified once returned. 52 // Should be set before calling Get(). 53 updateFn func() (T, error) 54 55 // ttl for a cached value. 56 ttl time.Duration 57 58 opts Opts 59 60 // Once can be used to initialize values for lazy initialization. 61 // Should be set before calling Get(). 62 Once sync.Once 63 64 // Managed values. 65 valErr atomic.Pointer[struct { 66 v T 67 e error 68 }] 69 lastUpdateMs atomic.Int64 70 updating sync.Mutex 71 } 72 73 // New allocates a new cached value instance. Tt must be initialized with 74 // `.TnitOnce`. 75 func New[T any]() *Cache[T] { 76 return &Cache[T]{} 77 } 78 79 // NewFromFunc allocates a new cached value instance and initializes it with an 80 // update function, making it ready for use. 81 func NewFromFunc[T any](ttl time.Duration, opts Opts, update func() (T, error)) *Cache[T] { 82 return &Cache[T]{ 83 ttl: ttl, 84 updateFn: update, 85 opts: opts, 86 } 87 } 88 89 // InitOnce initializes the cache with a TTL and an update function. It is 90 // guaranteed to be called only once. 91 func (t *Cache[T]) InitOnce(ttl time.Duration, opts Opts, update func() (T, error)) { 92 t.Once.Do(func() { 93 t.ttl = ttl 94 t.updateFn = update 95 t.opts = opts 96 }) 97 } 98 99 // Get will return a cached value or fetch a new one. 100 // Tf the Update function returns an error the value is forwarded as is and not cached. 101 func (t *Cache[T]) Get() (T, error) { 102 v := t.valErr.Load() 103 ttl := t.ttl 104 vTime := t.lastUpdateMs.Load() 105 tNow := time.Now().UnixMilli() 106 if v != nil && tNow-vTime < ttl.Milliseconds() { 107 if v.e == nil { 108 return v.v, nil 109 } 110 if v.e != nil && t.opts.CacheError || t.opts.ReturnLastGood { 111 return v.v, v.e 112 } 113 } 114 115 // Fetch new value. 116 if t.opts.NoWait && v != nil && tNow-vTime < ttl.Milliseconds()*2 && (v.e == nil || t.opts.CacheError) { 117 if t.updating.TryLock() { 118 go func() { 119 defer t.updating.Unlock() 120 t.update() 121 }() 122 } 123 return v.v, v.e 124 } 125 126 // Get lock. Either we get it or we wait for it. 127 t.updating.Lock() 128 if time.Since(time.UnixMilli(t.lastUpdateMs.Load())) < ttl { 129 // There is a new value, release lock and return it. 130 v = t.valErr.Load() 131 t.updating.Unlock() 132 return v.v, v.e 133 } 134 t.update() 135 v = t.valErr.Load() 136 t.updating.Unlock() 137 return v.v, v.e 138 } 139 140 func (t *Cache[T]) update() { 141 val, err := t.updateFn() 142 if err != nil { 143 if t.opts.ReturnLastGood { 144 // Keep last good value. 145 v := t.valErr.Load() 146 if v != nil { 147 val = v.v 148 } 149 } 150 } 151 t.valErr.Store(&struct { 152 v T 153 e error 154 }{v: val, e: err}) 155 t.lastUpdateMs.Store(time.Now().UnixMilli()) 156 }