vitess.io/vitess@v0.16.2/go/vt/binlog/updatestreamctl.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package binlog 18 19 import ( 20 "fmt" 21 "sync" 22 23 "context" 24 25 "vitess.io/vitess/go/mysql" 26 "vitess.io/vitess/go/stats" 27 "vitess.io/vitess/go/sync2" 28 "vitess.io/vitess/go/tb" 29 "vitess.io/vitess/go/vt/dbconfigs" 30 "vitess.io/vitess/go/vt/log" 31 "vitess.io/vitess/go/vt/topo" 32 "vitess.io/vitess/go/vt/vttablet/tabletserver/schema" 33 34 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 35 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 36 ) 37 38 /* API and config for UpdateStream Service */ 39 40 const ( 41 usDisabled int64 = iota 42 usEnabled 43 ) 44 45 var usStateNames = map[int64]string{ 46 usEnabled: "Enabled", 47 usDisabled: "Disabled", 48 } 49 50 var ( 51 streamCount = stats.NewCountersWithSingleLabel("UpdateStreamStreamCount", "update stream count", "type") 52 updateStreamErrors = stats.NewCountersWithSingleLabel("UpdateStreamErrors", "update stream error count", "type") 53 keyrangeStatements = stats.NewCounter("UpdateStreamKeyRangeStatements", "update stream key range statement count") 54 keyrangeTransactions = stats.NewCounter("UpdateStreamKeyRangeTransactions", "update stream key range transaction count") 55 tablesStatements = stats.NewCounter("UpdateStreamTablesStatements", "update stream table statement count") 56 tablesTransactions = stats.NewCounter("UpdateStreamTablesTransactions", "update stream table transaction count") 57 ) 58 59 // UpdateStreamControl is the interface an UpdateStream service implements 60 // to bring it up or down. 61 type UpdateStreamControl interface { 62 // InitDBConfigs is called after the db name is computed. 63 InitDBConfig(*dbconfigs.DBConfigs) 64 // RegisterService registers the UpdateStream service. 65 RegisterService() 66 // Enable will allow any new RPC calls 67 Enable() 68 // Disable will interrupt all current calls, and disallow any new call 69 Disable() 70 // IsEnabled returns true iff the service is enabled 71 IsEnabled() bool 72 } 73 74 // UpdateStreamControlMock is an implementation of UpdateStreamControl 75 // to be used in tests 76 type UpdateStreamControlMock struct { 77 enabled bool 78 sync.Mutex 79 } 80 81 // NewUpdateStreamControlMock creates a new UpdateStreamControlMock 82 func NewUpdateStreamControlMock() *UpdateStreamControlMock { 83 return &UpdateStreamControlMock{} 84 } 85 86 // InitDBConfig is part of UpdateStreamControl 87 func (m *UpdateStreamControlMock) InitDBConfig(*dbconfigs.DBConfigs) { 88 } 89 90 // RegisterService is part of UpdateStreamControl 91 func (m *UpdateStreamControlMock) RegisterService() { 92 } 93 94 // Enable is part of UpdateStreamControl 95 func (m *UpdateStreamControlMock) Enable() { 96 m.Lock() 97 m.enabled = true 98 m.Unlock() 99 } 100 101 // Disable is part of UpdateStreamControl 102 func (m *UpdateStreamControlMock) Disable() { 103 m.Lock() 104 m.enabled = false 105 m.Unlock() 106 } 107 108 // IsEnabled is part of UpdateStreamControl 109 func (m *UpdateStreamControlMock) IsEnabled() bool { 110 m.Lock() 111 defer m.Unlock() 112 return m.enabled 113 } 114 115 // UpdateStreamImpl is the real implementation of UpdateStream 116 // and UpdateStreamControl 117 type UpdateStreamImpl struct { 118 // the following variables are set at construction time 119 ts *topo.Server 120 keyspace string 121 cell string 122 cp dbconfigs.Connector 123 se *schema.Engine 124 125 // actionLock protects the following variables 126 actionLock sync.Mutex 127 state sync2.AtomicInt64 128 stateWaitGroup sync.WaitGroup 129 streams StreamList 130 } 131 132 // StreamList is a map of context.CancelFunc to mass-interrupt ongoing 133 // calls. 134 type StreamList struct { 135 sync.Mutex 136 currentIndex int 137 streams map[int]context.CancelFunc 138 } 139 140 // Init must be called before using the list. 141 func (sl *StreamList) Init() { 142 sl.Lock() 143 sl.streams = make(map[int]context.CancelFunc) 144 sl.currentIndex = 0 145 sl.Unlock() 146 } 147 148 // Add adds a CancelFunc to the map. 149 func (sl *StreamList) Add(c context.CancelFunc) int { 150 sl.Lock() 151 defer sl.Unlock() 152 153 sl.currentIndex++ 154 sl.streams[sl.currentIndex] = c 155 return sl.currentIndex 156 } 157 158 // Delete removes a CancelFunc from the list. 159 func (sl *StreamList) Delete(i int) { 160 sl.Lock() 161 delete(sl.streams, i) 162 sl.Unlock() 163 } 164 165 // Stop stops all the current streams. 166 func (sl *StreamList) Stop() { 167 sl.Lock() 168 for _, c := range sl.streams { 169 c() 170 } 171 sl.Unlock() 172 } 173 174 // RegisterUpdateStreamServiceFunc is the type to use for delayed 175 // registration of RPC servers until we have all the objects 176 type RegisterUpdateStreamServiceFunc func(UpdateStream) 177 178 // RegisterUpdateStreamServices is the list of all registration 179 // callbacks to invoke 180 var RegisterUpdateStreamServices []RegisterUpdateStreamServiceFunc 181 182 // NewUpdateStream returns a new UpdateStreamImpl object 183 func NewUpdateStream(ts *topo.Server, keyspace string, cell string, se *schema.Engine) *UpdateStreamImpl { 184 return &UpdateStreamImpl{ 185 ts: ts, 186 keyspace: keyspace, 187 cell: cell, 188 se: se, 189 } 190 } 191 192 // InitDBConfig should be invoked after the db name is computed. 193 func (updateStream *UpdateStreamImpl) InitDBConfig(dbcfgs *dbconfigs.DBConfigs) { 194 updateStream.cp = dbcfgs.DbaWithDB() 195 } 196 197 // RegisterService needs to be called to publish stats, and to start listening 198 // to clients. Only once instance can call this in a process. 199 func (updateStream *UpdateStreamImpl) RegisterService() { 200 // publish the stats 201 stats.Publish("UpdateStreamState", stats.StringFunc(func() string { 202 return usStateNames[updateStream.state.Get()] 203 })) 204 205 // and register all the RPC protocols 206 for _, f := range RegisterUpdateStreamServices { 207 f(updateStream) 208 } 209 } 210 211 func logError() { 212 if x := recover(); x != nil { 213 log.Errorf("%s at\n%s", x.(error).Error(), tb.Stack(4)) 214 } 215 } 216 217 // Enable will allow connections to the service 218 func (updateStream *UpdateStreamImpl) Enable() { 219 defer logError() 220 updateStream.actionLock.Lock() 221 defer updateStream.actionLock.Unlock() 222 if updateStream.IsEnabled() { 223 return 224 } 225 226 updateStream.state.Set(usEnabled) 227 updateStream.streams.Init() 228 log.Infof("Enabling update stream, dbname: %s", updateStream.cp.DBName()) 229 } 230 231 // Disable will disallow any connection to the service 232 func (updateStream *UpdateStreamImpl) Disable() { 233 defer logError() 234 updateStream.actionLock.Lock() 235 defer updateStream.actionLock.Unlock() 236 if !updateStream.IsEnabled() { 237 return 238 } 239 240 updateStream.state.Set(usDisabled) 241 updateStream.streams.Stop() 242 updateStream.stateWaitGroup.Wait() 243 log.Infof("Update Stream Disabled") 244 } 245 246 // IsEnabled returns true if UpdateStreamImpl is enabled 247 func (updateStream *UpdateStreamImpl) IsEnabled() bool { 248 return updateStream.state.Get() == usEnabled 249 } 250 251 // StreamKeyRange is part of the UpdateStream interface 252 func (updateStream *UpdateStreamImpl) StreamKeyRange(ctx context.Context, position string, keyRange *topodatapb.KeyRange, charset *binlogdatapb.Charset, callback func(trans *binlogdatapb.BinlogTransaction) error) (err error) { 253 pos, err := mysql.DecodePosition(position) 254 if err != nil { 255 return err 256 } 257 258 updateStream.actionLock.Lock() 259 if !updateStream.IsEnabled() { 260 updateStream.actionLock.Unlock() 261 log.Errorf("Unable to serve client request: Update stream service is not enabled") 262 return fmt.Errorf("update stream service is not enabled") 263 } 264 updateStream.stateWaitGroup.Add(1) 265 updateStream.actionLock.Unlock() 266 defer updateStream.stateWaitGroup.Done() 267 268 streamCount.Add("KeyRange", 1) 269 defer streamCount.Add("KeyRange", -1) 270 log.Infof("ServeUpdateStream starting @ %#v", pos) 271 272 // Calls cascade like this: binlog.Streamer->keyRangeFilterFunc->func(*binlogdatapb.BinlogTransaction)->callback 273 f := keyRangeFilterFunc(keyRange, func(trans *binlogdatapb.BinlogTransaction) error { 274 keyrangeStatements.Add(int64(len(trans.Statements))) 275 keyrangeTransactions.Add(1) 276 return callback(trans) 277 }) 278 bls := NewStreamer(updateStream.cp, updateStream.se, charset, pos, 0, f) 279 bls.resolverFactory, err = newKeyspaceIDResolverFactory(ctx, updateStream.ts, updateStream.keyspace, updateStream.cell) 280 if err != nil { 281 return fmt.Errorf("newKeyspaceIDResolverFactory failed: %v", err) 282 } 283 284 streamCtx, cancel := context.WithCancel(ctx) 285 i := updateStream.streams.Add(cancel) 286 defer updateStream.streams.Delete(i) 287 288 return bls.Stream(streamCtx) 289 } 290 291 // StreamTables is part of the UpdateStream interface 292 func (updateStream *UpdateStreamImpl) StreamTables(ctx context.Context, position string, tables []string, charset *binlogdatapb.Charset, callback func(trans *binlogdatapb.BinlogTransaction) error) (err error) { 293 pos, err := mysql.DecodePosition(position) 294 if err != nil { 295 return err 296 } 297 298 updateStream.actionLock.Lock() 299 if !updateStream.IsEnabled() { 300 updateStream.actionLock.Unlock() 301 log.Errorf("Unable to serve client request: Update stream service is not enabled") 302 return fmt.Errorf("update stream service is not enabled") 303 } 304 updateStream.stateWaitGroup.Add(1) 305 updateStream.actionLock.Unlock() 306 defer updateStream.stateWaitGroup.Done() 307 308 streamCount.Add("Tables", 1) 309 defer streamCount.Add("Tables", -1) 310 log.Infof("ServeUpdateStream starting @ %#v", pos) 311 312 // Calls cascade like this: binlog.Streamer->tablesFilterFunc->func(*binlogdatapb.BinlogTransaction)->callback 313 f := tablesFilterFunc(tables, func(trans *binlogdatapb.BinlogTransaction) error { 314 tablesStatements.Add(int64(len(trans.Statements))) 315 tablesTransactions.Add(1) 316 return callback(trans) 317 }) 318 bls := NewStreamer(updateStream.cp, updateStream.se, charset, pos, 0, f) 319 320 streamCtx, cancel := context.WithCancel(ctx) 321 i := updateStream.streams.Add(cancel) 322 defer updateStream.streams.Delete(i) 323 324 return bls.Stream(streamCtx) 325 } 326 327 // HandlePanic is part of the UpdateStream interface 328 func (updateStream *UpdateStreamImpl) HandlePanic(err *error) { 329 if x := recover(); x != nil { 330 log.Errorf("Uncaught panic:\n%v\n%s", x, tb.Stack(4)) 331 *err = fmt.Errorf("uncaught panic: %v", x) 332 } 333 }