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  }