github.com/m3db/m3@v1.5.0/src/x/watch/value.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package watch 22 23 import ( 24 "errors" 25 "fmt" 26 "sync" 27 "time" 28 29 "go.uber.org/zap" 30 31 xos "github.com/m3db/m3/src/x/os" 32 ) 33 34 var ( 35 errInitWatchTimeout = errors.New("init watch timeout") 36 errNilValue = errors.New("nil kv value") 37 ) 38 39 // Value is a resource that can be updated during runtime. 40 type Value interface { 41 // Watch starts watching for value updates. 42 Watch() error 43 44 // Unwatch stops watching for value updates. 45 Unwatch() 46 } 47 48 // NewUpdatableFn creates an updatable. 49 type NewUpdatableFn func() (Updatable, error) 50 51 // GetUpdateFn returns the latest value. 52 type GetUpdateFn func(updatable Updatable) (interface{}, error) 53 54 // ProcessFn processes an update. 55 type ProcessFn func(update interface{}) error 56 57 // processWithLockFn updates a value while holding a lock. 58 type processWithLockFn func(update interface{}) error 59 60 type valueStatus int 61 62 const ( 63 valueNotWatching valueStatus = iota 64 valueWatching 65 ) 66 67 type value struct { 68 sync.RWMutex 69 70 opts Options 71 log *zap.Logger 72 newUpdatableFn NewUpdatableFn 73 getUpdateFn GetUpdateFn 74 processFn ProcessFn 75 processWithLockFn processWithLockFn 76 key string 77 78 updatable Updatable 79 status valueStatus 80 } 81 82 // NewValue creates a new value. 83 func NewValue( 84 opts Options, 85 ) Value { 86 v := &value{ 87 opts: opts, 88 log: opts.InstrumentOptions().Logger(), 89 newUpdatableFn: opts.NewUpdatableFn(), 90 getUpdateFn: opts.GetUpdateFn(), 91 processFn: opts.ProcessFn(), 92 } 93 v.processWithLockFn = v.processWithLock 94 return v 95 } 96 97 func (v *value) Watch() error { 98 v.Lock() 99 defer v.Unlock() 100 101 if v.status == valueWatching { 102 return nil 103 } 104 updatable, err := v.newUpdatableFn() 105 if err != nil { 106 return CreateWatchError{ 107 innerError: err, 108 key: v.opts.Key(), 109 } 110 } 111 v.status = valueWatching 112 v.updatable = updatable 113 // NB(xichen): we want to start watching updates even though 114 // we may fail to initialize the value temporarily (e.g., during 115 // a network partition) so the value will be updated when the 116 // error condition is resolved. 117 defer func() { go v.watchUpdates(v.updatable) }() 118 119 interruptedCh := v.opts.InterruptedCh() 120 if interruptedCh == nil { 121 // NB(nate): if no interrupted channel is provided, then this wait is not 122 // gracefully interruptable. 123 interruptedCh = make(chan struct{}) 124 } 125 126 select { 127 case <-v.updatable.C(): 128 case <-time.After(v.opts.InitWatchTimeout()): 129 return InitValueError{ 130 innerError: errInitWatchTimeout, 131 key: v.opts.Key(), 132 } 133 case <-interruptedCh: 134 return xos.ErrInterrupted 135 } 136 137 update, err := v.getUpdateFn(v.updatable) 138 if err != nil { 139 return InitValueError{ 140 innerError: err, 141 key: v.opts.Key(), 142 } 143 } 144 145 if err = v.processWithLockFn(update); err != nil { 146 return InitValueError{ 147 innerError: err, 148 key: v.opts.Key(), 149 } 150 } 151 return nil 152 } 153 154 func (v *value) Unwatch() { 155 v.Lock() 156 defer v.Unlock() 157 158 // If status is nil, it means we are not watching. 159 if v.status == valueNotWatching { 160 return 161 } 162 v.updatable.Close() 163 v.status = valueNotWatching 164 v.updatable = nil 165 } 166 167 func (v *value) watchUpdates(updatable Updatable) { 168 for range updatable.C() { 169 v.Lock() 170 // If we are not watching, or we are watching with a different 171 // watch because we stopped the current watch and started a new 172 // one, return immediately. 173 if v.status != valueWatching || v.updatable != updatable { 174 v.Unlock() 175 return 176 } 177 update, err := v.getUpdateFn(updatable) 178 if err != nil { 179 v.log.Error("error getting update", 180 zap.String("key", v.opts.Key()), 181 zap.Error(err)) 182 v.Unlock() 183 continue 184 } 185 if err = v.processWithLockFn(update); err != nil { 186 v.log.Error("error applying update", 187 zap.String("key", v.opts.Key()), 188 zap.Error(err)) 189 } 190 v.Unlock() 191 } 192 } 193 194 func (v *value) processWithLock(update interface{}) error { 195 if update == nil { 196 return errNilValue 197 } 198 return v.processFn(update) 199 } 200 201 // CreateWatchError is returned when encountering an error creating a watch. 202 type CreateWatchError struct { 203 innerError error 204 key string 205 } 206 207 func (e CreateWatchError) Error() string { 208 return fmt.Sprintf("create watch error (key='%s'): %v", e.key, e.innerError) 209 } 210 211 // InitValueError is returned when encountering an error when initializing a value. 212 type InitValueError struct { 213 innerError error 214 key string 215 } 216 217 func (e InitValueError) Error() string { 218 return fmt.Sprintf("initializing value error (key='%s'): %v", e.key, e.innerError) 219 }