Refactor state tracking (...)

Move state tracking into the puller/scanner objects. This is a first
step towards resolving #1391.

Rename Puller and Scanner to roFolder and rwFolder as they have more
duties than just pulling and scanning, and don't need to be exported.
This commit is contained in:
Jakob Borg
2015-03-16 21:14:19 +01:00
parent 124b189cc0
commit bdbca75dfa
9 changed files with 276 additions and 158 deletions

View File

@@ -36,30 +36,6 @@ import (
"github.com/syndtr/goleveldb/leveldb"
)
type folderState int
const (
FolderIdle folderState = iota
FolderScanning
FolderSyncing
FolderCleaning
)
func (s folderState) String() string {
switch s {
case FolderIdle:
return "idle"
case FolderScanning:
return "scanning"
case FolderCleaning:
return "cleaning"
case FolderSyncing:
return "syncing"
default:
return "unknown"
}
}
// How many files to send in each Index/IndexUpdate message.
const (
indexTargetSize = 250 * 1024 // Aim for making index messages no larger than 250 KiB (uncompressed)
@@ -73,6 +49,9 @@ type service interface {
Stop()
Jobs() ([]string, []string) // In progress, Queued
BringToFront(string)
setState(folderState)
getState() (folderState, time.Time)
}
type Model struct {
@@ -95,10 +74,6 @@ type Model struct {
folderStatRefs map[string]*stats.FolderStatisticsReference // folder -> statsRef
fmut sync.RWMutex // protects the above
folderState map[string]folderState // folder -> state
folderStateChanged map[string]time.Time // folder -> time when state changed
smut sync.RWMutex
protoConn map[protocol.DeviceID]protocol.Connection
rawConn map[protocol.DeviceID]io.Closer
deviceVer map[protocol.DeviceID]string
@@ -120,26 +95,24 @@ var (
// for file data without altering the local folder in any way.
func NewModel(cfg *config.Wrapper, deviceName, clientName, clientVersion string, ldb *leveldb.DB) *Model {
m := &Model{
cfg: cfg,
db: ldb,
deviceName: deviceName,
clientName: clientName,
clientVersion: clientVersion,
folderCfgs: make(map[string]config.FolderConfiguration),
folderFiles: make(map[string]*db.FileSet),
folderDevices: make(map[string][]protocol.DeviceID),
deviceFolders: make(map[protocol.DeviceID][]string),
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
folderIgnores: make(map[string]*ignore.Matcher),
folderRunners: make(map[string]service),
folderStatRefs: make(map[string]*stats.FolderStatisticsReference),
folderState: make(map[string]folderState),
folderStateChanged: make(map[string]time.Time),
protoConn: make(map[protocol.DeviceID]protocol.Connection),
rawConn: make(map[protocol.DeviceID]io.Closer),
deviceVer: make(map[protocol.DeviceID]string),
finder: db.NewBlockFinder(ldb, cfg),
progressEmitter: NewProgressEmitter(cfg),
cfg: cfg,
db: ldb,
deviceName: deviceName,
clientName: clientName,
clientVersion: clientVersion,
folderCfgs: make(map[string]config.FolderConfiguration),
folderFiles: make(map[string]*db.FileSet),
folderDevices: make(map[string][]protocol.DeviceID),
deviceFolders: make(map[protocol.DeviceID][]string),
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
folderIgnores: make(map[string]*ignore.Matcher),
folderRunners: make(map[string]service),
folderStatRefs: make(map[string]*stats.FolderStatisticsReference),
protoConn: make(map[protocol.DeviceID]protocol.Connection),
rawConn: make(map[protocol.DeviceID]io.Closer),
deviceVer: make(map[protocol.DeviceID]string),
finder: db.NewBlockFinder(ldb, cfg),
progressEmitter: NewProgressEmitter(cfg),
}
if cfg.Options().ProgressUpdateIntervalS > -1 {
go m.progressEmitter.Serve()
@@ -153,7 +126,6 @@ func NewModel(cfg *config.Wrapper, deviceName, clientName, clientVersion string,
}
}
deadlockDetect(&m.fmut, time.Duration(timeout)*time.Second)
deadlockDetect(&m.smut, time.Duration(timeout)*time.Second)
deadlockDetect(&m.pmut, time.Duration(timeout)*time.Second)
return m
}
@@ -172,18 +144,7 @@ func (m *Model) StartFolderRW(folder string) {
if ok {
panic("cannot start already running folder " + folder)
}
p := &Puller{
folder: folder,
dir: cfg.Path,
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
model: m,
ignorePerms: cfg.IgnorePerms,
lenientMtimes: cfg.LenientMtimes,
progressEmitter: m.progressEmitter,
copiers: cfg.Copiers,
pullers: cfg.Pullers,
queue: newJobQueue(),
}
p := newRWFolder(m, cfg)
m.folderRunners[folder] = p
m.fmut.Unlock()
@@ -216,11 +177,7 @@ func (m *Model) StartFolderRO(folder string) {
if ok {
panic("cannot start already running folder " + folder)
}
s := &Scanner{
folder: folder,
intv: time.Duration(cfg.RescanIntervalS) * time.Second,
model: m,
}
s := newROFolder(m, folder, time.Duration(cfg.RescanIntervalS)*time.Second)
m.folderRunners[folder] = s
m.fmut.Unlock()
@@ -1154,11 +1111,15 @@ func (m *Model) ScanFolderSub(folder, sub string) error {
}
m.fmut.Lock()
fs, ok := m.folderFiles[folder]
fs := m.folderFiles[folder]
folderCfg := m.folderCfgs[folder]
ignores := m.folderIgnores[folder]
runner, ok := m.folderRunners[folder]
m.fmut.Unlock()
// Folders are added to folderRunners only when they are started. We can't
// scan them before they have started, so that's what we need to check for
// here.
if !ok {
return errors.New("no such folder")
}
@@ -1189,7 +1150,7 @@ func (m *Model) ScanFolderSub(folder, sub string) error {
Hashers: folderCfg.Hashers,
}
m.setState(folder, FolderScanning)
runner.setState(FolderScanning)
fchan, err := w.Walk()
if err != nil {
@@ -1289,7 +1250,7 @@ func (m *Model) ScanFolderSub(folder, sub string) error {
fs.Update(protocol.LocalDeviceID, batch)
}
m.setState(folder, FolderIdle)
runner.setState(FolderIdle)
return nil
}
@@ -1332,40 +1293,24 @@ func (m *Model) clusterConfig(device protocol.DeviceID) protocol.ClusterConfigMe
return cm
}
func (m *Model) setState(folder string, state folderState) {
m.smut.Lock()
oldState := m.folderState[folder]
changed, ok := m.folderStateChanged[folder]
if state != oldState {
m.folderState[folder] = state
m.folderStateChanged[folder] = time.Now()
eventData := map[string]interface{}{
"folder": folder,
"to": state.String(),
}
if ok {
eventData["duration"] = time.Since(changed).Seconds()
eventData["from"] = oldState.String()
}
events.Default.Log(events.StateChanged, eventData)
}
m.smut.Unlock()
}
func (m *Model) State(folder string) (string, time.Time) {
m.smut.RLock()
state := m.folderState[folder]
changed := m.folderStateChanged[folder]
m.smut.RUnlock()
m.fmut.RLock()
runner, ok := m.folderRunners[folder]
m.fmut.RUnlock()
if !ok {
return "", time.Time{}
}
state, changed := runner.getState()
return state.String(), changed
}
func (m *Model) Override(folder string) {
m.fmut.RLock()
fs := m.folderFiles[folder]
runner := m.folderRunners[folder]
m.fmut.RUnlock()
m.setState(folder, FolderScanning)
runner.setState(FolderScanning)
batch := make([]protocol.FileInfo, 0, indexBatchSize)
fs.WithNeed(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
need := fi.(protocol.FileInfo)
@@ -1391,7 +1336,7 @@ func (m *Model) Override(folder string) {
if len(batch) > 0 {
fs.Update(protocol.LocalDeviceID, batch)
}
m.setState(folder, FolderIdle)
runner.setState(FolderIdle)
}
// CurrentLocalVersion returns the change version for the given folder.