all: Implement suture v4-api (#6947)

This commit is contained in:
Simon Frei
2020-11-17 13:19:04 +01:00
committed by GitHub
parent e8fc465ea8
commit 9524b51708
65 changed files with 617 additions and 693 deletions
+71 -59
View File
@@ -18,7 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture"
"github.com/thejerf/suture/v4"
)
type defaultParser interface {
@@ -229,13 +229,35 @@ func AddressUnspecifiedLess(a, b net.Addr) bool {
return aIsUnspecified
}
// 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(ctx context.Context), creator string) suture.Service {
return asServiceWithError(func(ctx context.Context) error {
fn(ctx)
return nil
}, creator)
type FatalErr struct {
Err error
Status ExitStatus
}
func (e *FatalErr) Error() string {
return e.Err.Error()
}
func (e *FatalErr) Unwrap() error {
return e.Err
}
func (e *FatalErr) Is(target error) bool {
return target == suture.ErrTerminateSupervisorTree
}
type ExitStatus int
const (
ExitSuccess ExitStatus = 0
ExitError ExitStatus = 1
ExitNoUpgradeAvailable ExitStatus = 2
ExitRestart ExitStatus = 3
ExitUpgrade ExitStatus = 4
)
func (s ExitStatus) AsInt() int {
return int(s)
}
type ServiceWithError interface {
@@ -245,76 +267,35 @@ type ServiceWithError interface {
SetError(error)
}
// AsServiceWithError does the same as AsService, except that it keeps track
// of an error returned by the given function.
func AsServiceWithError(fn func(ctx context.Context) error, creator string) ServiceWithError {
return asServiceWithError(fn, creator)
}
func asServiceWithError(fn func(ctx context.Context) error, creator string) ServiceWithError {
ctx, cancel := context.WithCancel(context.Background())
s := &service{
serve: fn,
ctx: ctx,
cancel: cancel,
stopped: make(chan struct{}),
// AsService wraps the given function to implement suture.Service. In addition
// it keeps track of the returned error and allows querying and setting that error.
func AsService(fn func(ctx context.Context) error, creator string) ServiceWithError {
return &service{
creator: creator,
serve: fn,
mut: sync.NewMutex(),
}
close(s.stopped) // not yet started, don't block on Stop()
return s
}
type service struct {
creator string
serve func(ctx context.Context) error
ctx context.Context
cancel context.CancelFunc
stopped chan struct{}
err error
mut sync.Mutex
}
func (s *service) Serve() {
func (s *service) Serve(ctx context.Context) error {
s.mut.Lock()
select {
case <-s.ctx.Done():
s.mut.Unlock()
return
default:
}
s.err = nil
s.stopped = make(chan struct{})
s.mut.Unlock()
var err error
defer func() {
if err == context.Canceled {
err = nil
}
s.mut.Lock()
s.err = err
close(s.stopped)
s.mut.Unlock()
}()
err = s.serve(s.ctx)
}
err := s.serve(ctx)
func (s *service) Stop() {
s.mut.Lock()
select {
case <-s.ctx.Done():
s.mut.Unlock()
panic(fmt.Sprintf("Stop called more than once on %v", s))
default:
s.cancel()
}
// Cache s.stopped in a variable while we hold the mutex
// to prevent a data race with Serve's resetting it.
stopped := s.stopped
s.err = err
s.mut.Unlock()
<-stopped
return err
}
func (s *service) Error() error {
@@ -331,6 +312,37 @@ func (s *service) SetError(err error) {
func (s *service) String() string {
return fmt.Sprintf("Service@%p created by %v", s, s.creator)
}
// OnDone calls fn when ctx is cancelled.
func OnDone(ctx context.Context, fn func()) {
go func() {
<-ctx.Done()
fn()
}()
}
type doneService struct {
fn func()
}
func (s *doneService) Serve(ctx context.Context) error {
<-ctx.Done()
s.fn()
return nil
}
// OnSupervisorDone calls fn when sup is done.
func OnSupervisorDone(sup *suture.Supervisor, fn func()) {
sup.Add(&doneService{fn})
}
func Spec() suture.Spec {
return suture.Spec{
PassThroughPanics: true,
DontPropagateTermination: false,
}
}
func CallWithContext(ctx context.Context, fn func() error) error {
-19
View File
@@ -7,8 +7,6 @@
package util
import (
"context"
"strings"
"testing"
)
@@ -271,23 +269,6 @@ func TestInspecifiedAddressLess(t *testing.T) {
}
}
func TestUtilStopTwicePanic(t *testing.T) {
name := "foo"
s := AsService(func(ctx context.Context) {
<-ctx.Done()
}, name)
go s.Serve()
s.Stop()
defer func() {
if r := recover(); r == nil || !strings.Contains(r.(string), name) {
t.Fatalf(`expected panic containing "%v", got "%v"`, name, r)
}
}()
s.Stop()
}
func TestFillNil(t *testing.T) {
type A struct {
Slice []int