github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/orchestrator/reactor_state.go (about)

     1  // Copyright 2021 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 orchestrator
    15  
    16  import (
    17  	"reflect"
    18  	"time"
    19  
    20  	"github.com/goccy/go-json"
    21  	"github.com/pingcap/errors"
    22  	"github.com/pingcap/log"
    23  	"github.com/pingcap/tiflow/cdc/model"
    24  	cerrors "github.com/pingcap/tiflow/pkg/errors"
    25  	"github.com/pingcap/tiflow/pkg/etcd"
    26  	"github.com/pingcap/tiflow/pkg/orchestrator/util"
    27  	"go.uber.org/zap"
    28  )
    29  
    30  const defaultCaptureRemoveTTL = 5
    31  
    32  // GlobalReactorState represents a global state which stores all key-value pairs in ETCD
    33  type GlobalReactorState struct {
    34  	ClusterID      string
    35  	Role           string
    36  	Owner          map[string]struct{}
    37  	Captures       map[model.CaptureID]*model.CaptureInfo
    38  	Upstreams      map[model.UpstreamID]*model.UpstreamInfo
    39  	Changefeeds    map[model.ChangeFeedID]*ChangefeedReactorState
    40  	pendingPatches [][]DataPatch
    41  
    42  	// onCaptureAdded and onCaptureRemoved are hook functions
    43  	// to be called when captures are added and removed.
    44  	onCaptureAdded   func(captureID model.CaptureID, addr string)
    45  	onCaptureRemoved func(captureID model.CaptureID)
    46  
    47  	captureRemoveTTL int
    48  	toRemoveCaptures map[model.CaptureID]time.Time
    49  }
    50  
    51  // NewGlobalState creates a new global state.
    52  func NewGlobalState(clusterID string, captureSessionTTL int) *GlobalReactorState {
    53  	captureRemoveTTL := captureSessionTTL / 2
    54  	if captureRemoveTTL < defaultCaptureRemoveTTL {
    55  		captureRemoveTTL = defaultCaptureRemoveTTL
    56  	}
    57  	return &GlobalReactorState{
    58  		ClusterID:        clusterID,
    59  		Owner:            map[string]struct{}{},
    60  		Captures:         make(map[model.CaptureID]*model.CaptureInfo),
    61  		Upstreams:        make(map[model.UpstreamID]*model.UpstreamInfo),
    62  		Changefeeds:      make(map[model.ChangeFeedID]*ChangefeedReactorState),
    63  		captureRemoveTTL: captureRemoveTTL,
    64  		toRemoveCaptures: make(map[model.CaptureID]time.Time),
    65  	}
    66  }
    67  
    68  // NewGlobalStateForTest creates a new global state for test.
    69  func NewGlobalStateForTest(clusterID string) *GlobalReactorState {
    70  	return NewGlobalState(clusterID, 0)
    71  }
    72  
    73  // UpdatePendingChange implements the ReactorState interface
    74  func (s *GlobalReactorState) UpdatePendingChange() {
    75  	for c, t := range s.toRemoveCaptures {
    76  		if time.Since(t) >= time.Duration(s.captureRemoveTTL)*time.Second {
    77  			log.Info("remote capture offline", zap.Any("info", s.Captures[c]), zap.String("role", s.Role))
    78  			delete(s.Captures, c)
    79  			if s.onCaptureRemoved != nil {
    80  				s.onCaptureRemoved(c)
    81  			}
    82  			delete(s.toRemoveCaptures, c)
    83  		}
    84  	}
    85  }
    86  
    87  // Update implements the ReactorState interface
    88  func (s *GlobalReactorState) Update(key util.EtcdKey, value []byte, _ bool) error {
    89  	k := new(etcd.CDCKey)
    90  	err := k.Parse(s.ClusterID, key.String())
    91  	if err != nil {
    92  		return errors.Trace(err)
    93  	}
    94  
    95  	switch k.Tp {
    96  	case etcd.CDCKeyTypeOwner:
    97  		if value != nil {
    98  			s.Owner[k.OwnerLeaseID] = struct{}{}
    99  		} else {
   100  			delete(s.Owner, k.OwnerLeaseID)
   101  		}
   102  		return nil
   103  	case etcd.CDCKeyTypeCapture:
   104  		if value == nil {
   105  			log.Info("remote capture offline detected", zap.Any("info", s.Captures[k.CaptureID]), zap.String("role", s.Role))
   106  			s.toRemoveCaptures[k.CaptureID] = time.Now()
   107  			return nil
   108  		}
   109  
   110  		var newCaptureInfo model.CaptureInfo
   111  		err := newCaptureInfo.Unmarshal(value)
   112  		if err != nil {
   113  			return cerrors.ErrUnmarshalFailed.Wrap(err).GenWithStackByArgs()
   114  		}
   115  
   116  		log.Info("remote capture online", zap.Any("info", newCaptureInfo), zap.String("role", s.Role))
   117  		if s.onCaptureAdded != nil {
   118  			s.onCaptureAdded(k.CaptureID, newCaptureInfo.AdvertiseAddr)
   119  		}
   120  		s.Captures[k.CaptureID] = &newCaptureInfo
   121  	case etcd.CDCKeyTypeChangefeedInfo,
   122  		etcd.CDCKeyTypeChangeFeedStatus,
   123  		etcd.CDCKeyTypeTaskPosition:
   124  		changefeedState, exist := s.Changefeeds[k.ChangefeedID]
   125  		if !exist {
   126  			if value == nil {
   127  				return nil
   128  			}
   129  			changefeedState = NewChangefeedReactorState(s.ClusterID, k.ChangefeedID)
   130  			s.Changefeeds[k.ChangefeedID] = changefeedState
   131  		}
   132  		if err := changefeedState.UpdateCDCKey(k, value); err != nil {
   133  			return errors.Trace(err)
   134  		}
   135  		if value == nil && !changefeedState.Exist() {
   136  			s.pendingPatches = append(s.pendingPatches, changefeedState.getPatches())
   137  			delete(s.Changefeeds, k.ChangefeedID)
   138  		}
   139  	case etcd.CDCKeyTypeUpStream:
   140  		if value == nil {
   141  			log.Info("upstream is removed",
   142  				zap.Uint64("upstreamID", k.UpstreamID),
   143  				zap.Any("info", s.Upstreams[k.UpstreamID]),
   144  				zap.String("role", s.Role))
   145  			delete(s.Upstreams, k.UpstreamID)
   146  			return nil
   147  		}
   148  		var newUpstreamInfo model.UpstreamInfo
   149  		err := newUpstreamInfo.Unmarshal(value)
   150  		if err != nil {
   151  			return cerrors.ErrUnmarshalFailed.Wrap(err).GenWithStackByArgs()
   152  		}
   153  		log.Info("new upstream is add", zap.Uint64("upstream", k.UpstreamID),
   154  			zap.Any("info", newUpstreamInfo), zap.String("role", s.Role))
   155  		s.Upstreams[k.UpstreamID] = &newUpstreamInfo
   156  	case etcd.CDCKeyTypeMetaVersion:
   157  	default:
   158  		log.Warn("receive an unexpected etcd event", zap.String("key", key.String()),
   159  			zap.ByteString("value", value), zap.String("role", s.Role))
   160  	}
   161  	return nil
   162  }
   163  
   164  // GetPatches implements the ReactorState interface
   165  // Every []DataPatch slice in [][]DataPatch slice is the patches of a ChangefeedReactorState
   166  func (s *GlobalReactorState) GetPatches() [][]DataPatch {
   167  	pendingPatches := s.pendingPatches
   168  	for _, changefeedState := range s.Changefeeds {
   169  		pendingPatches = append(pendingPatches, changefeedState.getPatches())
   170  	}
   171  	s.pendingPatches = nil
   172  	return pendingPatches
   173  }
   174  
   175  // SetOnCaptureAdded registers a function that is called when a capture goes online.
   176  func (s *GlobalReactorState) SetOnCaptureAdded(f func(captureID model.CaptureID, addr string)) {
   177  	s.onCaptureAdded = f
   178  }
   179  
   180  // SetOnCaptureRemoved registers a function that is called when a capture goes offline.
   181  func (s *GlobalReactorState) SetOnCaptureRemoved(f func(captureID model.CaptureID)) {
   182  	s.onCaptureRemoved = f
   183  }
   184  
   185  // ChangefeedReactorState represents a changefeed state which stores all key-value pairs of a changefeed in ETCD
   186  type ChangefeedReactorState struct {
   187  	ClusterID     string
   188  	ID            model.ChangeFeedID
   189  	Info          *model.ChangeFeedInfo
   190  	Status        *model.ChangeFeedStatus
   191  	TaskPositions map[model.CaptureID]*model.TaskPosition
   192  
   193  	pendingPatches        []DataPatch
   194  	skipPatchesInThisTick bool
   195  }
   196  
   197  // NewChangefeedReactorState creates a new changefeed reactor state
   198  func NewChangefeedReactorState(clusterID string,
   199  	id model.ChangeFeedID,
   200  ) *ChangefeedReactorState {
   201  	return &ChangefeedReactorState{
   202  		ClusterID:     clusterID,
   203  		ID:            id,
   204  		TaskPositions: make(map[model.CaptureID]*model.TaskPosition),
   205  	}
   206  }
   207  
   208  // GetID returns the changefeed ID.
   209  func (s *ChangefeedReactorState) GetID() model.ChangeFeedID {
   210  	return s.ID
   211  }
   212  
   213  // GetChangefeedInfo returns the changefeed info.
   214  func (s *ChangefeedReactorState) GetChangefeedInfo() *model.ChangeFeedInfo {
   215  	return s.Info
   216  }
   217  
   218  // GetChangefeedStatus returns the changefeed status.
   219  func (s *ChangefeedReactorState) GetChangefeedStatus() *model.ChangeFeedStatus {
   220  	return s.Status
   221  }
   222  
   223  // SetWarning sets the warning to changefeed
   224  func (s *ChangefeedReactorState) SetWarning(lastError *model.RunningError) {
   225  	s.PatchInfo(func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   226  		if info == nil {
   227  			return nil, false, nil
   228  		}
   229  		info.Warning = lastError
   230  		return info, true, nil
   231  	})
   232  }
   233  
   234  // SetError sets the error to changefeed
   235  func (s *ChangefeedReactorState) SetError(lastError *model.RunningError) {
   236  	s.PatchInfo(func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   237  		if info == nil {
   238  			return nil, false, nil
   239  		}
   240  		info.Error = lastError
   241  		return info, true, nil
   242  	})
   243  }
   244  
   245  // RemoveChangefeed removes the changefeed and clean the information and status.
   246  func (s *ChangefeedReactorState) RemoveChangefeed() {
   247  	// remove info
   248  	s.PatchInfo(func(info *model.ChangeFeedInfo) (
   249  		*model.ChangeFeedInfo, bool, error,
   250  	) {
   251  		return nil, true, nil
   252  	})
   253  	// remove changefeedStatus
   254  	s.PatchStatus(
   255  		func(status *model.ChangeFeedStatus) (
   256  			*model.ChangeFeedStatus, bool, error,
   257  		) {
   258  			return nil, true, nil
   259  		})
   260  }
   261  
   262  // ResumeChnagefeed resumes the changefeed and set the checkpoint ts.
   263  func (s *ChangefeedReactorState) ResumeChnagefeed(overwriteCheckpointTs uint64) {
   264  	s.PatchInfo(func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   265  		changed := false
   266  		if info == nil {
   267  			return nil, changed, nil
   268  		}
   269  		if overwriteCheckpointTs > 0 {
   270  			info.StartTs = overwriteCheckpointTs
   271  			changed = true
   272  		}
   273  		if info.Error != nil {
   274  			info.Error = nil
   275  			changed = true
   276  		}
   277  		return info, changed, nil
   278  	})
   279  
   280  	s.PatchStatus(func(status *model.ChangeFeedStatus) (
   281  		*model.ChangeFeedStatus, bool, error,
   282  	) {
   283  		if overwriteCheckpointTs > 0 {
   284  			oldCheckpointTs := status.CheckpointTs
   285  			status = &model.ChangeFeedStatus{
   286  				CheckpointTs:      overwriteCheckpointTs,
   287  				MinTableBarrierTs: overwriteCheckpointTs,
   288  				AdminJobType:      model.AdminNone,
   289  			}
   290  			log.Info("overwriting the tableCheckpoint ts",
   291  				zap.String("namespace", s.ID.Namespace),
   292  				zap.String("changefeed", s.ID.ID),
   293  				zap.Any("oldCheckpointTs", oldCheckpointTs),
   294  				zap.Any("newCheckpointTs", status.CheckpointTs),
   295  			)
   296  			return status, true, nil
   297  		}
   298  		return status, false, nil
   299  	})
   300  }
   301  
   302  // TakeProcessorErrors reuturns the error of the changefeed and clean the error.
   303  func (s *ChangefeedReactorState) TakeProcessorErrors() []*model.RunningError {
   304  	var runningErrors map[string]*model.RunningError
   305  	for captureID, position := range s.TaskPositions {
   306  		if position.Error != nil {
   307  			if runningErrors == nil {
   308  				runningErrors = make(map[string]*model.RunningError)
   309  			}
   310  			runningErrors[position.Error.Code] = position.Error
   311  			log.Error("processor reports an error",
   312  				zap.String("namespace", s.ID.Namespace),
   313  				zap.String("changefeed", s.ID.ID),
   314  				zap.String("captureID", captureID),
   315  				zap.Any("error", position.Error))
   316  			s.PatchTaskPosition(captureID, func(position *model.TaskPosition) (*model.TaskPosition, bool, error) {
   317  				if position == nil {
   318  					return nil, false, nil
   319  				}
   320  				position.Error = nil
   321  				return position, true, nil
   322  			})
   323  		}
   324  	}
   325  	if runningErrors == nil {
   326  		return nil
   327  	}
   328  	result := make([]*model.RunningError, 0, len(runningErrors))
   329  	for _, err := range runningErrors {
   330  		result = append(result, err)
   331  	}
   332  	return result
   333  }
   334  
   335  // TakeProcessorWarnings reuturns the warning of the changefeed and clean the warning.
   336  func (s *ChangefeedReactorState) TakeProcessorWarnings() []*model.RunningError {
   337  	var runningWarnings map[string]*model.RunningError
   338  	for captureID, position := range s.TaskPositions {
   339  		if position.Warning != nil {
   340  			if runningWarnings == nil {
   341  				runningWarnings = make(map[string]*model.RunningError)
   342  			}
   343  			runningWarnings[position.Warning.Code] = position.Warning
   344  			log.Warn("processor reports a warning",
   345  				zap.String("namespace", s.ID.Namespace),
   346  				zap.String("changefeed", s.ID.ID),
   347  				zap.String("captureID", captureID),
   348  				zap.Any("warning", position.Warning))
   349  			s.PatchTaskPosition(captureID, func(position *model.TaskPosition) (*model.TaskPosition, bool, error) {
   350  				if position == nil {
   351  					return nil, false, nil
   352  				}
   353  				// set Warning to nil after it has been handled
   354  				position.Warning = nil
   355  				return position, true, nil
   356  			})
   357  		}
   358  	}
   359  	if runningWarnings == nil {
   360  		return nil
   361  	}
   362  	result := make([]*model.RunningError, 0, len(runningWarnings))
   363  	for _, err := range runningWarnings {
   364  		result = append(result, err)
   365  	}
   366  	return result
   367  }
   368  
   369  // CleanUpTaskPositions removes the task positions of the changefeed.
   370  func (s *ChangefeedReactorState) CleanUpTaskPositions() {
   371  	for captureID := range s.TaskPositions {
   372  		s.PatchTaskPosition(captureID, func(position *model.TaskPosition) (*model.TaskPosition, bool, error) {
   373  			return nil, true, nil
   374  		})
   375  	}
   376  }
   377  
   378  // UpdateChangefeedState returns the task status of the changefeed.
   379  func (s *ChangefeedReactorState) UpdateChangefeedState(feedState model.FeedState,
   380  	adminJobType model.AdminJobType,
   381  	epoch uint64,
   382  ) {
   383  	s.PatchStatus(func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) {
   384  		if status == nil {
   385  			return status, false, nil
   386  		}
   387  		if status.AdminJobType != adminJobType {
   388  			status.AdminJobType = adminJobType
   389  			return status, true, nil
   390  		}
   391  		return status, false, nil
   392  	})
   393  	s.PatchInfo(func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   394  		changed := false
   395  		if info == nil {
   396  			return nil, changed, nil
   397  		}
   398  		if info.State != feedState {
   399  			info.State = feedState
   400  			changed = true
   401  		}
   402  		if info.AdminJobType != adminJobType {
   403  			info.AdminJobType = adminJobType
   404  			changed = true
   405  
   406  			if epoch > 0 {
   407  				previous := info.Epoch
   408  				info.Epoch = epoch
   409  				log.Info("update changefeed epoch",
   410  					zap.String("namespace", s.ID.Namespace),
   411  					zap.String("changefeed", s.ID.ID),
   412  					zap.Uint64("perviousEpoch", previous),
   413  					zap.Uint64("currentEpoch", info.Epoch))
   414  			}
   415  		}
   416  		return info, changed, nil
   417  	})
   418  }
   419  
   420  // UpdatePendingChange implements the ReactorState interface
   421  func (s *ChangefeedReactorState) UpdatePendingChange() {
   422  }
   423  
   424  // Update implements the ReactorState interface
   425  func (s *ChangefeedReactorState) Update(key util.EtcdKey, value []byte, _ bool) error {
   426  	k := new(etcd.CDCKey)
   427  	if err := k.Parse(s.ClusterID, key.String()); err != nil {
   428  		return errors.Trace(err)
   429  	}
   430  	if err := s.UpdateCDCKey(k, value); err != nil {
   431  		log.Error("failed to update status", zap.String("key", key.String()), zap.ByteString("value", value))
   432  		return errors.Trace(err)
   433  	}
   434  	return nil
   435  }
   436  
   437  // UpdateCDCKey updates the state by a parsed etcd key
   438  func (s *ChangefeedReactorState) UpdateCDCKey(key *etcd.CDCKey, value []byte) error {
   439  	var e interface{}
   440  	switch key.Tp {
   441  	case etcd.CDCKeyTypeChangefeedInfo:
   442  		if key.ChangefeedID != s.ID {
   443  			return nil
   444  		}
   445  		if value == nil {
   446  			s.Info = nil
   447  			return nil
   448  		}
   449  		s.Info = new(model.ChangeFeedInfo)
   450  		e = s.Info
   451  	case etcd.CDCKeyTypeChangeFeedStatus:
   452  		if key.ChangefeedID != s.ID {
   453  			return nil
   454  		}
   455  		if value == nil {
   456  			s.Status = nil
   457  			return nil
   458  		}
   459  		s.Status = new(model.ChangeFeedStatus)
   460  		e = s.Status
   461  	case etcd.CDCKeyTypeTaskPosition:
   462  		if key.ChangefeedID != s.ID {
   463  			return nil
   464  		}
   465  		if value == nil {
   466  			delete(s.TaskPositions, key.CaptureID)
   467  			return nil
   468  		}
   469  		position := new(model.TaskPosition)
   470  		s.TaskPositions[key.CaptureID] = position
   471  		e = position
   472  	default:
   473  		return nil
   474  	}
   475  	if err := json.Unmarshal(value, e); err != nil {
   476  		return errors.Trace(err)
   477  	}
   478  	if key.Tp == etcd.CDCKeyTypeChangefeedInfo {
   479  		s.Info.VerifyAndComplete()
   480  	}
   481  	return nil
   482  }
   483  
   484  // Exist returns false if all keys of this changefeed in ETCD is not exist
   485  func (s *ChangefeedReactorState) Exist() bool {
   486  	return s.Info != nil || s.Status != nil || len(s.TaskPositions) != 0
   487  }
   488  
   489  // Active return true if the changefeed is ready to be processed
   490  func (s *ChangefeedReactorState) Active(captureID model.CaptureID) bool {
   491  	return s.Info != nil && s.Status != nil && s.Status.AdminJobType == model.AdminNone
   492  }
   493  
   494  // GetPatches implements the ReactorState interface
   495  func (s *ChangefeedReactorState) GetPatches() [][]DataPatch {
   496  	return [][]DataPatch{s.getPatches()}
   497  }
   498  
   499  func (s *ChangefeedReactorState) getPatches() []DataPatch {
   500  	pendingPatches := s.pendingPatches
   501  	s.pendingPatches = nil
   502  	return pendingPatches
   503  }
   504  
   505  // CheckCaptureAlive checks if the capture is alive, if the capture offline,
   506  // the etcd worker will exit and throw the ErrLeaseExpired error.
   507  func (s *ChangefeedReactorState) CheckCaptureAlive(captureID model.CaptureID) {
   508  	k := etcd.CDCKey{
   509  		ClusterID: s.ClusterID,
   510  		Tp:        etcd.CDCKeyTypeCapture,
   511  		CaptureID: captureID,
   512  	}
   513  	key := k.String()
   514  	patch := &SingleDataPatch{
   515  		Key: util.NewEtcdKey(key),
   516  		Func: func(v []byte) ([]byte, bool, error) {
   517  			// If v is empty, it means that the key-value pair of capture info is not exist.
   518  			// The key-value pair of capture info is written with lease,
   519  			// so if the capture info is not exist, the lease is expired
   520  			if len(v) == 0 {
   521  				return v, false, cerrors.ErrLeaseExpired.GenWithStackByArgs()
   522  			}
   523  			return v, false, nil
   524  		},
   525  	}
   526  	s.pendingPatches = append(s.pendingPatches, patch)
   527  }
   528  
   529  // CheckChangefeedNormal checks if the changefeed state is runnable,
   530  // if the changefeed status is not runnable, the etcd worker will skip all patch of this tick
   531  // the processor should call this function every tick to make sure the changefeed is runnable
   532  func (s *ChangefeedReactorState) CheckChangefeedNormal() {
   533  	s.skipPatchesInThisTick = false
   534  	s.PatchInfo(func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   535  		if info == nil || info.AdminJobType.IsStopState() {
   536  			s.skipPatchesInThisTick = true
   537  			return info, false, cerrors.ErrEtcdTryAgain.GenWithStackByArgs()
   538  		}
   539  		return info, false, nil
   540  	})
   541  	s.PatchStatus(func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) {
   542  		if status == nil {
   543  			return status, false, nil
   544  		}
   545  		if status.AdminJobType.IsStopState() {
   546  			s.skipPatchesInThisTick = true
   547  			return status, false, cerrors.ErrEtcdTryAgain.GenWithStackByArgs()
   548  		}
   549  		return status, false, nil
   550  	})
   551  }
   552  
   553  // PatchInfo appends a DataPatch which can modify the ChangeFeedInfo
   554  func (s *ChangefeedReactorState) PatchInfo(fn func(*model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error)) {
   555  	key := &etcd.CDCKey{
   556  		ClusterID:    s.ClusterID,
   557  		Tp:           etcd.CDCKeyTypeChangefeedInfo,
   558  		ChangefeedID: s.ID,
   559  	}
   560  	s.patchAny(key.String(), changefeedInfoTPI, func(e interface{}) (interface{}, bool, error) {
   561  		// e == nil means that the key is not exist before this patch
   562  		if e == nil {
   563  			return fn(nil)
   564  		}
   565  		return fn(e.(*model.ChangeFeedInfo))
   566  	})
   567  }
   568  
   569  // PatchStatus appends a DataPatch which can modify the ChangeFeedStatus
   570  func (s *ChangefeedReactorState) PatchStatus(fn func(*model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error)) {
   571  	key := &etcd.CDCKey{
   572  		ClusterID:    s.ClusterID,
   573  		Tp:           etcd.CDCKeyTypeChangeFeedStatus,
   574  		ChangefeedID: s.ID,
   575  	}
   576  	s.patchAny(key.String(), changefeedStatusTPI, func(e interface{}) (interface{}, bool, error) {
   577  		// e == nil means that the key is not exist before this patch
   578  		if e == nil {
   579  			return fn(nil)
   580  		}
   581  		return fn(e.(*model.ChangeFeedStatus))
   582  	})
   583  }
   584  
   585  // PatchTaskPosition appends a DataPatch which can modify the TaskPosition of a specified capture
   586  func (s *ChangefeedReactorState) PatchTaskPosition(captureID model.CaptureID, fn func(*model.TaskPosition) (*model.TaskPosition, bool, error)) {
   587  	key := &etcd.CDCKey{
   588  		ClusterID:    s.ClusterID,
   589  		Tp:           etcd.CDCKeyTypeTaskPosition,
   590  		CaptureID:    captureID,
   591  		ChangefeedID: s.ID,
   592  	}
   593  	s.patchAny(key.String(), taskPositionTPI, func(e interface{}) (interface{}, bool, error) {
   594  		// e == nil means that the key is not exist before this patch
   595  		if e == nil {
   596  			return fn(nil)
   597  		}
   598  		return fn(e.(*model.TaskPosition))
   599  	})
   600  }
   601  
   602  var (
   603  	taskPositionTPI     *model.TaskPosition
   604  	changefeedStatusTPI *model.ChangeFeedStatus
   605  	changefeedInfoTPI   *model.ChangeFeedInfo
   606  )
   607  
   608  func (s *ChangefeedReactorState) patchAny(key string, tpi interface{}, fn func(interface{}) (interface{}, bool, error)) {
   609  	patch := &SingleDataPatch{
   610  		Key: util.NewEtcdKey(key),
   611  		Func: func(v []byte) ([]byte, bool, error) {
   612  			if s.skipPatchesInThisTick {
   613  				return v, false, cerrors.ErrEtcdIgnore.GenWithStackByArgs()
   614  			}
   615  			var e interface{}
   616  			if v != nil {
   617  				tp := reflect.TypeOf(tpi)
   618  				e = reflect.New(tp.Elem()).Interface()
   619  				err := json.Unmarshal(v, e)
   620  				if err != nil {
   621  					return nil, false, errors.Trace(err)
   622  				}
   623  			}
   624  			ne, changed, err := fn(e)
   625  			if err != nil {
   626  				return nil, false, errors.Trace(err)
   627  			}
   628  			if !changed {
   629  				return v, false, nil
   630  			}
   631  			if reflect.ValueOf(ne).IsNil() {
   632  				return nil, true, nil
   633  			}
   634  			nv, err := json.Marshal(ne)
   635  			if err != nil {
   636  				return nil, false, errors.Trace(err)
   637  			}
   638  			return nv, true, nil
   639  		},
   640  	}
   641  	s.pendingPatches = append(s.pendingPatches, patch)
   642  }