@@ -22,6 +22,7 @@ import (
2222 "net/http"
2323 "net/http/pprof"
2424 "strconv"
25+ "sync"
2526 "testing"
2627 "time"
2728
@@ -477,6 +478,132 @@ func TestBootstrapWithUnStoppedChangefeed(t *testing.T) {
477478 }, waitTime , time .Millisecond * 5 )
478479}
479480
481+ func TestConcurrentStopAndSendEvents (t * testing.T ) {
482+ // Setup context
483+ ctx , cancel := context .WithCancel (context .Background ())
484+ defer cancel ()
485+
486+ // Initialize node info
487+ info := node .NewInfo ("127.0.0.1:28600" , "" )
488+ etcdClient := newMockEtcdClient (string (info .ID ))
489+ nodeManager := watcher .NewNodeManager (nil , etcdClient )
490+ appcontext .SetService (watcher .NodeManagerName , nodeManager )
491+ nodeManager .GetAliveNodes ()[info .ID ] = info
492+
493+ // Initialize message center
494+ mc := messaging .NewMessageCenter (ctx , info .ID , 0 , config .NewDefaultMessageCenterConfig (), nil )
495+ mc .Run (ctx )
496+ defer mc .Close ()
497+ appcontext .SetService (appcontext .MessageCenter , mc )
498+
499+ // Initialize backend
500+ ctrl := gomock .NewController (t )
501+ defer ctrl .Finish ()
502+ backend := mock_changefeed .NewMockBackend (ctrl )
503+ backend .EXPECT ().GetAllChangefeeds (gomock .Any ()).Return (map [common.ChangeFeedID ]* changefeed.ChangefeedMetaWrapper {}, nil ).AnyTimes ()
504+
505+ // Create coordinator
506+ cr := New (info , & mockPdClient {}, pdutil .NewClock4Test (), backend , "test-gc-service" , 100 , 10000 , time .Millisecond * 10 )
507+ co := cr .(* coordinator )
508+
509+ // Number of goroutines for each operation
510+ const (
511+ sendEventGoroutines = 10
512+ stopGoroutines = 5
513+ eventsPerGoroutine = 100
514+ )
515+
516+ var wg sync.WaitGroup
517+ wg .Add (sendEventGoroutines + stopGoroutines )
518+
519+ // Start the coordinator
520+ ctxRun , cancelRun := context .WithCancel (ctx )
521+ go func () {
522+ err := cr .Run (ctxRun )
523+ if err != nil && err != context .Canceled {
524+ t .Errorf ("Coordinator Run returned unexpected error: %v" , err )
525+ }
526+ }()
527+
528+ // Give coordinator some time to initialize
529+ time .Sleep (100 * time .Millisecond )
530+
531+ // Start goroutines to send events
532+ for i := 0 ; i < sendEventGoroutines ; i ++ {
533+ go func (id int ) {
534+ defer wg .Done ()
535+ defer func () {
536+ // Recover from potential panics
537+ if r := recover (); r != nil {
538+ t .Errorf ("Panic in send event goroutine %d: %v" , id , r )
539+ }
540+ }()
541+
542+ for j := 0 ; j < eventsPerGoroutine ; j ++ {
543+ // Try to send an event
544+ if co .closed .Load () {
545+ // Coordinator is already closed, stop sending
546+ return
547+ }
548+
549+ msg := & messaging.TargetMessage {
550+ Topic : messaging .CoordinatorTopic ,
551+ Type : messaging .TypeMaintainerHeartbeatRequest ,
552+ }
553+
554+ // Use recvMessages to send event to channel
555+ err := co .recvMessages (ctx , msg )
556+ if err != nil && err != context .Canceled {
557+ t .Logf ("Failed to send event in goroutine %d: %v" , id , err )
558+ }
559+
560+ // Small sleep to increase chance of race conditions
561+ time .Sleep (time .Millisecond )
562+ }
563+ }(i )
564+ }
565+
566+ // Start goroutines to stop the coordinator
567+ for i := 0 ; i < stopGoroutines ; i ++ {
568+ go func (id int ) {
569+ defer wg .Done ()
570+ // Small delay to ensure some events are sent first
571+ time .Sleep (time .Duration (10 + id * 5 ) * time .Millisecond )
572+ co .Stop ()
573+ }(i )
574+ }
575+
576+ // Wait for all goroutines to complete
577+ wg .Wait ()
578+
579+ // Cancel the context to ensure the coordinator stops
580+ cancelRun ()
581+
582+ // Give some time for the coordinator to fully stop
583+ time .Sleep (100 * time .Millisecond )
584+
585+ // Verify that the coordinator is closed
586+ require .True (t , co .closed .Load ())
587+
588+ // Verify that event channel is closed
589+ select {
590+ case _ , ok := <- co .eventCh .Out ():
591+ require .False (t , ok , "Event channel should be closed" )
592+ default :
593+ // Channel might be already drained, which is fine
594+ }
595+
596+ // Try sending another event - should not panic but may return error
597+ msg := & messaging.TargetMessage {
598+ Topic : messaging .CoordinatorTopic ,
599+ Type : messaging .TypeMaintainerHeartbeatRequest ,
600+ }
601+
602+ err := co .recvMessages (ctx , msg )
603+ require .NoError (t , err )
604+ require .True (t , co .closed .Load ())
605+ }
606+
480607type maintainNode struct {
481608 cancel context.CancelFunc
482609 mc messaging.MessageCenter
0 commit comments