Pārlūkot izejas kodu

lib/util: Add caller info to service (ref #5932) (#5973)

Simon Frei 6 gadi atpakaļ
vecāks
revīzija
031684116b
2 mainītis faili ar 49 papildinājumiem un 3 dzēšanām
  1. 28 2
      lib/util/utils.go
  2. 21 1
      lib/util/utils_test.go

+ 28 - 2
lib/util/utils.go

@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"net/url"
 	"reflect"
+	"runtime"
 	"strconv"
 	"strings"
 
@@ -178,7 +179,7 @@ func Address(network, host string) string {
 // AsService wraps the given function to implement suture.Service by calling
 // that function on serve and closing the passed channel when Stop is called.
 func AsService(fn func(stop chan struct{})) suture.Service {
-	return AsServiceWithError(func(stop chan struct{}) error {
+	return asServiceWithError(func(stop chan struct{}) error {
 		fn(stop)
 		return nil
 	})
@@ -186,6 +187,7 @@ func AsService(fn func(stop chan struct{})) suture.Service {
 
 type ServiceWithError interface {
 	suture.Service
+	fmt.Stringer
 	Error() error
 	SetError(error)
 }
@@ -193,7 +195,21 @@ type ServiceWithError interface {
 // AsServiceWithError does the same as AsService, except that it keeps track
 // of an error returned by the given function.
 func AsServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
+	return asServiceWithError(fn)
+}
+
+// caller retrieves information about the creator of the service, i.e. the stack
+// two levels up from itself.
+func caller() string {
+	pc := make([]uintptr, 1)
+	_ = runtime.Callers(4, pc)
+	f, _ := runtime.CallersFrames(pc).Next()
+	return f.Function
+}
+
+func asServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
 	s := &service{
+		caller:  caller(),
 		serve:   fn,
 		stop:    make(chan struct{}),
 		stopped: make(chan struct{}),
@@ -204,6 +220,7 @@ func AsServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
 }
 
 type service struct {
+	caller  string
 	serve   func(stop chan struct{}) error
 	stop    chan struct{}
 	stopped chan struct{}
@@ -235,7 +252,12 @@ func (s *service) Serve() {
 
 func (s *service) Stop() {
 	s.mut.Lock()
-	close(s.stop)
+	select {
+	case <-s.stop:
+		panic(fmt.Sprintf("Stop called more than once on %v", s))
+	default:
+		close(s.stop)
+	}
 	s.mut.Unlock()
 	<-s.stopped
 }
@@ -251,3 +273,7 @@ func (s *service) SetError(err error) {
 	s.err = err
 	s.mut.Unlock()
 }
+
+func (s *service) String() string {
+	return fmt.Sprintf("Service@%p created by %v", s, s.caller)
+}

+ 21 - 1
lib/util/utils_test.go

@@ -6,7 +6,10 @@
 
 package util
 
-import "testing"
+import (
+	"strings"
+	"testing"
+)
 
 type Defaulter struct {
 	Value string
@@ -222,3 +225,20 @@ func TestCopyMatching(t *testing.T) {
 		t.Error("NoCopy")
 	}
 }
+
+func TestUtilStopTwicePanic(t *testing.T) {
+	s := AsService(func(stop chan struct{}) {
+		<-stop
+	})
+
+	go s.Serve()
+	s.Stop()
+
+	defer func() {
+		expected := "lib/util.TestUtilStopTwicePanic"
+		if r := recover(); r == nil || !strings.Contains(r.(string), expected) {
+			t.Fatalf(`expected panic containing "%v", got "%v"`, expected, r)
+		}
+	}()
+	s.Stop()
+}