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 }