github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/store/queuestore.go (about) 1 // Copyright (c) 2015-2023 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 store 19 20 import ( 21 "encoding/json" 22 "errors" 23 "os" 24 "path/filepath" 25 "sort" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/google/uuid" 31 jsoniter "github.com/json-iterator/go" 32 "github.com/valyala/bytebufferpool" 33 ) 34 35 const ( 36 defaultLimit = 100000 // Default store limit. 37 defaultExt = ".unknown" 38 ) 39 40 // errLimitExceeded error is sent when the maximum limit is reached. 41 var errLimitExceeded = errors.New("the maximum store limit reached") 42 43 // QueueStore - Filestore for persisting items. 44 type QueueStore[_ any] struct { 45 sync.RWMutex 46 entryLimit uint64 47 directory string 48 fileExt string 49 50 entries map[string]int64 // key -> modtime as unix nano 51 } 52 53 // NewQueueStore - Creates an instance for QueueStore. 54 func NewQueueStore[I any](directory string, limit uint64, ext string) *QueueStore[I] { 55 if limit == 0 { 56 limit = defaultLimit 57 } 58 59 if ext == "" { 60 ext = defaultExt 61 } 62 63 return &QueueStore[I]{ 64 directory: directory, 65 entryLimit: limit, 66 fileExt: ext, 67 entries: make(map[string]int64, limit), 68 } 69 } 70 71 // Open - Creates the directory if not present. 72 func (store *QueueStore[_]) Open() error { 73 store.Lock() 74 defer store.Unlock() 75 76 if err := os.MkdirAll(store.directory, os.FileMode(0o770)); err != nil { 77 return err 78 } 79 80 files, err := store.list() 81 if err != nil { 82 return err 83 } 84 85 // Truncate entries. 86 if uint64(len(files)) > store.entryLimit { 87 files = files[:store.entryLimit] 88 } 89 90 for _, file := range files { 91 if file.IsDir() { 92 continue 93 } 94 key := strings.TrimSuffix(file.Name(), store.fileExt) 95 if fi, err := file.Info(); err == nil { 96 store.entries[key] = fi.ModTime().UnixNano() 97 } 98 } 99 100 return nil 101 } 102 103 // Delete - Remove the store directory from disk 104 func (store *QueueStore[_]) Delete() error { 105 return os.Remove(store.directory) 106 } 107 108 // PutMultiple - puts an item to the store. 109 func (store *QueueStore[I]) PutMultiple(item []I) error { 110 store.Lock() 111 defer store.Unlock() 112 if uint64(len(store.entries)) >= store.entryLimit { 113 return errLimitExceeded 114 } 115 // Generate a new UUID for the key. 116 key, err := uuid.NewRandom() 117 if err != nil { 118 return err 119 } 120 return store.multiWrite(key.String(), item) 121 } 122 123 // multiWrite - writes an item to the directory. 124 func (store *QueueStore[I]) multiWrite(key string, item []I) error { 125 buf := bytebufferpool.Get() 126 defer bytebufferpool.Put(buf) 127 128 enc := jsoniter.ConfigCompatibleWithStandardLibrary.NewEncoder(buf) 129 130 for i := range item { 131 err := enc.Encode(item[i]) 132 if err != nil { 133 return err 134 } 135 } 136 b := buf.Bytes() 137 138 path := filepath.Join(store.directory, key+store.fileExt) 139 err := os.WriteFile(path, b, os.FileMode(0o770)) 140 buf.Reset() 141 if err != nil { 142 return err 143 } 144 145 // Increment the item count. 146 store.entries[key] = time.Now().UnixNano() 147 148 return nil 149 } 150 151 // write - writes an item to the directory. 152 func (store *QueueStore[I]) write(key string, item I) error { 153 // Marshalls the item. 154 eventData, err := json.Marshal(item) 155 if err != nil { 156 return err 157 } 158 159 path := filepath.Join(store.directory, key+store.fileExt) 160 if err := os.WriteFile(path, eventData, os.FileMode(0o770)); err != nil { 161 return err 162 } 163 164 // Increment the item count. 165 store.entries[key] = time.Now().UnixNano() 166 167 return nil 168 } 169 170 // Put - puts an item to the store. 171 func (store *QueueStore[I]) Put(item I) error { 172 store.Lock() 173 defer store.Unlock() 174 if uint64(len(store.entries)) >= store.entryLimit { 175 return errLimitExceeded 176 } 177 // Generate a new UUID for the key. 178 key, err := uuid.NewRandom() 179 if err != nil { 180 return err 181 } 182 return store.write(key.String(), item) 183 } 184 185 // GetRaw - gets an item from the store. 186 func (store *QueueStore[I]) GetRaw(key string) (raw []byte, err error) { 187 store.RLock() 188 189 defer func(store *QueueStore[I]) { 190 store.RUnlock() 191 if err != nil { 192 // Upon error we remove the entry. 193 store.Del(key) 194 } 195 }(store) 196 197 raw, err = os.ReadFile(filepath.Join(store.directory, key+store.fileExt)) 198 if err != nil { 199 return 200 } 201 202 if len(raw) == 0 { 203 return raw, os.ErrNotExist 204 } 205 206 return 207 } 208 209 // Get - gets an item from the store. 210 func (store *QueueStore[I]) Get(key string) (item I, err error) { 211 store.RLock() 212 213 defer func(store *QueueStore[I]) { 214 store.RUnlock() 215 if err != nil { 216 // Upon error we remove the entry. 217 store.Del(key) 218 } 219 }(store) 220 221 var eventData []byte 222 eventData, err = os.ReadFile(filepath.Join(store.directory, key+store.fileExt)) 223 if err != nil { 224 return item, err 225 } 226 227 if len(eventData) == 0 { 228 return item, os.ErrNotExist 229 } 230 231 if err = json.Unmarshal(eventData, &item); err != nil { 232 return item, err 233 } 234 235 return item, nil 236 } 237 238 // Del - Deletes an entry from the store. 239 func (store *QueueStore[_]) Del(key string) error { 240 store.Lock() 241 defer store.Unlock() 242 return store.del(key) 243 } 244 245 // DelList - Deletes a list of entries from the store. 246 // Returns an error even if one key fails to be deleted. 247 func (store *QueueStore[_]) DelList(keys []string) error { 248 store.Lock() 249 defer store.Unlock() 250 251 for _, key := range keys { 252 if err := store.del(key); err != nil { 253 return err 254 } 255 } 256 257 return nil 258 } 259 260 // Len returns the entry count. 261 func (store *QueueStore[_]) Len() int { 262 store.RLock() 263 l := len(store.entries) 264 defer store.RUnlock() 265 return l 266 } 267 268 // lockless call 269 func (store *QueueStore[_]) del(key string) error { 270 err := os.Remove(filepath.Join(store.directory, key+store.fileExt)) 271 272 // Delete as entry no matter the result 273 delete(store.entries, key) 274 275 return err 276 } 277 278 // List - lists all files registered in the store. 279 func (store *QueueStore[_]) List() ([]string, error) { 280 store.RLock() 281 l := make([]string, 0, len(store.entries)) 282 for k := range store.entries { 283 l = append(l, k) 284 } 285 286 // Sort entries... 287 sort.Slice(l, func(i, j int) bool { 288 return store.entries[l[i]] < store.entries[l[j]] 289 }) 290 store.RUnlock() 291 292 return l, nil 293 } 294 295 // list will read all entries from disk. 296 // Entries are returned sorted by modtime, oldest first. 297 // Underlying entry list in store is *not* updated. 298 func (store *QueueStore[_]) list() ([]os.DirEntry, error) { 299 files, err := os.ReadDir(store.directory) 300 if err != nil { 301 return nil, err 302 } 303 304 // Sort the entries. 305 sort.Slice(files, func(i, j int) bool { 306 ii, err := files[i].Info() 307 if err != nil { 308 return false 309 } 310 ji, err := files[j].Info() 311 if err != nil { 312 return true 313 } 314 return ii.ModTime().Before(ji.ModTime()) 315 }) 316 317 return files, nil 318 } 319 320 // Extension will return the file extension used 321 // for the items stored in the queue. 322 func (store *QueueStore[_]) Extension() string { 323 return store.fileExt 324 }