fix panic on shutdown caused by double channel close (#209)
All checks were successful
coverage / build (push) Successful in 2m35s
test / test (push) Successful in 3m43s

This commit is contained in:
2025-04-27 15:37:18 +05:00
committed by GitHub
parent 604ad9cd9d
commit 85a78063d0
2 changed files with 57 additions and 3 deletions

View File

@@ -99,6 +99,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.
@@ -424,7 +425,7 @@ func (s *service) Stop() error {
} }
} }
close(s.done) s.notifyShutdown()
return nil return nil
} }
@@ -448,10 +449,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 {

View File

@@ -3,7 +3,9 @@ package micro
import ( import (
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/stretchr/testify/require"
"go.unistack.org/micro/v4/broker" "go.unistack.org/micro/v4/broker"
"go.unistack.org/micro/v4/client" "go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/config" "go.unistack.org/micro/v4/config"
@@ -737,3 +739,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())
}