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 }