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

     1  // Copyright 2020 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  	"context"
    18  	"encoding/json"
    19  	"regexp"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/pingcap/errors"
    27  	"github.com/pingcap/log"
    28  	cerrors "github.com/pingcap/tiflow/pkg/errors"
    29  	"github.com/pingcap/tiflow/pkg/etcd"
    30  	"github.com/pingcap/tiflow/pkg/migrate"
    31  	"github.com/pingcap/tiflow/pkg/orchestrator/util"
    32  	"github.com/prometheus/client_golang/prometheus"
    33  	"github.com/stretchr/testify/require"
    34  	"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
    35  	clientv3 "go.etcd.io/etcd/client/v3"
    36  	"go.uber.org/zap"
    37  	"golang.org/x/sync/errgroup"
    38  )
    39  
    40  const (
    41  	testEtcdKeyPrefix    = "/cdc_etcd_worker_test"
    42  	numGroups            = 10
    43  	numValuesPerGroup    = 5
    44  	totalTicksPerReactor = 1000
    45  )
    46  
    47  type simpleReactor struct {
    48  	state     *simpleReactorState
    49  	tickCount int
    50  	id        int
    51  }
    52  
    53  func (s *simpleReactor) Tick(_ context.Context, state ReactorState) (nextState ReactorState, err error) {
    54  	if s.tickCount >= totalTicksPerReactor {
    55  		return s.state, cerrors.ErrReactorFinished
    56  	}
    57  	s.tickCount++
    58  
    59  	newState := state.(*simpleReactorState)
    60  	if newState == nil {
    61  		return s.state, nil
    62  	}
    63  	s.state = newState
    64  
    65  	if s.id == 0 {
    66  		sum := s.state.sum
    67  		for _, delta := range s.state.deltas {
    68  			sum = sum - delta.old
    69  			sum = sum + delta.new
    70  		}
    71  
    72  		// check for consistency
    73  		expectedSum := 0
    74  		for i := range s.state.values {
    75  			for j := range s.state.values[i] {
    76  				expectedSum += s.state.values[i][j]
    77  			}
    78  		}
    79  		if sum != expectedSum {
    80  			log.Panic("state is inconsistent",
    81  				zap.Int("expectedSum", sum), zap.Int("actualSum", s.state.sum))
    82  		}
    83  
    84  		s.state.SetSum(sum)
    85  	} else {
    86  		i2 := s.id - 1
    87  		for i := range s.state.values {
    88  			s.state.Inc(i, i2)
    89  		}
    90  	}
    91  
    92  	s.state.deltas = s.state.deltas[:0]
    93  
    94  	return s.state, nil
    95  }
    96  
    97  type delta struct {
    98  	old int
    99  	new int
   100  	i1  int
   101  	i2  int
   102  }
   103  
   104  type simpleReactorState struct {
   105  	values  [][]int
   106  	sum     int
   107  	deltas  []*delta
   108  	patches []DataPatch
   109  }
   110  
   111  var keyParseRegexp = regexp.MustCompile(regexp.QuoteMeta(testEtcdKeyPrefix) + `/(.+)`)
   112  
   113  func (s *simpleReactorState) Get(i1, i2 int) int {
   114  	return s.values[i1][i2]
   115  }
   116  
   117  func (s *simpleReactorState) Inc(i1, i2 int) {
   118  	patch := &SingleDataPatch{
   119  		Key: util.NewEtcdKey(testEtcdKeyPrefix + "/" + strconv.Itoa(i1)),
   120  		Func: func(old []byte) ([]byte, bool, error) {
   121  			var oldJSON []int
   122  			err := json.Unmarshal(old, &oldJSON)
   123  			if err != nil {
   124  				return nil, false, errors.Trace(err)
   125  			}
   126  
   127  			oldJSON[i2]++
   128  			newValue, err := json.Marshal(oldJSON)
   129  			if err != nil {
   130  				return nil, false, errors.Trace(err)
   131  			}
   132  			return newValue, true, nil
   133  		},
   134  	}
   135  
   136  	s.patches = append(s.patches, patch)
   137  }
   138  
   139  func (s *simpleReactorState) SetSum(sum int) {
   140  	patch := &SingleDataPatch{
   141  		Key: util.NewEtcdKey(testEtcdKeyPrefix + "/sum"),
   142  		Func: func(_ []byte) ([]byte, bool, error) {
   143  			return []byte(strconv.Itoa(sum)), true, nil
   144  		},
   145  	}
   146  
   147  	s.patches = append(s.patches, patch)
   148  }
   149  
   150  func (s *simpleReactorState) UpdatePendingChange() {
   151  }
   152  
   153  func (s *simpleReactorState) Update(key util.EtcdKey, value []byte, isInit bool) error {
   154  	subMatches := keyParseRegexp.FindSubmatch(key.Bytes())
   155  	if len(subMatches) != 2 {
   156  		log.Panic("illegal Etcd key", zap.ByteString("key", key.Bytes()))
   157  	}
   158  
   159  	if string(subMatches[1]) == "sum" {
   160  		newSum, err := strconv.Atoi(string(value))
   161  		if err != nil {
   162  			log.Panic("illegal sum", zap.Error(err))
   163  		}
   164  		s.sum = newSum
   165  		return nil
   166  	}
   167  
   168  	index, err := strconv.Atoi(string(subMatches[1]))
   169  	if err != nil {
   170  		log.Panic("illegal index", zap.Error(err))
   171  	}
   172  
   173  	var newValues []int
   174  	err = json.Unmarshal(value, &newValues)
   175  	if err != nil {
   176  		log.Panic("illegal value", zap.Error(err))
   177  	}
   178  
   179  	for i2, v := range s.values[index] {
   180  		if v != newValues[i2] {
   181  			s.deltas = append(s.deltas, &delta{
   182  				old: v,
   183  				new: newValues[i2],
   184  				i1:  index,
   185  				i2:  i2,
   186  			})
   187  		}
   188  	}
   189  
   190  	s.values[index] = newValues
   191  	return nil
   192  }
   193  
   194  func (s *simpleReactorState) GetPatches() [][]DataPatch {
   195  	ret := s.patches
   196  	s.patches = nil
   197  	return [][]DataPatch{ret}
   198  }
   199  
   200  func setUpTest(t *testing.T) (func() *etcd.Client, func()) {
   201  	url, server, err := etcd.SetupEmbedEtcd(t.TempDir())
   202  	require.Nil(t, err)
   203  	endpoints := []string{url.String()}
   204  	return func() *etcd.Client {
   205  			rawCli, err := clientv3.NewFromURLs(endpoints)
   206  			require.Nil(t, err)
   207  			return etcd.Wrap(rawCli, map[string]prometheus.Counter{})
   208  		}, func() {
   209  			server.Close()
   210  		}
   211  }
   212  
   213  func TestEtcdSum(t *testing.T) {
   214  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
   215  	defer cancel()
   216  
   217  	newClient, closer := setUpTest(t)
   218  	defer closer()
   219  
   220  	cli := newClient()
   221  	defer func() {
   222  		_ = cli.Unwrap().Close()
   223  	}()
   224  	_, err := cli.Put(ctx, testEtcdKeyPrefix+"/sum", "0")
   225  	require.Nil(t, err)
   226  
   227  	initArray := make([]int, numValuesPerGroup)
   228  	jsonStr, err := json.Marshal(initArray)
   229  	require.Nil(t, err)
   230  
   231  	for i := 0; i < numGroups; i++ {
   232  		_, err := cli.Put(ctx, testEtcdKeyPrefix+"/"+strconv.Itoa(i), string(jsonStr))
   233  		require.Nil(t, err)
   234  	}
   235  
   236  	errg, ctx := errgroup.WithContext(ctx)
   237  	for i := 0; i < numValuesPerGroup+1; i++ {
   238  		finalI := i
   239  		errg.Go(func() error {
   240  			values := make([][]int, numGroups)
   241  			for j := range values {
   242  				values[j] = make([]int, numValuesPerGroup)
   243  			}
   244  
   245  			reactor := &simpleReactor{
   246  				state: nil,
   247  				id:    finalI,
   248  			}
   249  
   250  			initState := &simpleReactorState{
   251  				values:  values,
   252  				sum:     0,
   253  				deltas:  nil,
   254  				patches: nil,
   255  			}
   256  
   257  			cli := newClient()
   258  			cdcCli, err := etcd.NewCDCEtcdClient(ctx, cli.Unwrap(), "default")
   259  			require.Nil(t, err)
   260  			defer func() {
   261  				_ = cli.Unwrap().Close()
   262  			}()
   263  
   264  			etcdWorker, err := NewEtcdWorker(cdcCli, testEtcdKeyPrefix, reactor, initState,
   265  				&migrate.NoOpMigrator{})
   266  			if err != nil {
   267  				return errors.Trace(err)
   268  			}
   269  
   270  			return errors.Trace(etcdWorker.Run(ctx, nil, 10*time.Millisecond, "owner"))
   271  		})
   272  	}
   273  
   274  	err = errg.Wait()
   275  	if err != nil && (errors.Cause(err) == context.DeadlineExceeded ||
   276  		errors.Cause(err) == context.Canceled ||
   277  		strings.Contains(err.Error(), "etcdserver: request timeout")) {
   278  		return
   279  	}
   280  	require.Nil(t, err)
   281  }
   282  
   283  type intReactorState struct {
   284  	val       int
   285  	isUpdated bool
   286  	lastVal   int
   287  }
   288  
   289  func (s *intReactorState) UpdatePendingChange() {
   290  }
   291  
   292  func (s *intReactorState) Update(key util.EtcdKey, value []byte, isInit bool) error {
   293  	var err error
   294  	s.val, err = strconv.Atoi(string(value))
   295  	if err != nil {
   296  		log.Panic("intReactorState", zap.Error(err))
   297  	}
   298  	// As long as we can ensure that val is monotonically increasing,
   299  	// we can ensure that the linearizability of state changes
   300  	if s.lastVal > s.val {
   301  		log.Panic("linearizability check failed, lastVal must less than current val", zap.Int("lastVal", s.lastVal), zap.Int("val", s.val))
   302  	}
   303  	s.lastVal = s.val
   304  	s.isUpdated = !isInit
   305  	return nil
   306  }
   307  
   308  func (s *intReactorState) GetPatches() [][]DataPatch {
   309  	return [][]DataPatch{}
   310  }
   311  
   312  type linearizabilityReactor struct {
   313  	state     *intReactorState
   314  	tickCount int
   315  }
   316  
   317  func (r *linearizabilityReactor) Tick(ctx context.Context, state ReactorState) (nextState ReactorState, err error) {
   318  	r.state = state.(*intReactorState)
   319  	if r.state.isUpdated {
   320  		if r.state.val < r.tickCount {
   321  			log.Panic("linearizability check failed, val must larger than tickCount", zap.Int("expected", r.tickCount), zap.Int("actual", r.state.val))
   322  		}
   323  		r.tickCount++
   324  	}
   325  	if r.state.val == 1999 {
   326  		return r.state, cerrors.ErrReactorFinished
   327  	}
   328  	r.state.isUpdated = false
   329  	return r.state, nil
   330  }
   331  
   332  func TestLinearizability(t *testing.T) {
   333  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
   334  	defer cancel()
   335  
   336  	newClient, closer := setUpTest(t)
   337  	defer closer()
   338  
   339  	cli0 := newClient()
   340  	cdcCli, err := etcd.NewCDCEtcdClient(ctx, cli0.Unwrap(), "default")
   341  	require.Nil(t, err)
   342  	cli := newClient()
   343  	for i := 0; i < 1000; i++ {
   344  		_, err := cli.Put(ctx, testEtcdKeyPrefix+"/lin", strconv.Itoa(i))
   345  		require.Nil(t, err)
   346  	}
   347  
   348  	reactor, err := NewEtcdWorker(cdcCli, testEtcdKeyPrefix+"/lin", &linearizabilityReactor{
   349  		state:     nil,
   350  		tickCount: 999,
   351  	}, &intReactorState{
   352  		val:       0,
   353  		isUpdated: false,
   354  	}, &migrate.NoOpMigrator{})
   355  	require.Nil(t, err)
   356  	errg := &errgroup.Group{}
   357  	errg.Go(func() error {
   358  		return reactor.Run(ctx, nil, 10*time.Millisecond, "owner")
   359  	})
   360  
   361  	time.Sleep(500 * time.Millisecond)
   362  	for i := 999; i < 2000; i++ {
   363  		_, err := cli.Put(ctx, testEtcdKeyPrefix+"/lin", strconv.Itoa(i))
   364  		require.Nil(t, err)
   365  	}
   366  
   367  	err = errg.Wait()
   368  	require.Nil(t, err)
   369  
   370  	err = cli.Unwrap().Close()
   371  	require.Nil(t, err)
   372  	err = cli0.Unwrap().Close()
   373  	require.Nil(t, err)
   374  }
   375  
   376  type commonReactorState struct {
   377  	state          map[string]string
   378  	pendingPatches []DataPatch
   379  }
   380  
   381  func (s *commonReactorState) UpdatePendingChange() {
   382  }
   383  
   384  func (s *commonReactorState) Update(key util.EtcdKey, value []byte, isInit bool) error {
   385  	s.state[key.String()] = string(value)
   386  	return nil
   387  }
   388  
   389  func (s *commonReactorState) AppendPatch(key util.EtcdKey, fun func(old []byte) (newValue []byte, changed bool, err error)) {
   390  	s.pendingPatches = append(s.pendingPatches, &SingleDataPatch{
   391  		Key:  key,
   392  		Func: fun,
   393  	})
   394  }
   395  
   396  func (s *commonReactorState) GetPatches() [][]DataPatch {
   397  	pendingPatches := s.pendingPatches
   398  	s.pendingPatches = nil
   399  	return [][]DataPatch{pendingPatches}
   400  }
   401  
   402  type finishedReactor struct {
   403  	state   *commonReactorState
   404  	tickNum int
   405  	prefix  string
   406  }
   407  
   408  func (r *finishedReactor) Tick(ctx context.Context, state ReactorState) (nextState ReactorState, err error) {
   409  	r.state = state.(*commonReactorState)
   410  	if r.tickNum < 2 {
   411  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   412  			return append(old, []byte("abc")...), true, nil
   413  		})
   414  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   415  			return append(old, []byte("123")...), true, nil
   416  		})
   417  		r.tickNum++
   418  		return r.state, nil
   419  	}
   420  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   421  		return append(old, []byte("fin")...), true, nil
   422  	})
   423  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   424  		return nil, true, nil
   425  	})
   426  	return r.state, cerrors.ErrReactorFinished
   427  }
   428  
   429  func TestFinished(t *testing.T) {
   430  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
   431  	defer cancel()
   432  
   433  	newClient, closer := setUpTest(t)
   434  	defer closer()
   435  
   436  	cli := newClient()
   437  	cdcCli, err := etcd.NewCDCEtcdClient(ctx, cli.Unwrap(), "default")
   438  	require.Nil(t, err)
   439  
   440  	prefix := testEtcdKeyPrefix + "/finished"
   441  	reactor, err := NewEtcdWorker(cdcCli, prefix, &finishedReactor{
   442  		prefix: prefix,
   443  	}, &commonReactorState{
   444  		state: make(map[string]string),
   445  	}, &migrate.NoOpMigrator{})
   446  	require.Nil(t, err)
   447  	err = reactor.Run(ctx, nil, 10*time.Millisecond, "owner")
   448  	require.Nil(t, err)
   449  	resp, err := cli.Get(ctx, prefix+"/key1")
   450  	require.Nil(t, err)
   451  	require.Equal(t, string(resp.Kvs[0].Key), "/cdc_etcd_worker_test/finished/key1")
   452  	require.Equal(t, string(resp.Kvs[0].Value), "abcabcfin")
   453  	resp, err = cli.Get(ctx, prefix+"/key2")
   454  	require.Nil(t, err)
   455  	require.Len(t, resp.Kvs, 0)
   456  	err = cli.Unwrap().Close()
   457  	require.Nil(t, err)
   458  }
   459  
   460  type coverReactor struct {
   461  	state   *commonReactorState
   462  	tickNum int
   463  	prefix  string
   464  }
   465  
   466  func (r *coverReactor) Tick(ctx context.Context, state ReactorState) (nextState ReactorState, err error) {
   467  	r.state = state.(*commonReactorState)
   468  	if r.tickNum < 2 {
   469  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   470  			return append(old, []byte("abc")...), true, nil
   471  		})
   472  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   473  			return append(old, []byte("123")...), true, nil
   474  		})
   475  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   476  			return append(old, []byte("cba")...), true, nil
   477  		})
   478  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   479  			return append(old, []byte("321")...), true, nil
   480  		})
   481  		r.tickNum++
   482  		return r.state, nil
   483  	}
   484  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   485  		return append(old, []byte("fin")...), true, nil
   486  	})
   487  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   488  		return append(old, []byte("fin")...), true, nil
   489  	})
   490  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   491  		return nil, true, nil
   492  	})
   493  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   494  		return append(old, []byte("fin")...), true, nil
   495  	})
   496  	return r.state, cerrors.ErrReactorFinished
   497  }
   498  
   499  func TestCover(t *testing.T) {
   500  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
   501  	defer cancel()
   502  
   503  	newClient, closer := setUpTest(t)
   504  	defer closer()
   505  
   506  	cli := newClient()
   507  	cdcCli, err := etcd.NewCDCEtcdClient(ctx, cli.Unwrap(), "default")
   508  	require.Nil(t, err)
   509  
   510  	prefix := testEtcdKeyPrefix + "/cover"
   511  	reactor, err := NewEtcdWorker(cdcCli, prefix, &coverReactor{
   512  		prefix: prefix,
   513  	}, &commonReactorState{
   514  		state: make(map[string]string),
   515  	}, &migrate.NoOpMigrator{})
   516  	require.Nil(t, err)
   517  	err = reactor.Run(ctx, nil, 10*time.Millisecond, "owner")
   518  	require.Nil(t, err)
   519  	resp, err := cli.Get(ctx, prefix+"/key1")
   520  	require.Nil(t, err)
   521  	require.Equal(t, string(resp.Kvs[0].Key), "/cdc_etcd_worker_test/cover/key1")
   522  	require.Equal(t, string(resp.Kvs[0].Value), "abccbaabccbafinfin")
   523  	resp, err = cli.Get(ctx, prefix+"/key2")
   524  	require.Nil(t, err)
   525  	require.Equal(t, string(resp.Kvs[0].Key), "/cdc_etcd_worker_test/cover/key2")
   526  	require.Equal(t, string(resp.Kvs[0].Value), "fin")
   527  	err = cli.Unwrap().Close()
   528  	require.Nil(t, err)
   529  }
   530  
   531  type emptyTxnReactor struct {
   532  	state   *commonReactorState
   533  	tickNum int
   534  	prefix  string
   535  	cli     *etcd.Client
   536  }
   537  
   538  func (r *emptyTxnReactor) Tick(ctx context.Context, state ReactorState) (nextState ReactorState, err error) {
   539  	r.state = state.(*commonReactorState)
   540  	if r.tickNum == 0 {
   541  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   542  			return []byte("abc"), true, nil
   543  		})
   544  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   545  			return []byte("123"), true, nil
   546  		})
   547  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   548  			return nil, true, nil
   549  		})
   550  		r.tickNum++
   551  		return r.state, nil
   552  	}
   553  	if r.tickNum == 1 {
   554  		// Simulating other client writes
   555  		_, err := r.cli.Put(ctx, "/key3", "123")
   556  		if err != nil {
   557  			return nil, errors.Trace(err)
   558  		}
   559  
   560  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   561  			return []byte("123"), true, nil
   562  		})
   563  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   564  			return nil, true, nil
   565  		})
   566  		r.tickNum++
   567  		return r.state, nil
   568  	}
   569  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   570  		return nil, true, nil
   571  	})
   572  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   573  		return []byte("123"), true, nil
   574  	})
   575  	return r.state, cerrors.ErrReactorFinished
   576  }
   577  
   578  func TestEmptyTxn(t *testing.T) {
   579  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
   580  	defer cancel()
   581  
   582  	newClient, closer := setUpTest(t)
   583  	defer closer()
   584  
   585  	cli := newClient()
   586  	cdcCli, err := etcd.NewCDCEtcdClient(ctx, cli.Unwrap(), "default")
   587  	require.Nil(t, err)
   588  
   589  	prefix := testEtcdKeyPrefix + "/empty_txn"
   590  	reactor, err := NewEtcdWorker(cdcCli, prefix, &emptyTxnReactor{
   591  		prefix: prefix,
   592  		cli:    cli,
   593  	}, &commonReactorState{
   594  		state: make(map[string]string),
   595  	}, &migrate.NoOpMigrator{})
   596  	require.Nil(t, err)
   597  	err = reactor.Run(ctx, nil, 10*time.Millisecond, "owner")
   598  	require.Nil(t, err)
   599  	resp, err := cli.Get(ctx, prefix+"/key1")
   600  	require.Nil(t, err)
   601  	require.Len(t, resp.Kvs, 0)
   602  	resp, err = cli.Get(ctx, prefix+"/key2")
   603  	require.Nil(t, err)
   604  	require.Equal(t, string(resp.Kvs[0].Key), "/cdc_etcd_worker_test/empty_txn/key2")
   605  	require.Equal(t, string(resp.Kvs[0].Value), "123")
   606  	err = cli.Unwrap().Close()
   607  	require.Nil(t, err)
   608  }
   609  
   610  type emptyOrNilReactor struct {
   611  	state   *commonReactorState
   612  	tickNum int
   613  	prefix  string
   614  }
   615  
   616  func (r *emptyOrNilReactor) Tick(ctx context.Context, state ReactorState) (nextState ReactorState, err error) {
   617  	r.state = state.(*commonReactorState)
   618  	if r.tickNum == 0 {
   619  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   620  			return []byte(""), true, nil
   621  		})
   622  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   623  			return nil, true, nil
   624  		})
   625  		r.tickNum++
   626  		return r.state, nil
   627  	}
   628  	if r.tickNum == 1 {
   629  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   630  			return nil, true, nil
   631  		})
   632  		r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   633  			return []byte(""), true, nil
   634  		})
   635  		r.tickNum++
   636  		return r.state, nil
   637  	}
   638  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key1"), func(old []byte) (newValue []byte, changed bool, err error) {
   639  		return []byte(""), true, nil
   640  	})
   641  	r.state.AppendPatch(util.NewEtcdKey(r.prefix+"/key2"), func(old []byte) (newValue []byte, changed bool, err error) {
   642  		return nil, true, nil
   643  	})
   644  	return r.state, cerrors.ErrReactorFinished
   645  }
   646  
   647  func TestEmptyOrNil(t *testing.T) {
   648  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
   649  	defer cancel()
   650  
   651  	newClient, closer := setUpTest(t)
   652  	defer closer()
   653  
   654  	cli := newClient()
   655  	cdcCli, err := etcd.NewCDCEtcdClient(ctx, cli.Unwrap(), "default")
   656  	require.Nil(t, err)
   657  
   658  	prefix := testEtcdKeyPrefix + "/emptyOrNil"
   659  	reactor, err := NewEtcdWorker(cdcCli, prefix, &emptyOrNilReactor{
   660  		prefix: prefix,
   661  	}, &commonReactorState{
   662  		state: make(map[string]string),
   663  	}, &migrate.NoOpMigrator{})
   664  	require.Nil(t, err)
   665  	err = reactor.Run(ctx, nil, 10*time.Millisecond, "owner")
   666  	require.Nil(t, err)
   667  	resp, err := cli.Get(ctx, prefix+"/key1")
   668  	require.Nil(t, err)
   669  	require.Equal(t, string(resp.Kvs[0].Key), "/cdc_etcd_worker_test/emptyOrNil/key1")
   670  	require.Equal(t, string(resp.Kvs[0].Value), "")
   671  	resp, err = cli.Get(ctx, prefix+"/key2")
   672  	require.Nil(t, err)
   673  	require.Len(t, resp.Kvs, 0)
   674  	err = cli.Unwrap().Close()
   675  	require.Nil(t, err)
   676  }
   677  
   678  type modifyOneReactor struct {
   679  	state    *commonReactorState
   680  	key      []byte
   681  	value    []byte
   682  	finished bool
   683  
   684  	waitOnCh chan struct{}
   685  }
   686  
   687  func (r *modifyOneReactor) Tick(ctx context.Context, state ReactorState) (nextState ReactorState, err error) {
   688  	r.state = state.(*commonReactorState)
   689  	if !r.finished {
   690  		r.finished = true
   691  	} else {
   692  		return r.state, cerrors.ErrReactorFinished.GenWithStackByArgs()
   693  	}
   694  	if r.waitOnCh != nil {
   695  		select {
   696  		case <-ctx.Done():
   697  			return nil, errors.Trace(ctx.Err())
   698  		case <-r.waitOnCh:
   699  		}
   700  		select {
   701  		case <-ctx.Done():
   702  			return nil, errors.Trace(ctx.Err())
   703  		case <-r.waitOnCh:
   704  		}
   705  	}
   706  	r.state.AppendPatch(util.NewEtcdKeyFromBytes(r.key), func(old []byte) (newValue []byte, changed bool, err error) {
   707  		if len(old) > 0 {
   708  			return r.value, true, nil
   709  		}
   710  		return nil, false, nil
   711  	})
   712  	return r.state, nil
   713  }
   714  
   715  // TestModifyAfterDelete tests snapshot isolation when there is one modifying transaction delayed in the middle while a deleting transaction
   716  // commits. The first transaction should be aborted and retried, and isolation should not be violated.
   717  func TestModifyAfterDelete(t *testing.T) {
   718  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
   719  	defer cancel()
   720  
   721  	newClient, closer := setUpTest(t)
   722  	defer closer()
   723  
   724  	cli1 := newClient()
   725  	cdcCli1, err := etcd.NewCDCEtcdClient(ctx, cli1.Unwrap(), "default")
   726  	require.Nil(t, err)
   727  
   728  	cli2 := newClient()
   729  	cdcCli2, err := etcd.NewCDCEtcdClient(ctx, cli2.Unwrap(), "default")
   730  	require.Nil(t, err)
   731  
   732  	_, err = cli1.Put(ctx, "/test/key1", "original value")
   733  	require.Nil(t, err)
   734  
   735  	modifyReactor := &modifyOneReactor{
   736  		key:      []byte("/test/key1"),
   737  		value:    []byte("modified value"),
   738  		waitOnCh: make(chan struct{}),
   739  	}
   740  	worker1, err := NewEtcdWorker(cdcCli1, "/test", modifyReactor, &commonReactorState{
   741  		state: make(map[string]string),
   742  	}, &migrate.NoOpMigrator{})
   743  	require.Nil(t, err)
   744  
   745  	var wg sync.WaitGroup
   746  	wg.Add(1)
   747  	go func() {
   748  		defer wg.Done()
   749  		err := worker1.Run(ctx, nil, time.Millisecond*100, "owner")
   750  		require.Nil(t, err)
   751  	}()
   752  
   753  	modifyReactor.waitOnCh <- struct{}{}
   754  
   755  	deleteReactor := &modifyOneReactor{
   756  		key:   []byte("/test/key1"),
   757  		value: nil, // deletion
   758  	}
   759  	worker2, err := NewEtcdWorker(cdcCli2, "/test", deleteReactor, &commonReactorState{
   760  		state: make(map[string]string),
   761  	}, &migrate.NoOpMigrator{})
   762  	require.Nil(t, err)
   763  
   764  	err = worker2.Run(ctx, nil, time.Millisecond*100, "owner")
   765  	require.Nil(t, err)
   766  
   767  	modifyReactor.waitOnCh <- struct{}{}
   768  	wg.Wait()
   769  
   770  	resp, err := cli1.Get(ctx, "/test/key1")
   771  	require.Nil(t, err)
   772  	require.Len(t, resp.Kvs, 0)
   773  	require.Equal(t, worker1.deleteCounter, int64(1))
   774  
   775  	_ = cli1.Unwrap().Close()
   776  	_ = cli2.Unwrap().Close()
   777  }
   778  
   779  func TestRetryableError(t *testing.T) {
   780  	require.True(t, isRetryableError(cerrors.ErrEtcdTryAgain))
   781  	require.True(t, isRetryableError(cerrors.ErrReachMaxTry.Wrap(rpctypes.ErrTimeoutDueToLeaderFail)))
   782  	require.True(t, isRetryableError(errors.Trace(context.DeadlineExceeded)))
   783  	require.False(t, isRetryableError(context.Canceled))
   784  }