lib/ur: Send unreported failures on shutdown (#7164)

This commit is contained in:
Simon Frei 2020-12-22 20:17:14 +01:00 committed by GitHub
parent 4bcc38cf63
commit a20a5f61f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 296 additions and 206 deletions

View File

@ -39,10 +39,10 @@ import (
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/syncthing"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/util"
"github.com/pkg/errors"
)
@ -323,7 +323,7 @@ func main() {
}
if err != nil {
l.Warnln("Command line options:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
if options.logFile == "default" || options.logFile == "" {
@ -360,7 +360,7 @@ func main() {
)
if err != nil {
l.Warnln("Error reading device ID:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
@ -370,7 +370,7 @@ func main() {
if options.browserOnly {
if err := openGUI(protocol.EmptyDeviceID); err != nil {
l.Warnln("Failed to open web UI:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
return
}
@ -378,7 +378,7 @@ func main() {
if options.generateDir != "" {
if err := generate(options.generateDir); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
return
}
@ -386,14 +386,14 @@ func main() {
// Ensure that our home directory exists.
if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
l.Warnln("Failure on home directory:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
if options.upgradeTo != "" {
err := upgrade.ToURL(options.upgradeTo)
if err != nil {
l.Warnln("Error while Upgrading:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Upgraded from", options.upgradeTo)
return
@ -424,13 +424,13 @@ func main() {
os.Exit(exitCodeForUpgrade(err))
}
l.Infof("Upgraded to %q", release.Tag)
os.Exit(util.ExitUpgrade.AsInt())
os.Exit(svcutil.ExitUpgrade.AsInt())
}
if options.resetDatabase {
if err := resetDB(); err != nil {
l.Warnln("Resetting database:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Successfully reset database - it will be rebuilt after next start.")
return
@ -610,7 +610,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
cfg, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
if err != nil {
l.Warnln("Failed to initialize config:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
// Candidate builds should auto upgrade. Make sure the option is set,
@ -656,7 +656,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
} else {
l.Infof("Upgraded to %q, exiting now.", release.Tag)
os.Exit(util.ExitUpgrade.AsInt())
os.Exit(svcutil.ExitUpgrade.AsInt())
}
}
@ -684,7 +684,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
app, err := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
if err != nil {
l.Warnln("Failed to start Syncthing:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
if autoUpgradePossible {
@ -701,18 +701,18 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil {
l.Warnln("Creating profile:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
if err := pprof.StartCPUProfile(f); err != nil {
l.Warnln("Starting profile:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
}
go standbyMonitor(app, cfg)
if err := app.Start(); err != nil {
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
cleanConfigDirectory()
@ -725,7 +725,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
status := app.Wait()
if status == util.ExitError {
if status == svcutil.ExitError {
l.Warnln("Syncthing stopped with error:", app.Error())
}
@ -744,7 +744,7 @@ func setupSignalHandling(app *syncthing.App) {
signal.Notify(restartSign, sigHup)
go func() {
<-restartSign
app.Stop(util.ExitRestart)
app.Stop(svcutil.ExitRestart)
}()
// Exit with "success" code (no restart) on INT/TERM
@ -753,7 +753,7 @@ func setupSignalHandling(app *syncthing.App) {
signal.Notify(stopSign, os.Interrupt, sigTerm)
go func() {
<-stopSign
app.Stop(util.ExitSuccess)
app.Stop(svcutil.ExitSuccess)
}()
}
@ -790,7 +790,7 @@ func auditWriter(auditFile string) io.Writer {
fd, err = os.OpenFile(auditFile, auditFlags, 0600)
if err != nil {
l.Warnln("Audit:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
auditDest = auditFile
}
@ -840,7 +840,7 @@ func standbyMonitor(app *syncthing.App, cfg config.Wrapper) {
// things a moment to stabilize.
time.Sleep(restartDelay)
app.Stop(util.ExitRestart)
app.Stop(svcutil.ExitRestart)
return
}
now = time.Now()
@ -910,7 +910,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger)
sub.Unsubscribe()
l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
time.Sleep(time.Minute)
app.Stop(util.ExitUpgrade)
app.Stop(svcutil.ExitUpgrade)
return
}
}
@ -998,13 +998,13 @@ func setPauseState(cfg config.Wrapper, paused bool) {
}
if _, err := cfg.Replace(raw); err != nil {
l.Warnln("Cannot adjust paused state:", err)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
}
func exitCodeForUpgrade(err error) int {
if _, ok := err.(*errNoUpgrade); ok {
return util.ExitNoUpgradeAvailable.AsInt()
return svcutil.ExitNoUpgradeAvailable.AsInt()
}
return util.ExitError.AsInt()
return svcutil.ExitError.AsInt()
}

View File

@ -25,8 +25,8 @@ import (
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
)
var (
@ -99,7 +99,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if t := time.Since(restarts[0]); t < loopThreshold {
l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
copy(restarts[0:], restarts[1:])
@ -169,7 +169,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if err == nil {
// Successful exit indicates an intentional shutdown
os.Exit(util.ExitSuccess.AsInt())
os.Exit(svcutil.ExitSuccess.AsInt())
}
if exiterr, ok := err.(*exec.ExitError); ok {
@ -177,7 +177,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if stopped || runtimeOptions.noRestart {
os.Exit(exitCode)
}
if exitCode == util.ExitUpgrade.AsInt() {
if exitCode == svcutil.ExitUpgrade.AsInt() {
// Restart the monitor process to release the .old
// binary as part of the upgrade process.
l.Infoln("Restarting monitor...")
@ -189,7 +189,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
}
if runtimeOptions.noRestart {
os.Exit(util.ExitError.AsInt())
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Syncthing exited:", err)

View File

@ -49,11 +49,11 @@ import (
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/ur"
"github.com/syncthing/syncthing/lib/util"
)
// matches a bcrypt hash and not too much else
@ -89,7 +89,7 @@ type service struct {
startedOnce chan struct{} // the service has started successfully at least once
startupErr error
listenerAddr net.Addr
exitChan chan *util.FatalErr
exitChan chan *svcutil.FatalErr
guiErrors logger.Recorder
systemLog logger.Recorder
@ -123,7 +123,7 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam
tlsDefaultCommonName: tlsDefaultCommonName,
configChanged: make(chan struct{}),
startedOnce: make(chan struct{}),
exitChan: make(chan *util.FatalErr, 1),
exitChan: make(chan *svcutil.FatalErr, 1),
}
}
@ -474,7 +474,7 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
return true
}
func (s *service) fatal(err *util.FatalErr) {
func (s *service) fatal(err *svcutil.FatalErr) {
// s.exitChan is 1-buffered and whoever is first gets handled.
select {
case s.exitChan <- err:
@ -915,9 +915,9 @@ func (s *service) getDebugFile(w http.ResponseWriter, r *http.Request) {
func (s *service) postSystemRestart(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "restarting"}`, w)
s.fatal(&util.FatalErr{
s.fatal(&svcutil.FatalErr{
Err: errors.New("restart initiated by rest API"),
Status: util.ExitRestart,
Status: svcutil.ExitRestart,
})
}
@ -944,17 +944,17 @@ func (s *service) postSystemReset(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "resetting folder `+folder+`"}`, w)
}
s.fatal(&util.FatalErr{
s.fatal(&svcutil.FatalErr{
Err: errors.New("restart after db reset initiated by rest API"),
Status: util.ExitRestart,
Status: svcutil.ExitRestart,
})
}
func (s *service) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
s.flushResponse(`{"ok": "shutting down"}`, w)
s.fatal(&util.FatalErr{
s.fatal(&svcutil.FatalErr{
Err: errors.New("shutdown initiated by rest API"),
Status: util.ExitSuccess,
Status: svcutil.ExitSuccess,
})
}
@ -1390,9 +1390,9 @@ func (s *service) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
}
s.flushResponse(`{"ok": "restarting"}`, w)
s.fatal(&util.FatalErr{
s.fatal(&svcutil.FatalErr{
Err: errors.New("exit after upgrade initiated by rest API"),
Status: util.ExitUpgrade,
Status: svcutil.ExitUpgrade,
})
}
}

View File

@ -32,10 +32,10 @@ import (
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/ur"
"github.com/syncthing/syncthing/lib/util"
"github.com/thejerf/suture/v4"
)
@ -119,7 +119,7 @@ func TestStopAfterBrokenConfig(t *testing.T) {
defer os.Remove(token)
srv.started = make(chan string)
sup := suture.New("test", util.SpecWithDebugLogger(l))
sup := suture.New("test", svcutil.SpecWithDebugLogger(l))
sup.Add(srv)
ctx, cancel := context.WithCancel(context.Background())
sup.ServeBackground(ctx)

View File

@ -14,7 +14,7 @@ import (
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
)
type recv struct {
@ -33,8 +33,8 @@ type Interface interface {
type cast struct {
*suture.Supervisor
name string
reader util.ServiceWithError
writer util.ServiceWithError
reader svcutil.ServiceWithError
writer svcutil.ServiceWithError
outbox chan recv
inbox chan []byte
stopped chan struct{}
@ -45,7 +45,7 @@ type cast struct {
// methods to get a functional implementation of Interface.
func newCast(name string) *cast {
// Only log restarts in debug mode.
spec := util.SpecWithDebugLogger(l)
spec := svcutil.SpecWithDebugLogger(l)
// Don't retry too frenetically: an error to open a socket or
// whatever is usually something that is either permanent or takes
// a while to get solved...
@ -58,7 +58,7 @@ func newCast(name string) *cast {
outbox: make(chan recv, 16),
stopped: make(chan struct{}),
}
util.OnSupervisorDone(c.Supervisor, func() { close(c.stopped) })
svcutil.OnSupervisorDone(c.Supervisor, func() { close(c.stopped) })
return c
}
@ -72,8 +72,8 @@ func (c *cast) addWriter(svc func(ctx context.Context) error) {
c.Add(c.writer)
}
func (c *cast) createService(svc func(context.Context) error, suffix string) util.ServiceWithError {
return util.AsService(svc, fmt.Sprintf("%s/%s", c, suffix))
func (c *cast) createService(svc func(context.Context) error, suffix string) svcutil.ServiceWithError {
return svcutil.AsService(svc, fmt.Sprintf("%s/%s", c, suffix))
}
func (c *cast) String() string {

View File

@ -24,7 +24,7 @@ import (
"github.com/syncthing/syncthing/lib/connections/registry"
"github.com/syncthing/syncthing/lib/nat"
"github.com/syncthing/syncthing/lib/stun"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
)
func init() {
@ -35,7 +35,7 @@ func init() {
}
type quicListener struct {
util.ServiceWithError
svcutil.ServiceWithError
nat atomic.Value
onAddressesChangedNotifier
@ -205,7 +205,7 @@ func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.
conns: conns,
factory: f,
}
l.ServiceWithError = util.AsService(l.serve, l.String())
l.ServiceWithError = svcutil.AsService(l.serve, l.String())
l.nat.Store(stun.NATUnknown)
return l
}

View File

@ -19,7 +19,7 @@ import (
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/nat"
"github.com/syncthing/syncthing/lib/relay/client"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
)
func init() {
@ -30,7 +30,7 @@ func init() {
}
type relayListener struct {
util.ServiceWithError
svcutil.ServiceWithError
onAddressesChangedNotifier
uri *url.URL
@ -184,7 +184,7 @@ func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls
conns: conns,
factory: f,
}
t.ServiceWithError = util.AsService(t.serve, t.String())
t.ServiceWithError = svcutil.AsService(t.serve, t.String())
return t
}

View File

@ -24,6 +24,7 @@ import (
"github.com/syncthing/syncthing/lib/nat"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
@ -143,7 +144,7 @@ type service struct {
}
func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string, evLogger events.Logger) Service {
spec := util.SpecWithInfoLogger(l)
spec := svcutil.SpecWithInfoLogger(l)
service := &service{
Supervisor: suture.New("connections.Service", spec),
connectionStatusHandler: newConnectionStatusHandler(),
@ -191,12 +192,12 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
// the common handling regardless of whether the connection was
// incoming or outgoing.
service.Add(util.AsService(service.connect, fmt.Sprintf("%s/connect", service)))
service.Add(util.AsService(service.handle, fmt.Sprintf("%s/handle", service)))
service.Add(svcutil.AsService(service.connect, fmt.Sprintf("%s/connect", service)))
service.Add(svcutil.AsService(service.handle, fmt.Sprintf("%s/handle", service)))
service.Add(service.listenerSupervisor)
service.Add(service.natService)
util.OnSupervisorDone(service.Supervisor, func() {
svcutil.OnSupervisorDone(service.Supervisor, func() {
service.cfg.Unsubscribe(service.limiter)
service.cfg.Unsubscribe(service)
})

View File

@ -18,7 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/connections/registry"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/nat"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
)
func init() {
@ -29,7 +29,7 @@ func init() {
}
type tcpListener struct {
util.ServiceWithError
svcutil.ServiceWithError
onAddressesChangedNotifier
uri *url.URL
@ -207,7 +207,7 @@ func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.C
natService: natService,
factory: f,
}
l.ServiceWithError = util.AsService(l.serve, l.String())
l.ServiceWithError = svcutil.AsService(l.serve, l.String())
return l
}

View File

@ -25,6 +25,7 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sha256"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
"github.com/thejerf/suture/v4"
@ -73,7 +74,7 @@ type Lowlevel struct {
func NewLowlevel(backend backend.Backend, evLogger events.Logger, opts ...Option) (*Lowlevel, error) {
// Only log restarts in debug mode.
spec := util.SpecWithDebugLogger(l)
spec := svcutil.SpecWithDebugLogger(l)
db := &Lowlevel{
Supervisor: suture.New("db.Lowlevel", spec),
Backend: backend,
@ -89,7 +90,7 @@ func NewLowlevel(backend backend.Backend, evLogger events.Logger, opts ...Option
opt(db)
}
db.keyer = newDefaultKeyer(db.folderIdx, db.deviceIdx)
db.Add(util.AsService(db.gcRunner, "db.Lowlevel/gcRunner"))
db.Add(svcutil.AsService(db.gcRunner, "db.Lowlevel/gcRunner"))
if path := db.needsRepairPath(); path != "" {
if _, err := os.Lstat(path); err == nil {
l.Infoln("Database was marked for repair - this may take a while")

View File

@ -24,7 +24,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/thejerf/suture/v4"
)
@ -52,7 +52,7 @@ const (
func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, evLogger events.Logger) (FinderService, error) {
c := &localClient{
Supervisor: suture.New("local", util.SpecWithDebugLogger(l)),
Supervisor: suture.New("local", svcutil.SpecWithDebugLogger(l)),
myID: id,
addrList: addrList,
evLogger: evLogger,
@ -81,9 +81,9 @@ func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, evLogge
c.beacon = beacon.NewMulticast(addr)
}
c.Add(c.beacon)
c.Add(util.AsService(c.recvAnnouncements, fmt.Sprintf("%s/recv", c)))
c.Add(svcutil.AsService(c.recvAnnouncements, fmt.Sprintf("%s/recv", c)))
c.Add(util.AsService(c.sendLocalAnnouncements, fmt.Sprintf("%s/sendLocal", c)))
c.Add(svcutil.AsService(c.sendLocalAnnouncements, fmt.Sprintf("%s/sendLocal", c)))
return c, nil
}

View File

@ -18,6 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
)
@ -47,7 +48,7 @@ type manager struct {
func NewManager(myID protocol.DeviceID, cfg config.Wrapper, cert tls.Certificate, evLogger events.Logger, lister AddressLister) Manager {
m := &manager{
Supervisor: suture.New("discover.Manager", util.SpecWithDebugLogger(l)),
Supervisor: suture.New("discover.Manager", svcutil.SpecWithDebugLogger(l)),
myID: myID,
cfg: cfg,
cert: cert,
@ -57,7 +58,7 @@ func NewManager(myID protocol.DeviceID, cfg config.Wrapper, cert tls.Certificate
finders: make(map[string]cachedFinder),
mut: sync.NewRWMutex(),
}
m.Add(util.AsService(m.serve, m.String()))
m.Add(svcutil.AsService(m.serve, m.String()))
return m
}

View File

@ -18,8 +18,8 @@ import (
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
)
const maxDurationSinceLastEventReq = time.Minute
@ -52,7 +52,7 @@ type folderSummaryService struct {
func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID, evLogger events.Logger) FolderSummaryService {
service := &folderSummaryService{
Supervisor: suture.New("folderSummaryService", util.SpecWithDebugLogger(l)),
Supervisor: suture.New("folderSummaryService", svcutil.SpecWithDebugLogger(l)),
cfg: cfg,
model: m,
id: id,
@ -63,8 +63,8 @@ func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID,
lastEventReqMut: sync.NewMutex(),
}
service.Add(util.AsService(service.listenForUpdates, fmt.Sprintf("%s/listenForUpdates", service)))
service.Add(util.AsService(service.calculateSummaries, fmt.Sprintf("%s/calculateSummaries", service)))
service.Add(svcutil.AsService(service.listenForUpdates, fmt.Sprintf("%s/listenForUpdates", service)))
service.Add(svcutil.AsService(service.calculateSummaries, fmt.Sprintf("%s/calculateSummaries", service)))
return service
}

View File

@ -18,7 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/svcutil"
)
type indexSender struct {
@ -38,7 +38,7 @@ type indexSender struct {
func (s *indexSender) Serve(ctx context.Context) (err error) {
l.Debugf("Starting indexSender for %s to %s at %s (slv=%d)", s.folder, s.conn.ID(), s.conn, s.prevSequence)
defer func() {
err = util.NoRestartErr(err)
err = svcutil.NoRestartErr(err)
l.Debugf("Exiting indexSender for %s to %s at %s: %v", s.folder, s.conn.ID(), s.conn, err)
}()

View File

@ -33,9 +33,9 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/stats"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/ur/contract"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/versioner"
)
@ -199,7 +199,7 @@ var (
// where it sends index information to connected peers and responds to requests
// for file data without altering the local folder in any way.
func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string, evLogger events.Logger) Model {
spec := util.SpecWithDebugLogger(l)
spec := svcutil.SpecWithDebugLogger(l)
m := &model{
Supervisor: suture.New("model", spec),
@ -249,7 +249,7 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID.String())
}
m.Add(m.progressEmitter)
m.Add(util.AsService(m.serve, m.String()))
m.Add(svcutil.AsService(m.serve, m.String()))
return m
}
@ -262,7 +262,7 @@ func (m *model) serve(ctx context.Context) error {
if err := m.initFolders(); err != nil {
close(m.started)
return util.AsFatalErr(err, util.ExitError)
return svcutil.AsFatalErr(err, svcutil.ExitError)
}
close(m.started)
@ -271,7 +271,7 @@ func (m *model) serve(ctx context.Context) error {
case <-ctx.Done():
return ctx.Err()
case err := <-m.fatalChan:
return util.AsFatalErr(err, util.ExitError)
return svcutil.AsFatalErr(err, svcutil.ExitError)
}
}

View File

@ -10,8 +10,8 @@ import (
"time"
"github.com/syncthing/syncthing/lib/relay/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
"github.com/thejerf/suture/v4"
)
@ -45,7 +45,7 @@ func NewClient(uri *url.URL, certs []tls.Certificate, invitations chan protocol.
}
type commonClient struct {
util.ServiceWithError
svcutil.ServiceWithError
invitations chan protocol.SessionInvitation
closeInvitationsOnFinish bool
@ -61,7 +61,7 @@ func newCommonClient(invitations chan protocol.SessionInvitation, serve func(con
defer c.cleanup()
return serve(ctx)
}
c.ServiceWithError = util.AsService(newServe, creator)
c.ServiceWithError = svcutil.AsService(newServe, creator)
if c.invitations == nil {
c.closeInvitationsOnFinish = true
c.invitations = make(chan protocol.SessionInvitation)

177
lib/svcutil/svcutil.go Normal file
View File

@ -0,0 +1,177 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package svcutil
import (
"context"
"errors"
"fmt"
"time"
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture/v4"
)
const ServiceTimeout = 10 * time.Second
type FatalErr struct {
Err error
Status ExitStatus
}
// AsFatalErr wraps the given error creating a FatalErr. If the given error
// already is of type FatalErr, it is not wrapped again.
func AsFatalErr(err error, status ExitStatus) *FatalErr {
var ferr *FatalErr
if errors.As(err, &ferr) {
return ferr
}
return &FatalErr{
Err: err,
Status: status,
}
}
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
}
// NoRestartErr wraps the given error err (which may be nil) to make sure that
// `errors.Is(err, suture.ErrDoNotRestart) == true`.
func NoRestartErr(err error) error {
if err == nil {
return suture.ErrDoNotRestart
}
return &noRestartErr{err}
}
type noRestartErr struct {
err error
}
func (e *noRestartErr) Error() string {
return e.err.Error()
}
func (e *noRestartErr) Unwrap() error {
return e.err
}
func (e *noRestartErr) Is(target error) bool {
return target == suture.ErrDoNotRestart
}
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 {
suture.Service
fmt.Stringer
Error() error
SetError(error)
}
// 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(),
}
}
type service struct {
creator string
serve func(ctx context.Context) error
err error
mut sync.Mutex
}
func (s *service) Serve(ctx context.Context) error {
s.mut.Lock()
s.err = nil
s.mut.Unlock()
err := s.serve(ctx)
s.mut.Lock()
s.err = err
s.mut.Unlock()
return err
}
func (s *service) Error() error {
s.mut.Lock()
defer s.mut.Unlock()
return s.err
}
func (s *service) SetError(err error) {
s.mut.Lock()
s.err = err
s.mut.Unlock()
}
func (s *service) String() string {
return fmt.Sprintf("Service@%p created by %v", s, s.creator)
}
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 SpecWithDebugLogger(l logger.Logger) suture.Spec {
return spec(func(e suture.Event) { l.Debugln(e) })
}
func SpecWithInfoLogger(l logger.Logger) suture.Spec {
return spec(func(e suture.Event) { l.Infoln(e) })
}
func spec(eventHook suture.EventHook) suture.Spec {
return suture.Spec{
EventHook: eventHook,
Timeout: ServiceTimeout,
PassThroughPanics: true,
DontPropagateTermination: false,
}
}

View File

@ -37,10 +37,10 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sha256"
"github.com/syncthing/syncthing/lib/svcutil"
"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 (
@ -73,7 +73,7 @@ type App struct {
evLogger events.Logger
cert tls.Certificate
opts Options
exitStatus util.ExitStatus
exitStatus svcutil.ExitStatus
err error
stopOnce sync.Once
mainServiceCancel context.CancelFunc
@ -103,7 +103,7 @@ 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.
spec := util.SpecWithDebugLogger(l)
spec := svcutil.SpecWithDebugLogger(l)
a.mainService = suture.New("main", spec)
// Start the supervisor and wait for it to stop to handle cleanup.
@ -113,7 +113,7 @@ func (a *App) Start() error {
go a.run(ctx)
if err := a.startup(); err != nil {
a.stopWithErr(util.ExitError, err)
a.stopWithErr(svcutil.ExitError, err)
return err
}
@ -355,19 +355,19 @@ func (a *App) handleMainServiceError(err error) {
if err == nil || errors.Is(err, context.Canceled) {
return
}
var fatalErr *util.FatalErr
var fatalErr *svcutil.FatalErr
if errors.As(err, &fatalErr) {
a.exitStatus = fatalErr.Status
a.err = fatalErr.Err
return
}
a.err = err
a.exitStatus = util.ExitError
a.exitStatus = svcutil.ExitError
}
// Wait blocks until the app stops running. Also returns if the app hasn't been
// started yet.
func (a *App) Wait() util.ExitStatus {
func (a *App) Wait() svcutil.ExitStatus {
<-a.stopped
return a.exitStatus
}
@ -385,11 +385,11 @@ 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 util.ExitStatus) util.ExitStatus {
func (a *App) Stop(stopReason svcutil.ExitStatus) svcutil.ExitStatus {
return a.stopWithErr(stopReason, nil)
}
func (a *App) stopWithErr(stopReason util.ExitStatus, err error) util.ExitStatus {
func (a *App) stopWithErr(stopReason svcutil.ExitStatus, err error) svcutil.ExitStatus {
a.stopOnce.Do(func() {
a.exitStatus = stopReason
a.err = err

View File

@ -17,8 +17,8 @@ import (
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/util"
)
func tempCfgFilename(t *testing.T) string {
@ -90,7 +90,7 @@ func TestStartupFail(t *testing.T) {
}
done := make(chan struct{})
var waitE util.ExitStatus
var waitE svcutil.ExitStatus
go func() {
waitE = app.Wait()
close(done)
@ -102,8 +102,8 @@ func TestStartupFail(t *testing.T) {
case <-done:
}
if waitE != util.ExitError {
t.Errorf("Got exit status %v, expected %v", waitE, util.ExitError)
if waitE != svcutil.ExitError {
t.Errorf("Got exit status %v, expected %v", waitE, svcutil.ExitError)
}
if err = app.Error(); err != startErr {

View File

@ -17,6 +17,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/thejerf/suture/v4"
)
@ -28,6 +29,7 @@ var (
minDelay = 10 * time.Second
maxDelay = time.Minute
sendTimeout = time.Minute
finalSendTimeout = svcutil.ServiceTimeout / 2
evChanClosed = "failure event channel closed"
invalidEventDataType = "failure event data is not a string"
)
@ -116,11 +118,7 @@ outer:
now := time.Now()
for descr, stat := range h.buf {
if now.Sub(stat.last) > minDelay || now.Sub(stat.first) > maxDelay {
reports = append(reports, FailureReport{
Description: descr,
Count: stat.count,
Version: build.LongVersion,
})
reports = append(reports, newFailureReport(descr, stat.count))
delete(h.buf, descr)
}
}
@ -145,6 +143,13 @@ outer:
if sub != nil {
sub.Unsubscribe()
reports := make([]FailureReport, 0, len(h.buf))
for descr, stat := range h.buf {
reports = append(reports, newFailureReport(descr, stat.count))
}
timeout, cancel := context.WithTimeout(context.Background(), finalSendTimeout)
defer cancel()
sendFailureReports(timeout, reports, url)
}
return err
}
@ -207,3 +212,11 @@ func sendFailureReports(ctx context.Context, reports []FailureReport, url string
resp.Body.Close()
return
}
func newFailureReport(descr string, count int) FailureReport {
return FailureReport{
Description: descr,
Count: count,
Version: build.LongVersion,
}
}

View File

@ -8,7 +8,6 @@ package util
import (
"context"
"errors"
"fmt"
"net"
"net/url"
@ -17,9 +16,6 @@ import (
"strings"
"time"
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture/v4"
)
@ -263,19 +259,6 @@ type FatalErr struct {
Status ExitStatus
}
// AsFatalErr wraps the given error creating a FatalErr. If the given error
// already is of type FatalErr, it is not wrapped again.
func AsFatalErr(err error, status ExitStatus) *FatalErr {
var ferr *FatalErr
if errors.As(err, &ferr) {
return ferr
}
return &FatalErr{
Err: err,
Status: status,
}
}
func (e *FatalErr) Error() string {
return e.Err.Error()
}
@ -327,61 +310,6 @@ func (s ExitStatus) AsInt() int {
return int(s)
}
type ServiceWithError interface {
suture.Service
fmt.Stringer
Error() error
SetError(error)
}
// 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(),
}
}
type service struct {
creator string
serve func(ctx context.Context) error
err error
mut sync.Mutex
}
func (s *service) Serve(ctx context.Context) error {
s.mut.Lock()
s.err = nil
s.mut.Unlock()
err := s.serve(ctx)
s.mut.Lock()
s.err = err
s.mut.Unlock()
return err
}
func (s *service) Error() error {
s.mut.Lock()
defer s.mut.Unlock()
return s.err
}
func (s *service) SetError(err error) {
s.mut.Lock()
s.err = err
s.mut.Unlock()
}
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() {
@ -390,37 +318,6 @@ func OnDone(ctx context.Context, fn func()) {
}()
}
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 SpecWithDebugLogger(l logger.Logger) suture.Spec {
return spec(func(e suture.Event) { l.Debugln(e) })
}
func SpecWithInfoLogger(l logger.Logger) suture.Spec {
return spec(func(e suture.Event) { l.Infoln(e) })
}
func spec(eventHook suture.EventHook) suture.Spec {
return suture.Spec{
EventHook: eventHook,
PassThroughPanics: true,
DontPropagateTermination: false,
}
}
func CallWithContext(ctx context.Context, fn func() error) error {
var err error
done := make(chan struct{})