github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/table/client_test.go (about) 1 package table 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math/rand" 8 "path" 9 "runtime" 10 "sync" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/jonboulle/clockwork" 16 "github.com/stretchr/testify/require" 17 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Table" 18 "google.golang.org/grpc" 19 "google.golang.org/protobuf/proto" 20 "google.golang.org/protobuf/types/known/emptypb" 21 22 "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" 23 "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/config" 24 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 25 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 26 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xrand" 27 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" 28 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" 29 "github.com/ydb-platform/ydb-go-sdk/v3/table" 30 "github.com/ydb-platform/ydb-go-sdk/v3/testutil" 31 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 32 ) 33 34 func TestSessionPoolCreateAbnormalResult(t *testing.T) { 35 xtest.TestManyTimes(t, func(t testing.TB) { 36 limit := 100 37 ctx, cancel := xcontext.WithTimeout( 38 context.Background(), 39 55*time.Second, 40 ) 41 defer cancel() 42 p := newClientWithStubBuilder( 43 t, 44 testutil.NewBalancer( 45 testutil.WithInvokeHandlers( 46 testutil.InvokeHandlers{ 47 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 48 return &Ydb_Table.CreateSessionResult{ 49 SessionId: testutil.SessionID(), 50 }, nil 51 }, 52 testutil.TableDeleteSession: okHandler, 53 }, 54 ), 55 ), 56 limit, 57 config.WithSizeLimit(limit), 58 ) 59 defer func() { 60 _ = p.Close(context.Background()) 61 }() 62 r := xrand.New(xrand.WithLock()) 63 errCh := make(chan error, limit*10) 64 fn := func(wg *sync.WaitGroup) { 65 defer wg.Done() 66 childCtx, childCancel := xcontext.WithTimeout( 67 ctx, 68 time.Duration(r.Int64(int64(time.Minute))), 69 ) 70 defer childCancel() 71 s, err := p.internalPoolCreateSession(childCtx) 72 if s == nil && err == nil { 73 errCh <- fmt.Errorf("unexpected result: <%v, %w>", s, err) 74 } 75 } 76 wg := &sync.WaitGroup{} 77 wg.Add(limit * 10) 78 for i := 0; i < limit*10; i++ { 79 go fn(wg) 80 } 81 go func() { 82 wg.Wait() 83 close(errCh) 84 }() 85 for e := range errCh { 86 t.Fatal(e) 87 } 88 }, xtest.StopAfter(17*time.Second)) 89 } 90 91 func TestSessionPoolCloseWhenWaiting(t *testing.T) { 92 for _, test := range []struct { 93 name string 94 racy bool 95 }{ 96 { 97 name: "normal", 98 racy: false, 99 }, 100 { 101 name: "racy", 102 racy: true, 103 }, 104 } { 105 t.Run(test.name, func(t *testing.T) { 106 var ( 107 get = make(chan struct{}) 108 wait = make(chan struct{}) 109 got = make(chan error) 110 ) 111 p := newClientWithStubBuilder( 112 t, 113 testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 114 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 115 return &Ydb_Table.CreateSessionResult{ 116 SessionId: testutil.SessionID(), 117 }, nil 118 }, 119 })), 120 1, 121 config.WithSizeLimit(1), 122 config.WithTrace( 123 &trace.Table{ 124 OnPoolWait: func(trace.TablePoolWaitStartInfo) func(trace.TablePoolWaitDoneInfo) { 125 wait <- struct{}{} 126 127 return nil 128 }, 129 }, 130 ), 131 ) 132 defer func() { 133 _ = p.Close(context.Background()) 134 }() 135 136 mustGetSession(t, p) 137 138 go func() { 139 _, err := p.internalPoolGet( 140 context.Background(), 141 withTrace(&trace.Table{ 142 OnPoolGet: func(trace.TablePoolGetStartInfo) func(trace.TablePoolGetDoneInfo) { 143 get <- struct{}{} 144 145 return nil 146 }, 147 }), 148 ) 149 got <- err 150 }() 151 152 regWait := whenWantWaitCh(p) 153 <-get // Await for getter blocked on awaiting session. 154 <-regWait // Let the getter register itself in the wait queue. 155 156 if test.racy { 157 // We are testing the case, when session consumer registered 158 // himself in the wait queue, but not ready to receive the 159 // session when session arrives (that is, stuck between 160 // pushing channel in the list and reading from the channel). 161 _ = p.Close(context.Background()) 162 <-wait 163 } else { 164 // We are testing the normal case, when session consumer registered 165 // himself in the wait queue and successfully blocked on 166 // reading from signaling channel. 167 <-wait 168 // Let the waiting goroutine to block on reading from channel. 169 _ = p.Close(context.Background()) 170 } 171 172 const timeout = time.Second 173 select { 174 case err := <-got: 175 if !xerrors.Is(err, errClosedClient) { 176 t.Fatalf( 177 "unexpected error: %v; want %v", 178 err, errClosedClient, 179 ) 180 } 181 case <-p.clock.After(timeout): 182 t.Fatalf("no result after %s", timeout) 183 } 184 }) 185 } 186 } 187 188 func TestSessionPoolClose(t *testing.T) { 189 counter := 0 190 xtest.TestManyTimes(t, func(t testing.TB) { 191 counter++ 192 defer func() { 193 if counter%1000 == 0 { 194 t.Logf("%d times test passed", counter) 195 } 196 }() 197 198 p := newClientWithStubBuilder( 199 t, 200 testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 201 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 202 return &Ydb_Table.CreateSessionResult{ 203 SessionId: testutil.SessionID(), 204 }, nil 205 }, 206 testutil.TableDeleteSession: func(interface{}) (proto.Message, error) { 207 return &Ydb_Table.DeleteSessionResponse{}, nil 208 }, 209 })), 210 3, 211 config.WithSizeLimit(3), 212 config.WithIdleThreshold(time.Hour), 213 ) 214 defer func() { 215 _ = p.Close(context.Background()) 216 }() 217 218 var ( 219 s1 = mustGetSession(t, p) 220 s2 = mustGetSession(t, p) 221 s3 = mustGetSession(t, p) 222 closed1 = false 223 closed2 = false 224 closed3 = false 225 ) 226 227 s1.onClose = append(s1.onClose, func(s *session) { closed1 = true }) 228 s2.onClose = append(s2.onClose, func(s *session) { closed2 = true }) 229 s3.onClose = append(s3.onClose, func(s *session) { closed3 = true }) 230 231 mustPutSession(t, p, s1) 232 mustPutSession(t, p, s2) 233 mustClose(t, p) 234 235 if !closed1 { 236 t.Errorf("session1 was not closed") 237 } 238 if !closed2 { 239 t.Errorf("session2 was not closed") 240 } 241 if closed3 { 242 t.Fatalf("unexpected session close") 243 } 244 245 if err := p.Put(context.Background(), s3); !xerrors.Is(err, errClosedClient) { 246 t.Errorf( 247 "unexpected Put() error: %v; want %v", 248 err, errClosedClient, 249 ) 250 } 251 if !closed3 { 252 t.Fatalf("session was not closed") 253 } 254 }, xtest.StopAfter(17*time.Second)) 255 } 256 257 func TestRaceWgClosed(t *testing.T) { 258 defer func() { 259 if e := recover(); e != nil { 260 t.Fatal(e) 261 } 262 }() 263 264 var ( 265 limit = 100 266 start = time.Now() 267 counter int 268 ) 269 270 xtest.TestManyTimes(t, func(t testing.TB) { 271 counter++ 272 defer func() { 273 if counter%1000 == 0 { 274 t.Logf("%0.1fs: %d times test passed", time.Since(start).Seconds(), counter) 275 } 276 }() 277 ctx, cancel := xcontext.WithTimeout(context.Background(), 278 //nolint:gosec 279 time.Duration(rand.Int31n(int32(100*time.Millisecond))), 280 ) 281 defer cancel() 282 283 wg := sync.WaitGroup{} 284 p := newClientWithStubBuilder(t, 285 testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 286 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 287 return &Ydb_Table.CreateSessionResult{ 288 SessionId: testutil.SessionID(), 289 }, nil 290 }, 291 })), 292 limit, 293 config.WithSizeLimit(limit), 294 ) 295 for j := 0; j < limit*10; j++ { 296 wg.Add(1) 297 go func() { 298 defer wg.Done() 299 for { 300 err := p.Do(ctx, 301 func(ctx context.Context, s table.Session) error { 302 return nil 303 }, 304 ) 305 if err != nil && xerrors.Is(err, errClosedClient) { 306 return 307 } 308 } 309 }() 310 } 311 _ = p.Close(context.Background()) 312 wg.Wait() 313 }, xtest.StopAfter(27*time.Second)) 314 } 315 316 func TestSessionPoolDeleteReleaseWait(t *testing.T) { 317 for _, test := range []struct { 318 name string 319 racy bool 320 }{ 321 { 322 name: "normal", 323 racy: false, 324 }, 325 { 326 name: "racy", 327 racy: true, 328 }, 329 } { 330 t.Run(test.name, func(t *testing.T) { 331 var ( 332 get = make(chan struct{}, 1) 333 wait = make(chan struct{}) 334 got = make(chan struct{}, 1) 335 ) 336 p := newClientWithStubBuilder( 337 t, 338 testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 339 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 340 return &Ydb_Table.CreateSessionResult{ 341 SessionId: testutil.SessionID(), 342 }, nil 343 }, 344 })), 345 2, 346 config.WithSizeLimit(1), 347 config.WithIdleThreshold(time.Hour), 348 config.WithTrace( 349 &trace.Table{ 350 OnPoolGet: func(trace.TablePoolGetStartInfo) func(trace.TablePoolGetDoneInfo) { 351 get <- struct{}{} 352 353 return nil 354 }, 355 OnPoolWait: func(trace.TablePoolWaitStartInfo) func(trace.TablePoolWaitDoneInfo) { 356 wait <- struct{}{} 357 358 return nil 359 }, 360 }, 361 ), 362 ) 363 defer func() { 364 _ = p.Close(context.Background()) 365 }() 366 s := mustGetSession(t, p) 367 go func() { 368 defer func() { 369 close(got) 370 }() 371 _, _ = p.Get(context.Background()) 372 }() 373 374 regWait := whenWantWaitCh(p) 375 <-get // Await for getter blocked on awaiting session. 376 <-regWait // Let the getter register itself in the wait queue. 377 378 if test.racy { 379 // We are testing the case, when session consumer registered 380 // himself in the wait queue, but not ready to receive the 381 // session when session arrives (that is, it was stucked between 382 // pushing channel in the list and reading from the channel). 383 _ = s.Close(context.Background()) 384 <-wait 385 } else { 386 // We are testing the normal case, when session consumer registered 387 // himself in the wait queue and successfully blocked on 388 // reading from signaling channel. 389 <-wait 390 // Let the waiting goroutine to block on reading from channel. 391 runtime.Gosched() 392 _ = s.Close(context.Background()) 393 } 394 395 const timeout = time.Second 396 select { 397 case <-got: 398 case <-p.clock.After(timeout): 399 t.Fatalf("no internalPoolGet after %s", timeout) 400 } 401 }) 402 } 403 } 404 405 func TestSessionPoolRacyGet(t *testing.T) { 406 type createReq struct { 407 release chan struct{} 408 session *session 409 } 410 create := make(chan createReq) 411 p, err := newClient( 412 context.Background(), 413 nil, 414 (&StubBuilder{ 415 Limit: 1, 416 OnCreateSession: func(ctx context.Context) (*session, error) { 417 req := createReq{ 418 release: make(chan struct{}), 419 session: simpleSession(t), 420 } 421 create <- req 422 <-req.release 423 424 return req.session, nil 425 }, 426 }).createSession, 427 config.New( 428 config.WithSizeLimit(1), 429 config.WithIdleThreshold(-1), 430 ), 431 ) 432 require.NoError(t, err) 433 var ( 434 expSession *session 435 done = make(chan struct{}, 2) 436 ) 437 for i := 0; i < 2; i++ { 438 go func() { 439 defer func() { 440 done <- struct{}{} 441 }() 442 s, e := p.Get(context.Background()) 443 if e != nil { 444 err = e 445 446 return 447 } 448 if s != expSession { 449 err = fmt.Errorf("unexpected session: %v; want %v", s, expSession) 450 451 return 452 } 453 mustPutSession(t, p, s) 454 }() 455 } 456 if err != nil { 457 t.Fatal(err) 458 } 459 // Wait for both requests are created. 460 r1 := <-create 461 select { 462 case <-create: 463 t.Fatalf("session 2 on race created while client size 1") 464 case <-p.clock.After(time.Millisecond * 5): 465 // ok 466 } 467 468 // Release the first create session request. 469 // Created session must be stored in the Client. 470 expSession = r1.session 471 expSession.onClose = append(expSession.onClose, func(s *session) { 472 t.Fatalf("unexpected first session close") 473 }) 474 close(r1.release) 475 476 // Wait for r1's session will be stored in the Client. 477 <-done 478 479 // Ensure that session is in the Client. 480 s := mustGetSession(t, p) 481 mustPutSession(t, p, s) 482 } 483 484 func TestSessionPoolPutInFull(t *testing.T) { 485 p := newClientWithStubBuilder( 486 t, 487 testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 488 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 489 return &Ydb_Table.CreateSessionResult{ 490 SessionId: testutil.SessionID(), 491 }, nil 492 }, 493 })), 494 1, 495 config.WithSizeLimit(1), 496 config.WithIdleThreshold(-1), 497 ) 498 s := mustGetSession(t, p) 499 if err := p.Put(context.Background(), s); err != nil { 500 t.Fatalf("unexpected error on put session into non-full client: %v, wand: %v", err, nil) 501 } 502 503 if err := p.Put(context.Background(), simpleSession(t)); !xerrors.Is(err, errSessionPoolOverflow) { 504 t.Fatalf("unexpected error on put session into full client: %v, wand: %v", err, errSessionPoolOverflow) 505 } 506 } 507 508 func TestSessionPoolSizeLimitOverflow(t *testing.T) { 509 type sessionAndError struct { 510 session *session 511 err error 512 } 513 for _, test := range []struct { 514 name string 515 racy bool 516 }{ 517 { 518 name: "normal", 519 racy: false, 520 }, 521 { 522 name: "racy", 523 racy: true, 524 }, 525 } { 526 t.Run(test.name, func(t *testing.T) { 527 var ( 528 get = make(chan struct{}) 529 wait = make(chan struct{}) 530 got = make(chan sessionAndError) 531 ) 532 p := newClientWithStubBuilder( 533 t, 534 testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 535 testutil.TableCreateSession: func(interface{}) (result proto.Message, _ error) { 536 return &Ydb_Table.CreateSessionResult{ 537 SessionId: testutil.SessionID(), 538 }, nil 539 }, 540 })), 541 1, 542 config.WithSizeLimit(1), 543 ) 544 defer func() { 545 _ = p.Close(context.Background()) 546 }() 547 s := mustGetSession(t, p) 548 { 549 ctx, cancel := xcontext.WithCancel(context.Background()) 550 cancel() 551 if _, err := p.Get(ctx); !xerrors.Is(err, context.Canceled) { 552 t.Fatalf( 553 "unexpected error: %v; want %v", 554 err, context.Canceled, 555 ) 556 } 557 } 558 go func() { 559 session, err := p.internalPoolGet( 560 context.Background(), 561 withTrace(&trace.Table{ 562 OnPoolGet: func(trace.TablePoolGetStartInfo) func(trace.TablePoolGetDoneInfo) { 563 get <- struct{}{} 564 565 return nil 566 }, 567 OnPoolWait: func(trace.TablePoolWaitStartInfo) func(trace.TablePoolWaitDoneInfo) { 568 wait <- struct{}{} 569 570 return nil 571 }, 572 }), 573 ) 574 got <- sessionAndError{session, err} 575 }() 576 577 regWait := whenWantWaitCh(p) 578 <-get // Await for getter blocked on awaiting session. 579 <-regWait // Let the getter register itself in the wait queue. 580 581 if test.racy { 582 // We are testing the case, when session consumer registered 583 // himself in the wait queue, but not ready to receive the 584 // session when session arrives (that is, it was stucked between 585 // pushing channel in the list and reading from the channel). 586 _ = p.Put(context.Background(), s) 587 <-wait 588 } else { 589 // We are testing the normal case, when session consumer registered 590 // himself in the wait queue and successfully blocked on 591 // reading from signaling channel. 592 <-wait 593 // Let the waiting goroutine to block on reading from channel. 594 _ = p.Put(context.Background(), s) 595 } 596 597 const timeout = time.Second 598 select { 599 case se := <-got: 600 if se.err != nil { 601 t.Fatal(se.err) 602 } 603 if se.session != s { 604 t.Fatalf("unexpected session") 605 } 606 case <-p.clock.After(timeout): 607 t.Fatalf("no session after %s", timeout) 608 } 609 }) 610 } 611 } 612 613 func TestSessionPoolGetPut(t *testing.T) { 614 var ( 615 created int 616 deleted int 617 ) 618 assertCreated := func(exp int) { 619 if act := created; act != exp { 620 t.Errorf( 621 "unexpected number of created sessions: %v; want %v", 622 act, exp, 623 ) 624 } 625 } 626 assertDeleted := func(exp int) { 627 if act := deleted; act != exp { 628 t.Errorf( 629 "unexpected number of deleted sessions: %v; want %v", 630 act, exp, 631 ) 632 } 633 } 634 p := newClientWithStubBuilder( 635 t, 636 testutil.NewBalancer( 637 testutil.WithInvokeHandlers( 638 testutil.InvokeHandlers{ 639 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 640 created++ 641 642 return &Ydb_Table.CreateSessionResult{ 643 SessionId: testutil.SessionID(), 644 }, nil 645 }, 646 testutil.TableDeleteSession: func(interface{}) (proto.Message, error) { 647 deleted++ 648 649 return nil, nil 650 }, 651 }, 652 ), 653 ), 654 0, 655 config.WithSizeLimit(1), 656 ) 657 defer func() { 658 _ = p.Close(context.Background()) 659 }() 660 661 s := mustGetSession(t, p) 662 assertCreated(1) 663 664 mustPutSession(t, p, s) 665 assertDeleted(0) 666 667 mustGetSession(t, p) 668 assertCreated(1) 669 670 _ = s.Close(context.Background()) 671 assertDeleted(1) 672 673 mustGetSession(t, p) 674 assertCreated(2) 675 } 676 677 func TestSessionPoolCloseIdleSessions(t *testing.T) { 678 xtest.TestManyTimes(t, func(t testing.TB) { 679 var ( 680 idleThreshold = 4 * time.Second 681 closedCount atomic.Int64 682 fakeClock = clockwork.NewFakeClock() 683 ) 684 p := newClientWithStubBuilder( 685 t, 686 testutil.NewBalancer( 687 testutil.WithInvokeHandlers( 688 testutil.InvokeHandlers{ 689 testutil.TableDeleteSession: okHandler, 690 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 691 closedCount.Add(1) 692 693 return &Ydb_Table.CreateSessionResult{ 694 SessionId: testutil.SessionID(), 695 }, nil 696 }, 697 }, 698 ), 699 ), 700 2, 701 config.WithSizeLimit(2), 702 config.WithIdleThreshold(idleThreshold), 703 config.WithClock(fakeClock), 704 ) 705 706 s1 := mustGetSession(t, p) 707 s2 := mustGetSession(t, p) 708 709 // Put both sessions at the absolutely same time. 710 // That is, both sessions must be keepalived by a single tick. 711 mustPutSession(t, p, s1) 712 mustPutSession(t, p, s2) 713 714 // Emulate first simple tick event. We expect two sessions be keepalived. 715 fakeClock.Advance(idleThreshold / 2) 716 if !closedCount.CompareAndSwap(2, 0) { 717 t.Fatal("unexpected number of keepalives") 718 } 719 720 // Now internalPoolGet first session and "spent" some time working within it. 721 x := mustGetSession(t, p) 722 723 // Move time to idleThreshold / 2 724 fakeClock.Advance(idleThreshold / 2) 725 726 // Now put that session back and emulate keepalive moment. 727 mustPutSession(t, p, x) 728 729 // Move time to idleThreshold / 2 730 fakeClock.Advance(idleThreshold / 2) 731 // We expect here next tick to be registered after half of a idleThreshold. 732 // That is, x was touched half of idleThreshold ago, so we need to wait for 733 // the second half until we must touch it. 734 735 _ = p.Close(context.Background()) 736 }, xtest.StopAfter(12*time.Second)) 737 } 738 739 func TestSessionPoolDoublePut(t *testing.T) { 740 p := newClientWithStubBuilder( 741 t, 742 testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 743 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 744 return &Ydb_Table.CreateSessionResult{ 745 SessionId: testutil.SessionID(), 746 }, nil 747 }, 748 })), 749 1, 750 config.WithSizeLimit(2), 751 config.WithIdleThreshold(-1), 752 ) 753 754 s := mustGetSession(t, p) 755 mustPutSession(t, p, s) 756 757 defer func() { 758 if thePanic := recover(); thePanic == nil { 759 t.Fatalf("no panic") 760 } 761 }() 762 _ = p.Put(context.Background(), s) 763 } 764 765 func mustGetSession(t testing.TB, p *Client) *session { 766 wg := sync.WaitGroup{} 767 defer wg.Wait() 768 s, err := p.Get(context.Background()) 769 if err != nil { 770 t.Helper() 771 t.Fatalf("%s: %v", caller(), err) 772 } 773 774 return s 775 } 776 777 func mustPutSession(t testing.TB, p *Client, s *session) { 778 wg := sync.WaitGroup{} 779 defer wg.Wait() 780 if err := p.Put(context.Background(), s); err != nil { 781 t.Helper() 782 t.Fatalf("%s: %v", caller(), err) 783 } 784 } 785 786 func mustClose(t testing.TB, p closer.Closer) { 787 wg := sync.WaitGroup{} 788 defer wg.Wait() 789 if err := p.Close(context.Background()); err != nil { 790 t.Helper() 791 t.Fatalf("%s: %v", caller(), err) 792 } 793 } 794 795 func caller() string { 796 _, file, line, _ := runtime.Caller(2) 797 798 return fmt.Sprintf("%s:%d", path.Base(file), line) 799 } 800 801 var okHandler = func(interface{}) (proto.Message, error) { 802 return &emptypb.Empty{}, nil 803 } 804 805 var simpleCluster = testutil.NewBalancer( 806 testutil.WithInvokeHandlers( 807 testutil.InvokeHandlers{ 808 testutil.TableExecuteDataQuery: func(interface{}) (proto.Message, error) { 809 return &Ydb_Table.ExecuteQueryResult{ 810 TxMeta: &Ydb_Table.TransactionMeta{ 811 Id: "", 812 }, 813 }, nil 814 }, 815 testutil.TableBeginTransaction: func(interface{}) (proto.Message, error) { 816 return &Ydb_Table.BeginTransactionResult{ 817 TxMeta: &Ydb_Table.TransactionMeta{ 818 Id: "", 819 }, 820 }, nil 821 }, 822 testutil.TableExplainDataQuery: func(interface{}) (proto.Message, error) { 823 return &Ydb_Table.ExecuteQueryResult{}, nil 824 }, 825 testutil.TablePrepareDataQuery: func(interface{}) (proto.Message, error) { 826 return &Ydb_Table.PrepareQueryResult{}, nil 827 }, 828 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 829 return &Ydb_Table.CreateSessionResult{ 830 SessionId: testutil.SessionID(), 831 }, nil 832 }, 833 testutil.TableDeleteSession: func(interface{}) (proto.Message, error) { 834 return &Ydb_Table.DeleteSessionResponse{}, nil 835 }, 836 testutil.TableCommitTransaction: func(interface{}) (proto.Message, error) { 837 return &Ydb_Table.CommitTransactionResponse{}, nil 838 }, 839 testutil.TableRollbackTransaction: func(interface{}) (proto.Message, error) { 840 return &Ydb_Table.RollbackTransactionResponse{}, nil 841 }, 842 testutil.TableKeepAlive: func(interface{}) (proto.Message, error) { 843 return &Ydb_Table.KeepAliveResult{}, nil 844 }, 845 }, 846 ), 847 ) 848 849 func simpleSession(t *testing.T) *session { 850 s, err := newSession(context.Background(), simpleCluster, config.New()) 851 if err != nil { 852 t.Fatalf("newSession unexpected error: %v", err) 853 } 854 855 return s 856 } 857 858 type StubBuilder struct { 859 OnCreateSession func(ctx context.Context) (*session, error) 860 861 cc grpc.ClientConnInterface 862 Limit int 863 T testing.TB 864 865 mu xsync.Mutex 866 actual int 867 } 868 869 func newClientWithStubBuilder( 870 t testing.TB, 871 balancer balancer, 872 stubLimit int, 873 options ...config.Option, 874 ) *Client { 875 c, err := newClient( 876 context.Background(), 877 balancer, 878 (&StubBuilder{ 879 T: t, 880 Limit: stubLimit, 881 cc: balancer, 882 }).createSession, 883 config.New(options...), 884 ) 885 require.NoError(t, err) 886 887 return c 888 } 889 890 func (s *StubBuilder) createSession(ctx context.Context) (session *session, err error) { 891 defer s.mu.WithLock(func() { 892 if session != nil { 893 s.actual++ 894 } 895 }) 896 897 s.mu.WithLock(func() { 898 if s.Limit > 0 && s.actual == s.Limit { 899 err = fmt.Errorf("stub session: limit overflow") 900 } 901 }) 902 if err != nil { 903 return nil, err 904 } 905 906 if f := s.OnCreateSession; f != nil { 907 return f(ctx) 908 } 909 910 return newSession(ctx, s.cc, config.New()) 911 } 912 913 func (c *Client) debug() { 914 fmt.Print("head ") 915 for el := c.idle.Front(); el != nil; el = el.Next() { 916 s := el.Value.(*session) 917 x := c.index[s] 918 fmt.Printf("<-> %s(%d) ", s.ID(), x.touched.Unix()) 919 } 920 fmt.Print("<-> tail\n") 921 } 922 923 func whenWantWaitCh(p *Client) <-chan struct{} { 924 var ( 925 prev = p.testHookGetWaitCh 926 ch = make(chan struct{}) 927 ) 928 p.testHookGetWaitCh = func() { 929 p.testHookGetWaitCh = prev 930 close(ch) 931 } 932 933 return ch 934 } 935 936 func TestDeadlockOnUpdateNodes(t *testing.T) { 937 xtest.TestManyTimes(t, func(t testing.TB) { 938 ctx, cancel := xcontext.WithTimeout(context.Background(), 1*time.Second) 939 defer cancel() 940 var ( 941 nodes = make([]uint32, 0, 3) 942 nodeIDCounter = uint32(0) 943 ) 944 balancer := testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 945 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 946 sessionID := testutil.SessionID(testutil.WithNodeID(nodeIDCounter)) 947 nodeIDCounter++ 948 nodeID, err := nodeID(sessionID) 949 if err != nil { 950 return nil, err 951 } 952 nodes = append(nodes, nodeID) 953 954 return &Ydb_Table.CreateSessionResult{ 955 SessionId: sessionID, 956 }, nil 957 }, 958 })) 959 c := newClientWithStubBuilder(t, balancer, 3) 960 defer func() { 961 _ = c.Close(ctx) 962 }() 963 s1, err := c.Get(ctx) 964 require.NoError(t, err) 965 s2, err := c.Get(ctx) 966 require.NoError(t, err) 967 s3, err := c.Get(ctx) 968 require.NoError(t, err) 969 require.Len(t, nodes, 3) 970 err = c.Put(ctx, s1) 971 require.NoError(t, err) 972 err = c.Put(ctx, s2) 973 require.NoError(t, err) 974 err = c.Put(ctx, s3) 975 require.NoError(t, err) 976 }, xtest.StopAfter(12*time.Second)) 977 } 978 979 func TestDeadlockOnInternalPoolGCTick(t *testing.T) { 980 xtest.TestManyTimes(t, func(t testing.TB) { 981 ctx, cancel := xcontext.WithTimeout(context.Background(), 1*time.Second) 982 defer cancel() 983 var ( 984 nodes = make([]uint32, 0, 3) 985 nodeIDCounter = uint32(0) 986 ) 987 balancer := testutil.NewBalancer(testutil.WithInvokeHandlers(testutil.InvokeHandlers{ 988 testutil.TableCreateSession: func(interface{}) (proto.Message, error) { 989 sessionID := testutil.SessionID(testutil.WithNodeID(nodeIDCounter)) 990 nodeIDCounter++ 991 nodeID, err := nodeID(sessionID) 992 if err != nil { 993 return nil, err 994 } 995 nodes = append(nodes, nodeID) 996 997 return &Ydb_Table.CreateSessionResult{ 998 SessionId: sessionID, 999 }, nil 1000 }, 1001 })) 1002 c := newClientWithStubBuilder(t, balancer, 3) 1003 defer func() { 1004 _ = c.Close(ctx) 1005 }() 1006 s1, err := c.Get(ctx) 1007 if err != nil && errors.Is(err, context.DeadlineExceeded) { 1008 return 1009 } 1010 require.NoError(t, err) 1011 s2, err := c.Get(ctx) 1012 if err != nil && errors.Is(err, context.DeadlineExceeded) { 1013 return 1014 } 1015 require.NoError(t, err) 1016 s3, err := c.Get(ctx) 1017 if err != nil && errors.Is(err, context.DeadlineExceeded) { 1018 return 1019 } 1020 require.NoError(t, err) 1021 require.Len(t, nodes, 3) 1022 err = c.Put(ctx, s1) 1023 if err != nil && errors.Is(err, context.DeadlineExceeded) { 1024 return 1025 } 1026 require.NoError(t, err) 1027 err = c.Put(ctx, s2) 1028 if err != nil && errors.Is(err, context.DeadlineExceeded) { 1029 return 1030 } 1031 require.NoError(t, err) 1032 err = c.Put(ctx, s3) 1033 if err != nil && errors.Is(err, context.DeadlineExceeded) { 1034 return 1035 } 1036 require.NoError(t, err) 1037 c.internalPoolGCTick(ctx, 0) 1038 }, xtest.StopAfter(12*time.Second)) 1039 }