github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/relay/streamer.go (about)

     1  // Copyright 2019 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package relay
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"time"
    20  
    21  	"github.com/go-mysql-org/go-mysql/replication"
    22  	"github.com/pingcap/failpoint"
    23  	"github.com/pingcap/tiflow/dm/pkg/binlog/common"
    24  	"github.com/pingcap/tiflow/dm/pkg/binlog/event"
    25  	"github.com/pingcap/tiflow/dm/pkg/log"
    26  	"github.com/pingcap/tiflow/dm/pkg/terror"
    27  	"github.com/pingcap/tiflow/dm/pkg/utils"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  var heartbeatInterval = common.MasterHeartbeatPeriod
    32  
    33  // TODO: maybe one day we can make a pull request to go-mysql to support LocalStreamer.
    34  
    35  // LocalStreamer reads and parses binlog events from local binlog file.
    36  type LocalStreamer struct {
    37  	ch            chan *replication.BinlogEvent
    38  	ech           chan error
    39  	heatBeatTimer *time.Timer
    40  	err           error
    41  }
    42  
    43  // GetEvent gets the binlog event one by one, it will block until parser occurs some errors.
    44  // You can pass a context (like Cancel or Timeout) to break the block.
    45  func (s *LocalStreamer) GetEvent(ctx context.Context) (*replication.BinlogEvent, error) {
    46  	if s.err != nil {
    47  		return nil, terror.ErrNeedSyncAgain.Generate()
    48  	}
    49  
    50  	failpoint.Inject("GetEventFromLocalFailed", func(_ failpoint.Value) {
    51  		log.L().Info("get event from local failed", zap.String("failpoint", "GetEventFromLocalFailed"))
    52  		failpoint.Return(nil, terror.ErrSyncClosed.Generate())
    53  	})
    54  
    55  	failpoint.Inject("SetHeartbeatInterval", func(v failpoint.Value) {
    56  		i := v.(int)
    57  		log.L().Info("will change heartbeat interval", zap.Int("new", i))
    58  		heartbeatInterval = time.Duration(i) * time.Second
    59  	})
    60  
    61  	fired := false
    62  	s.heatBeatTimer.Reset(heartbeatInterval)
    63  	defer func() {
    64  		if !fired {
    65  			if !s.heatBeatTimer.Stop() {
    66  				<-s.heatBeatTimer.C
    67  			}
    68  		}
    69  	}()
    70  	select {
    71  	case <-s.heatBeatTimer.C:
    72  		fired = true
    73  		// MySQL will send heartbeat event 30s by default
    74  		heartbeatHeader := &replication.EventHeader{}
    75  		return event.GenHeartbeatEvent(heartbeatHeader), nil
    76  	case c := <-s.ch:
    77  		// special check for maybe truncated relay log
    78  		if c.Header.EventType == replication.IGNORABLE_EVENT {
    79  			if bytes.Equal(c.RawData, []byte(ErrorMaybeDuplicateEvent.Error())) {
    80  				return nil, ErrorMaybeDuplicateEvent
    81  			}
    82  		}
    83  		return c, nil
    84  	case s.err = <-s.ech:
    85  		return nil, s.err
    86  	case <-ctx.Done():
    87  		return nil, ctx.Err()
    88  	}
    89  }
    90  
    91  func (s *LocalStreamer) close() {
    92  	s.closeWithError(terror.ErrSyncClosed.Generate())
    93  }
    94  
    95  func (s *LocalStreamer) closeWithError(err error) {
    96  	if err == nil {
    97  		err = terror.ErrSyncClosed.Generate()
    98  	}
    99  	log.L().Error("close local streamer", log.ShortError(err))
   100  	select {
   101  	case s.ech <- err:
   102  	default:
   103  	}
   104  }
   105  
   106  func newLocalStreamer() *LocalStreamer {
   107  	s := new(LocalStreamer)
   108  
   109  	s.ch = make(chan *replication.BinlogEvent, 10240)
   110  	s.ech = make(chan error, 4)
   111  	s.heatBeatTimer = utils.NewStoppedTimer()
   112  
   113  	return s
   114  }