github.com/portworx/kvdb@v0.0.0-20241107215734-a185a966f535/common/common.go (about)

     1  package common
     2  
     3  import (
     4  	"container/list"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/portworx/kvdb"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  var (
    16  	path = "/var/cores/"
    17  )
    18  
    19  // ToBytes converts to value to a byte slice.
    20  func ToBytes(val interface{}) ([]byte, error) {
    21  	switch val.(type) {
    22  	case string:
    23  		return []byte(val.(string)), nil
    24  	case []byte:
    25  		b := make([]byte, len(val.([]byte)))
    26  		copy(b, val.([]byte))
    27  		return b, nil
    28  	default:
    29  		return json.Marshal(val)
    30  	}
    31  }
    32  
    33  // BaseKvdb provides common functionality across kvdb types
    34  type BaseKvdb struct {
    35  	// LockHoldTimeout is the maximum time any lock can be held
    36  	LockHoldTimeout time.Duration
    37  	// FatalCb invoked for fatal errors
    38  	FatalCb kvdb.FatalErrorCB
    39  	// lock to guard updates to timeout and fatalCb
    40  	lock sync.Mutex
    41  	// LockTryDuration is the max duration for which an attempt
    42  	// will be made to acquire a kvdb lock
    43  	LockTryDuration time.Duration
    44  }
    45  
    46  func (b *BaseKvdb) WrapperName() kvdb.WrapperName {
    47  	return kvdb.Wrapper_None
    48  }
    49  
    50  func (b *BaseKvdb) WrappedKvdb() kvdb.Kvdb {
    51  	return nil
    52  }
    53  
    54  func (b *BaseKvdb) Removed() {
    55  }
    56  
    57  func (b *BaseKvdb) SetWrappedKvdb(kvdb kvdb.Kvdb) error {
    58  	return fmt.Errorf("not suppoorted")
    59  }
    60  
    61  // SetFatalCb callback is invoked when an unrecoverable KVDB error happens.
    62  func (b *BaseKvdb) SetFatalCb(f kvdb.FatalErrorCB) {
    63  	b.lock.Lock()
    64  	defer b.lock.Unlock()
    65  	b.FatalCb = f
    66  }
    67  
    68  // SetLockHoldDuration has property such that if the lock is held past this duration,
    69  // then a configured fatal callback is called.
    70  func (b *BaseKvdb) SetLockHoldDuration(timeout time.Duration) {
    71  	b.lock.Lock()
    72  	defer b.lock.Unlock()
    73  	logrus.Infof("Setting lock hold duration to: %v", timeout)
    74  	b.LockHoldTimeout = timeout
    75  }
    76  
    77  func (b *BaseKvdb) GetLockTryDuration() time.Duration {
    78  	b.lock.Lock()
    79  	defer b.lock.Unlock()
    80  	return b.LockTryDuration
    81  }
    82  
    83  // CheckLockTimeout checks lock timeout.
    84  func (b *BaseKvdb) CheckLockTimeout(
    85  	key string,
    86  	startTime time.Time,
    87  	lockTimeout time.Duration,
    88  ) {
    89  	b.lock.Lock()
    90  	defer b.lock.Unlock()
    91  	if lockTimeout > 0 && time.Since(startTime) > lockTimeout {
    92  		b.lockTimedout(key, lockTimeout)
    93  	}
    94  }
    95  
    96  // GetLockHoldDuration gets lock timeout.
    97  func (b *BaseKvdb) GetLockHoldDuration() time.Duration {
    98  	b.lock.Lock()
    99  	defer b.lock.Unlock()
   100  	return b.LockHoldTimeout
   101  }
   102  
   103  // LockTimedout does lock timedout.
   104  func (b *BaseKvdb) LockTimedout(key string, timedOutAfter time.Duration) {
   105  	b.lock.Lock()
   106  	defer b.lock.Unlock()
   107  	b.lockTimedout(key, timedOutAfter)
   108  }
   109  
   110  // lockTimedout function is invoked if lock is held past configured timeout.
   111  func (b *BaseKvdb) lockTimedout(key string, timedOutAfter time.Duration) {
   112  	b.FatalCb(kvdb.ErrLockHoldTimeoutTriggered, "Lock %s hold timeout triggered after %s", key, timedOutAfter)
   113  }
   114  
   115  // SerializeAll Serializes all key value pairs to a byte array.
   116  func (b *BaseKvdb) SerializeAll(kvps kvdb.KVPairs) ([]byte, error) {
   117  	out, err := json.Marshal(kvps)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	return out, nil
   122  }
   123  
   124  // DeserializeAll Unmarshals a byte stream created from serializeAll into the kvdb tree.
   125  func (b *BaseKvdb) DeserializeAll(out []byte) (kvdb.KVPairs, error) {
   126  	var kvps kvdb.KVPairs
   127  	if err := json.Unmarshal(out, &kvps); err != nil {
   128  		return nil, err
   129  	}
   130  	return kvps, nil
   131  }
   132  
   133  // watchUpdate refers to an update to this kvdb
   134  type watchUpdate struct {
   135  	// key is the key that was updated
   136  	key string
   137  	// kvp is the key-value that was updated
   138  	kvp *kvdb.KVPair
   139  	// err is any error on update
   140  	err error
   141  }
   142  
   143  // WatchUpdateQueue is a producer consumer queue.
   144  type WatchUpdateQueue interface {
   145  	// Enqueue will enqueue an update. It is non-blocking.
   146  	Enqueue(key string, kvp *kvdb.KVPair, err error)
   147  	// Dequeue will either return an element from front of the queue or
   148  	// will block until element becomes available
   149  	Dequeue() (string, *kvdb.KVPair, error)
   150  }
   151  
   152  // watchQueue implements WatchUpdateQueue interface for watchUpdates
   153  type watchQueue struct {
   154  	// updates is the list of updates
   155  	updates *list.List
   156  	// m is the mutex to protect updates
   157  	m *sync.Mutex
   158  	// cv is used to coordinate the producer-consumer threads
   159  	cv *sync.Cond
   160  }
   161  
   162  // NewWatchUpdateQueue returns WatchUpdateQueue
   163  func NewWatchUpdateQueue() WatchUpdateQueue {
   164  	mtx := &sync.Mutex{}
   165  	return &watchQueue{
   166  		m:       mtx,
   167  		cv:      sync.NewCond(mtx),
   168  		updates: list.New()}
   169  }
   170  
   171  // Dequeue removes from queue.
   172  func (w *watchQueue) Dequeue() (string, *kvdb.KVPair, error) {
   173  	w.m.Lock()
   174  	for {
   175  		if w.updates.Len() > 0 {
   176  			el := w.updates.Front()
   177  			w.updates.Remove(el)
   178  			w.m.Unlock()
   179  			update := el.Value.(*watchUpdate)
   180  			return update.key, update.kvp, update.err
   181  		}
   182  		w.cv.Wait()
   183  	}
   184  }
   185  
   186  // Enqueue enqueues and never blocks
   187  func (w *watchQueue) Enqueue(key string, kvp *kvdb.KVPair, err error) {
   188  	w.m.Lock()
   189  	w.updates.PushBack(&watchUpdate{key: key, kvp: kvp, err: err})
   190  	w.cv.Signal()
   191  	w.m.Unlock()
   192  }
   193  
   194  // PrunePrefixes will return all the top level prefixes from a given list
   195  // so that any enumerate on these prefixes will not end up returning duplicate keys
   196  func PrunePrefixes(prefixes []string) []string {
   197  	prunedPrefixes := []string{}
   198  	for i := 0; i < len(prefixes); i++ {
   199  		foundPrefix := false
   200  		for j := 0; j < len(prefixes); j++ {
   201  			if i == j {
   202  				continue
   203  			}
   204  			if strings.HasPrefix(prefixes[i], prefixes[j]) {
   205  				foundPrefix = true
   206  			}
   207  		}
   208  		if !foundPrefix {
   209  			prunedPrefixes = append(prunedPrefixes, prefixes[i])
   210  		}
   211  	}
   212  	return prunedPrefixes
   213  }