github.com/m3db/m3@v1.5.0/src/dbnode/namespace/namespace_watch.go (about) 1 // Copyright (c) 2017 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 namespace 22 23 import ( 24 "errors" 25 "sync" 26 "time" 27 28 "github.com/m3db/m3/src/x/instrument" 29 30 "github.com/uber-go/tally" 31 "go.uber.org/zap" 32 ) 33 34 var ( 35 errAlreadyWatching = errors.New("database is already watching namespace updates") 36 errNotWatching = errors.New("database is not watching for namespace updates") 37 ) 38 39 type dbNamespaceWatch struct { 40 sync.Mutex 41 42 watching bool 43 watch Watch 44 doneCh chan struct{} 45 closedCh chan struct{} 46 47 update NamespaceUpdater 48 49 log *zap.Logger 50 metrics dbNamespaceWatchMetrics 51 reportInterval time.Duration 52 } 53 54 type dbNamespaceWatchMetrics struct { 55 activeNamespaces tally.Gauge 56 updates tally.Counter 57 } 58 59 func newWatchMetrics( 60 scope tally.Scope, 61 ) dbNamespaceWatchMetrics { 62 nsScope := scope.SubScope("namespace-watch") 63 return dbNamespaceWatchMetrics{ 64 activeNamespaces: nsScope.Gauge("active"), 65 updates: nsScope.Counter("updates"), 66 } 67 } 68 69 func NewNamespaceWatch( 70 update NamespaceUpdater, 71 w Watch, 72 iopts instrument.Options, 73 ) NamespaceWatch { 74 scope := iopts.MetricsScope() 75 return &dbNamespaceWatch{ 76 watch: w, 77 update: update, 78 log: iopts.Logger(), 79 metrics: newWatchMetrics(scope), 80 reportInterval: iopts.ReportInterval(), 81 } 82 } 83 84 func (w *dbNamespaceWatch) Start() error { 85 w.Lock() 86 defer w.Unlock() 87 88 if w.watching { 89 return errAlreadyWatching 90 } 91 92 w.watching = true 93 w.doneCh = make(chan struct{}, 1) 94 w.closedCh = make(chan struct{}, 1) 95 96 go w.startWatch() 97 98 return nil 99 } 100 101 func (w *dbNamespaceWatch) startWatch() { 102 reportClosingCh := make(chan struct{}, 1) 103 reportClosedCh := make(chan struct{}, 1) 104 go func() { 105 ticker := time.NewTicker(w.reportInterval) 106 defer func() { 107 ticker.Stop() 108 close(reportClosedCh) 109 }() 110 for { 111 select { 112 case <-ticker.C: 113 w.reportMetrics() 114 case <-reportClosingCh: 115 return 116 } 117 } 118 }() 119 120 defer func() { 121 // Issue closing signal to report channel 122 close(reportClosingCh) 123 // Wait for report channel to close 124 <-reportClosedCh 125 // Signal all closed 126 close(w.closedCh) 127 }() 128 129 for { 130 select { 131 case <-w.doneCh: 132 return 133 case _, ok := <-w.watch.C(): 134 if !ok { 135 return 136 } 137 138 if !w.isWatching() { 139 return 140 } 141 142 w.metrics.updates.Inc(1) 143 newMap := w.watch.Get() 144 w.log.Info("received update from kv namespace watch") 145 if err := w.update(newMap); err != nil { 146 w.log.Error("failed to update owned namespaces", 147 zap.Error(err)) 148 } 149 } 150 } 151 } 152 153 func (w *dbNamespaceWatch) isWatching() bool { 154 w.Lock() 155 defer w.Unlock() 156 return w.watching 157 } 158 159 func (w *dbNamespaceWatch) reportMetrics() { 160 m := w.watch.Get() 161 if m == nil { 162 w.metrics.activeNamespaces.Update(0) 163 return 164 } 165 w.metrics.activeNamespaces.Update(float64(len(m.Metadatas()))) 166 } 167 168 func (w *dbNamespaceWatch) Stop() error { 169 w.Lock() 170 defer w.Unlock() 171 172 if !w.watching { 173 return errNotWatching 174 } 175 176 w.watching = false 177 close(w.doneCh) 178 <-w.closedCh 179 180 return nil 181 } 182 183 func (w *dbNamespaceWatch) Close() error { 184 w.Lock() 185 watching := w.watching 186 w.Unlock() 187 if watching { 188 return w.Stop() 189 } 190 return nil 191 }