all: Implement suture v4-api (#6947)
This commit is contained in:
+71
-59
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user