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
+11 -20
View File
@@ -12,49 +12,40 @@ import (
"fmt"
"io"
"github.com/thejerf/suture"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/util"
)
// The auditService subscribes to events and writes these in JSON format, one
// event per line, to the specified writer.
type auditService struct {
suture.Service
w io.Writer // audit destination
sub events.Subscription
w io.Writer // audit destination
evLogger events.Logger
}
func newAuditService(w io.Writer, evLogger events.Logger) *auditService {
s := &auditService{
w: w,
sub: evLogger.Subscribe(events.AllEvents),
return &auditService{
w: w,
evLogger: evLogger,
}
s.Service = util.AsService(s.serve, s.String())
return s
}
// serve runs the audit service.
func (s *auditService) serve(ctx context.Context) {
func (s *auditService) Serve(ctx context.Context) error {
sub := s.evLogger.Subscribe(events.AllEvents)
defer sub.Unsubscribe()
enc := json.NewEncoder(s.w)
for {
select {
case ev := <-s.sub.C():
case ev := <-sub.C():
enc.Encode(ev)
case <-ctx.Done():
return
return ctx.Err()
}
}
}
// Stop stops the audit service.
func (s *auditService) Stop() {
s.Service.Stop()
s.sub.Unsubscribe()
}
func (s *auditService) String() string {
return fmt.Sprintf("auditService@%p", s)
}
+15 -4
View File
@@ -8,6 +8,7 @@ package syncthing
import (
"bytes"
"context"
"strings"
"testing"
"time"
@@ -18,8 +19,9 @@ import (
func TestAuditService(t *testing.T) {
buf := new(bytes.Buffer)
evLogger := events.NewLogger()
go evLogger.Serve()
defer evLogger.Stop()
ctx, cancel := context.WithCancel(context.Background())
go evLogger.Serve(ctx)
defer cancel()
sub := evLogger.Subscribe(events.AllEvents)
defer sub.Unsubscribe()
@@ -28,8 +30,16 @@ func TestAuditService(t *testing.T) {
// Make sure the event goes through before creating the service
<-sub.C()
auditCtx, auditCancel := context.WithCancel(context.Background())
service := newAuditService(buf, evLogger)
go service.Serve()
done := make(chan struct{})
go func() {
service.Serve(auditCtx)
close(done)
}()
// Subscription needs to happen in service.Serve
time.Sleep(10 * time.Millisecond)
// Event that should end up in the audit log
evLogger.Log(events.ConfigSaved, "the second event")
@@ -37,7 +47,8 @@ func TestAuditService(t *testing.T) {
// We need to give the events time to arrive, since the channels are buffered etc.
time.Sleep(10 * time.Millisecond)
service.Stop()
auditCancel()
<-done
// This event should not be logged, since we have stopped.
evLogger.Log(events.ConfigSaved, "the third event")
+51 -66
View File
@@ -9,6 +9,7 @@ package syncthing
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
@@ -19,7 +20,7 @@ import (
"sync"
"time"
"github.com/thejerf/suture"
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/lib/api"
"github.com/syncthing/syncthing/lib/build"
@@ -39,6 +40,7 @@ import (
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/ur"
"github.com/syncthing/syncthing/lib/util"
)
const (
@@ -50,20 +52,6 @@ const (
deviceCertLifetimeDays = 20 * 365
)
type ExitStatus int
func (s ExitStatus) AsInt() int {
return int(s)
}
const (
ExitSuccess ExitStatus = 0
ExitError ExitStatus = 1
ExitNoUpgradeAvailable ExitStatus = 2
ExitRestart ExitStatus = 3
ExitUpgrade ExitStatus = 4
)
type Options struct {
AssetDir string
AuditWriter io.Writer
@@ -78,18 +66,18 @@ type Options struct {
}
type App struct {
myID protocol.DeviceID
mainService *suture.Supervisor
cfg config.Wrapper
ll *db.Lowlevel
evLogger events.Logger
cert tls.Certificate
opts Options
exitStatus ExitStatus
err error
stopOnce sync.Once
stop chan struct{}
stopped chan struct{}
myID protocol.DeviceID
mainService *suture.Supervisor
cfg config.Wrapper
ll *db.Lowlevel
evLogger events.Logger
cert tls.Certificate
opts Options
exitStatus util.ExitStatus
err error
stopOnce sync.Once
mainServiceCancel context.CancelFunc
stopped chan struct{}
}
func New(cfg config.Wrapper, dbBackend backend.Backend, evLogger events.Logger, cert tls.Certificate, opts Options) *App {
@@ -99,7 +87,6 @@ func New(cfg config.Wrapper, dbBackend backend.Backend, evLogger events.Logger,
evLogger: evLogger,
opts: opts,
cert: cert,
stop: make(chan struct{}),
stopped: make(chan struct{}),
}
close(a.stopped) // Hasn't been started, so shouldn't block on Wait.
@@ -112,20 +99,20 @@ func New(cfg config.Wrapper, dbBackend backend.Backend, evLogger events.Logger,
func (a *App) Start() error {
// Create a main service manager. We'll add things to this as we go along.
// We want any logging it does to go through our log system.
a.mainService = suture.New("main", suture.Spec{
Log: func(line string) {
l.Debugln(line)
},
PassThroughPanics: true,
})
spec := util.Spec()
spec.EventHook = func(e suture.Event) {
l.Debugln(e)
}
a.mainService = suture.New("main", spec)
// Start the supervisor and wait for it to stop to handle cleanup.
a.stopped = make(chan struct{})
a.mainService.ServeBackground()
go a.run()
ctx, cancel := context.WithCancel(context.Background())
a.mainServiceCancel = cancel
go a.run(ctx)
if err := a.startup(); err != nil {
a.stopWithErr(ExitError, err)
a.stopWithErr(util.ExitError, err)
return err
}
@@ -343,14 +330,9 @@ func (a *App) startup() error {
return nil
}
func (a *App) run() {
<-a.stop
if shouldDebug() {
l.Debugln("Services before stop:")
printServiceTree(os.Stdout, a.mainService, 0)
}
a.mainService.Stop()
func (a *App) run(ctx context.Context) {
err := a.mainService.Serve(ctx)
a.handleMainServiceError(err)
done := make(chan struct{})
go func() {
@@ -368,9 +350,23 @@ func (a *App) run() {
close(a.stopped)
}
func (a *App) handleMainServiceError(err error) {
if err == nil || errors.Is(err, context.Canceled) {
return
}
var fatalErr *util.FatalErr
if errors.As(err, &fatalErr) {
a.exitStatus = fatalErr.Status
a.err = fatalErr.Err
return
}
a.err = err
a.exitStatus = util.ExitError
}
// Wait blocks until the app stops running. Also returns if the app hasn't been
// started yet.
func (a *App) Wait() ExitStatus {
func (a *App) Wait() util.ExitStatus {
<-a.stopped
return a.exitStatus
}
@@ -379,7 +375,7 @@ func (a *App) Wait() ExitStatus {
// for the app to stop before returning.
func (a *App) Error() error {
select {
case <-a.stop:
case <-a.stopped:
return a.err
default:
}
@@ -388,15 +384,19 @@ func (a *App) Error() error {
// Stop stops the app and sets its exit status to given reason, unless the app
// was already stopped before. In any case it returns the effective exit status.
func (a *App) Stop(stopReason ExitStatus) ExitStatus {
func (a *App) Stop(stopReason util.ExitStatus) util.ExitStatus {
return a.stopWithErr(stopReason, nil)
}
func (a *App) stopWithErr(stopReason ExitStatus, err error) ExitStatus {
func (a *App) stopWithErr(stopReason util.ExitStatus, err error) util.ExitStatus {
a.stopOnce.Do(func() {
a.exitStatus = stopReason
a.err = err
close(a.stop)
if shouldDebug() {
l.Debugln("Services before stop:")
printServiceTree(os.Stdout, a.mainService, 0)
}
a.mainServiceCancel()
})
<-a.stopped
return a.exitStatus
@@ -416,7 +416,7 @@ func (a *App) setupGUI(m model.Model, defaultSub, diskSub events.BufferedSubscri
summaryService := model.NewFolderSummaryService(a.cfg, m, a.myID, a.evLogger)
a.mainService.Add(summaryService)
apiSvc := api.New(a.myID, a.cfg, a.opts.AssetDir, tlsDefaultCommonName, m, defaultSub, diskSub, a.evLogger, discoverer, connectionsService, urService, summaryService, errors, systemLog, &controller{a}, a.opts.NoUpgrade)
apiSvc := api.New(a.myID, a.cfg, a.opts.AssetDir, tlsDefaultCommonName, m, defaultSub, diskSub, a.evLogger, discoverer, connectionsService, urService, summaryService, errors, systemLog, a.opts.NoUpgrade)
a.mainService.Add(apiSvc)
if err := apiSvc.WaitForStart(); err != nil {
@@ -440,21 +440,6 @@ func checkShortIDs(cfg config.Wrapper) error {
return nil
}
// Implements api.Controller
type controller struct{ *App }
func (e *controller) Restart() {
e.Stop(ExitRestart)
}
func (e *controller) Shutdown() {
e.Stop(ExitSuccess)
}
func (e *controller) ExitUpgrading() {
e.Stop(ExitUpgrade)
}
type supervisor interface{ Services() []suture.Service }
func printServiceTree(w io.Writer, sup supervisor, level int) {
+4 -3
View File
@@ -18,6 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/util"
)
func tempCfgFilename(t *testing.T) string {
@@ -86,7 +87,7 @@ func TestStartupFail(t *testing.T) {
}
done := make(chan struct{})
var waitE ExitStatus
var waitE util.ExitStatus
go func() {
waitE = app.Wait()
close(done)
@@ -98,8 +99,8 @@ func TestStartupFail(t *testing.T) {
case <-done:
}
if waitE != ExitError {
t.Errorf("Got exit status %v, expected %v", waitE, ExitError)
if waitE != util.ExitError {
t.Errorf("Got exit status %v, expected %v", waitE, util.ExitError)
}
if err = app.Error(); err != startErr {
+8 -19
View File
@@ -10,49 +10,38 @@ import (
"context"
"fmt"
"github.com/thejerf/suture"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/util"
)
// The verbose logging service subscribes to events and prints these in
// verbose format to the console using INFO level.
type verboseService struct {
suture.Service
sub events.Subscription
evLogger events.Logger
}
func newVerboseService(evLogger events.Logger) *verboseService {
s := &verboseService{
sub: evLogger.Subscribe(events.AllEvents),
return &verboseService{
evLogger: evLogger,
}
s.Service = util.AsService(s.serve, s.String())
return s
}
// serve runs the verbose logging service.
func (s *verboseService) serve(ctx context.Context) {
func (s *verboseService) Serve(ctx context.Context) error {
sub := s.evLogger.Subscribe(events.AllEvents)
defer sub.Unsubscribe()
for {
select {
case ev := <-s.sub.C():
case ev := <-sub.C():
formatted := s.formatEvent(ev)
if formatted != "" {
l.Verboseln(formatted)
}
case <-ctx.Done():
return
return ctx.Err()
}
}
}
// Stop stops the verbose logging service.
func (s *verboseService) Stop() {
s.Service.Stop()
s.sub.Unsubscribe()
}
func (s *verboseService) formatEvent(ev events.Event) string {
switch ev.Type {
case events.DownloadProgress, events.LocalIndexUpdated: