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

This commit is contained in:
Simon Frei 2019-10-16 09:06:16 +02:00 committed by Jakob Borg
parent a0c9db1d09
commit 031684116b
2 changed files with 49 additions and 3 deletions

View File

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"reflect" "reflect"
"runtime"
"strconv" "strconv"
"strings" "strings"
@ -178,7 +179,7 @@ func Address(network, host string) string {
// AsService wraps the given function to implement suture.Service by calling // AsService wraps the given function to implement suture.Service by calling
// that function on serve and closing the passed channel when Stop is called. // that function on serve and closing the passed channel when Stop is called.
func AsService(fn func(stop chan struct{})) suture.Service { 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) fn(stop)
return nil return nil
}) })
@ -186,6 +187,7 @@ func AsService(fn func(stop chan struct{})) suture.Service {
type ServiceWithError interface { type ServiceWithError interface {
suture.Service suture.Service
fmt.Stringer
Error() error Error() error
SetError(error) SetError(error)
} }
@ -193,7 +195,21 @@ type ServiceWithError interface {
// AsServiceWithError does the same as AsService, except that it keeps track // AsServiceWithError does the same as AsService, except that it keeps track
// of an error returned by the given function. // of an error returned by the given function.
func AsServiceWithError(fn func(stop chan struct{}) error) ServiceWithError { 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{ s := &service{
caller: caller(),
serve: fn, serve: fn,
stop: make(chan struct{}), stop: make(chan struct{}),
stopped: make(chan struct{}), stopped: make(chan struct{}),
@ -204,6 +220,7 @@ func AsServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
} }
type service struct { type service struct {
caller string
serve func(stop chan struct{}) error serve func(stop chan struct{}) error
stop chan struct{} stop chan struct{}
stopped chan struct{} stopped chan struct{}
@ -235,7 +252,12 @@ func (s *service) Serve() {
func (s *service) Stop() { func (s *service) Stop() {
s.mut.Lock() 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.mut.Unlock()
<-s.stopped <-s.stopped
} }
@ -251,3 +273,7 @@ func (s *service) SetError(err error) {
s.err = err s.err = err
s.mut.Unlock() s.mut.Unlock()
} }
func (s *service) String() string {
return fmt.Sprintf("Service@%p created by %v", s, s.caller)
}

View File

@ -6,7 +6,10 @@
package util package util
import "testing" import (
"strings"
"testing"
)
type Defaulter struct { type Defaulter struct {
Value string Value string
@ -222,3 +225,20 @@ func TestCopyMatching(t *testing.T) {
t.Error("NoCopy") 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()
}