@@ -22,6 +22,7 @@ import (
22
22
"slices"
23
23
"strconv"
24
24
"strings"
25
+ "sync/atomic"
25
26
"testing"
26
27
"time"
27
28
@@ -345,11 +346,22 @@ func TestTTLDeleteTaskDoDelete(t *testing.T) {
345
346
}
346
347
347
348
func TestTTLDeleteRateLimiter (t * testing.T ) {
348
- origDeleteLimit := variable . TTLDeleteRateLimit . Load ()
349
+ origGlobalDelRateLimiter := globalDelRateLimiter
349
350
defer func () {
350
- variable .TTLDeleteRateLimit .Store (origDeleteLimit )
351
+ globalDelRateLimiter = origGlobalDelRateLimiter
352
+ variable .TTLDeleteRateLimit .Store (variable .DefTiDBTTLDeleteRateLimit )
351
353
}()
352
354
355
+ // The global inner limiter should have a default config
356
+ require .Equal (t , 0 , variable .DefTiDBTTLDeleteRateLimit )
357
+ require .Equal (t , int64 (0 ), variable .TTLDeleteRateLimit .Load ())
358
+ require .Equal (t , int64 (0 ), globalDelRateLimiter .limit .Load ())
359
+ require .Equal (t , rate .Inf , globalDelRateLimiter .limiter .Limit ())
360
+ // The newDelRateLimiter() should return a default config
361
+ globalDelRateLimiter = newDelRateLimiter ()
362
+ require .Equal (t , int64 (0 ), globalDelRateLimiter .limit .Load ())
363
+ require .Equal (t , rate .Inf , globalDelRateLimiter .limiter .Limit ())
364
+
353
365
ctx , cancel := context .WithTimeout (context .Background (), time .Minute )
354
366
defer func () {
355
367
if cancel != nil {
@@ -358,21 +370,21 @@ func TestTTLDeleteRateLimiter(t *testing.T) {
358
370
}()
359
371
360
372
variable .TTLDeleteRateLimit .Store (100000 )
361
- require .NoError (t , globalDelRateLimiter .Wait (ctx ))
373
+ require .NoError (t , globalDelRateLimiter .WaitDelToken (ctx ))
362
374
require .Equal (t , rate .Limit (100000 ), globalDelRateLimiter .limiter .Limit ())
363
375
require .Equal (t , int64 (100000 ), globalDelRateLimiter .limit .Load ())
364
376
365
377
variable .TTLDeleteRateLimit .Store (0 )
366
- require .NoError (t , globalDelRateLimiter .Wait (ctx ))
367
- require .Equal (t , rate .Limit ( 0 ) , globalDelRateLimiter .limiter .Limit ())
378
+ require .NoError (t , globalDelRateLimiter .WaitDelToken (ctx ))
379
+ require .Equal (t , rate .Inf , globalDelRateLimiter .limiter .Limit ())
368
380
require .Equal (t , int64 (0 ), globalDelRateLimiter .limit .Load ())
369
381
370
382
// 0 stands for no limit
371
- require .NoError (t , globalDelRateLimiter .Wait (ctx ))
383
+ require .NoError (t , globalDelRateLimiter .WaitDelToken (ctx ))
372
384
// cancel ctx returns an error
373
385
cancel ()
374
386
cancel = nil
375
- require .EqualError (t , globalDelRateLimiter .Wait (ctx ), "context canceled" )
387
+ require .EqualError (t , globalDelRateLimiter .WaitDelToken (ctx ), "context canceled" )
376
388
}
377
389
378
390
func TestTTLDeleteTaskWorker (t * testing.T ) {
@@ -492,3 +504,62 @@ func TestTTLDeleteTaskWorker(t *testing.T) {
492
504
require .Equal (t , uint64 (0 ), tasks [3 ].statistics .SuccessRows .Load ())
493
505
require .Equal (t , uint64 (3 ), tasks [3 ].statistics .ErrorRows .Load ())
494
506
}
507
+
508
+ // TestDelRateLimiterConcurrency is used to test some concurrency cases of delRateLimiter.
509
+ // See issue: https://github.com/pingcap/tidb/issues/58484
510
+ // It tests the below case:
511
+ // 1. The `tidb_ttl_delete_rate_limit` set to some non-zero value such as 128.
512
+ // 2. Some delWorker delete rows concurrency and try to wait for the inner `rate.Limiter`.
513
+ // 3. Before internal `l.limiter.Wait` is called, the `tidb_ttl_delete_rate_limit` is set to 0.
514
+ // It resets the internal `rate.Limiter` (in the bug codes, its rate is set to 0).
515
+ // 4. The delWorkers in step 2 continue to call l.limiter.Wait.
516
+ // In the bug codes, some of them are blocked forever because the rate is set to 0.
517
+ func TestDelRateLimiterConcurrency (t * testing.T ) {
518
+ origGlobalDelRateLimiter := globalDelRateLimiter
519
+ defer func () {
520
+ globalDelRateLimiter = origGlobalDelRateLimiter
521
+ variable .TTLDeleteRateLimit .Store (variable .DefTiDBTTLDeleteRateLimit )
522
+ }()
523
+
524
+ globalDelRateLimiter = newDelRateLimiter ()
525
+ require .NoError (t , globalDelRateLimiter .WaitDelToken (context .Background ()))
526
+
527
+ variable .TTLDeleteRateLimit .Store (128 )
528
+ var waiting atomic.Int64
529
+ continue1 := make (chan struct {})
530
+ continue2 := make (chan struct {})
531
+ continue3 := make (chan struct {})
532
+ cnt := 4
533
+ for i := 0 ; i < cnt ; i ++ {
534
+ go func () {
535
+ ctx := context .WithValue (context .Background (), beforeWaitLimiterForTest , func () {
536
+ if waiting .Add (1 ) == int64 (cnt ) {
537
+ close (continue1 )
538
+ }
539
+ <- continue2
540
+ })
541
+ require .NoError (t , globalDelRateLimiter .WaitDelToken (ctx ))
542
+ if waiting .Add (- 1 ) == 0 {
543
+ close (continue3 )
544
+ }
545
+ }()
546
+ }
547
+
548
+ timeCtx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
549
+ defer cancel ()
550
+
551
+ select {
552
+ case <- continue1 :
553
+ variable .TTLDeleteRateLimit .Store (0 )
554
+ require .NoError (t , globalDelRateLimiter .WaitDelToken (timeCtx ))
555
+ close (continue2 )
556
+ case <- timeCtx .Done ():
557
+ require .FailNow (t , "timeout" )
558
+ }
559
+
560
+ select {
561
+ case <- continue3 :
562
+ case <- timeCtx .Done ():
563
+ require .FailNow (t , "timeout" )
564
+ }
565
+ }
0 commit comments