diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 831dc817d..bbf300755 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -670,6 +670,12 @@ func syncthingMain(runtimeOptions RuntimeOptions) { secs, _ := strconv.Atoi(t) appOpts.DeadlockTimeoutS = secs } + if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil { + appOpts.DBRecheckInterval = dur + } + if dur, err := time.ParseDuration(os.Getenv("STGCINDIRECTEVERY")); err == nil { + appOpts.DBIndirectGCInterval = dur + } app := syncthing.New(cfg, ldb, evLogger, cert, appOpts) diff --git a/lib/db/lowlevel.go b/lib/db/lowlevel.go index 9c97e6bc1..f62956bd1 100644 --- a/lib/db/lowlevel.go +++ b/lib/db/lowlevel.go @@ -9,7 +9,6 @@ package db import ( "bytes" "encoding/binary" - "os" "time" "github.com/syncthing/syncthing/lib/db/backend" @@ -31,16 +30,10 @@ const ( // Use indirection for the block list when it exceeds this many entries blocksIndirectionCutoff = 3 + + recheckDefaultInterval = 30 * 24 * time.Hour ) -var indirectGCInterval = indirectGCDefaultInterval - -func init() { - if dur, err := time.ParseDuration(os.Getenv("STGCINDIRECTEVERY")); err == nil { - indirectGCInterval = dur - } -} - // Lowlevel is the lowest level database interface. It has a very simple // purpose: hold the actual backend database, and the in-memory state // that belong to that database. In the same way that a single on disk @@ -48,27 +41,55 @@ func init() { // any given backend. type Lowlevel struct { backend.Backend - folderIdx *smallIndex - deviceIdx *smallIndex - keyer keyer - gcMut sync.RWMutex - gcKeyCount int - gcStop chan struct{} + folderIdx *smallIndex + deviceIdx *smallIndex + keyer keyer + gcMut sync.RWMutex + gcKeyCount int + gcStop chan struct{} + indirectGCInterval time.Duration + recheckInterval time.Duration } -func NewLowlevel(backend backend.Backend) *Lowlevel { +func NewLowlevel(backend backend.Backend, opts ...Option) *Lowlevel { db := &Lowlevel{ - Backend: backend, - folderIdx: newSmallIndex(backend, []byte{KeyTypeFolderIdx}), - deviceIdx: newSmallIndex(backend, []byte{KeyTypeDeviceIdx}), - gcMut: sync.NewRWMutex(), - gcStop: make(chan struct{}), + Backend: backend, + folderIdx: newSmallIndex(backend, []byte{KeyTypeFolderIdx}), + deviceIdx: newSmallIndex(backend, []byte{KeyTypeDeviceIdx}), + gcMut: sync.NewRWMutex(), + gcStop: make(chan struct{}), + indirectGCInterval: indirectGCDefaultInterval, + recheckInterval: recheckDefaultInterval, + } + for _, opt := range opts { + opt(db) } db.keyer = newDefaultKeyer(db.folderIdx, db.deviceIdx) go db.gcRunner() return db } +type Option func(*Lowlevel) + +// WithRecheckInterval sets the time interval in between metadata recalculations +// and consistency checks. +func WithRecheckInterval(dur time.Duration) Option { + return func(db *Lowlevel) { + if dur > 0 { + db.recheckInterval = dur + } + } +} + +// WithIndirectGCInterval sets the time interval in between GC runs. +func WithIndirectGCInterval(dur time.Duration) Option { + return func(db *Lowlevel) { + if dur > 0 { + db.indirectGCInterval = dur + } + } +} + func (db *Lowlevel) Close() error { close(db.gcStop) return db.Backend.Close() @@ -498,7 +519,7 @@ func (db *Lowlevel) gcRunner() { // directly, give the system a while to get up and running and do other // stuff first. (We might have migrations and stuff which would be // better off running before GC.) - next := db.timeUntil(indirectGCTimeKey, indirectGCInterval) + next := db.timeUntil(indirectGCTimeKey, db.indirectGCInterval) if next < time.Minute { next = time.Minute } @@ -515,7 +536,7 @@ func (db *Lowlevel) gcRunner() { l.Warnln("Database indirection GC failed:", err) } db.recordTime(indirectGCTimeKey) - t.Reset(db.timeUntil(indirectGCTimeKey, indirectGCInterval)) + t.Reset(db.timeUntil(indirectGCTimeKey, db.indirectGCInterval)) } } } @@ -669,7 +690,7 @@ func (db *Lowlevel) loadMetadataTracker(folder string) *metadataTracker { return db.getMetaAndCheck(folder) } - if age := time.Since(meta.Created()); age > databaseRecheckInterval { + if age := time.Since(meta.Created()); age > db.recheckInterval { l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, age) return db.getMetaAndCheck(folder) } diff --git a/lib/db/set.go b/lib/db/set.go index 226bb1527..887f5dcb3 100644 --- a/lib/db/set.go +++ b/lib/db/set.go @@ -13,7 +13,6 @@ package db import ( - "os" "time" "github.com/syncthing/syncthing/lib/db/backend" @@ -62,14 +61,6 @@ type FileIntf interface { // continue iteration, false to stop. type Iterator func(f FileIntf) bool -var databaseRecheckInterval = 30 * 24 * time.Hour - -func init() { - if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil { - databaseRecheckInterval = dur - } -} - func NewFileSet(folder string, fs fs.Filesystem, db *Lowlevel) *FileSet { return &FileSet{ folder: folder, diff --git a/lib/syncthing/syncthing.go b/lib/syncthing/syncthing.go index 15faed57f..44943771d 100644 --- a/lib/syncthing/syncthing.go +++ b/lib/syncthing/syncthing.go @@ -70,6 +70,9 @@ type Options struct { ProfilerURL string ResetDeltaIdxs bool Verbose bool + // null duration means use default value + DBRecheckInterval time.Duration + DBIndirectGCInterval time.Duration } type App struct { @@ -90,7 +93,7 @@ type App struct { func New(cfg config.Wrapper, dbBackend backend.Backend, evLogger events.Logger, cert tls.Certificate, opts Options) *App { a := &App{ cfg: cfg, - ll: db.NewLowlevel(dbBackend), + ll: db.NewLowlevel(dbBackend, db.WithRecheckInterval(opts.DBRecheckInterval), db.WithIndirectGCInterval(opts.DBIndirectGCInterval)), evLogger: evLogger, opts: opts, cert: cert,