parent
a0c9db1d09
commit
031684116b
|
@ -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()
|
||||||
|
select {
|
||||||
|
case <-s.stop:
|
||||||
|
panic(fmt.Sprintf("Stop called more than once on %v", s))
|
||||||
|
default:
|
||||||
close(s.stop)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue