// Open runs the invalidation loop. func (rci *RowcacheInvalidator) Open(dbname string, mysqld *mysqlctl.Mysqld) { rp, err := mysqld.MasterStatus() if err != nil { panic(NewTabletError(FATAL, "Rowcache invalidator aborting: cannot determine replication position: %v", err)) } if mysqld.Cnf().BinLogPath == "" { panic(NewTabletError(FATAL, "Rowcache invalidator aborting: binlog path not specified")) } ok := rci.svm.Go(func(_ *sync2.ServiceManager) { rci.mu.Lock() rci.dbname = dbname rci.mysqld = mysqld rci.evs = binlog.NewEventStreamer(dbname, mysqld.Cnf().BinLogPath) rci.GroupId.Set(rp.MasterLogGroupId) rci.mu.Unlock() rci.run() rci.mu.Lock() rci.evs = nil rci.mu.Unlock() }) if ok { log.Infof("Rowcache invalidator starting, dbname: %s, path: %s, logfile: %s, position: %d", dbname, mysqld.Cnf().BinLogPath, rp.MasterLogFile, rp.MasterLogPosition) } else { log.Infof("Rowcache invalidator already running") } }
func (rci *RowcacheInvalidator) run(ctx *sync2.ServiceContext) error { for { evs := binlog.NewEventStreamer(rci.dbname, rci.mysqld, rci.Position(), rci.processEvent) // We wrap this code in a func so we can catch all panics. // If an error is returned, we log it, wait 1 second, and retry. // This loop can only be stopped by calling Close. err := func() (inner error) { defer func() { if x := recover(); x != nil { inner = fmt.Errorf("%v: uncaught panic:\n%s", x, tb.Stack(4)) } }() return evs.Stream(ctx) }() if err == nil || !ctx.IsRunning() { break } if IsConnErr(err) { rci.checker.CheckMySQL() } log.Errorf("binlog.ServeUpdateStream returned err '%v', retrying in 1 second.", err.Error()) rci.qe.queryServiceStats.InternalErrors.Add("Invalidation", 1) time.Sleep(1 * time.Second) } log.Infof("Rowcache invalidator stopped") return nil }
// UpdateStream streams binlog events. func (tsv *TabletServer) UpdateStream(ctx context.Context, target *querypb.Target, position string, timestamp int64, sendReply func(*querypb.StreamEvent) error) error { // Parse the position if needed. var p replication.Position var err error if timestamp == 0 { p, err = replication.DecodePosition(position) if err != nil { return NewTabletError(vtrpcpb.ErrorCode_BAD_INPUT, "cannot parse position: %v", err) } } else if position != "" { return NewTabletError(vtrpcpb.ErrorCode_BAD_INPUT, "only one of position and timestamp should be specified") } // Validate proper target is used. if err = tsv.startRequest(target, false, false); err != nil { return err } defer tsv.endRequest(false) s := binlog.NewEventStreamer(tsv.dbconfigs.App.DbName, tsv.mysqld, p, timestamp, func(event *querypb.StreamEvent) error { return sendReply(event) }) // Create a cancelable wrapping context. streamCtx, streamCancel := context.WithCancel(ctx) i := tsv.updateStreamList.Add(streamCancel) defer tsv.updateStreamList.Delete(i) // And stream with it. err = s.Stream(streamCtx) switch err { case mysqlctl.ErrBinlogUnavailable: return NewTabletError(vtrpcpb.ErrorCode_QUERY_NOT_SERVED, "%v", err) case nil: return nil default: return NewTabletError(vtrpcpb.ErrorCode_INTERNAL_ERROR, "%v", err) } }