fix panic on shutdown caused by double channel close (#208)
This commit is contained in:
		
							
								
								
									
										20
									
								
								service.go
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								service.go
									
									
									
									
									
								
							| @@ -104,6 +104,7 @@ type service struct { | |||||||
| 	done chan struct{} | 	done chan struct{} | ||||||
| 	opts Options | 	opts Options | ||||||
| 	sync.RWMutex | 	sync.RWMutex | ||||||
|  | 	stopped bool | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewService creates and returns a new Service based on the packages within. | // NewService creates and returns a new Service based on the packages within. | ||||||
| @@ -429,7 +430,7 @@ func (s *service) Stop() error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	close(s.done) | 	s.notifyShutdown() | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| @@ -453,10 +454,23 @@ func (s *service) Run() error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// wait on context cancel |  | ||||||
| 	<-s.done | 	<-s.done | ||||||
|  |  | ||||||
| 	return s.Stop() | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // notifyShutdown marks the service as stopped and closes the done channel. | ||||||
|  | // It ensures the channel is closed only once, preventing multiple closures. | ||||||
|  | func (s *service) notifyShutdown() { | ||||||
|  | 	s.Lock() | ||||||
|  | 	if s.stopped { | ||||||
|  | 		s.Unlock() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	s.stopped = true | ||||||
|  | 	s.Unlock() | ||||||
|  |  | ||||||
|  | 	close(s.done) | ||||||
| } | } | ||||||
|  |  | ||||||
| type Namer interface { | type Namer interface { | ||||||
|   | |||||||
| @@ -4,7 +4,9 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
| 	"go.unistack.org/micro/v3/broker" | 	"go.unistack.org/micro/v3/broker" | ||||||
| 	"go.unistack.org/micro/v3/client" | 	"go.unistack.org/micro/v3/client" | ||||||
| 	"go.unistack.org/micro/v3/config" | 	"go.unistack.org/micro/v3/config" | ||||||
| @@ -773,3 +775,41 @@ func Test_getNameIndex(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| */ | */ | ||||||
|  |  | ||||||
|  | func TestServiceShutdown(t *testing.T) { | ||||||
|  | 	defer func() { | ||||||
|  | 		if r := recover(); r != nil { | ||||||
|  | 			t.Fatalf("service shutdown failed: %v", r) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	s, ok := NewService().(*service) | ||||||
|  | 	require.NotNil(t, s) | ||||||
|  | 	require.True(t, ok) | ||||||
|  |  | ||||||
|  | 	require.NoError(t, s.Start()) | ||||||
|  | 	require.False(t, s.stopped) | ||||||
|  |  | ||||||
|  | 	require.NoError(t, s.Stop()) | ||||||
|  | 	require.True(t, s.stopped) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestServiceMultipleShutdowns(t *testing.T) { | ||||||
|  | 	defer func() { | ||||||
|  | 		if r := recover(); r != nil { | ||||||
|  | 			t.Fatalf("service shutdown failed: %v", r) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	s := NewService() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		time.Sleep(10 * time.Millisecond) | ||||||
|  | 		// first call | ||||||
|  | 		require.NoError(t, s.Stop()) | ||||||
|  | 		// duplicate call | ||||||
|  | 		require.NoError(t, s.Stop()) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	require.NoError(t, s.Run()) | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user