github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/processor/manager.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 processor
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"io"
    20  	"time"
    21  
    22  	"github.com/pingcap/errors"
    23  	"github.com/pingcap/failpoint"
    24  	"github.com/pingcap/log"
    25  	"github.com/pingcap/ticdc/cdc/model"
    26  	tablepipeline "github.com/pingcap/ticdc/cdc/processor/pipeline"
    27  	cdcContext "github.com/pingcap/ticdc/pkg/context"
    28  	cerrors "github.com/pingcap/ticdc/pkg/errors"
    29  	"github.com/pingcap/ticdc/pkg/orchestrator"
    30  	"go.uber.org/zap"
    31  )
    32  
    33  type commandTp int
    34  
    35  const (
    36  	commandTpUnknow commandTp = iota //nolint:varcheck,deadcode
    37  	commandTpClose
    38  	commandTpWriteDebugInfo
    39  )
    40  
    41  type command struct {
    42  	tp      commandTp
    43  	payload interface{}
    44  	done    chan struct{}
    45  }
    46  
    47  // Manager is a manager of processor, which maintains the state and behavior of processors
    48  type Manager struct {
    49  	processors map[model.ChangeFeedID]*processor
    50  
    51  	commandQueue chan *command
    52  
    53  	newProcessor func(cdcContext.Context) *processor
    54  }
    55  
    56  // NewManager creates a new processor manager
    57  func NewManager() *Manager {
    58  	return &Manager{
    59  		processors:   make(map[model.ChangeFeedID]*processor),
    60  		commandQueue: make(chan *command, 4),
    61  		newProcessor: newProcessor,
    62  	}
    63  }
    64  
    65  // NewManager4Test creates a new processor manager for test
    66  func NewManager4Test(
    67  	createTablePipeline func(ctx cdcContext.Context, tableID model.TableID, replicaInfo *model.TableReplicaInfo) (tablepipeline.TablePipeline, error),
    68  ) *Manager {
    69  	m := NewManager()
    70  	m.newProcessor = func(ctx cdcContext.Context) *processor {
    71  		return newProcessor4Test(ctx, createTablePipeline)
    72  	}
    73  	return m
    74  }
    75  
    76  // Tick implements the `orchestrator.State` interface
    77  // the `state` parameter is sent by the etcd worker, the `state` must be a snapshot of KVs in etcd
    78  // the Tick function of Manager create or remove processor instances according to the specified `state`, or pass the `state` to processor instances
    79  func (m *Manager) Tick(stdCtx context.Context, state orchestrator.ReactorState) (nextState orchestrator.ReactorState, err error) {
    80  	ctx := stdCtx.(cdcContext.Context)
    81  	globalState := state.(*model.GlobalReactorState)
    82  	if err := m.handleCommand(); err != nil {
    83  		return state, err
    84  	}
    85  	captureID := ctx.GlobalVars().CaptureInfo.ID
    86  	var inactiveChangefeedCount int
    87  	for changefeedID, changefeedState := range globalState.Changefeeds {
    88  		if !changefeedState.Active(captureID) {
    89  			inactiveChangefeedCount++
    90  			m.closeProcessor(changefeedID)
    91  			continue
    92  		}
    93  		ctx := cdcContext.WithChangefeedVars(ctx, &cdcContext.ChangefeedVars{
    94  			ID:   changefeedID,
    95  			Info: changefeedState.Info,
    96  		})
    97  		processor, exist := m.processors[changefeedID]
    98  		if !exist {
    99  			if changefeedState.Status.AdminJobType.IsStopState() || changefeedState.TaskStatuses[captureID].AdminJobType.IsStopState() {
   100  				continue
   101  			}
   102  			// the processor should start after at least one table has been added to this capture
   103  			taskStatus := changefeedState.TaskStatuses[captureID]
   104  			if taskStatus == nil || (len(taskStatus.Tables) == 0 && len(taskStatus.Operation) == 0) {
   105  				continue
   106  			}
   107  			failpoint.Inject("processorManagerHandleNewChangefeedDelay", nil)
   108  			processor = m.newProcessor(ctx)
   109  			m.processors[changefeedID] = processor
   110  		}
   111  		if _, err := processor.Tick(ctx, changefeedState); err != nil {
   112  			m.closeProcessor(changefeedID)
   113  			if cerrors.ErrReactorFinished.Equal(errors.Cause(err)) {
   114  				continue
   115  			}
   116  			return state, errors.Trace(err)
   117  		}
   118  	}
   119  	// check if the processors in memory is leaked
   120  	if len(globalState.Changefeeds)-inactiveChangefeedCount != len(m.processors) {
   121  		for changefeedID := range m.processors {
   122  			if _, exist := globalState.Changefeeds[changefeedID]; !exist {
   123  				m.closeProcessor(changefeedID)
   124  			}
   125  		}
   126  	}
   127  	return state, nil
   128  }
   129  
   130  func (m *Manager) closeProcessor(changefeedID model.ChangeFeedID) {
   131  	if processor, exist := m.processors[changefeedID]; exist {
   132  		err := processor.Close()
   133  		if err != nil {
   134  			log.Warn("failed to close processor", zap.Error(err))
   135  		}
   136  		delete(m.processors, changefeedID)
   137  	}
   138  }
   139  
   140  // AsyncClose sends a close signal to Manager and closing all processors
   141  func (m *Manager) AsyncClose() {
   142  	m.sendCommand(commandTpClose, nil)
   143  }
   144  
   145  // WriteDebugInfo write the debug info to Writer
   146  func (m *Manager) WriteDebugInfo(w io.Writer) {
   147  	timeout := time.Second * 3
   148  	done := m.sendCommand(commandTpWriteDebugInfo, w)
   149  	// wait the debug info printed
   150  	select {
   151  	case <-done:
   152  	case <-time.After(timeout):
   153  		fmt.Fprintf(w, "failed to print debug info for processor\n")
   154  	}
   155  }
   156  
   157  func (m *Manager) sendCommand(tp commandTp, payload interface{}) chan struct{} {
   158  	timeout := time.Second * 3
   159  	cmd := &command{tp: tp, payload: payload, done: make(chan struct{})}
   160  	select {
   161  	case m.commandQueue <- cmd:
   162  	case <-time.After(timeout):
   163  		close(cmd.done)
   164  		log.Warn("the command queue is full, ignore this command", zap.Any("command", cmd))
   165  	}
   166  	return cmd.done
   167  }
   168  
   169  func (m *Manager) handleCommand() error {
   170  	var cmd *command
   171  	select {
   172  	case cmd = <-m.commandQueue:
   173  	default:
   174  		return nil
   175  	}
   176  	defer close(cmd.done)
   177  	switch cmd.tp {
   178  	case commandTpClose:
   179  		for changefeedID := range m.processors {
   180  			m.closeProcessor(changefeedID)
   181  		}
   182  		return cerrors.ErrReactorFinished
   183  	case commandTpWriteDebugInfo:
   184  		w := cmd.payload.(io.Writer)
   185  		m.writeDebugInfo(w)
   186  	default:
   187  		log.Warn("Unknown command in processor manager", zap.Any("command", cmd))
   188  	}
   189  	return nil
   190  }
   191  
   192  func (m *Manager) writeDebugInfo(w io.Writer) {
   193  	for changefeedID, processor := range m.processors {
   194  		fmt.Fprintf(w, "changefeedID: %s\n", changefeedID)
   195  		processor.WriteDebugInfo(w)
   196  		fmt.Fprintf(w, "\n")
   197  	}
   198  }