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