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