cmd/syncthing, lib/db: Store upgrade info to throttle queries (fixes #6513) (#6514)

This commit is contained in:
Simon Frei 2020-04-13 10:21:07 +02:00 committed by GitHub
parent 0e67c036bb
commit ab92f8520c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 42 deletions

View File

@ -30,6 +30,7 @@ import (
"github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/fs"
@ -147,7 +148,15 @@ var (
innerProcess = os.Getenv("STMONITORED") != "" innerProcess = os.Getenv("STMONITORED") != ""
noDefaultFolder = os.Getenv("STNODEFAULTFOLDER") != "" noDefaultFolder = os.Getenv("STNODEFAULTFOLDER") != ""
errConcurrentUpgrade = errors.New("upgrade prevented by other running Syncthing instance") upgradeCheckInterval = 5 * time.Minute
upgradeRetryInterval = time.Hour
upgradeCheckKey = "lastUpgradeCheck"
upgradeTimeKey = "lastUpgradeTime"
upgradeVersionKey = "lastUpgradeVersion"
errConcurrentUpgrade = errors.New("upgrade prevented by other running Syncthing instance")
errTooEarlyUpgradeCheck = fmt.Errorf("last upgrade check happened less than %v ago, skipping", upgradeCheckInterval)
errTooEarlyUpgrade = fmt.Errorf("last upgrade happened less than %v ago, skipping", upgradeRetryInterval)
) )
type RuntimeOptions struct { type RuntimeOptions struct {
@ -399,7 +408,14 @@ func main() {
if options.doUpgrade { if options.doUpgrade {
release, err := checkUpgrade() release, err := checkUpgrade()
if err == nil { if err == nil {
err = performUpgrade(release) // Use leveldb database locks to protect against concurrent upgrades
ldb, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
if err != nil {
err = upgradeViaRest()
} else {
_ = ldb.Close()
err = upgrade.To(release)
}
} }
if err != nil { if err != nil {
l.Warnln("Upgrade:", err) l.Warnln("Upgrade:", err)
@ -526,25 +542,6 @@ func checkUpgrade() (upgrade.Release, error) {
return release, nil return release, nil
} }
func performUpgradeDirect(release upgrade.Release) error {
// Use leveldb database locks to protect against concurrent upgrades
if _, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto); err != nil {
return errConcurrentUpgrade
}
return upgrade.To(release)
}
func performUpgrade(release upgrade.Release) error {
if err := performUpgradeDirect(release); err != nil {
if err != errConcurrentUpgrade {
return err
}
l.Infoln("Attempting upgrade through running Syncthing...")
return upgradeViaRest()
}
return nil
}
func upgradeViaRest() error { func upgradeViaRest() error {
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger) cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
u, err := url.Parse(cfg.GUI().URL()) u, err := url.Parse(cfg.GUI().URL())
@ -627,25 +624,33 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
// not, as otherwise they cannot step off the candidate channel. // not, as otherwise they cannot step off the candidate channel.
} }
dbFile := locations.Get(locations.Database)
ldb, err := syncthing.OpenDBBackend(dbFile, cfg.Options().DatabaseTuning)
if err != nil {
l.Warnln("Error opening database:", err)
os.Exit(1)
}
// Check if auto-upgrades should be done and if yes, do an initial // Check if auto-upgrades should be done and if yes, do an initial
// upgrade immedately. The auto-upgrade routine can only be started // upgrade immedately. The auto-upgrade routine can only be started
// later after App is initialised. // later after App is initialised.
shouldAutoUpgrade := shouldUpgrade(cfg, runtimeOptions) shouldAutoUpgrade := shouldUpgrade(cfg, runtimeOptions)
if shouldAutoUpgrade { if shouldAutoUpgrade {
// Try to do upgrade directly // try to do upgrade directly and log the error if relevant.
release, err := checkUpgrade() release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb))
if err == nil { if err == nil {
if err = performUpgradeDirect(release); err == nil { err = upgrade.To(release)
l.Infof("Upgraded to %q, exiting now.", release.Tag)
os.Exit(syncthing.ExitUpgrade.AsInt())
}
} }
// Log the error if relevant.
if err != nil { if err != nil {
if _, ok := err.(errNoUpgrade); !ok { if _, ok := err.(errNoUpgrade); ok || err == errTooEarlyUpgradeCheck || err == errTooEarlyUpgrade {
l.Debugln("Initial automatic upgrade:", err)
} else {
l.Infoln("Initial automatic upgrade:", err) l.Infoln("Initial automatic upgrade:", err)
} }
} else {
l.Infof("Upgraded to %q, exiting now.", release.Tag)
os.Exit(syncthing.ExitUpgrade.AsInt())
} }
} }
@ -655,13 +660,6 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
setPauseState(cfg, true) setPauseState(cfg, true)
} }
dbFile := locations.Get(locations.Database)
ldb, err := syncthing.OpenDBBackend(dbFile, cfg.Options().DatabaseTuning)
if err != nil {
l.Warnln("Error opening database:", err)
os.Exit(1)
}
appOpts := runtimeOptions.Options appOpts := runtimeOptions.Options
if runtimeOptions.auditEnabled { if runtimeOptions.auditEnabled {
appOpts.AuditWriter = auditWriter(runtimeOptions.auditFile) appOpts.AuditWriter = auditWriter(runtimeOptions.auditFile)
@ -852,7 +850,7 @@ func shouldUpgrade(cfg config.Wrapper, runtimeOptions RuntimeOptions) bool {
} }
func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) { func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) {
timer := time.NewTimer(0) timer := time.NewTimer(upgradeCheckInterval)
sub := evLogger.Subscribe(events.DeviceConnected) sub := evLogger.Subscribe(events.DeviceConnected)
for { for {
select { select {
@ -907,6 +905,26 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger)
} }
} }
func initialAutoUpgradeCheck(misc *db.NamespacedKV) (upgrade.Release, error) {
if last, ok, err := misc.Time(upgradeCheckKey); err == nil && ok && time.Since(last) < upgradeCheckInterval {
return upgrade.Release{}, errTooEarlyUpgradeCheck
}
_ = misc.PutTime(upgradeCheckKey, time.Now())
release, err := checkUpgrade()
if err != nil {
return upgrade.Release{}, err
}
if lastVersion, ok, err := misc.String(upgradeVersionKey); err == nil && ok && lastVersion == release.Tag {
// Only check time if we try to upgrade to the same release.
if lastTime, ok, err := misc.Time(upgradeTimeKey); err == nil && ok && time.Since(lastTime) < upgradeRetryInterval {
return upgrade.Release{}, errTooEarlyUpgrade
}
}
_ = misc.PutString(upgradeVersionKey, release.Tag)
_ = misc.PutTime(upgradeTimeKey, time.Now())
return release, nil
}
// cleanConfigDirectory removes old, unused configuration and index formats, a // cleanConfigDirectory removes old, unused configuration and index formats, a
// suitable time after they have gone out of fashion. // suitable time after they have gone out of fashion.
func cleanConfigDirectory() { func cleanConfigDirectory() {

View File

@ -16,13 +16,13 @@ import (
// NamespacedKV is a simple key-value store using a specific namespace within // NamespacedKV is a simple key-value store using a specific namespace within
// a leveldb. // a leveldb.
type NamespacedKV struct { type NamespacedKV struct {
db *Lowlevel db backend.Backend
prefix string prefix string
} }
// NewNamespacedKV returns a new NamespacedKV that lives in the namespace // NewNamespacedKV returns a new NamespacedKV that lives in the namespace
// specified by the prefix. // specified by the prefix.
func NewNamespacedKV(db *Lowlevel, prefix string) *NamespacedKV { func NewNamespacedKV(db backend.Backend, prefix string) *NamespacedKV {
return &NamespacedKV{ return &NamespacedKV{
db: db, db: db,
prefix: prefix, prefix: prefix,
@ -133,18 +133,18 @@ func (n NamespacedKV) prefixedKey(key string) []byte {
// NewDeviceStatisticsNamespace creates a KV namespace for device statistics // NewDeviceStatisticsNamespace creates a KV namespace for device statistics
// for the given device. // for the given device.
func NewDeviceStatisticsNamespace(db *Lowlevel, device string) *NamespacedKV { func NewDeviceStatisticsNamespace(db backend.Backend, device string) *NamespacedKV {
return NewNamespacedKV(db, string(KeyTypeDeviceStatistic)+device) return NewNamespacedKV(db, string(KeyTypeDeviceStatistic)+device)
} }
// NewFolderStatisticsNamespace creates a KV namespace for folder statistics // NewFolderStatisticsNamespace creates a KV namespace for folder statistics
// for the given folder. // for the given folder.
func NewFolderStatisticsNamespace(db *Lowlevel, folder string) *NamespacedKV { func NewFolderStatisticsNamespace(db backend.Backend, folder string) *NamespacedKV {
return NewNamespacedKV(db, string(KeyTypeFolderStatistic)+folder) return NewNamespacedKV(db, string(KeyTypeFolderStatistic)+folder)
} }
// NewMiscDateNamespace creates a KV namespace for miscellaneous metadata. // NewMiscDateNamespace creates a KV namespace for miscellaneous metadata.
func NewMiscDataNamespace(db *Lowlevel) *NamespacedKV { func NewMiscDataNamespace(db backend.Backend) *NamespacedKV {
return NewNamespacedKV(db, string(KeyTypeMiscData)) return NewNamespacedKV(db, string(KeyTypeMiscData))
} }