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 }